diff --git a/winsup/cygwin/ChangeLog b/winsup/cygwin/ChangeLog index dcef6f846..0aee7f3a9 100644 --- a/winsup/cygwin/ChangeLog +++ b/winsup/cygwin/ChangeLog @@ -1,3 +1,25 @@ +2007-08-12 Corinna Vinschen + + * ntdll.h: Add descriptive comments to special Rtl functions. + (STATUS_OBJECT_PATH_NOT_FOUND): Define. + (STATUS_BUFFER_OVERFLOW): Define. + (FILE_SUPERSEDED): Define. + (FILE_OPENED): Define. + (FILE_CREATED): Define. + (FILE_OVERWRITTEN): Define. + (FILE_EXISTS): Define. + (FILE_DOES_NOT_EXIST): Define. + (PIO_APC_ROUTINE): Typedef. + (NtFsControlFile): Fix parameter types to use PIO_APC_ROUTINE. + (NtWriteFile): Declare. + (RtlInt64ToHexUnicodeString): Declare. + * strfuncs.cc: Include ntdll.h. + (RtlInt64ToHexUnicodeString): New function. + * syscalls.cc (try_to_bin): Rewrite using native NT functions. + Only try to create recycle bin after unsuccessfully trying to move + file. Also try to create special files in recycle bin so that Windows + Explorer isn't unnecessarily stampeded. + 2007-08-10 Corinna Vinschen * path.cc (fillout_mntent): Fix calculation of unicode buffer size. diff --git a/winsup/cygwin/ntdll.h b/winsup/cygwin/ntdll.h index 1475dd68e..3c94010ca 100644 --- a/winsup/cygwin/ntdll.h +++ b/winsup/cygwin/ntdll.h @@ -20,6 +20,7 @@ #define STATUS_ACCESS_DENIED ((NTSTATUS) 0xc0000022) #define STATUS_BUFFER_TOO_SMALL ((NTSTATUS) 0xc0000023) #define STATUS_OBJECT_NAME_NOT_FOUND ((NTSTATUS) 0xc0000034) +#define STATUS_OBJECT_PATH_NOT_FOUND ((NTSTATUS) 0xc000003A) #define STATUS_SHARING_VIOLATION ((NTSTATUS) 0xc0000043) #define STATUS_DELETE_PENDING ((NTSTATUS) 0xc0000056) #define STATUS_WORKING_SET_QUOTA ((NTSTATUS) 0xc00000a1) @@ -27,6 +28,7 @@ #define STATUS_DIRECTORY_NOT_EMPTY ((NTSTATUS) 0xc0000101) #define STATUS_NOT_ALL_ASSIGNED ((NTSTATUS) 0x00000106) #define STATUS_INVALID_LEVEL ((NTSTATUS) 0xc0000148) +#define STATUS_BUFFER_OVERFLOW ((NTSTATUS) 0x80000005) #define STATUS_NO_MORE_FILES ((NTSTATUS) 0x80000006) #define PDI_MODULES 0x01 #define PDI_HEAPS 0x04 @@ -41,6 +43,13 @@ #define WSLE_PAGE_SHARE_COUNT_MASK 0x0E0 #define WSLE_PAGE_SHAREABLE 0x100 +#define FILE_SUPERSEDED 0 +#define FILE_OPENED 1 +#define FILE_CREATED 2 +#define FILE_OVERWRITTEN 3 +#define FILE_EXISTS 4 +#define FILE_DOES_NOT_EXIST 5 + /* Device Characteristics. */ #define FILE_REMOVABLE_MEDIA 0x00000001 #define FILE_READ_ONLY_DEVICE 0x00000002 @@ -736,6 +745,8 @@ typedef struct _FILE_FULL_EA_INFORMATION CHAR EaName[1]; } FILE_FULL_EA_INFORMATION, *PFILE_FULL_EA_INFORMATION; +typedef VOID NTAPI (*PIO_APC_ROUTINE)(PVOID, PIO_STATUS_BLOCK, ULONG); + /* Function declarations for ntdll.dll. These don't appear in any standard Win32 header. */ extern "C" @@ -753,7 +764,7 @@ extern "C" PTOKEN_GROUPS, PTOKEN_PRIVILEGES, PTOKEN_OWNER, PTOKEN_PRIMARY_GROUP, PTOKEN_DEFAULT_DACL, PTOKEN_SOURCE); - NTSTATUS NTAPI NtFsControlFile (HANDLE, HANDLE, PVOID, PVOID, + NTSTATUS NTAPI NtFsControlFile (HANDLE, HANDLE, PIO_APC_ROUTINE, PVOID, PIO_STATUS_BLOCK, ULONG, PVOID, ULONG, PVOID, ULONG); NTSTATUS NTAPI NtLockVirtualMemory (HANDLE, PVOID *, ULONG *, ULONG); @@ -799,6 +810,9 @@ extern "C" PSECURITY_DESCRIPTOR); NTSTATUS NTAPI NtUnlockVirtualMemory (HANDLE, PVOID *, ULONG *, ULONG); NTSTATUS NTAPI NtUnmapViewOfSection (HANDLE, PVOID); + NTSTATUS NTAPI NtWriteFile (HANDLE, HANDLE, PIO_APC_ROUTINE, PVOID, + PIO_STATUS_BLOCK, PVOID, ULONG, PLARGE_INTEGER, + PULONG); NTSTATUS NTAPI RtlAppendUnicodeToString (PUNICODE_STRING, PCWSTR); NTSTATUS NTAPI RtlAppendUnicodeStringToString (PUNICODE_STRING, PUNICODE_STRING); @@ -830,8 +844,11 @@ extern "C" BOOLEAN); /* A few Rtl functions are either actually macros, or they just don't - exist even though they would be a big help. We implement them here - as inline functions. */ + exist even though they would be a big help. We implement them here, + partly as inline functions. */ + + /* RtlInitEmptyUnicodeString is defined as a macro in wdm.h, but that file + is missing entirely in w32api. */ inline VOID NTAPI RtlInitEmptyUnicodeString(PUNICODE_STRING dest, PCWSTR buf, USHORT len) @@ -840,6 +857,11 @@ extern "C" dest->MaximumLength = len; dest->Buffer = (PWSTR) buf; } + /* Like RtlInitEmptyUnicodeString, but initialize Length to len, too. + This is for instance useful when creating a UNICODE_STRING from an + NtQueryInformationFile info buffer, where the length of the filename + is known, but you can't rely on the string being 0-terminated. + If you know it's 0-terminated, just use RtlInitUnicodeString(). */ inline VOID NTAPI RtlInitCountedUnicodeString (PUNICODE_STRING dest, PCWSTR buf, USHORT len) @@ -847,20 +869,29 @@ extern "C" dest->Length = dest->MaximumLength = len; dest->Buffer = (PWSTR) buf; } + /* Split path into dirname and basename part. This function does not + copy anything! It just initializes the dirname and basename + UNICODE_STRINGs so that their Buffer members point to the right spot + into path's Buffer, and the Length (and MaximumLength) members are set + to match the dirname part and the basename part. + Note that dirname's Length is set so that it also includes the trailing + backslash. If you don't need it, just subtract sizeof(WCHAR) from + dirname.Length. */ inline - VOID NTAPI RtlSplitUnicodePath (PUNICODE_STRING path, PUNICODE_STRING dir, - PUNICODE_STRING file) + VOID NTAPI RtlSplitUnicodePath (PUNICODE_STRING path, PUNICODE_STRING dirname, + PUNICODE_STRING basename) { USHORT len = path->Length / sizeof (WCHAR); while (len > 0 && path->Buffer[--len] != L'\\') ; ++len; - if (dir) - RtlInitCountedUnicodeString (dir, path->Buffer, len * sizeof (WCHAR)); - if (file) - RtlInitCountedUnicodeString (file, &path->Buffer[len], + if (dirname) + RtlInitCountedUnicodeString (dirname, path->Buffer, len * sizeof (WCHAR)); + if (basename) + RtlInitCountedUnicodeString (basename, &path->Buffer[len], path->Length - len * sizeof (WCHAR)); } + /* Check if prefix is a prefix of path. */ inline BOOLEAN NTAPI RtlEqualUnicodePathPrefix (PUNICODE_STRING path, PCWSTR prefix, BOOLEAN caseinsensitive) @@ -873,6 +904,7 @@ extern "C" ? pref.Length : path->Length); return RtlEqualUnicodeString (&p, &pref, caseinsensitive); } + /* Check if sufffix is a sufffix of path. */ inline BOOL NTAPI RtlEqualUnicodePathSuffix (PUNICODE_STRING path, PCWSTR suffix, BOOLEAN caseinsensitive) @@ -888,4 +920,12 @@ extern "C" RtlInitCountedUnicodeString (&p, path->Buffer, path->Length); return RtlEqualUnicodeString (&p, &suf, caseinsensitive); } + /* Implemented in strfuncs.cc. Create a Hex UNICODE_STRING from a given + 64 bit integer value. If append is TRUE, append the hex string, + otherwise overwrite dest. Returns either STAUTUS_SUCCESS, or + STATUS_BUFFER_OVERFLOW, if the unicode buffer is too small (hasn't + room for 16 WCHARs). */ + NTSTATUS NTAPI RtlInt64ToHexUnicodeString (ULONGLONG value, + PUNICODE_STRING dest, + BOOLEAN append); } diff --git a/winsup/cygwin/strfuncs.cc b/winsup/cygwin/strfuncs.cc index c0029fb1f..91ed160c3 100644 --- a/winsup/cygwin/strfuncs.cc +++ b/winsup/cygwin/strfuncs.cc @@ -12,6 +12,7 @@ details. */ #include "winsup.h" #include #include +#include codepage_type current_codepage = ansi_cp; @@ -41,3 +42,23 @@ sys_mbstowcs (WCHAR *tgt, const char *src, int len) int res = MultiByteToWideChar (get_cp (), 0, src, -1, tgt, len); return res; } + +static WCHAR hex_wchars[] = L"0123456789abcdef"; + +NTSTATUS NTAPI +RtlInt64ToHexUnicodeString (ULONGLONG value, PUNICODE_STRING dest, + BOOLEAN append) +{ + USHORT len = append ? dest->Length : 0; + if (dest->MaximumLength - len < 16 * (int) sizeof (WCHAR)) + return STATUS_BUFFER_OVERFLOW; + PWCHAR end = (PWCHAR) ((PBYTE) dest->Buffer + len); + register PWCHAR p = end + 16; + while (p-- > end) + { + *p = hex_wchars[value & 0xf]; + value >>= 4; + } + dest->Length += 16 * sizeof (WCHAR); + return STATUS_SUCCESS; +} diff --git a/winsup/cygwin/syscalls.cc b/winsup/cygwin/syscalls.cc index 89cff28f9..2ac5f267f 100644 --- a/winsup/cygwin/syscalls.cc +++ b/winsup/cygwin/syscalls.cc @@ -139,94 +139,231 @@ dup2 (int oldfd, int newfd) return cygheap->fdtab.dup2 (oldfd, newfd); } +static char desktop_ini[] = + "[.ShellClassInfo]\r\nCLSID={645FF040-5081-101B-9F08-00AA002F954E}\r\n"; +static BYTE info2[] = +{ + 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x20, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + static void try_to_bin (path_conv &win32_path, HANDLE h) { NTSTATUS status; + OBJECT_ATTRIBUTES attr; IO_STATUS_BLOCK io; - char recycler[CYG_MAX_PATH + 20]; + HANDLE rootdir = NULL, recyclerdir = NULL; + USHORT recycler_base_len = 0, recycler_user_len = 0; + UNICODE_STRING root, recycler, fname; + WCHAR recyclerbuf[NAME_MAX + 1]; /* Enough for recycler + SID + filename */ + PFILE_NAME_INFORMATION pfni; + PFILE_INTERNAL_INFORMATION pfii; + PFILE_RENAME_INFORMATION pfri; + BYTE infobuf[sizeof (FILE_NAME_INFORMATION ) + 32767 * sizeof (WCHAR)]; - rootdir (win32_path, recycler); - char *c = recycler + strlen (recycler); - if (wincap.has_recycle_dot_bin ()) + pfni = (PFILE_NAME_INFORMATION) infobuf; + status = NtQueryInformationFile (h, &io, pfni, sizeof infobuf, + FileNameInformation); + if (!NT_SUCCESS (status)) { - strcpy (c, "$Recycle.Bin"); /* NTFS and FAT since Vista */ - c += 12; - } - else if (win32_path.fs_is_ntfs ()) - { - strcpy (c, "RECYCLER"); /* NTFS up to 2K3 */ - c += 8; - } - else if (win32_path.fs_is_fat ()) - { - strcpy (c, "Recycled"); /* FAT up to 2K3 */ - c += 8; + debug_printf ("NtQueryInformationFile (FileNameInformation) failed, %08x", + status); + goto out; } + /* The filename could change, the parent dir not. So we split both paths + and take the prefix. However, there are two special cases: + - The handle refers to the root dir of the volume. + - The handle refers to the recycler or a subdir. + Both cases are handled by just returning and not even trying to move + them into the recycler. */ + if (pfni->FileNameLength == 2) /* root dir. */ + goto out; + /* Initialize recycler path. */ + RtlInitEmptyUnicodeString (&recycler, recyclerbuf, sizeof recyclerbuf); + if (wincap.has_recycle_dot_bin ()) /* NTFS and FAT since Vista */ + RtlAppendUnicodeToString (&recycler, L"\\$Recycle.Bin\\"); + else if (win32_path.fs_is_ntfs ()) /* NTFS up to 2K3 */ + RtlAppendUnicodeToString (&recycler, L"\\RECYCLER\\"); + else if (win32_path.fs_is_fat ()) /* FAT up to 2K3 */ + RtlAppendUnicodeToString (&recycler, L"\\Recycled\\"); else - return; + goto out; + /* Is the file a subdir of the recycler? */ + RtlInitCountedUnicodeString(&fname, pfni->FileName, pfni->FileNameLength); + if (RtlEqualUnicodePathPrefix (&fname, recycler.Buffer, TRUE)) + goto out; + /* Is fname the recycler? Temporarily hide trailing backslash. */ + recycler.Length -= sizeof (WCHAR); + if (RtlEqualUnicodeString (&fname, &recycler, TRUE)) + goto out; - /* Yes, we can really do that. Typically the recycle bin is created - by the first user actually using the bin. The permissions are the - default permissions propagated from the root directory. */ - if (GetFileAttributes (recycler) == INVALID_FILE_ATTRIBUTES) + /* Create root dir path from file name information. */ + RtlSplitUnicodePath (&fname, &fname, NULL); + RtlSplitUnicodePath (win32_path.get_nt_native_path (), &root, NULL); + root.Length -= fname.Length - sizeof (WCHAR); + + /* Open root directory. */ + InitializeObjectAttributes (&attr, &root, OBJ_CASE_INSENSITIVE, NULL, NULL); + status = NtOpenFile (&rootdir, FILE_TRAVERSE, &attr, &io, + FILE_SHARE_VALID_FLAGS, FILE_OPEN_FOR_BACKUP_INTENT); + if (!NT_SUCCESS (status)) { - if (!CreateDirectory (recycler, NULL)) - { - debug_printf ("Can't create folder %s, %E", recycler); - return; - } - SetFileAttributes (recycler, - FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN); + debug_printf ("NtOpenFile (%S) failed, %08x", &root, status); + goto out; } - /* Up to Windows 2003 Server, the default settings for the top level recycle - bin are so that everybody has the right to create files in it. Starting - with Vista, users are by default not allowed to create files in that - directory, only subdirectories. Too bad, but that requires to move - files to the user's own recycler subdir. Instead of adding yet another - special case, we just move the stuff to the user's recycler, especially - since only shared files are moved at all. */ + /* Strip leading backslash */ + ++recycler.Buffer; + recycler.Length -= sizeof (WCHAR); + /* Store length of recycler base dir, should it be necessary to create it. */ + recycler_base_len = recycler.Length; + /* On NTFS the recycler dir contains user specific subdirs, which are the + actual recycle bins per user. The name if this dir is the string + representation of the user SID. */ if (win32_path.fs_is_ntfs ()) { - *c++ = '\\'; - cygheap->user.get_windows_id (c); - while (*c) - ++c; - if (GetFileAttributes (recycler) == INVALID_FILE_ATTRIBUTES) - { - if (!CreateDirectory (recycler, - sec_user ((PSECURITY_ATTRIBUTES) alloca (1024), - cygheap->user.sid ()))) - { - debug_printf ("Can't create folder %s, %E", recycler); - return; - } - SetFileAttributes (recycler, - FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN); - } + UNICODE_STRING sid; + WCHAR sidbuf[128]; + /* Unhide trailing backslash. */ + recycler.Length += sizeof (WCHAR); + RtlInitEmptyUnicodeString (&sid, sidbuf, sizeof sidbuf); + /* In contrast to what MSDN claims, this function is already available + since NT4. */ + RtlConvertSidToUnicodeString (&sid, cygheap->user.sid (), FALSE); + RtlAppendUnicodeStringToString (&recycler, &sid); + recycler_user_len = recycler.Length; } - /* Create hopefully unique filename. */ - __small_sprintf (c, "\\cyg%016X", hash_path_name (myself->uid, - win32_path.get_win32 ())); - c += 20; - - /* Length of the WCHAR path in bytes. */ - ULONG len = 2 * (c - recycler); - /* Choose size big enough to fit a local native NT path into it. */ - ULONG size = sizeof (FILE_RENAME_INFORMATION) + len + 10; - PFILE_RENAME_INFORMATION pfri = (PFILE_RENAME_INFORMATION) alloca (size); - + RtlAppendUnicodeToString (&recycler, L"\\cyg"); + pfii = (PFILE_INTERNAL_INFORMATION) infobuf; + status = NtQueryInformationFile (h, &io, pfii, sizeof infobuf, + FileInternalInformation); + if (!NT_SUCCESS (status)) + { + debug_printf ("NtQueryInformationFile (FileInternalInformation) failed, " + "%08x", status); + goto out; + } + RtlInt64ToHexUnicodeString (pfii->FileId.QuadPart, &recycler, TRUE); + /* Shoot. */ + pfri = (PFILE_RENAME_INFORMATION) infobuf; pfri->ReplaceIfExists = TRUE; - pfri->RootDirectory = NULL; - UNICODE_STRING uname = { 0, len + 10, pfri->FileName }; - get_nt_native_path (recycler, uname); - pfri->FileNameLength = uname.Length; - status = NtSetInformationFile (h, &io, pfri, size, FileRenameInformation); + pfri->RootDirectory = rootdir; + pfri->FileNameLength = recycler.Length; + memcpy (pfri->FileName, recycler.Buffer, recycler.Length); + status = NtSetInformationFile (h, &io, pfri, sizeof infobuf, + FileRenameInformation); + if (status == STATUS_OBJECT_PATH_NOT_FOUND) + { + /* Ok, so the recycler and/or the recycler/SID directory don't exist. + First reopen root dir with permission to create subdirs. */ + NtClose (rootdir); + status = NtOpenFile (&rootdir, FILE_ADD_SUBDIRECTORY, &attr, &io, + FILE_SHARE_VALID_FLAGS, FILE_OPEN_FOR_BACKUP_INTENT); + if (!NT_SUCCESS (status)) + { + debug_printf ("NtOpenFile (%S) failed, %08x", &recycler, status); + goto out; + } + /* Then check if recycler exists by opening and potentially creating it. + Yes, we can really do that. Typically the recycle bin is created + by the first user actually using the bin. The permissions are the + default permissions propagated from the root directory. */ + InitializeObjectAttributes (&attr, &recycler, OBJ_CASE_INSENSITIVE, + rootdir, NULL); + recycler.Length = recycler_base_len; + status = NtCreateFile (&recyclerdir, + READ_CONTROL + | (win32_path.fs_is_ntfs () ? 0 : FILE_ADD_FILE), + &attr, &io, NULL, + FILE_ATTRIBUTE_DIRECTORY + | FILE_ATTRIBUTE_SYSTEM + | FILE_ATTRIBUTE_HIDDEN, + FILE_SHARE_VALID_FLAGS, FILE_OPEN_IF, + FILE_DIRECTORY_FILE, NULL, 0); + if (!NT_SUCCESS (status)) + { + debug_printf ("NtCreateFile (%S) failed, %08x", &recycler, status); + goto out; + } + /* Next, if necessary, check if the recycler/SID dir exists and + create it if not. */ + if (win32_path.fs_is_ntfs ()) + { + NtClose (recyclerdir); + recycler.Length = recycler_user_len; + status = NtCreateFile (&recyclerdir, READ_CONTROL | FILE_ADD_FILE, + &attr, &io, NULL, FILE_ATTRIBUTE_DIRECTORY + | FILE_ATTRIBUTE_SYSTEM + | FILE_ATTRIBUTE_HIDDEN, + FILE_SHARE_VALID_FLAGS, FILE_OPEN_IF, + FILE_DIRECTORY_FILE, NULL, 0); + if (!NT_SUCCESS (status)) + { + debug_printf ("NtCreateFile (%S) failed, %08x", + &recycler, status); + goto out; + } + } + /* The desktop.ini and INFO2 (pre-Vista) files are expected by + Windows Explorer. Otherwise, the created bin is treated as + corrupted */ + if (io.Information == FILE_CREATED) + { + HANDLE fh; + RtlInitUnicodeString (&fname, L"desktop.ini"); + InitializeObjectAttributes (&attr, &fname, OBJ_CASE_INSENSITIVE, + recyclerdir, NULL); + status = NtCreateFile (&fh, FILE_GENERIC_WRITE, &attr, &io, NULL, + FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN, + FILE_SHARE_VALID_FLAGS, FILE_CREATE, + FILE_SYNCHRONOUS_IO_NONALERT + | FILE_NON_DIRECTORY_FILE, NULL, 0); + if (!NT_SUCCESS (status)) + debug_printf ("NtCreateFile (%S) failed, %08x", &recycler, status); + else + { + status = NtWriteFile (fh, NULL, NULL, NULL, &io, desktop_ini, + sizeof desktop_ini - 1, NULL, NULL); + if (!NT_SUCCESS (status)) + debug_printf ("NtWriteFile (%S) failed, %08x", &fname, status); + NtClose (fh); + } + if (!wincap.has_recycle_dot_bin ()) /* No INFO2 file since Vista */ + { + RtlInitUnicodeString (&fname, L"INFO2"); + status = NtCreateFile (&fh, FILE_GENERIC_WRITE, &attr, &io, NULL, + FILE_ATTRIBUTE_ARCHIVE + | FILE_ATTRIBUTE_HIDDEN, + FILE_SHARE_VALID_FLAGS, FILE_CREATE, + FILE_SYNCHRONOUS_IO_NONALERT + | FILE_NON_DIRECTORY_FILE, NULL, 0); + if (!NT_SUCCESS (status)) + debug_printf ("NtCreateFile (%S) failed, %08x", + &recycler, status); + else + { + status = NtWriteFile (fh, NULL, NULL, NULL, &io, info2, + sizeof info2, NULL, NULL); + if (!NT_SUCCESS (status)) + debug_printf ("NtWriteFile (%S) failed, %08x", + &fname, status); + NtClose (fh); + } + } + } + NtClose (recyclerdir); + /* Shoot again. */ + status = NtSetInformationFile (h, &io, pfri, sizeof infobuf, + FileRenameInformation); + } if (!NT_SUCCESS (status)) debug_printf ("Move %s to %s failed, status = %p", win32_path.get_win32 (), recycler, status); +out: + if (rootdir) + NtClose (rootdir); } static NTSTATUS