Cygwin: flock: Fix overlap handling in lf_setlock() and lf_clearlock()
[newlib-cygwin.git] / winsup / cygwin / dir.cc
blobdf0749197dbfe573b1dd7a872c4001587d3b1177
1 /* dir.cc: Posix directory-related routines
3 This file is part of Cygwin.
5 This software is a copyrighted work licensed under the terms of the
6 Cygwin license. Please consult the file "CYGWIN_LICENSE" for
7 details. */
9 #include "winsup.h"
10 #include <stdlib.h>
11 #include <unistd.h>
12 #include <sys/stat.h>
14 #define _LIBC
15 #include <dirent.h>
17 #include "cygerrno.h"
18 #include "security.h"
19 #include "path.h"
20 #include "fhandler.h"
21 #include "dtable.h"
22 #include "cygheap.h"
23 #include "cygtls.h"
24 #include "tls_pbuf.h"
26 extern "C" int
27 dirfd (DIR *dir)
29 __try
31 if (dir->__d_cookie == __DIRENT_COOKIE)
32 return dir->__d_fd;
33 syscall_printf ("-1 = dirfd (%p)", dir);
34 set_errno (EINVAL);
36 __except (EFAULT) {}
37 __endtry
38 return -1;
41 /* Symbol kept for backward compatibility. Don't remove. Don't declare
42 in public header file. */
43 extern "C" DIR *
44 __opendir_with_d_ino (const char *name)
46 return opendir (name);
49 /* opendir: POSIX 5.1.2.1 */
50 extern "C" DIR *
51 opendir (const char *name)
53 fhandler_base *fh;
54 DIR *res;
56 fh = build_fh_name (name, PC_SYM_FOLLOW);
57 if (!fh)
58 res = NULL;
59 else if (fh->error ())
61 set_errno (fh->error ());
62 res = NULL;
64 else if (!fh->exists ())
66 set_errno (ENOENT);
67 res = NULL;
69 else if (!fh->pc.isdir ())
71 set_errno (ENOTDIR);
72 res = NULL;
74 else
75 res = fh->opendir (-1);
77 if (!res && fh)
78 delete fh;
79 /* Applications calling flock(2) on dirfd(fd) need this... */
80 if (res && !fh->nohandle ())
81 fh->set_unique_id ();
82 return res;
85 extern "C" DIR *
86 fdopendir (int fd)
88 DIR *res = NULL;
90 cygheap_fdget cfd (fd);
91 if (cfd >= 0)
92 res = cfd->opendir (fd);
93 return res;
96 static int
97 readdir_worker (DIR *dir, dirent *de)
99 int res = 0;
101 __try
103 if (dir->__d_cookie != __DIRENT_COOKIE)
105 syscall_printf ("%p = readdir (%p)", NULL, dir);
106 res = EBADF;
107 __leave;
110 de->d_ino = 0;
111 de->d_type = DT_UNKNOWN;
112 de->d_reclen = sizeof *de;
113 memset (&de->__d_unused1, 0, sizeof (de->__d_unused1));
115 res = ((fhandler_base *) dir->__fh)->readdir (dir, de);
117 if (res == ENMFILE)
119 if (!(dir->__flags & dirent_saw_dot))
121 strcpy (de->d_name, ".");
122 dir->__flags |= dirent_saw_dot;
123 dir->__d_position++;
124 res = 0;
126 else if (!(dir->__flags & dirent_saw_dot_dot))
128 strcpy (de->d_name, "..");
129 dir->__flags |= dirent_saw_dot_dot;
130 dir->__d_position++;
131 res = 0;
135 if (!res && !de->d_ino)
137 bool is_dot = false;
138 bool is_dot_dot = false;
140 if (de->d_name[0] == '.')
142 if (de->d_name[1] == '\0')
143 is_dot = true;
144 else if (de->d_name[1] == '.' && de->d_name[2] == '\0')
145 is_dot_dot = true;
148 if (is_dot_dot && !(dir->__flags & dirent_isroot))
149 de->d_ino = readdir_get_ino (((fhandler_base *)
150 dir->__fh)->get_name (),
151 true);
152 else
154 /* Compute d_ino by combining filename hash with directory hash. */
155 de->d_ino = ((fhandler_base *) dir->__fh)->get_ino ();
156 if (!is_dot && !is_dot_dot)
158 PUNICODE_STRING w32name =
159 ((fhandler_base *) dir->__fh)->pc.get_nt_native_path ();
160 DWORD devn = ((fhandler_base *) dir->__fh)->get_device ();
161 /* Paths below /proc don't have a Win32 pendant. */
162 if (isproc_dev (devn))
163 de->d_ino = hash_path_name (de->d_ino, L"/");
164 else if (w32name->Buffer[w32name->Length / sizeof (WCHAR) - 1]
165 != L'\\')
166 de->d_ino = hash_path_name (de->d_ino, L"\\");
167 de->d_ino = hash_path_name (de->d_ino, de->d_name);
172 __except (NO_ERROR)
174 res = EFAULT;
176 __endtry
177 return res;
180 /* readdir: POSIX 5.1.2.1 */
181 extern "C" struct dirent *
182 readdir (DIR *dir)
184 int res = readdir_worker (dir, dir->__d_dirent);
185 if (res == 0)
186 return dir->__d_dirent;
187 if (res != ENMFILE)
188 set_errno (res);
189 return NULL;
192 extern "C" int
193 readdir_r (DIR *__restrict dir, dirent *__restrict de, dirent **__restrict ode)
195 int res = readdir_worker (dir, de);
196 if (!res)
197 *ode = de;
198 else
200 *ode = NULL;
201 if (res == ENMFILE)
202 res = 0;
204 return res;
207 extern "C"
208 ssize_t posix_getdents(int fd, void *buf, size_t nbytes, int flags)
210 struct posix_dent *dent_buf, *src;
211 ssize_t cnt = 0;
213 cygheap_fdget cfd (fd);
214 /* Valid descriptor? */
215 if (cfd < 0)
216 return -1;
217 /* Valid flags? Right now only DT_FORCE_TYPE is defined */
218 if ((flags & ~DT_FORCE_TYPE) != 0)
220 set_errno (EINVAL);
221 return -1;
223 /* Create type-safe buffer pointer */
224 dent_buf = (struct posix_dent *) buf;
225 /* Check if nbytes is big enough to fit at least one struct posix_dent */
226 if (nbytes < sizeof (struct posix_dent))
228 set_errno (EINVAL);
229 return -1;
231 /* Create internal DIR * on first invocation */
232 if (!cfd->getdents_dir ())
234 cfd->getdents_dir (cfd->opendir (fd));
235 if (!cfd->getdents_dir ())
236 return -1;
238 /* Now loop until EOF or buf is full */
239 while (nbytes >= sizeof (struct posix_dent))
241 /* Our struct posix_dent is identical to struct dirent */
242 src = (struct posix_dent *) readdir (cfd->getdents_dir ());
243 if (!src)
244 break;
245 /* Handle the suggested DT_FORCE_TYPE flag */
246 if (src->d_type == DT_UNKNOWN && (flags & DT_FORCE_TYPE))
248 struct stat st;
250 if (!fstatat (fd, src->d_name, &st, AT_SYMLINK_NOFOLLOW))
251 src->d_type = IFTODT (st.st_mode);
253 *dent_buf++ = *src;
254 ++cnt;
255 nbytes -= sizeof (struct posix_dent);
257 return cnt * sizeof (struct posix_dent);
260 /* telldir */
261 extern "C" long
262 telldir (DIR *dir)
264 __try
266 if (dir->__d_cookie == __DIRENT_COOKIE)
267 return ((fhandler_base *) dir->__fh)->telldir (dir);
268 set_errno (EBADF);
270 __except (EFAULT) {}
271 __endtry
272 return -1;
275 /* telldir was never defined using off_t in POSIX, only in early versions
276 of glibc. We have to keep the function in as entry point for backward
277 compatibility. */
278 extern "C" off_t
279 telldir64 (DIR *dir)
281 return (off_t) telldir (dir);
284 /* seekdir */
285 extern "C" void
286 seekdir (DIR *dir, long loc)
288 __try
290 if (dir->__d_cookie == __DIRENT_COOKIE)
292 dir->__flags &= dirent_info_mask;
293 ((fhandler_base *) dir->__fh)->seekdir (dir, loc);
296 __except (EFAULT) {}
297 __endtry
300 /* seekdir was never defined using off_t in POSIX, only in early versions
301 of glibc. We have to keep the function in as entry point for backward
302 compatibility. */
303 extern "C" void
304 seekdir64 (DIR *dir, off_t loc)
306 seekdir (dir, (long) loc);
309 /* rewinddir: POSIX 5.1.2.1 */
310 extern "C" void
311 rewinddir (DIR *dir)
313 __try
315 if (dir->__d_cookie == __DIRENT_COOKIE)
317 dir->__flags &= dirent_info_mask;
318 ((fhandler_base *) dir->__fh)->rewinddir (dir);
320 set_errno (EINVAL); /* Diagnosis */
322 __except (EFAULT) {}
323 __endtry
326 static int
327 closedir_worker (DIR *dir, int *fdret)
329 __try
331 if (dir->__d_cookie == __DIRENT_COOKIE)
333 /* Reset the marker in case the caller tries to use `dir' again. */
334 dir->__d_cookie = 0;
336 int res = ((fhandler_base *) dir->__fh)->closedir (dir);
338 if (fdret)
339 *fdret = dir->__d_fd;
340 else
341 close (dir->__d_fd);
343 free (dir->__d_dirname);
344 free (dir->__d_dirent);
345 free (dir);
346 return res;
348 set_errno (EBADF);
350 __except (EFAULT) {}
351 __endtry
352 return -1;
355 /* closedir: POSIX 5.1.2.1 */
356 extern "C" int
357 closedir (DIR *dir)
359 int res;
361 res = closedir_worker (dir, NULL);
362 syscall_printf ("%R = closedir(%p)", res, dir);
363 return res;
366 extern "C" int
367 fdclosedir (DIR *dir)
369 int fd = -1;
371 closedir_worker (dir, &fd);
372 syscall_printf ("%d = fdclosedir(%p)", fd, dir);
373 return fd;
376 /* mkdir: POSIX 5.4.1.1 */
377 extern "C" int
378 mkdir (const char *dir, mode_t mode)
380 int res = -1;
381 fhandler_base *fh = NULL;
382 tmp_pathbuf tp;
384 __try
386 if (!*dir)
388 set_errno (ENOENT);
389 __leave;
391 /* Following Linux, and intentionally ignoring POSIX, do not
392 resolve the last component of DIR if it is a symlink, even if
393 DIR has a trailing slash. Achieve this by stripping trailing
394 slashes or backslashes.
396 Exception: If DIR == 'x:' followed by one or more slashes or
397 backslashes, and if there's at least one backslash, assume
398 that the user is referring to the root directory of drive x.
399 Retain one backslash in this case. */
400 if (isdirsep (dir[strlen (dir) - 1]))
402 /* This converts // to /, but since both give EEXIST, we're okay. */
403 char *buf;
404 char *p = stpcpy (buf = tp.c_get (), dir) - 1;
405 bool msdos = false;
406 dir = buf;
407 while (p > dir && isdirsep (*p))
409 if (*p == '\\')
410 msdos = true;
411 *p-- = '\0';
413 if (msdos && p == dir + 1 && isdrive (dir))
414 p[1] = '\\';
416 if (!(fh = build_fh_name (dir, PC_SYM_NOFOLLOW)))
417 __leave; /* errno already set */;
419 if (fh->error ())
421 debug_printf ("got %d error from build_fh_name", fh->error ());
422 set_errno (fh->error ());
424 else if (fh->exists ())
425 set_errno (EEXIST);
426 else if (has_dot_last_component (dir, true))
427 set_errno (ENOENT);
428 else if (!fh->mkdir (mode))
429 res = 0;
430 delete fh;
432 __except (EFAULT) {}
433 __endtry
434 syscall_printf ("%R = mkdir(%s, %d)", res, dir, mode);
435 return res;
438 /* rmdir: POSIX 5.5.2.1 */
439 extern "C" int
440 rmdir (const char *dir)
442 int res = -1;
443 fhandler_base *fh = NULL;
444 tmp_pathbuf tp;
446 __try
448 if (!*dir)
450 set_errno (ENOENT);
451 __leave;
453 /* Following Linux, and intentionally ignoring POSIX, do not
454 resolve the last component of DIR if it is a symlink, even if
455 DIR has a trailing slash. Achieve this by stripping trailing
456 slashes or backslashes.
458 Exception: If DIR == 'x:' followed by one or more slashes or
459 backslashes, and if there's at least one backslash, assume
460 that the user is referring to the root directory of drive x.
461 Retain one backslash in this case. */
462 if (isdirsep (dir[strlen (dir) - 1]))
464 /* This converts // to /, but since both give ENOTEMPTY,
465 we're okay. */
466 char *buf;
467 char *p = stpcpy (buf = tp.c_get (), dir) - 1;
468 bool msdos = false;
469 dir = buf;
470 while (p > dir && isdirsep (*p))
472 if (*p == '\\')
473 msdos = true;
474 *p-- = '\0';
476 if (msdos && p == dir + 1 && isdrive (dir))
477 p[1] = '\\';
479 if (!(fh = build_fh_name (dir, PC_SYM_NOFOLLOW)))
480 __leave; /* errno already set */;
482 if (fh->error ())
484 debug_printf ("got %d error from build_fh_name", fh->error ());
485 set_errno (fh->error ());
487 else if (!fh->exists ())
488 set_errno (ENOENT);
489 else if (has_dot_last_component (dir, false))
490 set_errno (EINVAL);
491 else if (!fh->rmdir ())
492 res = 0;
493 delete fh;
495 __except (EFAULT) {}
496 __endtry
497 syscall_printf ("%R = rmdir(%s)", res, dir);
498 return res;