| /* fhandler_disk_file.cc |
| |
| 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 <winioctl.h> |
| #include <lm.h> |
| #include <stdlib.h> |
| #include <cygwin/acl.h> |
| #include <sys/statvfs.h> |
| #include "cygerrno.h" |
| #include "security.h" |
| #include "path.h" |
| #include "fhandler.h" |
| #include "dtable.h" |
| #include "cygheap.h" |
| #include "shared_info.h" |
| #include "pinfo.h" |
| #include "ntdll.h" |
| #include "tls_pbuf.h" |
| #include "devices.h" |
| #include "ldap.h" |
| |
| #define _COMPILING_NEWLIB |
| #include <dirent.h> |
| |
| enum __DIR_mount_type { |
| __DIR_mount_none = 0, |
| __DIR_mount_target, |
| __DIR_mount_virt_target |
| }; |
| |
| class __DIR_mounts |
| { |
| int count; |
| const char *parent_dir; |
| int parent_dir_len; |
| UNICODE_STRING mounts[MAX_MOUNTS]; |
| bool found[MAX_MOUNTS + 3]; |
| UNICODE_STRING cygdrive; |
| |
| #define __DIR_PROC (MAX_MOUNTS) |
| #define __DIR_CYGDRIVE (MAX_MOUNTS+1) |
| #define __DIR_DEV (MAX_MOUNTS+2) |
| |
| public: |
| __DIR_mounts (const char *posix_path) |
| : parent_dir (posix_path) |
| { |
| parent_dir_len = strlen (parent_dir); |
| count = mount_table->get_mounts_here (parent_dir, parent_dir_len, mounts, |
| &cygdrive); |
| rewind (); |
| } |
| ~__DIR_mounts () |
| { |
| for (int i = 0; i < count; ++i) |
| RtlFreeUnicodeString (&mounts[i]); |
| RtlFreeUnicodeString (&cygdrive); |
| } |
| /* For an entry within this dir, check if a mount point exists. */ |
| bool check_mount (PUNICODE_STRING fname) |
| { |
| if (parent_dir_len == 1) /* root dir */ |
| { |
| if (RtlEqualUnicodeString (fname, &ro_u_proc, FALSE)) |
| { |
| found[__DIR_PROC] = true; |
| return true; |
| } |
| if (RtlEqualUnicodeString (fname, &ro_u_dev, FALSE)) |
| { |
| found[__DIR_DEV] = true; |
| return true; |
| } |
| if (fname->Length / sizeof (WCHAR) == mount_table->cygdrive_len - 2 |
| && RtlEqualUnicodeString (fname, &cygdrive, FALSE)) |
| { |
| found[__DIR_CYGDRIVE] = true; |
| return true; |
| } |
| } |
| for (int i = 0; i < count; ++i) |
| if (RtlEqualUnicodeString (fname, &mounts[i], FALSE)) |
| { |
| found[i] = true; |
| return true; |
| } |
| return false; |
| } |
| /* On each call, add another mount point within this directory, which is |
| not backed by a real subdir. */ |
| __DIR_mount_type check_missing_mount (PUNICODE_STRING retname = NULL) |
| { |
| for (int i = 0; i < count; ++i) |
| if (!found[i]) |
| { |
| found[i] = true; |
| if (retname) |
| *retname = mounts[i]; |
| return __DIR_mount_target; |
| } |
| if (parent_dir_len == 1) /* root dir */ |
| { |
| if (!found[__DIR_PROC]) |
| { |
| found[__DIR_PROC] = true; |
| if (retname) |
| *retname = ro_u_proc; |
| return __DIR_mount_virt_target; |
| } |
| if (!found[__DIR_DEV]) |
| { |
| found[__DIR_DEV] = true; |
| if (retname) |
| *retname = ro_u_dev; |
| return __DIR_mount_virt_target; |
| } |
| if (!found[__DIR_CYGDRIVE]) |
| { |
| found[__DIR_CYGDRIVE] = true; |
| if (cygdrive.Length > 0) |
| { |
| if (retname) |
| *retname = cygdrive; |
| return __DIR_mount_virt_target; |
| } |
| } |
| } |
| return __DIR_mount_none; |
| } |
| void rewind () { memset (found, 0, sizeof found); } |
| }; |
| |
| inline bool |
| path_conv::isgood_inode (ino_t ino) const |
| { |
| /* If the FS doesn't support nonambiguous inode numbers anyway, bail out |
| immediately. */ |
| if (!hasgood_inode ()) |
| return false; |
| /* If the inode numbers are 64 bit numbers or if it's a local FS, they |
| are to be trusted. */ |
| if (ino > UINT32_MAX || !isremote ()) |
| return true; |
| /* The inode numbers returned from a remote NT4 NTFS are ephemeral |
| 32 bit numbers. */ |
| if (fs_is_ntfs ()) |
| return false; |
| /* Starting with version 3.5.4, Samba returns the real inode numbers, if |
| the file is on the same device as the root of the share (Samba function |
| get_FileIndex). 32 bit inode numbers returned by older versions (likely |
| < 3.0) are ephemeral. */ |
| if (fs_is_samba () && fs.samba_version () < 0x03050400) |
| return false; |
| /* Otherwise, trust the inode numbers unless proved otherwise. */ |
| return true; |
| } |
| |
| /* Check reparse point to determine if it should be treated as a |
| posix symlink or as a normal file/directory. Logic is explained |
| in detail in check_reparse_point_target in path.cc. */ |
| static inline bool |
| readdir_check_reparse_point (POBJECT_ATTRIBUTES attr, bool remote) |
| { |
| NTSTATUS status; |
| HANDLE reph; |
| IO_STATUS_BLOCK io; |
| tmp_pathbuf tp; |
| UNICODE_STRING symbuf; |
| bool ret = false; |
| |
| status = NtOpenFile (&reph, READ_CONTROL, attr, &io, FILE_SHARE_VALID_FLAGS, |
| FILE_OPEN_FOR_BACKUP_INTENT | FILE_OPEN_REPARSE_POINT); |
| if (NT_SUCCESS (status)) |
| { |
| PREPARSE_DATA_BUFFER rp = (PREPARSE_DATA_BUFFER) tp.c_get (); |
| ret = (check_reparse_point_target (reph, remote, rp, &symbuf) > 0); |
| NtClose (reph); |
| } |
| return ret; |
| } |
| |
| inline ino_t |
| path_conv::get_ino_by_handle (HANDLE hdl) |
| { |
| IO_STATUS_BLOCK io; |
| FILE_INTERNAL_INFORMATION fii; |
| |
| if (NT_SUCCESS (NtQueryInformationFile (hdl, &io, &fii, sizeof fii, |
| FileInternalInformation)) |
| && isgood_inode (fii.IndexNumber.QuadPart)) |
| return fii.IndexNumber.QuadPart; |
| return 0; |
| } |
| |
| /* For files on NFS shares, we request an EA of type NfsV3Attributes. |
| This returns the content of a struct fattr3 as defined in RFC 1813. |
| The content is the NFS equivalent of struct stat. so there's not much |
| to do here except for copying. */ |
| int __reg2 |
| fhandler_base::fstat_by_nfs_ea (struct stat *buf) |
| { |
| fattr3 *nfs_attr = pc.nfsattr (); |
| PWCHAR domain; |
| cyg_ldap cldap; |
| bool ldap_open = false; |
| |
| if (get_io_handle ()) |
| { |
| /* NFS stumbles over its own caching. If you write to the file, |
| a subsequent fstat does not return the actual size of the file, |
| but the size at the time the handle has been opened. Unless |
| access through another handle invalidates the caching within the |
| NFS client. */ |
| if (get_access () & GENERIC_WRITE) |
| FlushFileBuffers (get_io_handle ()); |
| pc.get_finfo (get_io_handle ()); |
| } |
| buf->st_dev = nfs_attr->fsid; |
| buf->st_ino = nfs_attr->fileid; |
| buf->st_mode = (nfs_attr->mode & 0xfff) |
| | nfs_type_mapping[nfs_attr->type & 7]; |
| buf->st_nlink = nfs_attr->nlink; |
| if (cygheap->pg.nss_pwd_db ()) |
| { |
| /* Try to map UNIX uid/gid to Cygwin uid/gid. If there's no mapping in |
| the cache, try to fetch it from the configured RFC 2307 domain (see |
| last comment in cygheap_domain_info::init() for more information) and |
| add it to the mapping cache. */ |
| buf->st_uid = cygheap->ugid_cache.get_uid (nfs_attr->uid); |
| if (buf->st_uid == ILLEGAL_UID) |
| { |
| uid_t map_uid = ILLEGAL_UID; |
| |
| domain = cygheap->dom.get_rfc2307_domain (); |
| if ((ldap_open = (cldap.open (domain) == NO_ERROR))) |
| map_uid = cldap.remap_uid (nfs_attr->uid); |
| if (map_uid == ILLEGAL_UID) |
| map_uid = MAP_UNIX_TO_CYGWIN_ID (nfs_attr->uid); |
| cygheap->ugid_cache.add_uid (nfs_attr->uid, map_uid); |
| buf->st_uid = map_uid; |
| } |
| } |
| else /* fake files being owned by current user. */ |
| buf->st_uid = myself->uid; |
| if (cygheap->pg.nss_grp_db ()) |
| { |
| /* See above */ |
| buf->st_gid = cygheap->ugid_cache.get_gid (nfs_attr->gid); |
| if (buf->st_gid == ILLEGAL_GID) |
| { |
| gid_t map_gid = ILLEGAL_GID; |
| |
| domain = cygheap->dom.get_rfc2307_domain (); |
| if ((ldap_open || cldap.open (domain) == NO_ERROR)) |
| map_gid = cldap.remap_gid (nfs_attr->gid); |
| if (map_gid == ILLEGAL_GID) |
| map_gid = MAP_UNIX_TO_CYGWIN_ID (nfs_attr->gid); |
| cygheap->ugid_cache.add_gid (nfs_attr->gid, map_gid); |
| buf->st_gid = map_gid; |
| } |
| } |
| else /* fake files being owned by current group. */ |
| buf->st_gid = myself->gid; |
| buf->st_rdev = makedev (nfs_attr->rdev.specdata1, |
| nfs_attr->rdev.specdata2); |
| buf->st_size = nfs_attr->size; |
| buf->st_blksize = PREFERRED_IO_BLKSIZE; |
| buf->st_blocks = (nfs_attr->used + S_BLKSIZE - 1) / S_BLKSIZE; |
| buf->st_atim.tv_sec = nfs_attr->atime.tv_sec; |
| buf->st_atim.tv_nsec = nfs_attr->atime.tv_nsec; |
| buf->st_mtim.tv_sec = nfs_attr->mtime.tv_sec; |
| buf->st_mtim.tv_nsec = nfs_attr->mtime.tv_nsec; |
| buf->st_ctim.tv_sec = nfs_attr->ctime.tv_sec; |
| buf->st_ctim.tv_nsec = nfs_attr->ctime.tv_nsec; |
| return 0; |
| } |
| |
| int __reg2 |
| fhandler_base::fstat_by_handle (struct stat *buf) |
| { |
| HANDLE h = get_stat_handle (); |
| NTSTATUS status = 0; |
| |
| /* If the file has been opened for other purposes than stat, we can't rely |
| on the information stored in pc.fai. So we overwrite them here. */ |
| if (get_io_handle ()) |
| { |
| status = pc.get_finfo (h); |
| if (!NT_SUCCESS (status)) |
| { |
| debug_printf ("%y = NtQueryInformationFile(%S, FileAllInformation)", |
| status, pc.get_nt_native_path ()); |
| return -1; |
| } |
| } |
| if (pc.isgood_inode (pc.fai ()->InternalInformation.IndexNumber.QuadPart)) |
| ino = pc.fai ()->InternalInformation.IndexNumber.QuadPart; |
| return fstat_helper (buf); |
| } |
| |
| int __reg2 |
| fhandler_base::fstat_by_name (struct stat *buf) |
| { |
| NTSTATUS status; |
| OBJECT_ATTRIBUTES attr; |
| IO_STATUS_BLOCK io; |
| UNICODE_STRING dirname; |
| UNICODE_STRING basename; |
| HANDLE dir; |
| struct { |
| FILE_ID_BOTH_DIR_INFORMATION fdi; |
| WCHAR buf[NAME_MAX + 1]; |
| } fdi_buf; |
| |
| if (!ino && pc.hasgood_inode () && !pc.has_buggy_fileid_dirinfo ()) |
| { |
| RtlSplitUnicodePath (pc.get_nt_native_path (), &dirname, &basename); |
| InitializeObjectAttributes (&attr, &dirname, pc.objcaseinsensitive (), |
| NULL, NULL); |
| status = NtOpenFile (&dir, SYNCHRONIZE | FILE_LIST_DIRECTORY, |
| &attr, &io, FILE_SHARE_VALID_FLAGS, |
| FILE_SYNCHRONOUS_IO_NONALERT |
| | FILE_OPEN_FOR_BACKUP_INTENT |
| | FILE_DIRECTORY_FILE); |
| if (!NT_SUCCESS (status)) |
| debug_printf ("%y = NtOpenFile(%S)", status, |
| pc.get_nt_native_path ()); |
| else |
| { |
| status = NtQueryDirectoryFile (dir, NULL, NULL, NULL, &io, |
| &fdi_buf.fdi, sizeof fdi_buf, |
| FileIdBothDirectoryInformation, |
| TRUE, &basename, TRUE); |
| NtClose (dir); |
| if (!NT_SUCCESS (status)) |
| debug_printf ("%y = NtQueryDirectoryFile(%S)", status, |
| pc.get_nt_native_path ()); |
| else |
| ino = fdi_buf.fdi.FileId.QuadPart; |
| } |
| } |
| return fstat_helper (buf); |
| } |
| |
| int __reg2 |
| fhandler_base::fstat_fs (struct stat *buf) |
| { |
| int res = -1; |
| int oret; |
| int open_flags = O_RDONLY | O_BINARY; |
| |
| if (get_stat_handle ()) |
| { |
| if (!nohandle () && !is_fs_special ()) |
| res = pc.fs_is_nfs () ? fstat_by_nfs_ea (buf) : fstat_by_handle (buf); |
| if (res) |
| res = fstat_by_name (buf); |
| return res; |
| } |
| /* First try to open with generic read access. This allows to read the file |
| in fstat_helper (when checking for executability) without having to |
| re-open it. Opening a file can take a lot of time on network drives |
| so we try to avoid that. */ |
| oret = open_fs (open_flags, 0); |
| if (!oret) |
| { |
| query_open (query_read_attributes); |
| oret = open_fs (open_flags, 0); |
| } |
| if (oret) |
| { |
| /* We now have a valid handle, regardless of the "nohandle" state. |
| Since fhandler_base::close only calls CloseHandle if !nohandle, |
| we have to set it to false before calling close and restore |
| the state afterwards. */ |
| res = pc.fs_is_nfs () ? fstat_by_nfs_ea (buf) : fstat_by_handle (buf); |
| bool no_handle = nohandle (); |
| nohandle (false); |
| close_fs (); |
| nohandle (no_handle); |
| set_io_handle (NULL); |
| } |
| if (res) |
| res = fstat_by_name (buf); |
| |
| return res; |
| } |
| |
| int __reg2 |
| fhandler_base::fstat_helper (struct stat *buf) |
| { |
| IO_STATUS_BLOCK st; |
| FILE_COMPRESSION_INFORMATION fci; |
| HANDLE h = get_stat_handle (); |
| PFILE_ALL_INFORMATION pfai = pc.fai (); |
| ULONG attributes = pc.file_attributes (); |
| |
| to_timestruc_t (&pfai->BasicInformation.LastAccessTime, &buf->st_atim); |
| to_timestruc_t (&pfai->BasicInformation.LastWriteTime, &buf->st_mtim); |
| /* If the ChangeTime is 0, the underlying FS doesn't support this timestamp |
| (FAT for instance). If so, it's faked using LastWriteTime. */ |
| to_timestruc_t (pfai->BasicInformation.ChangeTime.QuadPart |
| ? &pfai->BasicInformation.ChangeTime |
| : &pfai->BasicInformation.LastWriteTime, |
| &buf->st_ctim); |
| to_timestruc_t (&pfai->BasicInformation.CreationTime, &buf->st_birthtim); |
| buf->st_dev = get_dev (); |
| /* CV 2011-01-13: Observations on the Cygwin mailing list point to an |
| interesting behaviour in some Windows versions. Apparently the size of |
| a directory is computed at the time the directory is first scanned. This |
| can result in two subsequent NtQueryInformationFile calls to return size |
| 0 in the first call and size > 0 in the second call. This in turn can |
| affect applications like newer tar. |
| FIXME: Is the allocation size affected as well? */ |
| buf->st_size = pc.isdir () |
| ? 0 |
| : (off_t) pfai->StandardInformation.EndOfFile.QuadPart; |
| /* The number of links to a directory includes the number of subdirectories |
| in the directory, since all those subdirectories point to it. However, |
| this is painfully slow, so we do without it. */ |
| buf->st_nlink = pc.fai()->StandardInformation.NumberOfLinks; |
| |
| /* Enforce namehash as inode number on untrusted file systems. */ |
| buf->st_ino = ino ?: get_ino (); |
| |
| buf->st_blksize = PREFERRED_IO_BLKSIZE; |
| |
| if (buf->st_size == 0 |
| && pfai->StandardInformation.AllocationSize.QuadPart == 0LL) |
| /* File is empty and no blocks are preallocated. */ |
| buf->st_blocks = 0; |
| else if (pfai->StandardInformation.AllocationSize.QuadPart > 0LL) |
| /* A successful NtQueryInformationFile returns the allocation size |
| correctly for compressed and sparse files as well. |
| Allocation size 0 is ignored here because (at least) Windows 10 |
| 1607 always returns 0 for CompactOS compressed files. */ |
| buf->st_blocks = (pfai->StandardInformation.AllocationSize.QuadPart |
| + S_BLKSIZE - 1) / S_BLKSIZE; |
| else if ((pfai->StandardInformation.AllocationSize.QuadPart == 0LL |
| || ::has_attribute (attributes, FILE_ATTRIBUTE_COMPRESSED |
| | FILE_ATTRIBUTE_SPARSE_FILE)) |
| && h && !is_fs_special () |
| && !NtQueryInformationFile (h, &st, (PVOID) &fci, sizeof fci, |
| FileCompressionInformation)) |
| /* Otherwise we request the actual amount of bytes allocated for |
| compressed, sparsed and CompactOS files. */ |
| buf->st_blocks = (fci.CompressedFileSize.QuadPart + S_BLKSIZE - 1) |
| / S_BLKSIZE; |
| else |
| /* Otherwise compute no. of blocks from file size. */ |
| buf->st_blocks = (buf->st_size + S_BLKSIZE - 1) / S_BLKSIZE; |
| |
| buf->st_mode = 0; |
| /* Using a side effect: get_file_attributes checks for directory. |
| This is used, to set S_ISVTX, if needed. */ |
| if (pc.isdir ()) |
| buf->st_mode = S_IFDIR; |
| else if (pc.issymlink ()) |
| { |
| buf->st_size = pc.get_symlink_length (); |
| /* symlinks are everything for everyone! */ |
| buf->st_mode = S_IFLNK | S_IRWXU | S_IRWXG | S_IRWXO; |
| get_file_attribute (h, pc, NULL, |
| &buf->st_uid, &buf->st_gid); |
| goto done; |
| } |
| else if (pc.issocket ()) |
| buf->st_mode = S_IFSOCK; |
| |
| if (!get_file_attribute (is_fs_special () && !pc.issocket () ? NULL : h, pc, |
| &buf->st_mode, &buf->st_uid, &buf->st_gid)) |
| { |
| /* If read-only attribute is set, modify ntsec return value */ |
| if (::has_attribute (attributes, FILE_ATTRIBUTE_READONLY) |
| && !pc.isdir () && !pc.issymlink ()) |
| buf->st_mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH); |
| |
| if (buf->st_mode & S_IFMT) |
| /* nothing */; |
| else if (!is_fs_special ()) |
| buf->st_mode |= S_IFREG; |
| else |
| { |
| buf->st_dev = buf->st_rdev = dev (); |
| buf->st_mode = dev ().mode (); |
| buf->st_size = 0; |
| } |
| } |
| else |
| { |
| buf->st_mode |= STD_RBITS; |
| |
| if (!::has_attribute (attributes, FILE_ATTRIBUTE_READONLY)) |
| buf->st_mode |= STD_WBITS; |
| /* | S_IWGRP | S_IWOTH; we don't give write to group etc */ |
| |
| if (pc.isdir ()) |
| buf->st_mode |= S_IFDIR | STD_WBITS | STD_XBITS; |
| else if (buf->st_mode & S_IFMT) |
| /* nothing */; |
| else if (is_fs_special ()) |
| { |
| buf->st_dev = buf->st_rdev = dev (); |
| buf->st_mode = dev ().mode (); |
| buf->st_size = 0; |
| } |
| else |
| { |
| buf->st_mode |= S_IFREG; |
| /* Check suffix for executable file. */ |
| if (pc.exec_state () != is_executable) |
| { |
| PUNICODE_STRING path = pc.get_nt_native_path (); |
| |
| if (RtlEqualUnicodePathSuffix (path, &ro_u_exe, TRUE) |
| || RtlEqualUnicodePathSuffix (path, &ro_u_lnk, TRUE) |
| || RtlEqualUnicodePathSuffix (path, &ro_u_com, TRUE)) |
| pc.set_exec (); |
| } |
| /* No known suffix, check file header. This catches binaries and |
| shebang scripts. */ |
| if (pc.exec_state () == dont_know_if_executable) |
| { |
| OBJECT_ATTRIBUTES attr; |
| NTSTATUS status = 0; |
| IO_STATUS_BLOCK io; |
| |
| /* We have to re-open the file. Either the file is not opened |
| for reading, or the read will change the file position of the |
| original handle. */ |
| status = NtOpenFile (&h, SYNCHRONIZE | FILE_READ_DATA, |
| pc.init_reopen_attr (attr, h), &io, |
| FILE_SHARE_VALID_FLAGS, |
| FILE_OPEN_FOR_BACKUP_INTENT |
| | FILE_SYNCHRONOUS_IO_NONALERT); |
| if (!NT_SUCCESS (status)) |
| debug_printf ("%y = NtOpenFile(%S)", status, |
| pc.get_nt_native_path ()); |
| else |
| { |
| LARGE_INTEGER off = { QuadPart:0LL }; |
| char magic[3]; |
| |
| status = NtReadFile (h, NULL, NULL, NULL, |
| &io, magic, 3, &off, NULL); |
| if (!NT_SUCCESS (status)) |
| debug_printf ("%y = NtReadFile(%S)", status, |
| pc.get_nt_native_path ()); |
| else if (has_exec_chars (magic, io.Information)) |
| { |
| /* Heureka, it's an executable */ |
| pc.set_exec (); |
| buf->st_mode |= STD_XBITS; |
| } |
| NtClose (h); |
| } |
| } |
| } |
| if (pc.exec_state () == is_executable) |
| buf->st_mode |= STD_XBITS; |
| |
| /* This fakes the permissions of all files to match the current umask. */ |
| buf->st_mode &= ~(cygheap->umask); |
| /* If the FS supports ACLs, we're here because we couldn't even open |
| the file for READ_CONTROL access. Chances are high that the file's |
| security descriptor has no ACE for "Everyone", so we should not fake |
| any access for "others". */ |
| if (has_acls ()) |
| buf->st_mode &= ~(S_IROTH | S_IWOTH | S_IXOTH); |
| } |
| |
| done: |
| syscall_printf ("0 = fstat (%S, %p) st_size=%D, st_mode=0%o, st_ino=%D" |
| "st_atim=%lx.%lx st_ctim=%lx.%lx " |
| "st_mtim=%lx.%lx st_birthtim=%lx.%lx", |
| pc.get_nt_native_path (), buf, |
| buf->st_size, buf->st_mode, buf->st_ino, |
| buf->st_atim.tv_sec, buf->st_atim.tv_nsec, |
| buf->st_ctim.tv_sec, buf->st_ctim.tv_nsec, |
| buf->st_mtim.tv_sec, buf->st_mtim.tv_nsec, |
| buf->st_birthtim.tv_sec, buf->st_birthtim.tv_nsec); |
| return 0; |
| } |
| |
| int __reg2 |
| fhandler_disk_file::fstat (struct stat *buf) |
| { |
| return fstat_fs (buf); |
| } |
| |
| int __reg2 |
| fhandler_disk_file::fstatvfs (struct statvfs *sfs) |
| { |
| int ret = -1, opened = 0; |
| NTSTATUS status; |
| IO_STATUS_BLOCK io; |
| FILE_FS_FULL_SIZE_INFORMATION full_fsi; |
| /* We must not use the stat handle here, even if it exists. The handle |
| has been opened with FILE_OPEN_REPARSE_POINT, thus, in case of a volume |
| mount point, it points to the FS of the mount point, rather than to the |
| mounted FS. */ |
| HANDLE fh = get_handle (); |
| |
| if (!fh) |
| { |
| OBJECT_ATTRIBUTES attr; |
| opened = NT_SUCCESS (NtOpenFile (&fh, READ_CONTROL, |
| pc.get_object_attr (attr, sec_none_nih), |
| &io, FILE_SHARE_VALID_FLAGS, |
| FILE_OPEN_FOR_BACKUP_INTENT)); |
| if (!opened) |
| { |
| /* Can't open file. Try again with parent dir. */ |
| UNICODE_STRING dirname; |
| RtlSplitUnicodePath (pc.get_nt_native_path (), &dirname, NULL); |
| attr.ObjectName = &dirname; |
| opened = NT_SUCCESS (NtOpenFile (&fh, READ_CONTROL, &attr, &io, |
| FILE_SHARE_VALID_FLAGS, |
| FILE_OPEN_FOR_BACKUP_INTENT)); |
| if (!opened) |
| goto out; |
| } |
| } |
| |
| sfs->f_files = ULONG_MAX; |
| sfs->f_ffree = ULONG_MAX; |
| sfs->f_favail = ULONG_MAX; |
| sfs->f_fsid = pc.fs_serial_number (); |
| sfs->f_flag = pc.fs_flags (); |
| sfs->f_namemax = pc.fs_name_len (); |
| /* Get allocation related information. */ |
| status = NtQueryVolumeInformationFile (fh, &io, &full_fsi, sizeof full_fsi, |
| FileFsFullSizeInformation); |
| if (NT_SUCCESS (status)) |
| { |
| sfs->f_bsize = full_fsi.BytesPerSector * full_fsi.SectorsPerAllocationUnit; |
| sfs->f_frsize = sfs->f_bsize; |
| sfs->f_blocks = (fsblkcnt_t) full_fsi.TotalAllocationUnits.QuadPart; |
| sfs->f_bfree = (fsblkcnt_t) |
| full_fsi.ActualAvailableAllocationUnits.QuadPart; |
| sfs->f_bavail = (fsblkcnt_t) |
| full_fsi.CallerAvailableAllocationUnits.QuadPart; |
| if (sfs->f_bfree > sfs->f_bavail) |
| { |
| /* Quotas active. We can't trust TotalAllocationUnits. */ |
| NTFS_VOLUME_DATA_BUFFER nvdb; |
| |
| status = NtFsControlFile (fh, NULL, NULL, NULL, &io, |
| FSCTL_GET_NTFS_VOLUME_DATA, |
| NULL, 0, &nvdb, sizeof nvdb); |
| if (!NT_SUCCESS (status)) |
| debug_printf ("%y = NtFsControlFile(%S, FSCTL_GET_NTFS_VOLUME_DATA)", |
| status, pc.get_nt_native_path ()); |
| else |
| sfs->f_blocks = (fsblkcnt_t) nvdb.TotalClusters.QuadPart; |
| } |
| ret = 0; |
| } |
| else if (status == STATUS_INVALID_PARAMETER /* Netapp */ |
| || status == STATUS_INVALID_INFO_CLASS) |
| { |
| FILE_FS_SIZE_INFORMATION fsi; |
| status = NtQueryVolumeInformationFile (fh, &io, &fsi, sizeof fsi, |
| FileFsSizeInformation); |
| if (NT_SUCCESS (status)) |
| { |
| sfs->f_bsize = fsi.BytesPerSector * fsi.SectorsPerAllocationUnit; |
| sfs->f_frsize = sfs->f_bsize; |
| sfs->f_blocks = (fsblkcnt_t) fsi.TotalAllocationUnits.QuadPart; |
| sfs->f_bfree = sfs->f_bavail = |
| (fsblkcnt_t) fsi.AvailableAllocationUnits.QuadPart; |
| ret = 0; |
| } |
| else |
| debug_printf ("%y = NtQueryVolumeInformationFile" |
| "(%S, FileFsSizeInformation)", |
| status, pc.get_nt_native_path ()); |
| } |
| else |
| debug_printf ("%y = NtQueryVolumeInformationFile" |
| "(%S, FileFsFullSizeInformation)", |
| status, pc.get_nt_native_path ()); |
| out: |
| if (opened) |
| NtClose (fh); |
| syscall_printf ("%d = fstatvfs(%s, %p)", ret, get_name (), sfs); |
| return ret; |
| } |
| |
| int __reg1 |
| fhandler_disk_file::fchmod (mode_t mode) |
| { |
| extern int chmod_device (path_conv& pc, mode_t mode); |
| int ret = -1; |
| int oret = 0; |
| NTSTATUS status; |
| IO_STATUS_BLOCK io; |
| |
| if (pc.is_fs_special ()) |
| return chmod_device (pc, mode); |
| |
| if (!get_handle ()) |
| { |
| query_open (query_write_dac); |
| if (!(oret = open (O_BINARY, 0))) |
| { |
| /* Need WRITE_DAC to write ACLs. */ |
| if (pc.has_acls ()) |
| return -1; |
| /* Otherwise FILE_WRITE_ATTRIBUTES is sufficient. */ |
| query_open (query_write_attributes); |
| if (!(oret = open (O_BINARY, 0))) |
| return -1; |
| } |
| } |
| |
| if (pc.fs_is_nfs ()) |
| { |
| /* chmod on NFS shares works by writing an EA of type NfsV3Attributes. |
| Only type and mode have to be set. Apparently type isn't checked |
| for consistency, so it's sufficent to set it to NF3REG all the time. */ |
| struct { |
| FILE_FULL_EA_INFORMATION ffei; |
| char buf[sizeof (NFS_V3_ATTR) + sizeof (fattr3)]; |
| } ffei_buf; |
| ffei_buf.ffei.NextEntryOffset = 0; |
| ffei_buf.ffei.Flags = 0; |
| ffei_buf.ffei.EaNameLength = sizeof (NFS_V3_ATTR) - 1; |
| ffei_buf.ffei.EaValueLength = sizeof (fattr3); |
| strcpy (ffei_buf.ffei.EaName, NFS_V3_ATTR); |
| fattr3 *nfs_attr = (fattr3 *) (ffei_buf.ffei.EaName |
| + ffei_buf.ffei.EaNameLength + 1); |
| memset (nfs_attr, 0, sizeof (fattr3)); |
| nfs_attr->type = NF3REG; |
| nfs_attr->mode = mode; |
| status = NtSetEaFile (get_handle (), &io, |
| &ffei_buf.ffei, sizeof ffei_buf); |
| if (!NT_SUCCESS (status)) |
| __seterrno_from_nt_status (status); |
| else |
| ret = 0; |
| goto out; |
| } |
| |
| if (pc.has_acls ()) |
| { |
| security_descriptor sd, sd_ret; |
| uid_t uid; |
| gid_t gid; |
| tmp_pathbuf tp; |
| aclent_t *aclp; |
| bool standard_acl = false; |
| int nentries, idx; |
| |
| if (!get_file_sd (get_handle (), pc, sd, false)) |
| { |
| aclp = (aclent_t *) tp.c_get (); |
| if ((nentries = get_posix_access (sd, NULL, &uid, &gid, |
| aclp, MAX_ACL_ENTRIES, |
| &standard_acl)) >= 0) |
| { |
| /* Overwrite ACL permissions as required by POSIX 1003.1e |
| draft 17. */ |
| aclp[0].a_perm = (mode >> 6) & S_IRWXO; |
| |
| /* POSIXly correct: If CLASS_OBJ is present, chmod only modifies |
| CLASS_OBJ, not GROUP_OBJ. |
| |
| Deliberate deviation from POSIX 1003.1e: If the ACL is a |
| "standard" ACL, that is, it only contains POSIX permissions |
| as well as entries for the Administrators group and SYSTEM, |
| then it's kind of a POSIX-only ACL in a twisted, Windowsy |
| way. If so, we change GROUP_OBJ and CLASS_OBJ perms. */ |
| if (standard_acl |
| && (idx = searchace (aclp, nentries, GROUP_OBJ)) >= 0) |
| aclp[idx].a_perm = (mode >> 3) & S_IRWXO; |
| if (nentries > MIN_ACL_ENTRIES |
| && (idx = searchace (aclp, nentries, CLASS_OBJ)) >= 0) |
| aclp[idx].a_perm = (mode >> 3) & S_IRWXO; |
| |
| if ((idx = searchace (aclp, nentries, OTHER_OBJ)) >= 0) |
| aclp[idx].a_perm = mode & S_IRWXO; |
| if (pc.isdir ()) |
| mode |= S_IFDIR; |
| if (set_posix_access (mode, uid, gid, aclp, nentries, sd_ret, |
| pc.fs_is_samba ())) |
| ret = set_file_sd (get_handle (), pc, sd_ret, false); |
| } |
| } |
| } |
| |
| /* If the mode has any write bits set, the DOS R/O flag is in the way. */ |
| if (mode & (S_IWUSR | S_IWGRP | S_IWOTH)) |
| pc &= (DWORD) ~FILE_ATTRIBUTE_READONLY; |
| else if (!pc.has_acls ()) /* Never set DOS R/O if security is used. */ |
| pc |= (DWORD) FILE_ATTRIBUTE_READONLY; |
| if (S_ISSOCK (mode)) |
| pc |= (DWORD) FILE_ATTRIBUTE_SYSTEM; |
| |
| status = NtSetAttributesFile (get_handle (), pc.file_attributes ()); |
| /* MVFS needs a good amount of kicking to be convinced that it has to write |
| back metadata changes and to invalidate the cached metadata information |
| stored for the given handle. This method to open a second handle to |
| the file and write the same metadata information twice has been found |
| experimentally: http://cygwin.com/ml/cygwin/2009-07/msg00533.html */ |
| if (pc.fs_is_mvfs () && NT_SUCCESS (status) && !oret) |
| { |
| OBJECT_ATTRIBUTES attr; |
| HANDLE fh; |
| |
| if (NT_SUCCESS (NtOpenFile (&fh, FILE_WRITE_ATTRIBUTES, |
| pc.init_reopen_attr (attr, get_handle ()), |
| &io, FILE_SHARE_VALID_FLAGS, |
| FILE_OPEN_FOR_BACKUP_INTENT))) |
| { |
| NtSetAttributesFile (fh, pc.file_attributes ()); |
| NtClose (fh); |
| } |
| } |
| /* Correct NTFS security attributes have higher priority */ |
| if (!pc.has_acls ()) |
| { |
| if (!NT_SUCCESS (status)) |
| __seterrno_from_nt_status (status); |
| else |
| ret = 0; |
| } |
| |
| out: |
| if (oret) |
| close_fs (); |
| |
| return ret; |
| } |
| |
| int __reg2 |
| fhandler_disk_file::fchown (uid_t uid, gid_t gid) |
| { |
| int oret = 0; |
| int ret = -1; |
| security_descriptor sd, sd_ret; |
| mode_t attr = pc.isdir () ? S_IFDIR : 0; |
| uid_t old_uid; |
| gid_t old_gid; |
| tmp_pathbuf tp; |
| aclent_t *aclp; |
| int nentries; |
| |
| if (!pc.has_acls ()) |
| { |
| /* fake - if not supported, pretend we're like win95 |
| where it just works */ |
| /* FIXME: Could be supported on NFS when user->uid mapping is in place. */ |
| return 0; |
| } |
| |
| if (!get_handle ()) |
| { |
| query_open (query_write_control); |
| if (!(oret = fhandler_disk_file::open (O_BINARY, 0))) |
| return -1; |
| } |
| |
| if (get_file_sd (get_handle (), pc, sd, false)) |
| goto out; |
| |
| aclp = (aclent_t *) tp.c_get (); |
| if ((nentries = get_posix_access (sd, &attr, &old_uid, &old_gid, |
| aclp, MAX_ACL_ENTRIES)) < 0) |
| goto out; |
| |
| if (uid == ILLEGAL_UID) |
| uid = old_uid; |
| if (gid == ILLEGAL_GID) |
| gid = old_gid; |
| if (uid == old_uid && gid == old_gid) |
| { |
| ret = 0; |
| goto out; |
| } |
| |
| /* Windows ACLs can contain permissions for one group, while being owned by |
| another user/group. The permission bits returned above are pretty much |
| useless then. Creating a new ACL with these useless permissions results |
| in a potentially broken symlink. So what we do here is to set the |
| underlying permissions of symlinks to a sensible value which allows the |
| world to read the symlink and only the new owner to change it. */ |
| if (pc.issymlink ()) |
| for (int idx = 0; idx < nentries; ++idx) |
| { |
| aclp[idx].a_perm |= S_IROTH; |
| if (aclp[idx].a_type & USER_OBJ) |
| aclp[idx].a_perm |= S_IWOTH; |
| } |
| |
| if (set_posix_access (attr, uid, gid, aclp, nentries, sd_ret, |
| pc.fs_is_samba ())) |
| ret = set_file_sd (get_handle (), pc, sd_ret, true); |
| |
| /* If you're running a Samba server with no winbind, the uid<->SID mapping |
| is disfunctional. Even trying to chown to your own account fails since |
| the account used on the server is the UNIX account which gets used for |
| the standard user mapping. This is a default mechanism which doesn't |
| know your real Windows SID. There are two possible error codes in |
| different Samba releases for this situation, one of them unfortunately |
| the not very significant STATUS_ACCESS_DENIED. Instead of relying on |
| the error codes, we're using the below very simple heuristic. |
| If set_file_sd failed, and the original user account was either already |
| unknown, or one of the standard UNIX accounts, we're faking success. */ |
| if (ret == -1 && pc.fs_is_samba ()) |
| { |
| PSID sid; |
| |
| if (uid == old_uid |
| || ((sid = sidfromuid (old_uid, NULL)) != NO_SID |
| && RtlEqualPrefixSid (sid, |
| well_known_samba_unix_user_fake_sid))) |
| { |
| debug_printf ("Faking chown worked on standalone Samba"); |
| ret = 0; |
| } |
| } |
| |
| out: |
| if (oret) |
| close_fs (); |
| |
| return ret; |
| } |
| |
| int __reg3 |
| fhandler_disk_file::facl (int cmd, int nentries, aclent_t *aclbufp) |
| { |
| int res = -1; |
| int oret = 0; |
| |
| if (!pc.has_acls ()) |
| { |
| cant_access_acl: |
| switch (cmd) |
| { |
| |
| case SETACL: |
| /* Open for writing required to be able to set ctime |
| (even though setting the ACL is just pretended). */ |
| if (!get_handle ()) |
| oret = open (O_WRONLY | O_BINARY, 0); |
| res = 0; |
| break; |
| case GETACL: |
| if (!aclbufp) |
| set_errno (EFAULT); |
| else if (nentries < MIN_ACL_ENTRIES) |
| set_errno (ENOSPC); |
| else |
| { |
| struct stat st; |
| if (!fstat (&st)) |
| { |
| aclbufp[0].a_type = USER_OBJ; |
| aclbufp[0].a_id = st.st_uid; |
| aclbufp[0].a_perm = (st.st_mode & S_IRWXU) >> 6; |
| aclbufp[1].a_type = GROUP_OBJ; |
| aclbufp[1].a_id = st.st_gid; |
| aclbufp[1].a_perm = (st.st_mode & S_IRWXG) >> 3; |
| aclbufp[2].a_type = OTHER_OBJ; |
| aclbufp[2].a_id = ILLEGAL_GID; |
| aclbufp[2].a_perm = st.st_mode & S_IRWXO; |
| res = MIN_ACL_ENTRIES; |
| } |
| } |
| break; |
| case GETACLCNT: |
| res = MIN_ACL_ENTRIES; |
| break; |
| default: |
| set_errno (EINVAL); |
| break; |
| } |
| } |
| else |
| { |
| if ((cmd == SETACL && !get_handle ()) |
| || (cmd != SETACL && !get_stat_handle ())) |
| { |
| query_open (cmd == SETACL ? query_write_dac : query_read_control); |
| if (!(oret = open (O_BINARY, 0))) |
| { |
| if (cmd == GETACL || cmd == GETACLCNT) |
| goto cant_access_acl; |
| return -1; |
| } |
| } |
| switch (cmd) |
| { |
| case SETACL: |
| if (!aclsort32 (nentries, 0, aclbufp)) |
| { |
| bool rw = false; |
| res = setacl (get_handle (), pc, nentries, aclbufp, rw); |
| if (rw) |
| { |
| IO_STATUS_BLOCK io; |
| FILE_BASIC_INFORMATION fbi; |
| fbi.CreationTime.QuadPart |
| = fbi.LastAccessTime.QuadPart |
| = fbi.LastWriteTime.QuadPart |
| = fbi.ChangeTime.QuadPart = 0LL; |
| fbi.FileAttributes = (pc.file_attributes () |
| & ~FILE_ATTRIBUTE_READONLY) |
| ?: FILE_ATTRIBUTE_NORMAL; |
| NtSetInformationFile (get_handle (), &io, &fbi, sizeof fbi, |
| FileBasicInformation); |
| } |
| } |
| break; |
| case GETACL: |
| if (!aclbufp) |
| set_errno(EFAULT); |
| else { |
| res = getacl (get_stat_handle (), pc, nentries, aclbufp); |
| /* For this ENOSYS case, see security.cc:get_file_attribute(). */ |
| if (res == -1 && get_errno () == ENOSYS) |
| goto cant_access_acl; |
| } |
| break; |
| case GETACLCNT: |
| res = getacl (get_stat_handle (), pc, 0, NULL); |
| /* Ditto. */ |
| if (res == -1 && get_errno () == ENOSYS) |
| goto cant_access_acl; |
| break; |
| default: |
| set_errno (EINVAL); |
| break; |
| } |
| } |
| |
| if (oret) |
| close_fs (); |
| |
| return res; |
| } |
| |
| ssize_t |
| fhandler_disk_file::fgetxattr (const char *name, void *value, size_t size) |
| { |
| if (pc.is_fs_special ()) |
| { |
| set_errno (ENOTSUP); |
| return -1; |
| } |
| return read_ea (get_handle (), pc, name, (char *) value, size); |
| } |
| |
| int |
| fhandler_disk_file::fsetxattr (const char *name, const void *value, size_t size, |
| int flags) |
| { |
| if (pc.is_fs_special ()) |
| { |
| set_errno (ENOTSUP); |
| return -1; |
| } |
| return write_ea (get_handle (), pc, name, (const char *) value, size, flags); |
| } |
| |
| int |
| fhandler_disk_file::fadvise (off_t offset, off_t length, int advice) |
| { |
| if (advice < POSIX_FADV_NORMAL || advice > POSIX_FADV_NOREUSE) |
| return EINVAL; |
| |
| /* Windows only supports advice flags for the whole file. We're using |
| a simplified test here so that we don't have to ask for the actual |
| file size. Length == 0 means all bytes starting at offset anyway. |
| So we only actually follow the advice, if it's given for offset == 0. */ |
| if (offset != 0) |
| return 0; |
| |
| /* We only support normal and sequential mode for now. Everything which |
| is not POSIX_FADV_SEQUENTIAL is treated like POSIX_FADV_NORMAL. */ |
| if (advice != POSIX_FADV_SEQUENTIAL) |
| advice = POSIX_FADV_NORMAL; |
| |
| IO_STATUS_BLOCK io; |
| FILE_MODE_INFORMATION fmi; |
| NTSTATUS status = NtQueryInformationFile (get_handle (), &io, |
| &fmi, sizeof fmi, |
| FileModeInformation); |
| if (NT_SUCCESS (status)) |
| { |
| fmi.Mode &= ~FILE_SEQUENTIAL_ONLY; |
| if (advice == POSIX_FADV_SEQUENTIAL) |
| fmi.Mode |= FILE_SEQUENTIAL_ONLY; |
| status = NtSetInformationFile (get_handle (), &io, &fmi, sizeof fmi, |
| FileModeInformation); |
| if (NT_SUCCESS (status)) |
| return 0; |
| __seterrno_from_nt_status (status); |
| } |
| |
| return geterrno_from_nt_status (status); |
| } |
| |
| int |
| fhandler_disk_file::ftruncate (off_t length, bool allow_truncate) |
| { |
| int res = 0; |
| |
| if (length < 0 || !get_handle ()) |
| res = EINVAL; |
| else if (pc.isdir ()) |
| res = EISDIR; |
| else if (!(get_access () & GENERIC_WRITE)) |
| res = EBADF; |
| else |
| { |
| NTSTATUS status; |
| IO_STATUS_BLOCK io; |
| FILE_STANDARD_INFORMATION fsi; |
| FILE_END_OF_FILE_INFORMATION feofi; |
| |
| status = NtQueryInformationFile (get_handle (), &io, &fsi, sizeof fsi, |
| FileStandardInformation); |
| if (!NT_SUCCESS (status)) |
| return geterrno_from_nt_status (status); |
| |
| /* If called through posix_fallocate, silently succeed if length |
| is less than the file's actual length. */ |
| if (!allow_truncate && length < fsi.EndOfFile.QuadPart) |
| return 0; |
| |
| feofi.EndOfFile.QuadPart = length; |
| /* Create sparse files only when called through ftruncate, not when |
| called through posix_fallocate. */ |
| if (allow_truncate && pc.support_sparse () |
| && !has_attribute (FILE_ATTRIBUTE_SPARSE_FILE) |
| && length >= fsi.EndOfFile.QuadPart + (128 * 1024)) |
| { |
| status = NtFsControlFile (get_handle (), NULL, NULL, NULL, &io, |
| FSCTL_SET_SPARSE, NULL, 0, NULL, 0); |
| if (NT_SUCCESS (status)) |
| pc.file_attributes (pc.file_attributes () |
| | FILE_ATTRIBUTE_SPARSE_FILE); |
| syscall_printf ("%y = NtFsControlFile(%S, FSCTL_SET_SPARSE)", |
| status, pc.get_nt_native_path ()); |
| } |
| status = NtSetInformationFile (get_handle (), &io, |
| &feofi, sizeof feofi, |
| FileEndOfFileInformation); |
| if (!NT_SUCCESS (status)) |
| res = geterrno_from_nt_status (status); |
| } |
| return res; |
| } |
| |
| int |
| fhandler_disk_file::link (const char *newpath) |
| { |
| size_t nlen = strlen (newpath); |
| path_conv newpc (newpath, PC_SYM_NOFOLLOW | PC_POSIX | PC_NULLEMPTY, stat_suffixes); |
| if (newpc.error) |
| { |
| set_errno (newpc.error); |
| return -1; |
| } |
| |
| if (newpc.exists ()) |
| { |
| syscall_printf ("file '%S' exists?", newpc.get_nt_native_path ()); |
| set_errno (EEXIST); |
| return -1; |
| } |
| |
| if (isdirsep (newpath[nlen - 1]) || has_dot_last_component (newpath, false)) |
| { |
| set_errno (ENOENT); |
| return -1; |
| } |
| |
| char new_buf[nlen + 5]; |
| if (!newpc.error) |
| { |
| /* If the original file is a lnk special file (except for sockets), |
| and if the original file has a .lnk suffix, add one to the hardlink |
| as well. */ |
| if (pc.is_lnk_special () && !pc.issocket () |
| && RtlEqualUnicodePathSuffix (pc.get_nt_native_path (), |
| &ro_u_lnk, TRUE)) |
| { |
| /* Shortcut hack. */ |
| stpcpy (stpcpy (new_buf, newpath), ".lnk"); |
| newpath = new_buf; |
| newpc.check (newpath, PC_SYM_NOFOLLOW); |
| } |
| else if (!pc.isdir () |
| && pc.is_binary () |
| && RtlEqualUnicodePathSuffix (pc.get_nt_native_path (), |
| &ro_u_exe, TRUE) |
| && !RtlEqualUnicodePathSuffix (newpc.get_nt_native_path (), |
| &ro_u_exe, TRUE)) |
| { |
| /* Executable hack. */ |
| stpcpy (stpcpy (new_buf, newpath), ".exe"); |
| newpath = new_buf; |
| newpc.check (newpath, PC_SYM_NOFOLLOW); |
| } |
| } |
| |
| /* We only need READ_CONTROL access so the handle returned in pc is |
| sufficient. And if the file couldn't be opened with READ_CONTROL |
| access in path_conv, we won't be able to do it here anyway. */ |
| HANDLE fh = get_stat_handle (); |
| if (!fh) |
| { |
| set_errno (EACCES); |
| return -1; |
| } |
| PUNICODE_STRING tgt = newpc.get_nt_native_path (); |
| ULONG size = sizeof (FILE_LINK_INFORMATION) + tgt->Length; |
| PFILE_LINK_INFORMATION pfli = (PFILE_LINK_INFORMATION) alloca (size); |
| pfli->ReplaceIfExists = FALSE; |
| pfli->RootDirectory = NULL; |
| memcpy (pfli->FileName, tgt->Buffer, pfli->FileNameLength = tgt->Length); |
| |
| NTSTATUS status; |
| IO_STATUS_BLOCK io; |
| status = NtSetInformationFile (fh, &io, pfli, size, FileLinkInformation); |
| if (!NT_SUCCESS (status)) |
| { |
| if (status == STATUS_INVALID_DEVICE_REQUEST |
| || status == STATUS_NOT_SUPPORTED) |
| /* FS doesn't support hard links. Linux returns EPERM. */ |
| set_errno (EPERM); |
| else |
| __seterrno_from_nt_status (status); |
| return -1; |
| } |
| else if (pc.file_attributes () & FILE_ATTRIBUTE_TEMPORARY) |
| { |
| /* If the original file has been opened with O_TMPFILE the file has |
| FILE_ATTRIBUTE_TEMPORARY set. After a successful hardlink the |
| file is not temporary anymore in the usual sense. So we remove |
| FILE_ATTRIBUTE_TEMPORARY here, even if this makes the original file |
| visible in directory enumeration. */ |
| OBJECT_ATTRIBUTES attr; |
| status = NtOpenFile (&fh, FILE_WRITE_ATTRIBUTES, |
| pc.init_reopen_attr (attr, fh), &io, |
| FILE_SHARE_VALID_FLAGS, FILE_OPEN_FOR_BACKUP_INTENT); |
| if (!NT_SUCCESS (status)) |
| debug_printf ("Opening for removing TEMPORARY attrib failed, " |
| "status = %y", status); |
| else |
| { |
| FILE_BASIC_INFORMATION fbi; |
| |
| fbi.CreationTime.QuadPart = fbi.LastAccessTime.QuadPart |
| = fbi.LastWriteTime.QuadPart = fbi.ChangeTime.QuadPart = 0LL; |
| fbi.FileAttributes = (pc.file_attributes () |
| & ~FILE_ATTRIBUTE_TEMPORARY) |
| ?: FILE_ATTRIBUTE_NORMAL; |
| status = NtSetInformationFile (fh, &io, &fbi, sizeof fbi, |
| FileBasicInformation); |
| if (!NT_SUCCESS (status)) |
| debug_printf ("Removing the TEMPORARY attrib failed, status = %y", |
| status); |
| NtClose (fh); |
| } |
| } |
| return 0; |
| } |
| |
| int |
| fhandler_disk_file::utimens (const struct timespec *tvp) |
| { |
| return utimens_fs (tvp); |
| } |
| |
| int |
| fhandler_base::utimens_fs (const struct timespec *tvp) |
| { |
| struct timespec timeofday; |
| struct timespec tmp[2]; |
| bool closeit = false; |
| |
| if (!get_handle ()) |
| { |
| query_open (query_write_attributes); |
| if (!open_fs (O_BINARY, 0)) |
| { |
| /* It's documented in MSDN that FILE_WRITE_ATTRIBUTES is sufficient |
| to change the timestamps. Unfortunately it's not sufficient for a |
| remote HPFS which requires GENERIC_WRITE, so we just retry to open |
| for writing, though this fails for R/O files of course. */ |
| query_open (no_query); |
| if (!open_fs (O_WRONLY | O_BINARY, 0)) |
| { |
| syscall_printf ("Opening file failed"); |
| return -1; |
| } |
| } |
| closeit = true; |
| } |
| |
| clock_gettime (CLOCK_REALTIME, &timeofday); |
| if (!tvp) |
| tmp[1] = tmp[0] = timeofday; |
| else |
| { |
| if ((tvp[0].tv_nsec < UTIME_NOW || tvp[0].tv_nsec > 999999999L) |
| || (tvp[1].tv_nsec < UTIME_NOW || tvp[1].tv_nsec > 999999999L)) |
| { |
| if (closeit) |
| close_fs (); |
| set_errno (EINVAL); |
| return -1; |
| } |
| tmp[0] = (tvp[0].tv_nsec == UTIME_NOW) ? timeofday : tvp[0]; |
| tmp[1] = (tvp[1].tv_nsec == UTIME_NOW) ? timeofday : tvp[1]; |
| } |
| debug_printf ("incoming lastaccess %ly %ly", tmp[0].tv_sec, tmp[0].tv_nsec); |
| |
| IO_STATUS_BLOCK io; |
| FILE_BASIC_INFORMATION fbi; |
| |
| fbi.CreationTime.QuadPart = 0LL; |
| /* UTIME_OMIT is handled in timespec_to_filetime by setting FILETIME to 0. */ |
| timespec_to_filetime (&tmp[0], &fbi.LastAccessTime); |
| timespec_to_filetime (&tmp[1], &fbi.LastWriteTime); |
| fbi.ChangeTime.QuadPart = 0LL; |
| fbi.FileAttributes = 0; |
| NTSTATUS status = NtSetInformationFile (get_handle (), &io, &fbi, sizeof fbi, |
| FileBasicInformation); |
| /* For this special case for MVFS see the comment in |
| fhandler_disk_file::fchmod. */ |
| if (pc.fs_is_mvfs () && NT_SUCCESS (status) && !closeit) |
| { |
| OBJECT_ATTRIBUTES attr; |
| HANDLE fh; |
| |
| if (NT_SUCCESS (NtOpenFile (&fh, FILE_WRITE_ATTRIBUTES, |
| pc.init_reopen_attr (attr, get_handle ()), |
| &io, FILE_SHARE_VALID_FLAGS, |
| FILE_OPEN_FOR_BACKUP_INTENT))) |
| { |
| NtSetInformationFile (fh, &io, &fbi, sizeof fbi, |
| FileBasicInformation); |
| NtClose (fh); |
| } |
| } |
| if (closeit) |
| close_fs (); |
| /* Opening a directory on a 9x share from a NT machine works(!), but |
| then NtSetInformationFile fails with STATUS_NOT_SUPPORTED. Oh well... */ |
| if (!NT_SUCCESS (status) && status != STATUS_NOT_SUPPORTED) |
| { |
| __seterrno_from_nt_status (status); |
| return -1; |
| } |
| return 0; |
| } |
| |
| fhandler_disk_file::fhandler_disk_file () : |
| fhandler_base (), prw_handle (NULL) |
| { |
| } |
| |
| fhandler_disk_file::fhandler_disk_file (path_conv &pc) : |
| fhandler_base (), prw_handle (NULL) |
| { |
| set_name (pc); |
| } |
| |
| int |
| fhandler_disk_file::open (int flags, mode_t mode) |
| { |
| return open_fs (flags, mode); |
| } |
| |
| int |
| fhandler_disk_file::close () |
| { |
| /* Close extra pread/pwrite handle, if it exists. */ |
| if (prw_handle) |
| NtClose (prw_handle); |
| return fhandler_base::close (); |
| } |
| |
| int |
| fhandler_disk_file::fcntl (int cmd, intptr_t arg) |
| { |
| int res; |
| |
| switch (cmd) |
| { |
| case F_LCK_MANDATORY: /* Mandatory locking only works on files. */ |
| mandatory_locking (!!arg); |
| need_fork_fixup (true); |
| res = 0; |
| break; |
| default: |
| res = fhandler_base::fcntl (cmd, arg); |
| break; |
| } |
| return res; |
| } |
| |
| int |
| fhandler_disk_file::dup (fhandler_base *child, int flags) |
| { |
| fhandler_disk_file *fhc = (fhandler_disk_file *) child; |
| |
| int ret = fhandler_base::dup (child, flags); |
| if (!ret && prw_handle |
| && !DuplicateHandle (GetCurrentProcess (), prw_handle, |
| GetCurrentProcess (), &fhc->prw_handle, |
| 0, TRUE, DUPLICATE_SAME_ACCESS)) |
| fhc->prw_handle = NULL; |
| return ret; |
| } |
| |
| void |
| fhandler_disk_file::fixup_after_fork (HANDLE parent) |
| { |
| prw_handle = NULL; |
| mandatory_locking (false); |
| fhandler_base::fixup_after_fork (parent); |
| } |
| |
| int |
| fhandler_base::open_fs (int flags, mode_t mode) |
| { |
| /* Unfortunately NT allows to open directories for writing, but that's |
| disallowed according to SUSv3. */ |
| if (pc.isdir () && (flags & O_ACCMODE) != O_RDONLY) |
| { |
| set_errno (EISDIR); |
| return 0; |
| } |
| |
| bool new_file = !exists (); |
| |
| int res = fhandler_base::open (flags | O_DIROPEN, mode); |
| if (res) |
| { |
| /* The file info in pc is wrong at this point for newly created files. |
| Refresh it before fetching any file info. */ |
| if (new_file) |
| pc.get_finfo (get_io_handle ()); |
| |
| if (pc.isgood_inode (pc.get_ino ())) |
| ino = pc.get_ino (); |
| } |
| |
| syscall_printf ("%d = fhandler_disk_file::open(%S, %y)", res, |
| pc.get_nt_native_path (), flags); |
| return res; |
| } |
| |
| /* POSIX demands that pread/pwrite don't change the current file position. |
| While NtReadFile/NtWriteFile support atomic seek-and-io, both change the |
| file pointer if the file handle has been opened for synchonous I/O. |
| Using this handle for pread/pwrite would break atomicity, because the |
| read/write operation would have to be followed by a seek back to the old |
| file position. What we do is to open another handle to the file on the |
| first call to either pread or pwrite. This is used for any subsequent |
| pread/pwrite. Thus the current file position of the "normal" file |
| handle is not touched. |
| |
| FIXME: |
| |
| Note that this is just a hack. The problem with this approach is that |
| a change to the file permissions might disallow to open the file with |
| the required permissions to read or write. This appears to be a border case, |
| but that's exactly what git does. It creates the file for reading and |
| writing and after writing it, it chmods the file to read-only. Then it |
| calls pread on the file to examine the content. This works, but if git |
| would use the original handle to pwrite to the file, it would be broken |
| with our approach. |
| |
| One way to implement this is to open the pread/pwrite handle right at |
| file open time. We would simply maintain two handles, which wouldn't |
| be much of a problem given how we do that for other fhandler types as |
| well. |
| |
| However, ultimately fhandler_disk_file should become a derived class of |
| fhandler_base_overlapped. Each raw_read or raw_write would fetch the |
| actual file position, read/write from there, and then set the file |
| position again. Fortunately, while the file position is not maintained |
| by the I/O manager, it can be fetched and set to a new value by all |
| processes holding a handle to that file object. Pread and pwrite differ |
| from raw_read and raw_write just by not touching the current file pos. |
| Actually they could be merged with raw_read/raw_write if we add a position |
| parameter to the latter. */ |
| |
| int |
| fhandler_disk_file::prw_open (bool write) |
| { |
| NTSTATUS status; |
| IO_STATUS_BLOCK io; |
| OBJECT_ATTRIBUTES attr; |
| |
| /* First try to open with the original access mask */ |
| ACCESS_MASK access = get_access (); |
| status = NtOpenFile (&prw_handle, access, |
| pc.init_reopen_attr (attr, get_handle ()), &io, |
| FILE_SHARE_VALID_FLAGS, get_options ()); |
| if (status == STATUS_ACCESS_DENIED) |
| { |
| /* If we get an access denied, chmod has been called. Try again |
| with just the required rights to perform the called function. */ |
| access &= write ? ~GENERIC_READ : ~GENERIC_WRITE; |
| status = NtOpenFile (&prw_handle, access, &attr, &io, |
| FILE_SHARE_VALID_FLAGS, get_options ()); |
| } |
| debug_printf ("%y = NtOpenFile (%p, %y, %S, io, %y, %y)", |
| status, prw_handle, access, pc.get_nt_native_path (), |
| FILE_SHARE_VALID_FLAGS, get_options ()); |
| if (!NT_SUCCESS (status)) |
| { |
| __seterrno_from_nt_status (status); |
| return -1; |
| } |
| return 0; |
| } |
| |
| ssize_t __reg3 |
| fhandler_disk_file::pread (void *buf, size_t count, off_t offset) |
| { |
| ssize_t res; |
| |
| if ((get_flags () & O_ACCMODE) == O_WRONLY) |
| { |
| set_errno (EBADF); |
| return -1; |
| } |
| |
| /* In binary mode, we can use an atomic NtReadFile call. |
| Windows mandatory locking semantics disallow to use another HANDLE. */ |
| if (rbinary () && !mandatory_locking ()) |
| { |
| extern int __stdcall is_at_eof (HANDLE h); |
| NTSTATUS status; |
| IO_STATUS_BLOCK io; |
| LARGE_INTEGER off = { QuadPart:offset }; |
| |
| if (!prw_handle && prw_open (false)) |
| goto non_atomic; |
| status = NtReadFile (prw_handle, NULL, NULL, NULL, &io, buf, count, |
| &off, NULL); |
| if (status == STATUS_END_OF_FILE) |
| res = 0; |
| else if (!NT_SUCCESS (status)) |
| { |
| if (pc.isdir ()) |
| { |
| set_errno (EISDIR); |
| return -1; |
| } |
| if (status == (NTSTATUS) STATUS_ACCESS_VIOLATION) |
| { |
| if (is_at_eof (prw_handle)) |
| { |
| res = 0; |
| goto out; |
| } |
| switch (mmap_is_attached_or_noreserve (buf, count)) |
| { |
| case MMAP_NORESERVE_COMMITED: |
| status = NtReadFile (prw_handle, NULL, NULL, NULL, &io, |
| buf, count, &off, NULL); |
| if (NT_SUCCESS (status)) |
| { |
| res = io.Information; |
| goto out; |
| } |
| break; |
| case MMAP_RAISE_SIGBUS: |
| raise (SIGBUS); |
| default: |
| break; |
| } |
| } |
| __seterrno_from_nt_status (status); |
| return -1; |
| } |
| else |
| res = io.Information; |
| } |
| else |
| { |
| non_atomic: |
| /* Text mode stays slow and non-atomic. */ |
| off_t curpos = lseek (0, SEEK_CUR); |
| if (curpos < 0 || lseek (offset, SEEK_SET) < 0) |
| res = -1; |
| else |
| { |
| size_t tmp_count = count; |
| read (buf, tmp_count); |
| if (lseek (curpos, SEEK_SET) >= 0) |
| res = (ssize_t) tmp_count; |
| else |
| res = -1; |
| } |
| } |
| out: |
| debug_printf ("%d = pread(%p, %ld, %D)\n", res, buf, count, offset); |
| return res; |
| } |
| |
| ssize_t __reg3 |
| fhandler_disk_file::pwrite (void *buf, size_t count, off_t offset) |
| { |
| if ((get_flags () & O_ACCMODE) == O_RDONLY) |
| { |
| set_errno (EBADF); |
| return -1; |
| } |
| |
| /* In binary mode, we can use an atomic NtWriteFile call. |
| Windows mandatory locking semantics disallow to use another HANDLE. */ |
| if (wbinary () && !mandatory_locking ()) |
| { |
| NTSTATUS status; |
| IO_STATUS_BLOCK io; |
| LARGE_INTEGER off = { QuadPart:offset }; |
| |
| if (!prw_handle && prw_open (true)) |
| goto non_atomic; |
| status = NtWriteFile (prw_handle, NULL, NULL, NULL, &io, buf, count, |
| &off, NULL); |
| if (!NT_SUCCESS (status)) |
| { |
| __seterrno_from_nt_status (status); |
| return -1; |
| } |
| return io.Information; |
| } |
| |
| non_atomic: |
| /* Text mode stays slow and non-atomic. */ |
| int res; |
| off_t curpos = lseek (0, SEEK_CUR); |
| if (curpos < 0 || lseek (offset, SEEK_SET) < 0) |
| res = curpos; |
| else |
| { |
| res = (ssize_t) write (buf, count); |
| if (lseek (curpos, SEEK_SET) < 0) |
| res = -1; |
| } |
| debug_printf ("%d = pwrite(%p, %ld, %D)\n", res, buf, count, offset); |
| return res; |
| } |
| |
| int |
| fhandler_disk_file::mkdir (mode_t mode) |
| { |
| int res = -1; |
| SECURITY_ATTRIBUTES sa = sec_none_nih; |
| NTSTATUS status; |
| HANDLE dir; |
| OBJECT_ATTRIBUTES attr; |
| IO_STATUS_BLOCK io; |
| PFILE_FULL_EA_INFORMATION p = NULL; |
| ULONG plen = 0; |
| ULONG access = FILE_LIST_DIRECTORY | SYNCHRONIZE; |
| |
| if (pc.fs_is_nfs ()) |
| { |
| /* When creating a dir on an NFS share, we have to set the |
| file mode by writing a NFS fattr3 structure with the |
| correct mode bits set. */ |
| plen = sizeof (FILE_FULL_EA_INFORMATION) + sizeof (NFS_V3_ATTR) |
| + sizeof (fattr3); |
| p = (PFILE_FULL_EA_INFORMATION) alloca (plen); |
| p->NextEntryOffset = 0; |
| p->Flags = 0; |
| p->EaNameLength = sizeof (NFS_V3_ATTR) - 1; |
| p->EaValueLength = sizeof (fattr3); |
| strcpy (p->EaName, NFS_V3_ATTR); |
| fattr3 *nfs_attr = (fattr3 *) (p->EaName + p->EaNameLength + 1); |
| memset (nfs_attr, 0, sizeof (fattr3)); |
| nfs_attr->type = NF3DIR; |
| nfs_attr->mode = (mode & 07777) & ~cygheap->umask; |
| } |
| else if (has_acls () && !isremote ()) |
| /* If the filesystem supports ACLs, we will overwrite the DACL after the |
| call to NtCreateFile. This requires a handle with READ_CONTROL and |
| WRITE_DAC access, otherwise get_file_sd and set_file_sd both have to |
| open the file again. |
| FIXME: On remote NTFS shares open sometimes fails because even the |
| creator of the file doesn't have the right to change the DACL. |
| I don't know what setting that is or how to recognize such a share, |
| so for now we don't request WRITE_DAC on remote drives. */ |
| access |= READ_CONTROL | WRITE_DAC; |
| status = NtCreateFile (&dir, access, pc.get_object_attr (attr, sa), &io, NULL, |
| FILE_ATTRIBUTE_DIRECTORY, FILE_SHARE_VALID_FLAGS, |
| FILE_CREATE, |
| FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT |
| | FILE_OPEN_FOR_BACKUP_INTENT, |
| p, plen); |
| if (NT_SUCCESS (status)) |
| { |
| /* Set the "directory attribute" so that pc.isdir() returns correct |
| value in subsequent function calls. */ |
| pc.file_attributes (FILE_ATTRIBUTE_DIRECTORY); |
| if (has_acls ()) |
| set_created_file_access (dir, pc, mode & 07777); |
| NtClose (dir); |
| res = 0; |
| } |
| else |
| __seterrno_from_nt_status (status); |
| |
| return res; |
| } |
| |
| int |
| fhandler_disk_file::rmdir () |
| { |
| extern NTSTATUS unlink_nt (path_conv &pc); |
| |
| if (!pc.isdir ()) |
| { |
| set_errno (ENOTDIR); |
| return -1; |
| } |
| if (!pc.exists ()) |
| { |
| set_errno (ENOENT); |
| return -1; |
| } |
| |
| NTSTATUS status = unlink_nt (pc); |
| |
| /* Check for existence of remote dirs after trying to delete them. |
| Two reasons: |
| - Sometimes SMB indicates failure when it really succeeds. |
| - Removing a directory on a Samba drive using an old Samba version |
| sometimes doesn't return an error, if the directory can't be removed |
| because it's not empty. */ |
| if (isremote ()) |
| { |
| OBJECT_ATTRIBUTES attr; |
| FILE_BASIC_INFORMATION fbi; |
| NTSTATUS q_status; |
| |
| q_status = NtQueryAttributesFile (pc.get_object_attr (attr, sec_none_nih), |
| &fbi); |
| if (!NT_SUCCESS (status) && q_status == STATUS_OBJECT_NAME_NOT_FOUND) |
| status = STATUS_SUCCESS; |
| else if (pc.fs_is_samba () |
| && NT_SUCCESS (status) && NT_SUCCESS (q_status)) |
| status = STATUS_DIRECTORY_NOT_EMPTY; |
| } |
| if (!NT_SUCCESS (status)) |
| { |
| __seterrno_from_nt_status (status); |
| return -1; |
| } |
| return 0; |
| } |
| |
| /* This is the minimal number of entries which fit into the readdir cache. |
| The number of bytes allocated by the cache is determined by this number, |
| To tune caching, just tweak this number. To get a feeling for the size, |
| the size of the readdir cache is DIR_NUM_ENTRIES * 624 + 4 bytes. */ |
| |
| #define DIR_NUM_ENTRIES 100 /* Cache size 62404 bytes */ |
| |
| #define DIR_BUF_SIZE (DIR_NUM_ENTRIES \ |
| * (sizeof (FILE_ID_BOTH_DIR_INFORMATION) \ |
| + (NAME_MAX + 1) * sizeof (WCHAR))) |
| |
| struct __DIR_cache |
| { |
| char __cache[DIR_BUF_SIZE]; |
| ULONG __pos; |
| }; |
| |
| #define d_cachepos(d) (((__DIR_cache *) (d)->__d_dirname)->__pos) |
| #define d_cache(d) (((__DIR_cache *) (d)->__d_dirname)->__cache) |
| |
| #define d_mounts(d) ((__DIR_mounts *) (d)->__d_internal) |
| |
| DIR * |
| fhandler_disk_file::opendir (int fd) |
| { |
| DIR *dir; |
| DIR *res = NULL; |
| |
| if (!pc.isdir ()) |
| set_errno (ENOTDIR); |
| else if ((dir = (DIR *) malloc (sizeof (DIR))) == NULL) |
| set_errno (ENOMEM); |
| else if ((dir->__d_dirname = (char *) malloc ( sizeof (struct __DIR_cache))) |
| == NULL) |
| { |
| set_errno (ENOMEM); |
| goto free_dir; |
| } |
| else if ((dir->__d_dirent = |
| (struct dirent *) malloc (sizeof (struct dirent))) == NULL) |
| { |
| set_errno (ENOMEM); |
| goto free_dirname; |
| } |
| else |
| { |
| cygheap_fdnew cfd; |
| if (cfd < 0 && fd < 0) |
| goto free_dirent; |
| |
| dir->__d_dirent->__d_version = __DIRENT_VERSION; |
| dir->__d_cookie = __DIRENT_COOKIE; |
| dir->__handle = INVALID_HANDLE_VALUE; |
| dir->__d_position = 0; |
| dir->__flags = (get_name ()[0] == '/' && get_name ()[1] == '\0') |
| ? dirent_isroot : 0; |
| dir->__d_internal = 0; |
| |
| if (pc.iscygdrive ()) |
| { |
| if (fd < 0 && !open (O_RDONLY, 0)) |
| goto free_mounts; |
| } |
| else |
| { |
| dir->__d_internal = (uintptr_t) new __DIR_mounts (get_name ()); |
| d_cachepos (dir) = 0; |
| if (fd < 0) |
| { |
| /* opendir() case. Initialize with given directory name and |
| NULL directory handle. */ |
| OBJECT_ATTRIBUTES attr; |
| NTSTATUS status; |
| IO_STATUS_BLOCK io; |
| /* Tools like ls(1) call dirfd() to fetch the directory |
| descriptor for calls to facl or fstat. The tight access mask |
| used so far is not sufficient to reuse the handle for these |
| calls, instead the facl/fstat calls find the handle to be |
| unusable and have to re-open the file for reading attributes |
| and control data. So, what we do here is to try to open the |
| directory with more relaxed access mask which enables to use |
| the handle for the aforementioned purpose. This should work |
| in almost all cases. Only if it doesn't work due to |
| permission problems, we drop the additional access bits and |
| try again. */ |
| ACCESS_MASK fstat_mask = READ_CONTROL | FILE_READ_ATTRIBUTES; |
| |
| do |
| { |
| status = NtOpenFile (&get_handle (), |
| SYNCHRONIZE | FILE_LIST_DIRECTORY |
| | fstat_mask, |
| pc.get_object_attr (attr, sec_none_nih), |
| &io, FILE_SHARE_VALID_FLAGS, |
| FILE_SYNCHRONOUS_IO_NONALERT |
| | FILE_OPEN_FOR_BACKUP_INTENT |
| | FILE_DIRECTORY_FILE); |
| if (!NT_SUCCESS (status)) |
| { |
| if (status == STATUS_ACCESS_DENIED && fstat_mask) |
| fstat_mask = 0; |
| else |
| { |
| __seterrno_from_nt_status (status); |
| goto free_mounts; |
| } |
| } |
| } |
| while (!NT_SUCCESS (status)); |
| } |
| |
| /* FileIdBothDirectoryInformation was unsupported on XP when |
| accessing UDF. It's not clear if the call isn't also unsupported |
| on other OS/FS combinations. Instead of testing for yet another |
| error code, use FileIdBothDirectoryInformation only on FSes |
| supporting persistent ACLs. |
| |
| NFS clients hide dangling symlinks from directory queries, |
| unless you use the FileNamesInformation info class. |
| FileIdBothDirectoryInformation works fine, but only if the NFS |
| share is mounted to a drive letter. TODO: We don't test that |
| here for now, but it might be worth to test if there's a speed |
| gain in using FileIdBothDirectoryInformation, because it doesn't |
| require to open the file to read the inode number. */ |
| if (pc.hasgood_inode ()) |
| { |
| dir->__flags |= dirent_set_d_ino; |
| if (pc.fs_is_nfs ()) |
| dir->__flags |= dirent_nfs_d_ino; |
| else if (!pc.has_buggy_fileid_dirinfo ()) |
| dir->__flags |= dirent_get_d_ino; |
| } |
| } |
| if (fd >= 0) |
| dir->__d_fd = fd; |
| else |
| { |
| /* Filling cfd with `this' (aka storing this in the file |
| descriptor table should only happen after it's clear that |
| opendir doesn't fail, otherwise we end up cfree'ing the |
| fhandler twice, once in opendir() in dir.cc, the second |
| time on exit. Nasty, nasty... */ |
| cfd = this; |
| dir->__d_fd = cfd; |
| } |
| set_close_on_exec (true); |
| dir->__fh = this; |
| res = dir; |
| } |
| |
| syscall_printf ("%p = opendir (%s)", res, get_name ()); |
| return res; |
| |
| free_mounts: |
| delete d_mounts (dir); |
| free_dirent: |
| free (dir->__d_dirent); |
| free_dirname: |
| free (dir->__d_dirname); |
| free_dir: |
| free (dir); |
| return res; |
| } |
| |
| ino_t __reg2 |
| readdir_get_ino (const char *path, bool dot_dot) |
| { |
| char *fname; |
| struct stat st; |
| HANDLE hdl; |
| OBJECT_ATTRIBUTES attr; |
| IO_STATUS_BLOCK io; |
| ino_t ino = 0; |
| |
| if (dot_dot) |
| { |
| fname = (char *) alloca (strlen (path) + 4); |
| char *c = stpcpy (fname, path); |
| if (c[-1] != '/') |
| *c++ = '/'; |
| strcpy (c, ".."); |
| path = fname; |
| } |
| path_conv pc (path, PC_SYM_NOFOLLOW | PC_POSIX | PC_NOWARN | PC_KEEP_HANDLE); |
| if (pc.isspecial ()) |
| { |
| if (!stat_worker (pc, &st)) |
| ino = st.st_ino; |
| } |
| else if (!pc.hasgood_inode ()) |
| ino = hash_path_name (0, pc.get_nt_native_path ()); |
| else if ((hdl = pc.handle ()) != NULL |
| || NT_SUCCESS (NtOpenFile (&hdl, READ_CONTROL, |
| pc.get_object_attr (attr, sec_none_nih), |
| &io, FILE_SHARE_VALID_FLAGS, |
| FILE_OPEN_FOR_BACKUP_INTENT |
| | (pc.is_rep_symlink () |
| ? FILE_OPEN_REPARSE_POINT : 0))) |
| ) |
| { |
| ino = pc.get_ino_by_handle (hdl); |
| if (!ino) |
| ino = hash_path_name (0, pc.get_nt_native_path ()); |
| } |
| return ino; |
| } |
| |
| int |
| fhandler_disk_file::readdir_helper (DIR *dir, dirent *de, DWORD w32_err, |
| DWORD attr, PUNICODE_STRING fname) |
| { |
| if (w32_err) |
| { |
| switch (d_mounts (dir)->check_missing_mount (fname)) |
| { |
| case __DIR_mount_none: |
| fname->Length = 0; |
| return geterrno_from_win_error (w32_err); |
| case __DIR_mount_virt_target: |
| de->d_type = DT_DIR; |
| break; |
| default: |
| break; |
| } |
| attr = 0; |
| dir->__flags &= ~dirent_set_d_ino; |
| } |
| |
| /* Set d_type if type can be determined from file attributes. For .lnk |
| symlinks, d_type will be reset below. Reparse points can be NTFS |
| symlinks, even if they have the FILE_ATTRIBUTE_DIRECTORY flag set. */ |
| if (attr && !(attr & ~FILE_ATTRIBUTE_VALID_FLAGS)) |
| { |
| if (attr & FILE_ATTRIBUTE_DIRECTORY) |
| de->d_type = DT_DIR; |
| /* FILE_ATTRIBUTE_SYSTEM might denote system-bit type symlinks. */ |
| else if (!(attr & FILE_ATTRIBUTE_SYSTEM)) |
| de->d_type = DT_REG; |
| } |
| |
| /* Check for reparse points that can be treated as posix symlinks. |
| Mountpoints and unknown or unhandled reparse points will be treated |
| as normal file/directory/unknown. In all cases, returning the INO of |
| the reparse point (not of the target) matches behavior of posix systems. |
| */ |
| if (attr & FILE_ATTRIBUTE_REPARSE_POINT) |
| { |
| OBJECT_ATTRIBUTES oattr; |
| |
| InitializeObjectAttributes (&oattr, fname, pc.objcaseinsensitive (), |
| get_handle (), NULL); |
| if (readdir_check_reparse_point (&oattr, isremote ())) |
| de->d_type = DT_LNK; |
| } |
| |
| /* Check for Windows shortcut. If it's a Cygwin or U/WIN symlink, drop the |
| .lnk suffix and set d_type accordingly. */ |
| if ((attr & (FILE_ATTRIBUTE_DIRECTORY |
| | FILE_ATTRIBUTE_REPARSE_POINT |
| | FILE_ATTRIBUTE_READONLY)) == FILE_ATTRIBUTE_READONLY |
| && fname->Length > 4 * sizeof (WCHAR)) |
| { |
| UNICODE_STRING uname; |
| |
| RtlInitCountedUnicodeString (&uname, |
| fname->Buffer |
| + fname->Length / sizeof (WCHAR) - 4, |
| 4 * sizeof (WCHAR)); |
| if (RtlEqualUnicodeString (&uname, &ro_u_lnk, TRUE)) |
| { |
| tmp_pathbuf tp; |
| char *file = tp.c_get (); |
| char *p = stpcpy (file, pc.get_posix ()); |
| if (p[-1] != '/') |
| *p++ = '/'; |
| sys_wcstombs (p, NT_MAX_PATH - (p - file), |
| fname->Buffer, fname->Length / sizeof (WCHAR)); |
| path_conv fpath (file, PC_SYM_NOFOLLOW); |
| if (fpath.issymlink ()) |
| { |
| fname->Length -= 4 * sizeof (WCHAR); |
| de->d_type = DT_LNK; |
| } |
| else if (fpath.isfifo ()) |
| { |
| fname->Length -= 4 * sizeof (WCHAR); |
| de->d_type = DT_FIFO; |
| } |
| else if (fpath.is_fs_special ()) |
| { |
| fname->Length -= 4 * sizeof (WCHAR); |
| de->d_type = S_ISCHR (fpath.dev.mode ()) ? DT_CHR : DT_BLK; |
| } |
| } |
| } |
| |
| sys_wcstombs (de->d_name, NAME_MAX + 1, fname->Buffer, |
| fname->Length / sizeof (WCHAR)); |
| |
| /* Don't try to optimize relative to dir->__d_position. On several |
| filesystems it's no safe bet that "." and ".." entries always |
| come first. */ |
| if (de->d_name[0] == '.') |
| { |
| if (de->d_name[1] == '\0') |
| dir->__flags |= dirent_saw_dot; |
| else if (de->d_name[1] == '.' && de->d_name[2] == '\0') |
| dir->__flags |= dirent_saw_dot_dot; |
| } |
| return 0; |
| } |
| |
| int |
| fhandler_disk_file::readdir (DIR *dir, dirent *de) |
| { |
| int res = 0; |
| NTSTATUS status = STATUS_SUCCESS; |
| PFILE_ID_BOTH_DIR_INFORMATION buf = NULL; |
| PWCHAR FileName; |
| ULONG FileNameLength; |
| ULONG FileAttributes; |
| IO_STATUS_BLOCK io; |
| UNICODE_STRING fname; |
| |
| /* d_cachepos always refers to the next cache entry to use. If it's 0 |
| we must reload the cache. */ |
| restart: |
| FileAttributes = 0; |
| if (d_cachepos (dir) == 0) |
| { |
| if ((dir->__flags & dirent_get_d_ino)) |
| { |
| status = NtQueryDirectoryFile (get_handle (), NULL, NULL, NULL, &io, |
| d_cache (dir), DIR_BUF_SIZE, |
| FileIdBothDirectoryInformation, |
| FALSE, NULL, dir->__d_position == 0); |
| /* FileIdBothDirectoryInformation isn't supported for remote drives |
| on NT4 and 2K systems. There are also hacked versions of |
| Samba 3.0.x out there (Debian-based it seems), which return |
| STATUS_NOT_SUPPORTED rather than handling this info class. |
| We just fall back to using a standard directory query in |
| this case and note this case using the dirent_get_d_ino flag. */ |
| if (!NT_SUCCESS (status) && status != STATUS_NO_MORE_FILES |
| && (status == STATUS_INVALID_LEVEL |
| || status == STATUS_NOT_SUPPORTED |
| || status == STATUS_INVALID_PARAMETER |
| || status == STATUS_INVALID_NETWORK_RESPONSE |
| || status == STATUS_INVALID_INFO_CLASS)) |
| dir->__flags &= ~dirent_get_d_ino; |
| /* Something weird happens on Samba up to version 3.0.21c, which is |
| fixed in 3.0.22. FileIdBothDirectoryInformation seems to work |
| nicely, but only up to the 128th entry in the directory. After |
| reaching this entry, the next call to NtQueryDirectoryFile |
| (FileIdBothDirectoryInformation) returns STATUS_INVALID_LEVEL. |
| Why should we care, we can just switch to |
| FileBothDirectoryInformation, isn't it? Nope! The next call to |
| NtQueryDirectoryFile(FileBothDirectoryInformation) actually |
| returns STATUS_NO_MORE_FILES, regardless how many files are left |
| unread in the directory. This does not happen when using |
| FileBothDirectoryInformation right from the start, but since |
| we can't decide whether the server we're talking with has this |
| bug or not, we end up serving Samba shares always in the slow |
| mode using FileBothDirectoryInformation. So, what we do here is |
| to implement the solution suggested by Andrew Tridgell, we just |
| reread all entries up to dir->d_position using |
| FileBothDirectoryInformation. |
| However, We do *not* mark this server as broken and fall back to |
| using FileBothDirectoryInformation further on. This would slow |
| down every access to such a server, even for directories under |
| 128 entries. Also, bigger dirs only suffer from one additional |
| call per full directory scan, which shouldn't be too big a hit. |
| This can easily be changed if necessary. */ |
| if (status == STATUS_INVALID_LEVEL && dir->__d_position) |
| { |
| d_cachepos (dir) = 0; |
| for (int cnt = 0; cnt < dir->__d_position; ++cnt) |
| { |
| if (d_cachepos (dir) == 0) |
| { |
| status = NtQueryDirectoryFile (get_handle (), NULL, NULL, |
| NULL, &io, d_cache (dir), |
| DIR_BUF_SIZE, |
| FileBothDirectoryInformation, |
| FALSE, NULL, cnt == 0); |
| if (!NT_SUCCESS (status)) |
| goto go_ahead; |
| } |
| buf = (PFILE_ID_BOTH_DIR_INFORMATION) (d_cache (dir) |
| + d_cachepos (dir)); |
| if (buf->NextEntryOffset == 0) |
| d_cachepos (dir) = 0; |
| else |
| d_cachepos (dir) += buf->NextEntryOffset; |
| } |
| goto go_ahead; |
| } |
| } |
| if (!(dir->__flags & dirent_get_d_ino)) |
| status = NtQueryDirectoryFile (get_handle (), NULL, NULL, NULL, &io, |
| d_cache (dir), DIR_BUF_SIZE, |
| (dir->__flags & dirent_nfs_d_ino) |
| ? FileNamesInformation |
| : FileBothDirectoryInformation, |
| FALSE, NULL, dir->__d_position == 0); |
| } |
| |
| go_ahead: |
| |
| if (status == STATUS_NO_MORE_FILES) |
| /*nothing*/; |
| else if (!NT_SUCCESS (status)) |
| debug_printf ("NtQueryDirectoryFile failed, status %y, win32 error %u", |
| status, RtlNtStatusToDosError (status)); |
| else |
| { |
| buf = (PFILE_ID_BOTH_DIR_INFORMATION) (d_cache (dir) + d_cachepos (dir)); |
| if (buf->NextEntryOffset == 0) |
| d_cachepos (dir) = 0; |
| else |
| d_cachepos (dir) += buf->NextEntryOffset; |
| if ((dir->__flags & dirent_get_d_ino)) |
| { |
| FileName = buf->FileName; |
| FileNameLength = buf->FileNameLength; |
| FileAttributes = buf->FileAttributes; |
| if ((dir->__flags & dirent_set_d_ino)) |
| de->d_ino = buf->FileId.QuadPart; |
| } |
| else if ((dir->__flags & dirent_nfs_d_ino)) |
| { |
| FileName = ((PFILE_NAMES_INFORMATION) buf)->FileName; |
| FileNameLength = ((PFILE_NAMES_INFORMATION) buf)->FileNameLength; |
| } |
| else |
| { |
| FileName = ((PFILE_BOTH_DIR_INFORMATION) buf)->FileName; |
| FileNameLength = |
| ((PFILE_BOTH_DIR_INFORMATION) buf)->FileNameLength; |
| FileAttributes = |
| ((PFILE_BOTH_DIR_INFORMATION) buf)->FileAttributes; |
| } |
| /* We don't show O_TMPFILE files in the filesystem. This is a kludge, |
| so we may end up removing this snippet again. */ |
| if (FileAttributes & FILE_ATTRIBUTE_TEMPORARY) |
| goto restart; |
| RtlInitCountedUnicodeString (&fname, FileName, FileNameLength); |
| d_mounts (dir)->check_mount (&fname); |
| if (de->d_ino == 0 && (dir->__flags & dirent_set_d_ino)) |
| { |
| /* Don't try to optimize relative to dir->__d_position. On several |
| filesystems it's no safe bet that "." and ".." entries always |
| come first. */ |
| if (FileNameLength == sizeof (WCHAR) && FileName[0] == '.') |
| de->d_ino = pc.get_ino_by_handle (get_handle ()); |
| else if (FileNameLength == 2 * sizeof (WCHAR) |
| && FileName[0] == L'.' && FileName[1] == L'.') |
| { |
| if (!(dir->__flags & dirent_isroot)) |
| de->d_ino = readdir_get_ino (get_name (), true); |
| else |
| de->d_ino = pc.get_ino_by_handle (get_handle ()); |
| } |
| else |
| { |
| OBJECT_ATTRIBUTES attr; |
| HANDLE hdl; |
| NTSTATUS f_status; |
| |
| InitializeObjectAttributes (&attr, &fname, |
| pc.objcaseinsensitive (), |
| get_handle (), NULL); |
| /* FILE_OPEN_REPARSE_POINT on NFS is a no-op, so the normal |
| NtOpenFile here returns the inode number of the symlink target, |
| rather than the inode number of the symlink itself. |
| |
| Worse, trying to open a symlink without setting the special |
| "ActOnSymlink" EA triggers a bug in Windows 7 which results |
| in a timeout of up to 20 seconds, followed by two exceptions |
| in the NT kernel. |
| |
| Since both results are far from desirable, we open symlinks |
| on NFS so that we get the right inode and a happy W7. |
| And, since some filesystems choke on the EAs, we don't |
| use them unconditionally. */ |
| f_status = (dir->__flags & dirent_nfs_d_ino) |
| ? NtCreateFile (&hdl, READ_CONTROL, &attr, &io, |
| NULL, 0, FILE_SHARE_VALID_FLAGS, |
| FILE_OPEN, FILE_OPEN_FOR_BACKUP_INTENT, |
| &nfs_aol_ffei, sizeof nfs_aol_ffei) |
| : NtOpenFile (&hdl, READ_CONTROL, &attr, &io, |
| FILE_SHARE_VALID_FLAGS, |
| FILE_OPEN_FOR_BACKUP_INTENT |
| | FILE_OPEN_REPARSE_POINT); |
| if (NT_SUCCESS (f_status)) |
| { |
| /* We call NtQueryInformationFile here, rather than |
| pc.get_ino_by_handle(), otherwise we can't short-circuit |
| dirent_set_d_ino correctly. */ |
| FILE_INTERNAL_INFORMATION fii; |
| f_status = NtQueryInformationFile (hdl, &io, &fii, sizeof fii, |
| FileInternalInformation); |
| NtClose (hdl); |
| if (NT_SUCCESS (f_status)) |
| { |
| if (pc.isgood_inode (fii.IndexNumber.QuadPart)) |
| de->d_ino = fii.IndexNumber.QuadPart; |
| else |
| /* Untrusted file system. Don't try to fetch inode |
| number again. */ |
| dir->__flags &= ~dirent_set_d_ino; |
| } |
| } |
| } |
| } |
| } |
| |
| if (!(res = readdir_helper (dir, de, RtlNtStatusToDosError (status), |
| FileAttributes, &fname))) |
| dir->__d_position++; |
| else if (!(dir->__flags & dirent_saw_dot)) |
| { |
| strcpy (de->d_name , "."); |
| de->d_ino = pc.get_ino_by_handle (get_handle ()); |
| de->d_type = DT_DIR; |
| dir->__d_position++; |
| dir->__flags |= dirent_saw_dot; |
| res = 0; |
| } |
| else if (!(dir->__flags & dirent_saw_dot_dot)) |
| { |
| strcpy (de->d_name , ".."); |
| if (!(dir->__flags & dirent_isroot)) |
| de->d_ino = readdir_get_ino (get_name (), true); |
| else |
| de->d_ino = pc.get_ino_by_handle (get_handle ()); |
| de->d_type = DT_DIR; |
| dir->__d_position++; |
| dir->__flags |= dirent_saw_dot_dot; |
| res = 0; |
| } |
| |
| syscall_printf ("%d = readdir(%p, %p) (L\"%lS\" > \"%ls\") (attr %y > type %d)", |
| res, dir, &de, res ? NULL : &fname, res ? "***" : de->d_name, |
| FileAttributes, de->d_type); |
| return res; |
| } |
| |
| long |
| fhandler_disk_file::telldir (DIR *dir) |
| { |
| return dir->__d_position; |
| } |
| |
| void |
| fhandler_disk_file::seekdir (DIR *dir, long loc) |
| { |
| rewinddir (dir); |
| while (loc > dir->__d_position) |
| if (!::readdir (dir)) |
| break; |
| } |
| |
| void |
| fhandler_disk_file::rewinddir (DIR *dir) |
| { |
| d_cachepos (dir) = 0; |
| dir->__d_position = 0; |
| d_mounts (dir)->rewind (); |
| } |
| |
| int |
| fhandler_disk_file::closedir (DIR *dir) |
| { |
| int res = 0; |
| |
| delete d_mounts (dir); |
| syscall_printf ("%d = closedir(%p, %s)", res, dir, get_name ()); |
| return res; |
| } |
| |
| fhandler_cygdrive::fhandler_cygdrive () : |
| fhandler_disk_file (), ndrives (0), pdrive (NULL) |
| { |
| } |
| |
| int |
| fhandler_cygdrive::open (int flags, mode_t mode) |
| { |
| if ((flags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL)) |
| { |
| set_errno (EEXIST); |
| return 0; |
| } |
| if (flags & O_WRONLY) |
| { |
| set_errno (EISDIR); |
| return 0; |
| } |
| /* Open a fake handle to \\Device\\Null */ |
| return open_null (flags); |
| } |
| |
| void |
| fhandler_cygdrive::set_drives () |
| { |
| pdrive = pdrive_buf; |
| ndrives = GetLogicalDriveStrings (sizeof pdrive_buf, pdrive_buf) / DRVSZ; |
| } |
| |
| int |
| fhandler_cygdrive::fstat (struct stat *buf) |
| { |
| fhandler_base::fstat (buf); |
| buf->st_ino = 2; |
| buf->st_mode = S_IFDIR | STD_RBITS | STD_XBITS; |
| buf->st_nlink = 1; |
| return 0; |
| } |
| |
| int __reg2 |
| fhandler_cygdrive::fstatvfs (struct statvfs *sfs) |
| { |
| /* Virtual file system. Just return an empty buffer with a few values |
| set to something useful. Just as on Linux. */ |
| memset (sfs, 0, sizeof (*sfs)); |
| sfs->f_bsize = sfs->f_frsize = 4096; |
| sfs->f_flag = ST_RDONLY; |
| sfs->f_namemax = NAME_MAX; |
| return 0; |
| } |
| |
| DIR * |
| fhandler_cygdrive::opendir (int fd) |
| { |
| DIR *dir; |
| |
| dir = fhandler_disk_file::opendir (fd); |
| if (dir && !ndrives) |
| set_drives (); |
| |
| return dir; |
| } |
| |
| int |
| fhandler_cygdrive::readdir (DIR *dir, dirent *de) |
| { |
| WCHAR drive[] = L"X:"; |
| |
| while (true) |
| { |
| if (!pdrive || !*pdrive) |
| { |
| if (!(dir->__flags & dirent_saw_dot)) |
| { |
| de->d_name[0] = '.'; |
| de->d_name[1] = '\0'; |
| de->d_ino = 2; |
| } |
| return ENMFILE; |
| } |
| disk_type dt = get_disk_type ((drive[0] = *pdrive, drive)); |
| if (dt == DT_SHARE_SMB) |
| { |
| /* Calling NetUseGetInfo on SMB drives allows to fetch the |
| current state of the drive without trying to open a file |
| descriptor on the share (GetFileAttributes). This avoids |
| waiting for SMB timeouts. Of course, there's a downside: |
| If a drive becomes availabe again, it can take a couple of |
| minutes to recognize it. As long as this didn't happen, |
| the drive will not show up in the cygdrive dir. */ |
| PUSE_INFO_1 pui1; |
| DWORD status; |
| |
| if (NetUseGetInfo (NULL, drive, 1, (PBYTE *) &pui1) == NERR_Success) |
| { |
| status = pui1->ui1_status; |
| NetApiBufferFree (pui1); |
| if (status == USE_OK) |
| break; |
| } |
| } |
| else if (dt != DT_FLOPPY |
| && GetFileAttributes (pdrive) != INVALID_FILE_ATTRIBUTES) |
| break; |
| pdrive = strchr (pdrive, '\0') + 1; |
| } |
| *de->d_name = cyg_tolower (*pdrive); |
| de->d_name[1] = '\0'; |
| user_shared->warned_msdos = true; |
| de->d_ino = readdir_get_ino (pdrive, false); |
| dir->__d_position++; |
| pdrive = strchr (pdrive, '\0') + 1; |
| syscall_printf ("%p = readdir (%p) (%s)", &de, dir, de->d_name); |
| return 0; |
| } |
| |
| void |
| fhandler_cygdrive::rewinddir (DIR *dir) |
| { |
| pdrive = pdrive_buf; |
| dir->__d_position = 0; |
| } |
| |
| int |
| fhandler_cygdrive::closedir (DIR *dir) |
| { |
| pdrive = pdrive_buf; |
| return 0; |
| } |