| /* dir.cc: Posix directory-related routines |
| |
| This file is part of Cygwin. |
| |
| This software is a copyrighted work licensed under the terms of the |
| Cygwin license. Please consult the file "CYGWIN_LICENSE" for |
| details. */ |
| |
| #include "winsup.h" |
| #include <stdlib.h> |
| #include <unistd.h> |
| |
| #define _COMPILING_NEWLIB |
| #include <dirent.h> |
| |
| #include "cygerrno.h" |
| #include "security.h" |
| #include "path.h" |
| #include "fhandler.h" |
| #include "dtable.h" |
| #include "cygheap.h" |
| #include "cygtls.h" |
| #include "tls_pbuf.h" |
| |
| extern "C" int |
| dirfd (DIR *dir) |
| { |
| __try |
| { |
| if (dir->__d_cookie == __DIRENT_COOKIE) |
| return dir->__d_fd; |
| syscall_printf ("-1 = dirfd (%p)", dir); |
| set_errno (EINVAL); |
| } |
| __except (EFAULT) {} |
| __endtry |
| return -1; |
| } |
| |
| /* Symbol kept for backward compatibility. Don't remove. Don't declare |
| in public header file. */ |
| extern "C" DIR * |
| __opendir_with_d_ino (const char *name) |
| { |
| return opendir (name); |
| } |
| |
| /* opendir: POSIX 5.1.2.1 */ |
| extern "C" DIR * |
| opendir (const char *name) |
| { |
| fhandler_base *fh; |
| DIR *res; |
| |
| fh = build_fh_name (name, PC_SYM_FOLLOW); |
| if (!fh) |
| res = NULL; |
| else if (fh->error ()) |
| { |
| set_errno (fh->error ()); |
| res = NULL; |
| } |
| else if (fh->exists ()) |
| res = fh->opendir (-1); |
| else |
| { |
| set_errno (ENOENT); |
| res = NULL; |
| } |
| |
| if (!res && fh) |
| delete fh; |
| /* Applications calling flock(2) on dirfd(fd) need this... */ |
| if (res && !fh->nohandle ()) |
| fh->set_unique_id (); |
| return res; |
| } |
| |
| extern "C" DIR * |
| fdopendir (int fd) |
| { |
| DIR *res = NULL; |
| |
| cygheap_fdget cfd (fd); |
| if (cfd >= 0) |
| res = cfd->opendir (fd); |
| return res; |
| } |
| |
| static int |
| readdir_worker (DIR *dir, dirent *de) |
| { |
| int res = 0; |
| |
| __try |
| { |
| if (dir->__d_cookie != __DIRENT_COOKIE) |
| { |
| syscall_printf ("%p = readdir (%p)", NULL, dir); |
| res = EBADF; |
| __leave; |
| } |
| |
| de->d_ino = 0; |
| de->d_type = DT_UNKNOWN; |
| memset (&de->__d_unused1, 0, sizeof (de->__d_unused1)); |
| |
| res = ((fhandler_base *) dir->__fh)->readdir (dir, de); |
| |
| if (res == ENMFILE) |
| { |
| if (!(dir->__flags & dirent_saw_dot)) |
| { |
| strcpy (de->d_name, "."); |
| dir->__flags |= dirent_saw_dot; |
| dir->__d_position++; |
| res = 0; |
| } |
| else if (!(dir->__flags & dirent_saw_dot_dot)) |
| { |
| strcpy (de->d_name, ".."); |
| dir->__flags |= dirent_saw_dot_dot; |
| dir->__d_position++; |
| res = 0; |
| } |
| } |
| |
| if (!res && !de->d_ino) |
| { |
| bool is_dot = false; |
| bool is_dot_dot = false; |
| |
| if (de->d_name[0] == '.') |
| { |
| if (de->d_name[1] == '\0') |
| is_dot = true; |
| else if (de->d_name[1] == '.' && de->d_name[2] == '\0') |
| is_dot_dot = true; |
| } |
| |
| if (is_dot_dot && !(dir->__flags & dirent_isroot)) |
| de->d_ino = readdir_get_ino (((fhandler_base *) |
| dir->__fh)->get_name (), |
| true); |
| else |
| { |
| /* Compute d_ino by combining filename hash with directory hash. */ |
| de->d_ino = ((fhandler_base *) dir->__fh)->get_ino (); |
| if (!is_dot && !is_dot_dot) |
| { |
| PUNICODE_STRING w32name = |
| ((fhandler_base *) dir->__fh)->pc.get_nt_native_path (); |
| DWORD devn = ((fhandler_base *) dir->__fh)->get_device (); |
| /* Paths below /proc don't have a Win32 pendant. */ |
| if (isproc_dev (devn)) |
| de->d_ino = hash_path_name (de->d_ino, L"/"); |
| else if (w32name->Buffer[w32name->Length / sizeof (WCHAR) - 1] |
| != L'\\') |
| de->d_ino = hash_path_name (de->d_ino, L"\\"); |
| de->d_ino = hash_path_name (de->d_ino, de->d_name); |
| } |
| } |
| } |
| /* This fills out the old 32 bit d_ino field for old applications, |
| build under Cygwin before 1.5.x. */ |
| de->__d_internal1 = de->d_ino; |
| } |
| __except (NO_ERROR) |
| { |
| res = EFAULT; |
| } |
| __endtry |
| return res; |
| } |
| |
| /* readdir: POSIX 5.1.2.1 */ |
| extern "C" struct dirent * |
| readdir (DIR *dir) |
| { |
| int res = readdir_worker (dir, dir->__d_dirent); |
| if (res == 0) |
| return dir->__d_dirent; |
| if (res != ENMFILE) |
| set_errno (res); |
| return NULL; |
| } |
| |
| extern "C" int |
| readdir_r (DIR *__restrict dir, dirent *__restrict de, dirent **__restrict ode) |
| { |
| int res = readdir_worker (dir, de); |
| if (!res) |
| *ode = de; |
| else |
| { |
| *ode = NULL; |
| if (res == ENMFILE) |
| res = 0; |
| } |
| return res; |
| } |
| |
| /* telldir */ |
| extern "C" long |
| telldir (DIR *dir) |
| { |
| __try |
| { |
| if (dir->__d_cookie == __DIRENT_COOKIE) |
| return ((fhandler_base *) dir->__fh)->telldir (dir); |
| set_errno (EBADF); |
| } |
| __except (EFAULT) {} |
| __endtry |
| return -1; |
| } |
| |
| /* telldir was never defined using off_t in POSIX, only in early versions |
| of glibc. We have to keep the function in as entry point for backward |
| compatibility. */ |
| extern "C" off_t |
| telldir64 (DIR *dir) |
| { |
| return (off_t) telldir (dir); |
| } |
| |
| /* seekdir */ |
| extern "C" void |
| seekdir (DIR *dir, long loc) |
| { |
| __try |
| { |
| if (dir->__d_cookie == __DIRENT_COOKIE) |
| { |
| dir->__flags &= dirent_info_mask; |
| ((fhandler_base *) dir->__fh)->seekdir (dir, loc); |
| } |
| set_errno (EINVAL); /* Diagnosis */ |
| } |
| __except (EFAULT) {} |
| __endtry |
| } |
| |
| /* seekdir was never defined using off_t in POSIX, only in early versions |
| of glibc. We have to keep the function in as entry point for backward |
| compatibility. */ |
| extern "C" void |
| seekdir64 (DIR *dir, off_t loc) |
| { |
| seekdir (dir, (long) loc); |
| } |
| |
| /* rewinddir: POSIX 5.1.2.1 */ |
| extern "C" void |
| rewinddir (DIR *dir) |
| { |
| __try |
| { |
| if (dir->__d_cookie == __DIRENT_COOKIE) |
| { |
| dir->__flags &= dirent_info_mask; |
| ((fhandler_base *) dir->__fh)->rewinddir (dir); |
| } |
| set_errno (EINVAL); /* Diagnosis */ |
| } |
| __except (EFAULT) {} |
| __endtry |
| } |
| |
| /* closedir: POSIX 5.1.2.1 */ |
| extern "C" int |
| closedir (DIR *dir) |
| { |
| __try |
| { |
| if (dir->__d_cookie == __DIRENT_COOKIE) |
| { |
| /* Reset the marker in case the caller tries to use `dir' again. */ |
| dir->__d_cookie = 0; |
| |
| int res = ((fhandler_base *) dir->__fh)->closedir (dir); |
| |
| close (dir->__d_fd); |
| free (dir->__d_dirname); |
| free (dir->__d_dirent); |
| free (dir); |
| syscall_printf ("%R = closedir(%p)", res, dir); |
| return res; |
| } |
| set_errno (EBADF); |
| } |
| __except (EFAULT) {} |
| __endtry |
| syscall_printf ("%R = closedir(%p)", -1, dir); |
| return -1; |
| } |
| |
| /* mkdir: POSIX 5.4.1.1 */ |
| extern "C" int |
| mkdir (const char *dir, mode_t mode) |
| { |
| int res = -1; |
| fhandler_base *fh = NULL; |
| tmp_pathbuf tp; |
| |
| __try |
| { |
| /* POSIX says mkdir("symlink-to-missing/") should create the |
| directory "missing", but Linux rejects it with EEXIST. Copy |
| Linux behavior for now. */ |
| |
| if (!*dir) |
| { |
| set_errno (ENOENT); |
| __leave; |
| } |
| if (isdirsep (dir[strlen (dir) - 1])) |
| { |
| /* This converts // to /, but since both give EEXIST, we're okay. */ |
| char *buf; |
| char *p = stpcpy (buf = tp.c_get (), dir) - 1; |
| dir = buf; |
| while (p > dir && isdirsep (*p)) |
| *p-- = '\0'; |
| } |
| if (!(fh = build_fh_name (dir, PC_SYM_NOFOLLOW))) |
| __leave; /* errno already set */; |
| |
| if (fh->error ()) |
| { |
| debug_printf ("got %d error from build_fh_name", fh->error ()); |
| set_errno (fh->error ()); |
| } |
| else if (has_dot_last_component (dir, true)) |
| set_errno (fh->exists () ? EEXIST : ENOENT); |
| else if (!fh->mkdir (mode)) |
| res = 0; |
| delete fh; |
| } |
| __except (EFAULT) {} |
| __endtry |
| syscall_printf ("%R = mkdir(%s, %d)", res, dir, mode); |
| return res; |
| } |
| |
| /* rmdir: POSIX 5.5.2.1 */ |
| extern "C" int |
| rmdir (const char *dir) |
| { |
| int res = -1; |
| fhandler_base *fh = NULL; |
| |
| __try |
| { |
| if (!(fh = build_fh_name (dir, PC_SYM_NOFOLLOW))) |
| __leave; /* errno already set */; |
| |
| if (fh->error ()) |
| { |
| debug_printf ("got %d error from build_fh_name", fh->error ()); |
| set_errno (fh->error ()); |
| } |
| else if (!fh->exists ()) |
| set_errno (ENOENT); |
| else if (has_dot_last_component (dir, false)) |
| set_errno (EINVAL); |
| else if (isdev_dev (fh->dev ())) |
| set_errno (ENOTEMPTY); |
| else if (!fh->rmdir ()) |
| res = 0; |
| delete fh; |
| } |
| __except (EFAULT) {} |
| __endtry |
| syscall_printf ("%R = rmdir(%s)", res, dir); |
| return res; |
| } |