1/* fchdir replacement.
2   Copyright (C) 2006, 2007 Free Software Foundation, Inc.
3
4   This program is free software; you can redistribute it and/or modify
5   it under the terms of the GNU General Public License as published by
6   the Free Software Foundation; either version 2, or (at your option)
7   any later version.
8
9   This program is distributed in the hope that it will be useful,
10   but WITHOUT ANY WARRANTY; without even the implied warranty of
11   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   GNU General Public License for more details.
13
14   You should have received a copy of the GNU General Public License
15   along with this program; if not, write to the Free Software Foundation,
16   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
17
18#include <config.h>
19
20/* Specification.  */
21#include <unistd.h>
22
23#include <errno.h>
24#include <fcntl.h>
25#include <stdarg.h>
26#include <stdlib.h>
27#include <string.h>
28#include <sys/types.h>
29#include <sys/stat.h>
30#include <dirent.h>
31
32#include "canonicalize.h"
33#include "dirfd.h"
34
35/* This replacement assumes that a directory is not renamed while opened
36   through a file descriptor.  */
37
38/* Array of file descriptors opened.  If it points to a directory, it stores
39   info about this directory; otherwise it stores an errno value of ENOTDIR.  */
40typedef struct
41{
42  char *name;       /* Absolute name of the directory, or NULL.  */
43  int saved_errno;  /* If name == NULL: The error code describing the failure
44		       reason.  */
45} dir_info_t;
46static dir_info_t *dirs;
47static size_t dirs_allocated;
48
49/* Try to ensure dirs has enough room for a slot at index fd.  */
50static void
51ensure_dirs_slot (size_t fd)
52{
53  if (fd >= dirs_allocated)
54    {
55      size_t new_allocated;
56      dir_info_t *new_dirs;
57      size_t i;
58
59      new_allocated = 2 * dirs_allocated + 1;
60      if (new_allocated <= fd)
61	new_allocated = fd + 1;
62      new_dirs =
63	(dirs != NULL
64	 ? (dir_info_t *) realloc (dirs, new_allocated * sizeof (dir_info_t))
65	 : (dir_info_t *) malloc (new_allocated * sizeof (dir_info_t)));
66      if (new_dirs != NULL)
67	{
68	  for (i = dirs_allocated; i < new_allocated; i++)
69	    {
70	      new_dirs[i].name = NULL;
71	      new_dirs[i].saved_errno = ENOTDIR;
72	    }
73	  dirs = new_dirs;
74	  dirs_allocated = new_allocated;
75	}
76    }
77}
78
79/* Override open() and close(), to keep track of the open file descriptors.  */
80
81int
82close (int fd)
83#undef close
84{
85  int retval = close (fd);
86
87  if (retval >= 0 && fd >= 0 && fd < dirs_allocated)
88    {
89      if (dirs[fd].name != NULL)
90	free (dirs[fd].name);
91      dirs[fd].name = NULL;
92      dirs[fd].saved_errno = ENOTDIR;
93    }
94  return retval;
95}
96
97int
98open (const char *filename, int flags, ...)
99#undef open
100{
101  mode_t mode;
102  int fd;
103  struct stat statbuf;
104
105  mode = 0;
106  if (flags & O_CREAT)
107    {
108      va_list arg;
109      va_start (arg, flags);
110
111      /* If mode_t is narrower than int, use the promoted type (int),
112	 not mode_t.  Use sizeof to guess whether mode_t is narrower;
113	 we don't know of any practical counterexamples.  */
114      mode = (sizeof (mode_t) < sizeof (int)
115	      ? va_arg (arg, int)
116	      : va_arg (arg, mode_t));
117
118      va_end (arg);
119    }
120  fd = open (filename, flags, mode);
121  if (fd >= 0)
122    {
123      ensure_dirs_slot (fd);
124      if (fd < dirs_allocated
125	  && fstat (fd, &statbuf) >= 0 && S_ISDIR (statbuf.st_mode))
126	{
127	  dirs[fd].name = canonicalize_file_name (filename);
128	  if (dirs[fd].name == NULL)
129	    dirs[fd].saved_errno = errno;
130	}
131    }
132  return fd;
133}
134
135/* Override opendir() and closedir(), to keep track of the open file
136   descriptors.  Needed because there is a function dirfd().  */
137
138int
139closedir (DIR *dp)
140#undef closedir
141{
142  int fd = dirfd (dp);
143  int retval = closedir (dp);
144
145  if (retval >= 0 && fd >= 0 && fd < dirs_allocated)
146    {
147      if (dirs[fd].name != NULL)
148	free (dirs[fd].name);
149      dirs[fd].name = NULL;
150      dirs[fd].saved_errno = ENOTDIR;
151    }
152  return retval;
153}
154
155DIR *
156opendir (const char *filename)
157#undef opendir
158{
159  DIR *dp;
160
161  dp = opendir (filename);
162  if (dp != NULL)
163    {
164      int fd = dirfd (dp);
165      if (fd >= 0)
166	{
167	  ensure_dirs_slot (fd);
168	  if (fd < dirs_allocated)
169	    {
170	      dirs[fd].name = canonicalize_file_name (filename);
171	      if (dirs[fd].name == NULL)
172	        dirs[fd].saved_errno = errno;
173	    }
174	}
175    }
176  return dp;
177}
178
179/* Override dup() and dup2(), to keep track of open file descriptors.  */
180
181int
182dup (int oldfd)
183#undef dup
184{
185  int newfd = dup (oldfd);
186
187  if (oldfd >= 0 && newfd >= 0)
188    {
189      ensure_dirs_slot (newfd);
190      if (newfd < dirs_allocated)
191	{
192	  if (oldfd < dirs_allocated)
193	    {
194	      if (dirs[oldfd].name != NULL)
195		{
196		  dirs[newfd].name = strdup (dirs[oldfd].name);
197		  if (dirs[newfd].name == NULL)
198		    dirs[newfd].saved_errno = ENOMEM;
199		}
200	      else
201		{
202		  dirs[newfd].name = NULL;
203		  dirs[newfd].saved_errno = dirs[oldfd].saved_errno;
204		}
205	    }
206	  else
207	    {
208	      dirs[newfd].name = NULL;
209	      dirs[newfd].saved_errno = ENOMEM;
210	    }
211	}
212    }
213  return newfd;
214}
215
216int
217dup2 (int oldfd, int newfd)
218#undef dup2
219{
220  int retval = dup2 (oldfd, newfd);
221
222  if (retval >= 0 && oldfd >= 0 && newfd >= 0 && newfd != oldfd)
223    {
224      ensure_dirs_slot (newfd);
225      if (newfd < dirs_allocated)
226	{
227	  if (oldfd < dirs_allocated)
228	    {
229	      if (dirs[oldfd].name != NULL)
230		{
231		  dirs[newfd].name = strdup (dirs[oldfd].name);
232		  if (dirs[newfd].name == NULL)
233		    dirs[newfd].saved_errno = ENOMEM;
234		}
235	      else
236		{
237		  dirs[newfd].name = NULL;
238		  dirs[newfd].saved_errno = dirs[oldfd].saved_errno;
239		}
240	    }
241	  else
242	    {
243	      dirs[newfd].name = NULL;
244	      dirs[newfd].saved_errno = ENOMEM;
245	    }
246	}
247    }
248  return retval;
249}
250
251/* Implement fchdir() in terms of chdir().  */
252
253int
254fchdir (int fd)
255{
256  if (fd >= 0)
257    {
258      if (fd < dirs_allocated)
259	{
260	  if (dirs[fd].name != NULL)
261	    return chdir (dirs[fd].name);
262	  else
263	    {
264	      errno = dirs[fd].saved_errno;
265	      return -1;
266	    }
267	}
268      else
269	{
270	  errno = ENOMEM;
271	  return -1;
272	}
273    }
274  else
275    {
276      errno = EBADF;
277      return -1;
278    }
279}
280