From b59fc58326854539265e183d905b0c0ad8964857 Mon Sep 17 00:00:00 2001 From: Bartosz Sosnowski Date: Thu, 17 May 2018 09:27:09 +0200 Subject: [PATCH] win, fs: uv_fs_fchmod support for -A files Adds uv_fs_chmod support for files with the Archive attribute cleared Ref: https://github.com/libuv/libuv/pull/1777 Ref: https://github.com/nodejs/node/issues/12803 PR-URL: https://github.com/libuv/libuv/pull/1819 Reviewed-By: Ben Noordhuis Reviewed-By: Colin Ihrig --- src/win/fs.c | 49 +++++++++++++++-- test/test-fs.c | 133 +++++++++++++++++++++++++++++++++++++++++++++++ test/test-list.h | 4 ++ 3 files changed, 183 insertions(+), 3 deletions(-) diff --git a/src/win/fs.c b/src/win/fs.c index 30b87ac5..5f84a126 100644 --- a/src/win/fs.c +++ b/src/win/fs.c @@ -1490,6 +1490,7 @@ static void fs__chmod(uv_fs_t* req) { static void fs__fchmod(uv_fs_t* req) { int fd = req->file.fd; + int clear_archive_flag; HANDLE handle; NTSTATUS nt_status; IO_STATUS_BLOCK io_status; @@ -1497,7 +1498,11 @@ static void fs__fchmod(uv_fs_t* req) { VERIFY_FD(fd, req); - handle = uv__get_osfhandle(fd); + handle = ReOpenFile(uv__get_osfhandle(fd), FILE_WRITE_ATTRIBUTES, 0, 0); + if (handle == INVALID_HANDLE_VALUE) { + SET_REQ_WIN32_ERROR(req, GetLastError()); + return; + } nt_status = pNtQueryInformationFile(handle, &io_status, @@ -1507,7 +1512,27 @@ static void fs__fchmod(uv_fs_t* req) { if (!NT_SUCCESS(nt_status)) { SET_REQ_WIN32_ERROR(req, pRtlNtStatusToDosError(nt_status)); - return; + goto fchmod_cleanup; + } + + /* Test if the Archive attribute is cleared */ + if ((file_info.FileAttributes & FILE_ATTRIBUTE_ARCHIVE) == 0) { + /* Set Archive flag, otherwise setting or clearing the read-olny + flag will not work */ + file_info.FileAttributes |= FILE_ATTRIBUTE_ARCHIVE; + nt_status = pNtSetInformationFile(handle, + &io_status, + &file_info, + sizeof file_info, + FileBasicInformation); + if (!NT_SUCCESS(nt_status)) { + SET_REQ_WIN32_ERROR(req, pRtlNtStatusToDosError(nt_status)); + goto fchmod_cleanup; + } + /* Remeber to clear the flag later on */ + clear_archive_flag = 1; + } else { + clear_archive_flag = 0; } if (req->fs.info.mode & _S_IWRITE) { @@ -1524,10 +1549,28 @@ static void fs__fchmod(uv_fs_t* req) { if (!NT_SUCCESS(nt_status)) { SET_REQ_WIN32_ERROR(req, pRtlNtStatusToDosError(nt_status)); - return; + goto fchmod_cleanup; + } + + if (clear_archive_flag) { + file_info.FileAttributes &= ~FILE_ATTRIBUTE_ARCHIVE; + if (file_info.FileAttributes == 0) { + file_info.FileAttributes = FILE_ATTRIBUTE_NORMAL; + } + nt_status = pNtSetInformationFile(handle, + &io_status, + &file_info, + sizeof file_info, + FileBasicInformation); + if (!NT_SUCCESS(nt_status)) { + SET_REQ_WIN32_ERROR(req, pRtlNtStatusToDosError(nt_status)); + goto fchmod_cleanup; + } } SET_REQ_SUCCESS(req); +fchmod_cleanup: + CloseHandle(handle); } diff --git a/test/test-fs.c b/test/test-fs.c index 000a151a..e99b3e53 100644 --- a/test/test-fs.c +++ b/test/test-fs.c @@ -3230,3 +3230,136 @@ TEST_IMPL(fs_exclusive_sharing_mode) { return 0; } #endif + +#ifdef _WIN32 +int call_icacls(const char* command, ...) { + char icacls_command[1024]; + va_list args; + + va_start(args, command); + vsnprintf(icacls_command, ARRAYSIZE(icacls_command), command, args); + va_end(args); + return system(icacls_command); +} + +TEST_IMPL(fs_open_readonly_acl) { + uv_passwd_t pwd; + uv_fs_t req; + int r; + + /* + Based on Node.js test from + https://github.com/nodejs/node/commit/3ba81e34e86a5c32658e218cb6e65b13e8326bc5 + + If anything goes wrong, you can delte the test_fle_icacls with: + + icacls test_file_icacls /remove "%USERNAME%" /inheritance:e + attrib -r test_file_icacls + del test_file_icacls + */ + + /* Setup - clear the ACL and remove the file */ + loop = uv_default_loop(); + r = uv_os_get_passwd(&pwd); + ASSERT(r == 0); + call_icacls("icacls test_file_icacls /remove \"%s\" /inheritance:e", + pwd.username); + uv_fs_chmod(loop, &req, "test_file_icacls", S_IWUSR, NULL); + unlink("test_file_icacls"); + + /* Create the file */ + r = uv_fs_open(loop, + &open_req1, + "test_file_icacls", + O_RDONLY | O_CREAT, + S_IRUSR, + NULL); + ASSERT(r >= 0); + ASSERT(open_req1.result >= 0); + uv_fs_req_cleanup(&open_req1); + r = uv_fs_close(NULL, &close_req, open_req1.result, NULL); + ASSERT(r == 0); + ASSERT(close_req.result == 0); + uv_fs_req_cleanup(&close_req); + + /* Set up ACL */ + r = call_icacls("icacls test_file_icacls /inheritance:r /remove \"%s\"", + pwd.username); + if (r != 0) { + goto acl_cleanup; + } + r = call_icacls("icacls test_file_icacls /grant \"%s\":RX", pwd.username); + if (r != 0) { + goto acl_cleanup; + } + + /* Try opening the file */ + r = uv_fs_open(NULL, &open_req1, "test_file_icacls", O_RDONLY, 0, NULL); + if (r < 0) { + goto acl_cleanup; + } + uv_fs_req_cleanup(&open_req1); + r = uv_fs_close(NULL, &close_req, open_req1.result, NULL); + if (r != 0) { + goto acl_cleanup; + } + uv_fs_req_cleanup(&close_req); + + acl_cleanup: + /* Cleanup */ + call_icacls("icacls test_file_icacls /remove \"%s\" /inheritance:e", + pwd.username); + unlink("test_file_icacls"); + uv_os_free_passwd(&pwd); + ASSERT(r == 0); + MAKE_VALGRIND_HAPPY(); + return 0; +} +#endif + +#ifdef _WIN32 +TEST_IMPL(fs_fchmod_archive_readolny) { + uv_fs_t req; + uv_file file; + int r; + /* Test clearing read-only flag from files with Archive flag cleared */ + + /* Setup*/ + unlink("test_file"); + r = uv_fs_open(NULL, + &req, + "test_file", + O_WRONLY | O_CREAT, + S_IWUSR | S_IRUSR, + NULL); + ASSERT(r >= 0); + ASSERT(req.result >= 0); + file = req.result; + uv_fs_req_cleanup(&req); + r = uv_fs_close(NULL, &req, file, NULL); + ASSERT(r == 0); + uv_fs_req_cleanup(&req); + /* Make the file read-only and clear archive flag */ + r = SetFileAttributes("test_file", FILE_ATTRIBUTE_READONLY); + ASSERT(r != 0); + check_permission("test_file", 0400); + /* Try fchmod */ + r = uv_fs_open(NULL, &req, "test_file", O_RDONLY, 0, NULL); + ASSERT(r >= 0); + ASSERT(req.result >= 0); + file = req.result; + uv_fs_req_cleanup(&req); + r = uv_fs_fchmod(NULL, &req, file, S_IWUSR, NULL); + ASSERT(r == 0); + ASSERT(req.result == 0); + uv_fs_req_cleanup(&req); + r = uv_fs_close(NULL, &req, file, NULL); + ASSERT(r == 0); + uv_fs_req_cleanup(&req); + check_permission("test_file", S_IWUSR); + + /* Restore Archive flag for rest of the tests */ + r = SetFileAttributes("test_file", FILE_ATTRIBUTE_ARCHIVE); + ASSERT(r != 0); +} +#endif diff --git a/test/test-list.h b/test/test-list.h index b89930d7..1da0099b 100644 --- a/test/test-list.h +++ b/test/test-list.h @@ -337,6 +337,8 @@ TEST_DECLARE (fs_file_pos_after_op_with_offset) TEST_DECLARE (fs_null_req) #ifdef _WIN32 TEST_DECLARE (fs_exclusive_sharing_mode) +TEST_DECLARE (fs_open_readonly_acl) +TEST_DECLARE (fs_fchmod_archive_readolny) #endif TEST_DECLARE (threadpool_queue_work_simple) TEST_DECLARE (threadpool_queue_work_einval) @@ -869,6 +871,8 @@ TASK_LIST_START TEST_ENTRY (fs_null_req) #ifdef _WIN32 TEST_ENTRY (fs_exclusive_sharing_mode) + TEST_ENTRY (fs_open_readonly_acl) + TEST_ENTRY (fs_fchmod_archive_readolny) #endif TEST_ENTRY (get_osfhandle_valid_handle) TEST_ENTRY (threadpool_queue_work_simple)