#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)
#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
#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
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"
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);
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);
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)
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)
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)
? 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)
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);
}
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;
+ debug_printf ("NtQueryInformationFile (FileNameInformation) failed, %08x",
+ status);
+ goto out;
}
- else if (win32_path.fs_is_ntfs ())
+ /* 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
+ 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;
+
+ /* 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))
{
- strcpy (c, "RECYCLER"); /* NTFS up to 2K3 */
- c += 8;
+ debug_printf ("NtOpenFile (%S) failed, %08x", &root, status);
+ goto out;
}
- else if (win32_path.fs_is_fat ())
+
+ /* 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 ())
{
- strcpy (c, "Recycled"); /* FAT up to 2K3 */
- c += 8;
+ 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;
}
- else
- return;
-
- /* 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 hopefully unique filename. */
+ RtlAppendUnicodeToString (&recycler, L"\\cyg");
+ pfii = (PFILE_INTERNAL_INFORMATION) infobuf;
+ status = NtQueryInformationFile (h, &io, pfii, sizeof infobuf,
+ FileInternalInformation);
+ if (!NT_SUCCESS (status))
{
- if (!CreateDirectory (recycler, NULL))
+ 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 = 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 ("Can't create folder %s, %E", recycler);
- return;
+ debug_printf ("NtOpenFile (%S) failed, %08x", &recycler, status);
+ goto out;
}
- SetFileAttributes (recycler,
- FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
- }
-
- /* 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. */
- if (win32_path.fs_is_ntfs ())
- {
- *c++ = '\\';
- cygheap->user.get_windows_id (c);
- while (*c)
- ++c;
- if (GetFileAttributes (recycler) == INVALID_FILE_ATTRIBUTES)
+ /* 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))
{
- if (!CreateDirectory (recycler,
- sec_user ((PSECURITY_ATTRIBUTES) alloca (1024),
- cygheap->user.sid ())))
+ 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 */
{
- debug_printf ("Can't create folder %s, %E", recycler);
- return;
+ 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);
+ }
}
- SetFileAttributes (recycler,
- FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
}
+ NtClose (recyclerdir);
+ /* Shoot again. */
+ status = NtSetInformationFile (h, &io, pfri, sizeof infobuf,
+ FileRenameInformation);
}
-
- /* 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);
-
- 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);
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