blob: e08c66d74e7d0782feebe3f7171263750102ccb1 [file] [log] [blame] [raw]
/* A part of the Native C Library for Windows NT
Copyright 2007-2015 PC GO Ld.
Copyright 2015-2026 Rivoreo
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at
your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
*/
#include <windows.h>
#include <nt.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <pathname.h>
#include <ntstatus.h>
// Can't use read-only pointer...
static int remove_directory(OBJECT_ATTRIBUTES *obj_attr) {
long int status;
void *handle;
#ifdef NATIVELIBC_DENY_REMOVE_DIRECTORY_BY_SYMBOLIC_LINK
status = NtOpenSymbolicLinkObject(&handle, 0, obj_attr);
if(status != STATUS_OBJECT_TYPE_MISMATCH) {
// Refuse to operate on a symbolic link even the original path contents a '/' at end
if(status >= 0 || status == STATUS_ACCESS_DENIED) {
if(status >= 0) NtClose(handle);
errno = ENOTDIR;
} else __set_errno_from_ntstatus(status);
return -1;
}
#endif
status = NtOpenDirectoryObject(&handle, DIRECTORY_QUERY | DELETE, obj_attr);
//printf("nativelibc debug: rmdir: check is dir object: status = 0x%lx\n", status);
if(status != STATUS_OBJECT_TYPE_MISMATCH) {
if(status < 0) {
__set_errno_from_ntstatus(status);
return -1;
}
unsigned long int context = 0;
status = NtQueryDirectoryObject(handle, NULL, 0, 0, 1, &context, NULL);
//printf("nativelibc debug: rmdir: check dir object contents: status = 0x%lx\n", status);
if(status != STATUS_NO_MORE_ENTRIES) {
NtClose(handle);
if(status < 0) __set_errno_from_ntstatus(status);
else errno = ENOTEMPTY;
return -1;
}
OBJECT_BASIC_INFORMATION obi;
status = NtQueryObject(handle, ObjectBasicInformation, &obi, sizeof obi, NULL);
//printf("nativelibc debug: rmdir: check other opens: NtQueryObject status = 0x%lx\n", status);
if(status < 0) {
NtClose(handle);
__set_errno_from_ntstatus(status);
return -1;
}
if(obi.HandleCount > 1) {
NtClose(handle);
errno = EBUSY;
return -1;
}
status = NtMakeTemporaryObject(handle);
//printf("nativelibc debug: rmdir: remove dir object: status = 0x%lx\n", status);
NtClose(handle);
return __set_errno_from_ntstatus(status) ? -1 : 0;
}
IO_STATUS_BLOCK io_status;
unsigned long int file_share = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
unsigned long int options = FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_FOR_BACKUP_INTENT;
status = NtOpenFile(&handle, FILE_LIST_DIRECTORY | DELETE | SYNCHRONIZE, obj_attr, &io_status, file_share, options);
//printf("nativelibc debug: rmdir: check is dir file: status = 0x%lx\n", status);
if(status != STATUS_OBJECT_TYPE_MISMATCH) {
if(status < 0) {
__set_errno_from_ntstatus(status);
return -1;
}
size_t fni_size = sizeof(FILE_NAMES_INFORMATION) + 2 * sizeof(wchar_t);
FILE_NAMES_INFORMATION *fni = malloc(fni_size);
if(!fni) {
NtClose(handle);
errno = ENOMEM;
return -1;
}
while(1) {
status = NtQueryDirectoryFile(handle, NULL, NULL, NULL, &io_status, fni, fni_size, FileNamesInformation, 0, NULL, 0);
//printf("nativelibc debug: rmdir: check dir file contents: status = 0x%lx\n", status);
if(status == STATUS_NO_MORE_FILES) break;
if(status == STATUS_BUFFER_OVERFLOW) goto notempty;
if(status < 0) {
free(fni);
NtClose(handle);
__set_errno_from_ntstatus(status);
return -1;
}
if(fni->FileNameLength > 2 * sizeof(wchar_t) || fni->FileName[0] != L'.' ||
(fni->FileNameLength > sizeof(wchar_t) && fni->FileName[1] != L'.')) {
notempty:
free(fni);
NtClose(handle);
errno = ENOTEMPTY;
return -1;
}
}
free(fni);
FILE_DISPOSITION_INFORMATION fdi = { .DeleteFile = 1 };
status = NtSetInformationFile(handle, &io_status, &fdi, sizeof fdi, FileDispositionInformation);
//printf("nativelibc debug: rmdir: remove dir file: status = 0x%lx\n", status);
if(__set_errno_from_ntstatus(NtClose(handle))) return -1;
return __set_errno_from_ntstatus(status) ? -1 : 0;
}
// The pathname exists but is not a directory
errno = ENOTDIR;
return -1;
}
// Handle will be closed
static int unlink_object(void *handle) {
OBJECT_BASIC_INFORMATION obi;
long int status = NtQueryObject(handle, ObjectBasicInformation, &obi, sizeof obi, NULL);
if(status < 0) {
NtClose(handle);
__set_errno_from_ntstatus(status);
return -1;
}
if(obi.HandleCount > 1) {
NtClose(handle);
errno = EBUSY;
return -1;
}
status = NtMakeTemporaryObject(handle);
long int close_status = NtClose(handle);
if(status < 0) {
__set_errno_from_ntstatus(status);
return -1;
}
if(close_status < 0) {
__set_errno_from_ntstatus(close_status);
return -1;
}
return 0;
}
int unlinkat(int dir_fd, const char *path, int flags) {
if(dir_fd == -1) {
errno = EBADF;
return -1;
}
if(!path) {
errno = EFAULT;
return -1;
}
if(!*path) {
errno = ENOENT;
return -1;
}
if(flags & ~AT_REMOVEDIR) {
errno = EINVAL;
return -1;
}
if(strcmp(path, "/") == 0) {
// Special case
errno = EISDIR;
return -1;
}
UNICODE_STRING nt_path;
RtlCreateUnicodeStringFromAsciiz(&nt_path, path);
int request_dir = 0;
if(nt_path.Length > sizeof(wchar_t) && nt_path.Buffer[nt_path.Length / sizeof(wchar_t) - 1] == L'/') {
// This function may not allow unlinking of a directory and will fail anyways; just continue for setting errno.
request_dir = 1;
nt_path.Buffer[nt_path.Length / sizeof(wchar_t) - 1] = 0;
nt_path.Length -= sizeof(wchar_t);
nt_path.MaximumLength -= sizeof(wchar_t);
}
PATHNAME_UNIX2NT_UTF16_STRUCT(nt_path);
OBJECT_ATTRIBUTES obj_attr = {
.Length = sizeof(OBJECT_ATTRIBUTES), .ObjectName = &nt_path
};
if(*path != '/') {
if(dir_fd == AT_FDCWD) {
RtlFreeUnicodeString(&nt_path);
errno = EPERM;
return -1;
}
obj_attr.RootDirectory = (void *)dir_fd;
}
void *handle;
if(!request_dir) {
long int status = NtOpenSymbolicLinkObject(&handle, DELETE, &obj_attr);
//printf("nativelibc debug: unlink: status = 0x%lx\n", status);
if(status != STATUS_OBJECT_TYPE_MISMATCH) {
RtlFreeUnicodeString(&nt_path);
if(status < 0) {
__set_errno_from_ntstatus(status);
return -1;
}
if(flags & AT_REMOVEDIR) {
NtClose(handle);
errno = ENOTDIR;
return -1;
}
return unlink_object(handle);
}
}
if(flags & AT_REMOVEDIR) {
int r = remove_directory(&obj_attr);
int e = errno;
RtlFreeUnicodeString(&nt_path);
errno = e;
return r;
}
long int status = NtOpenDirectoryObject(&handle, 0, &obj_attr);
//printf("nativelibc debug: unlink: status = 0x%lx\n", status);
if(status != STATUS_OBJECT_TYPE_MISMATCH) {
RtlFreeUnicodeString(&nt_path);
#ifdef NATIVELIBC_ALLOW_UNLINK_DIRECTORY
if(status < 0) __set_errno_from_ntstatus(status);
else return unlink_object(handle);
#else
if(status >= 0 || status == STATUS_ACCESS_DENIED) {
if(status >= 0) NtClose(handle);
errno = EPERM;
} else __set_errno_from_ntstatus(status);
#endif
return -1;
}
IO_STATUS_BLOCK io_status;
unsigned long int file_share = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
unsigned long int options = FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_FOR_BACKUP_INTENT;
status = NtOpenFile(&handle, DELETE | SYNCHRONIZE, &obj_attr, &io_status, file_share, options);
RtlFreeUnicodeString(&nt_path);
if(status < 0) {
__set_errno_from_ntstatus(status);
return -1;
}
if(request_dir) {
NtClose(handle);
errno = ENOTDIR;
return -1;
}
//status = NtDeleteFile(&obj_attr);
FILE_DISPOSITION_INFORMATION fdi;
fdi.DeleteFile = 1;
status = NtSetInformationFile(handle, &io_status, &fdi, sizeof fdi, FileDispositionInformation);
if(__set_errno_from_ntstatus(NtClose(handle))) return -1;
return __set_errno_from_ntstatus(status) ? -1 : 0;
}
int unlink(const char *path) {
return unlinkat(AT_FDCWD, path, 0);
}