/* dir.cc: Posix directory-related routines Copyright 1996, 1997, 1998, 1999, 2000, 2001 Cygnus Solutions. 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 #include #include #include #define _COMPILING_NEWLIB #include #include "sync.h" #include "sigproc.h" #include "pinfo.h" #include "cygerrno.h" #include "fhandler.h" #include "perprocess.h" #include "path.h" #include "security.h" #include "dtable.h" #include "cygheap.h" /* Cygwin internal */ /* Return whether the directory of a file is writable. Return 1 if it is. Otherwise, return 0, and set errno appropriately. */ int __stdcall writable_directory (const char *file) { #if 0 char dir[strlen (file) + 1]; strcpy (dir, file); const char *usedir; char *slash = strrchr (dir, '\\'); if (slash == NULL) usedir = "."; else if (slash == dir) { usedir = "\\"; } else { *slash = '\0'; usedir = dir; } int acc = access (usedir, W_OK); return acc == 0; #else return 1; #endif } /* opendir: POSIX 5.1.2.1 */ extern "C" DIR * opendir (const char *dirname) { int len; DIR *dir; DIR *res = 0; struct stat statbuf; path_conv real_dirname (dirname, PC_SYM_FOLLOW | PC_FULL); if (real_dirname.error) { set_errno (real_dirname.error); goto failed; } if (stat (cygheap->root.length () ? dirname : real_dirname.get_win32 (), &statbuf) == -1) goto failed; if (!(statbuf.st_mode & S_IFDIR)) { set_errno (ENOTDIR); goto failed; } len = strlen (real_dirname.get_win32 ()); if (len > MAX_PATH - 3) { set_errno (ENAMETOOLONG); goto failed; } if ((dir = (DIR *) malloc (sizeof (DIR))) == NULL) { set_errno (ENOMEM); goto failed; } if ((dir->__d_dirname = (char *) malloc (len + 3)) == NULL) { free (dir); set_errno (ENOMEM); goto failed; } if ((dir->__d_dirent = (struct dirent *) malloc (sizeof (struct dirent))) == NULL) { free (dir->__d_dirname); free (dir); set_errno (ENOMEM); goto failed; } strcpy (dir->__d_dirname, real_dirname.get_win32 ()); /* FindFirstFile doesn't seem to like duplicate /'s. */ len = strlen (dir->__d_dirname); if (len == 0 || SLASH_P (dir->__d_dirname[len - 1])) strcat (dir->__d_dirname, "*"); else strcat (dir->__d_dirname, "\\*"); /**/ dir->__d_cookie = __DIRENT_COOKIE; dir->__d_u.__d_data.__handle = INVALID_HANDLE_VALUE; dir->__d_position = 0; dir->__d_dirhash = statbuf.st_ino; res = dir; failed: syscall_printf ("%p = opendir (%s)", res, dirname); return res; } /* readdir: POSIX 5.1.2.1 */ extern "C" struct dirent * readdir (DIR * dir) { WIN32_FIND_DATA buf; HANDLE handle; struct dirent *res = NULL; if (dir->__d_cookie != __DIRENT_COOKIE) { set_errno (EBADF); syscall_printf ("%p = readdir (%p)", res, dir); return res; } if (dir->__d_u.__d_data.__handle == INVALID_HANDLE_VALUE && dir->__d_position == 0) { handle = FindFirstFileA (dir->__d_dirname, &buf); DWORD lasterr = GetLastError (); dir->__d_u.__d_data.__handle = handle; if (handle == INVALID_HANDLE_VALUE && (lasterr != ERROR_NO_MORE_FILES)) { seterrno_from_win_error (__FILE__, __LINE__, lasterr); return res; } } else if (!FindNextFileA (dir->__d_u.__d_data.__handle, &buf)) { DWORD lasterr = GetLastError (); (void) FindClose (dir->__d_u.__d_data.__handle); dir->__d_u.__d_data.__handle = INVALID_HANDLE_VALUE; /* POSIX says you shouldn't set errno when readdir can't find any more files; so, if another error we leave it set. */ if (lasterr != ERROR_NO_MORE_FILES) seterrno_from_win_error (__FILE__, __LINE__, lasterr); syscall_printf ("%p = readdir (%p)", res, dir); return res; } /* We get here if `buf' contains valid data. */ strcpy (dir->__d_dirent->d_name, buf.cFileName); /* Check for Windows shortcut. If it's a Cygwin or U/WIN symlink, drop the .lnk suffix. */ if (buf.dwFileAttributes & FILE_ATTRIBUTE_READONLY) { char *c = dir->__d_dirent->d_name; int len = strlen (c); if (strcasematch (c + len - 4, ".lnk")) { char fbuf[MAX_PATH + 1]; strcpy (fbuf, dir->__d_dirname); strcpy (fbuf + strlen (fbuf) - 1, dir->__d_dirent->d_name); path_conv fpath (fbuf, PC_SYM_NOFOLLOW); if (fpath.issymlink ()) c[len - 4] = '\0'; } } /* Compute d_ino by combining filename hash with the directory hash (which was stored in dir->__d_dirhash when opendir was called). */ if (buf.cFileName[0] == '.') { if (buf.cFileName[1] == '\0') dir->__d_dirent->d_ino = dir->__d_dirhash; else if (buf.cFileName[1] != '.' || buf.cFileName[2] != '\0') goto hashit; else { char *p, up[strlen (dir->__d_dirname) + 1]; strcpy (up, dir->__d_dirname); if (!(p = strrchr (up, '\\'))) goto hashit; *p = '\0'; if (!(p = strrchr (up, '\\'))) dir->__d_dirent->d_ino = hash_path_name (0, "."); else { *p = '\0'; dir->__d_dirent->d_ino = hash_path_name (0, up); } } } else { hashit: ino_t dino = hash_path_name (dir->__d_dirhash, "\\"); dir->__d_dirent->d_ino = hash_path_name (dino, buf.cFileName); } ++dir->__d_position; res = dir->__d_dirent; syscall_printf ("%p = readdir (%p) (%s)", &dir->__d_dirent, dir, buf.cFileName); return res; } /* telldir */ extern "C" off_t telldir (DIR * dir) { if (dir->__d_cookie != __DIRENT_COOKIE) return 0; return dir->__d_position; } /* seekdir */ extern "C" void seekdir (DIR * dir, off_t loc) { if (dir->__d_cookie != __DIRENT_COOKIE) return; rewinddir (dir); while (loc > dir->__d_position) if (! readdir (dir)) break; } /* rewinddir: POSIX 5.1.2.1 */ extern "C" void rewinddir (DIR * dir) { syscall_printf ("rewinddir (%p)", dir); if (dir->__d_cookie != __DIRENT_COOKIE) return; if (dir->__d_u.__d_data.__handle != INVALID_HANDLE_VALUE) { (void) FindClose (dir->__d_u.__d_data.__handle); dir->__d_u.__d_data.__handle = INVALID_HANDLE_VALUE; } dir->__d_position = 0; } /* closedir: POSIX 5.1.2.1 */ extern "C" int closedir (DIR * dir) { if (dir->__d_cookie != __DIRENT_COOKIE) { set_errno (EBADF); syscall_printf ("-1 = closedir (%p)", dir); return -1; } if (dir->__d_u.__d_data.__handle != INVALID_HANDLE_VALUE && FindClose (dir->__d_u.__d_data.__handle) == 0) { __seterrno (); syscall_printf ("-1 = closedir (%p)", dir); return -1; } /* Reset the marker in case the caller tries to use `dir' again. */ dir->__d_cookie = 0; free (dir->__d_dirname); free (dir->__d_dirent); free (dir); syscall_printf ("0 = closedir (%p)", dir); return 0; } /* mkdir: POSIX 5.4.1.1 */ extern "C" int mkdir (const char *dir, mode_t mode) { int res = -1; path_conv real_dir (dir, PC_SYM_NOFOLLOW); if (real_dir.error) { set_errno (real_dir.case_clash ? ECASECLASH : real_dir.error); goto done; } nofinalslash(real_dir.get_win32 (), real_dir.get_win32 ()); if (! writable_directory (real_dir.get_win32 ())) goto done; if (CreateDirectoryA (real_dir.get_win32 (), 0)) { set_file_attribute (real_dir.has_acls (), real_dir.get_win32 (), S_IFDIR | ((mode & 0777) & ~cygheap->umask)); res = 0; } else __seterrno (); done: syscall_printf ("%d = mkdir (%s, %d)", res, dir, mode); return res; } /* rmdir: POSIX 5.5.2.1 */ extern "C" int rmdir (const char *dir) { int res = -1; path_conv real_dir (dir, PC_SYM_NOFOLLOW); if (real_dir.error) { set_errno (real_dir.error); goto done; } if (RemoveDirectoryA (real_dir.get_win32 ())) { /* RemoveDirectory on a samba drive doesn't return an error if the directory can't be removed because it's not empty. Checking for existence afterwards keeps us informed about success. */ if (GetFileAttributesA (real_dir.get_win32 ()) != (DWORD) -1) set_errno (ENOTEMPTY); else res = 0; } else if (GetLastError() == ERROR_ACCESS_DENIED) { /* Under Windows 9X or on a samba share, ERROR_ACCESS_DENIED is returned if you try to remove a file. On 9X the same error is returned if you try to remove a non-empty directory. */ if (real_dir.file_attributes () != (DWORD) -1 && !(real_dir.file_attributes () & FILE_ATTRIBUTE_DIRECTORY)) set_errno (ENOTDIR); else if (os_being_run != winNT) set_errno (ENOTEMPTY); else __seterrno (); } else __seterrno (); done: syscall_printf ("%d = rmdir (%s)", res, dir); return res; }