From 23896e53ad299d63b65760d9e690ec2b6e2df8c6 Mon Sep 17 00:00:00 2001 From: Apple Date: Thu, 25 Apr 2019 18:09:42 +0000 Subject: [PATCH] copyfile-166.tar.gz --- .gitignore | 4 + copyfile.3 | 33 ++++- copyfile.c | 72 ++++++----- copyfile.h | 2 +- copyfile.xcodeproj/project.pbxproj | 9 ++ copyfile_private.h | 9 +- copyfile_test/copyfile_test.entitlements | Bin 0 -> 90 bytes copyfile_test/main.c | 2 + copyfile_test/sparse_test.c | 33 +++-- copyfile_test/sparse_test.h | 3 + copyfile_test/stat_test.c | 149 +++++++++++++++++++++++ copyfile_test/stat_test.h | 14 +++ copyfile_test/test_utils.c | 22 +++- copyfile_test/test_utils.h | 6 + xattr_flags.h | 2 +- xattr_name_with_flags.3 | 9 +- xattr_properties.h | 36 +----- 17 files changed, 297 insertions(+), 108 deletions(-) create mode 100644 .gitignore create mode 100644 copyfile_test/copyfile_test.entitlements create mode 100644 copyfile_test/stat_test.c create mode 100644 copyfile_test/stat_test.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..909d75d --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +copyfile.xcodeproj/project.xcworkspace +copyfile.xcodeproj/xcuserdata/ +.DS_Store +build/ diff --git a/copyfile.3 b/copyfile.3 index ceb898b..be1aa31 100644 --- a/copyfile.3 +++ b/copyfile.3 @@ -1,7 +1,7 @@ .\" .\" Copyright (c) 2002 Apple Computer, Inc. All rights reserved. .\" -.Dd August 30, 2017 +.Dd November 2, 2017 .Dt COPYFILE 3 .Os .Sh NAME @@ -185,7 +185,8 @@ This flag is equivalent to (COPYFILE_EXCL | COPYFILE_ACL | COPYFILE_STAT | COPYF | COPYFILE_NOFOLLOW_SRC). Note that if cloning is successful, progress callbacks will not be invoked. Note also that there is no support for cloning directories: if a directory is provided as the source, -an error will be returned. +an error will be returned. Since this flag implies COPYFILE_NOFOLLOW_SRC, symbolic links themselves will +be cloned instead of their targets. (This is only applicable for the .Fn copyfile function.) @@ -196,7 +197,8 @@ This flag is equivalent to (COPYFILE_EXCL | COPYFILE_ACL | COPYFILE_STAT | COPYF | COPYFILE_NOFOLLOW_SRC). Note that if cloning is successful, progress callbacks will not be invoked. Note also that there is no support for cloning directories: if a directory is provided as the source and -COPYFILE_CLONE_FORCE is not passed, this will instead copy the directory. Recursive copying however is +COPYFILE_CLONE_FORCE is not passed, this will instead copy the directory. Since this flag implies COPYFILE_NOFOLLOW_SRC, +symbolic links themselves will be cloned instead of their targets. Recursive copying however is supported, see below for more information. (This is only applicable for the .Fn copyfile @@ -217,6 +219,18 @@ This is a convenience macro, equivalent to If the src file has quarantine information, add the QTN_FLAG_DO_NOT_TRANSLOCATE flag to the quarantine information of the dst file. This allows a bundle to run in place instead of being translocated. .El .Pp +Copying files into a directory is supported. If +.Va to +is a directory, +.Va from +will be copied into +.Va to +(if +.Va from +is a directory, +copying its contents requires use of the COPYFILE_RECURSIVE parameter, +which is documented below). +.Pp The .Fn copyfile_state_get and @@ -478,6 +492,19 @@ and .Dv COPYFILE_UNLINK flags are not used during a recursive copy, and will result in an error being returned. +.Pp +Note that if the source path ends in a +.Va / +its contents are copied rather than the directory itself (like cp(1)). +The behavior of a recursive copy on a directory hierarchy also depends +on the contents of the destination. If the destination is a directory, +the source directory (or its contents, if the source path ends in a +.Va / +) will be copied into it. If the destination exists but is not a +directory, and the source is a non-empty directory, the copy will fail; +the exact error set depends on the flags provided to +.Fn copyfile +initially. .Sh Progress Callback In addition to the recursive callbacks described above, .Fn copyfile diff --git a/copyfile.c b/copyfile.c index 99e6d42..286d0ff 100644 --- a/copyfile.c +++ b/copyfile.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004-2010 Apple, Inc. All rights reserved. + * Copyright (c) 2004-2019 Apple, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * @@ -61,16 +61,16 @@ #define qtn_file_t void * #define QTN_SERIALIZED_DATA_MAX 0 static void * qtn_file_alloc(void) { return NULL; } -static int qtn_file_init_with_fd(void *x, int y) { return -1; } -static int qtn_file_init_with_path(void *x, const char *path) { return -1; } -static int qtn_file_init_with_data(void *x, const void *data, size_t len) { return -1; } -static void qtn_file_free(void *x) { return; } -static int qtn_file_apply_to_fd(void *x, int y) { return 0; } -static char *qtn_error(int x) { return NULL; } -static int qtn_file_to_data(void *x, char *y, size_t z) { return -1; } -static void *qtn_file_clone(void *x) { return NULL; } -static uint32_t qtn_file_get_flags(void *x) { return 0; } -static int qtn_file_set_flags(void *x, uint32_t flags) { return 0; } +static int qtn_file_init_with_fd(__unused void *x, __unused int y) { return -1; } +static int qtn_file_init_with_path(__unused void *x, __unused const char *path) { return -1; } +static int qtn_file_init_with_data(__unused void *x, __unused const void *data, __unused size_t len) { return -1; } +static void qtn_file_free(__unused void *x) { return; } +static int qtn_file_apply_to_fd(__unused void *x, __unused int y) { return 0; } +static char *qtn_error(__unused int x) { return NULL; } +static int qtn_file_to_data(__unused void *x, __unused char *y, __unused size_t *z) { return -1; } +static void *qtn_file_clone(__unused void *x) { return NULL; } +static uint32_t qtn_file_get_flags(__unused void *x) { return 0; } +static int qtn_file_set_flags(__unused void *x, __unused uint32_t flags) { return 0; } #define XATTR_QUARANTINE_NAME "figgledidiggledy" #define QTN_FLAG_DO_NOT_TRANSLOCATE 0 #endif /* TARGET_OS_IPHONE */ @@ -237,7 +237,7 @@ sort_xattrname_list(void *start, size_t length) tmp = ptrs[indx++] = (char*)start; - while (tmp = memchr(tmp, 0, ((char*)start + length) - tmp)) { + while ((tmp = memchr(tmp, 0, ((char*)start + length) - tmp))) { if (indx == nel) { nel += 10; ptrs = realloc(ptrs, sizeof(char**) * nel); @@ -645,7 +645,7 @@ copytree(copyfile_state_t s) retval = -1; goto done; } - if (s->flags & (COPYFILE_MOVE | COPYFILE_UNLINK | COPYFILE_CHECK | COPYFILE_PACK | COPYFILE_UNPACK)) { + if (s->flags & (COPYFILE_MOVE | COPYFILE_UNLINK | COPYFILE_CHECK | COPYFILE_PACK | COPYFILE_UNPACK | COPYFILE_CLONE_FORCE)) { errno = EINVAL; retval = -1; goto done; @@ -2701,6 +2701,8 @@ error_exit: /* * Attempt to set the destination file's stat information -- including * flags and time-related fields -- to the source's. + * Note that we must set file flags *last*, as setting a flag like + * UF_IMMUTABLE can prevent us from setting other attributes. */ static int copyfile_stat(copyfile_state_t s) { @@ -2713,6 +2715,20 @@ static int copyfile_stat(copyfile_state_t s) struct timespec acc_time; } ma_times; + /* Try to set m/atimes using setattrlist(), for nanosecond precision. */ + memset(&attrlist, 0, sizeof(attrlist)); + attrlist.bitmapcount = ATTR_BIT_MAP_COUNT; + attrlist.commonattr = ATTR_CMN_MODTIME | ATTR_CMN_ACCTIME; + ma_times.mod_time = s->sb.st_mtimespec; + ma_times.acc_time = s->sb.st_atimespec; + (void)fsetattrlist(s->dst_fd, &attrlist, &ma_times, sizeof(ma_times), 0); + + /* If this fails, we don't care */ + (void)fchown(s->dst_fd, s->sb.st_uid, s->sb.st_gid); + + /* This may have already been done in copyfile_security() */ + (void)fchmod(s->dst_fd, s->sb.st_mode & ~S_IFMT); + /* * NFS doesn't support chflags; ignore errors as a result, since * we don't return failure for this. @@ -2721,32 +2737,17 @@ static int copyfile_stat(copyfile_state_t s) added_flags |= UF_HIDDEN; /* - * We need to check if SF_RESTRICTED was set on the destination - * by the kernel. If it was, don't drop it. + * We need to check if certain flags were set on the destination + * by the kernel. If they were, don't drop them. */ if (fstat(s->dst_fd, &dst_sb)) return -1; - if (dst_sb.st_flags & SF_RESTRICTED) - added_flags |= SF_RESTRICTED; + added_flags |= (dst_sb.st_flags & COPYFILE_PRESERVE_FLAGS); /* Copy file flags, masking out any we don't want to preserve */ dst_flags = (s->sb.st_flags & ~COPYFILE_OMIT_FLAGS) | added_flags; (void)fchflags(s->dst_fd, dst_flags); - /* If this fails, we don't care */ - (void)fchown(s->dst_fd, s->sb.st_uid, s->sb.st_gid); - - /* This may have already been done in copyfile_security() */ - (void)fchmod(s->dst_fd, s->sb.st_mode & ~S_IFMT); - - /* Try to set m/atimes using setattrlist(), for nanosecond precision. */ - memset(&attrlist, 0, sizeof(attrlist)); - attrlist.bitmapcount = ATTR_BIT_MAP_COUNT; - attrlist.commonattr = ATTR_CMN_MODTIME | ATTR_CMN_ACCTIME; - ma_times.mod_time = s->sb.st_mtimespec; - ma_times.acc_time = s->sb.st_atimespec; - (void)fsetattrlist(s->dst_fd, &attrlist, &ma_times, sizeof(ma_times), 0); - return 0; } @@ -3003,7 +3004,7 @@ static int copyfile_xattr(copyfile_state_t s) { errno = error; ret = -1; - copyfile_warn("could not set attributes %s on destination file descriptor: %s", name, strerror(error)); + copyfile_warn("could not set attributes %s on destination file descriptor", name); continue; } } @@ -3252,9 +3253,6 @@ int main(int c, char *v[]) * Copyright (c) 2004 Apple Computer, Inc. All rights reserved. */ - -#define offsetof(type, member) __builtin_offsetof(type, member) - #define XATTR_MAXATTRLEN (16*1024*1024) @@ -4425,10 +4423,10 @@ static int copyfile_pack(copyfile_state_t s) filehdr->appledouble.version = ADH_VERSION; filehdr->appledouble.numEntries = 2; filehdr->appledouble.entries[0].type = AD_FINDERINFO; - filehdr->appledouble.entries[0].offset = (u_int32_t)offsetof(apple_double_header_t, finfo); + filehdr->appledouble.entries[0].offset = (u_int32_t)__builtin_offsetof(apple_double_header_t, finfo); filehdr->appledouble.entries[0].length = FINDERINFOSIZE; filehdr->appledouble.entries[1].type = AD_RESOURCE; - filehdr->appledouble.entries[1].offset = (u_int32_t)offsetof(apple_double_header_t, pad); + filehdr->appledouble.entries[1].offset = (u_int32_t)__builtin_offsetof(apple_double_header_t, pad); filehdr->appledouble.entries[1].length = 0; bcopy(ADH_MACOSX, filehdr->appledouble.filler, sizeof(filehdr->appledouble.filler)); diff --git a/copyfile.h b/copyfile.h index efea9dd..1e98a99 100644 --- a/copyfile.h +++ b/copyfile.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004-2010 Apple, Inc. All rights reserved. + * Copyright (c) 2004-2019 Apple, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * diff --git a/copyfile.xcodeproj/project.pbxproj b/copyfile.xcodeproj/project.pbxproj index b8ec504..f282750 100644 --- a/copyfile.xcodeproj/project.pbxproj +++ b/copyfile.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 098AF3B622692BF300F9BA42 /* stat_test.c in Sources */ = {isa = PBXBuildFile; fileRef = 098AF3B522692BF300F9BA42 /* stat_test.c */; }; 721D4F071EA95283000F0555 /* copyfile.c in Sources */ = {isa = PBXBuildFile; fileRef = FCCE17C1135A658F002CEE6D /* copyfile.c */; }; 721D4F081EA95290000F0555 /* xattr_flags.c in Sources */ = {isa = PBXBuildFile; fileRef = 72406E621676C3C80099568B /* xattr_flags.c */; }; 72406E631676C3C80099568B /* xattr_flags.c in Sources */ = {isa = PBXBuildFile; fileRef = 72406E621676C3C80099568B /* xattr_flags.c */; }; @@ -34,6 +35,9 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 098AF3B422692BF300F9BA42 /* stat_test.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = stat_test.h; sourceTree = ""; }; + 098AF3B522692BF300F9BA42 /* stat_test.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = stat_test.c; sourceTree = ""; }; + 098AF3B7226A510E00F9BA42 /* copyfile_test.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = copyfile_test.entitlements; sourceTree = ""; }; 3F1EFD4C185C4EB400D1C970 /* copyfile.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = copyfile.xcconfig; path = xcodescripts/copyfile.xcconfig; sourceTree = ""; }; 721D4F051EA95008000F0555 /* libcopyfile.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libcopyfile.tbd; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.13.sdk/usr/lib/system/libcopyfile.tbd; sourceTree = DEVELOPER_DIR; }; 72406E621676C3C80099568B /* xattr_flags.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = xattr_flags.c; sourceTree = ""; }; @@ -87,10 +91,13 @@ 726EE9DA1E9423E50017A5B9 /* main.c */, 726EE9DE1E9425160017A5B9 /* sparse_test.c */, 726EE9DF1E9425160017A5B9 /* sparse_test.h */, + 098AF3B522692BF300F9BA42 /* stat_test.c */, + 098AF3B422692BF300F9BA42 /* stat_test.h */, 726EE9E51E946D590017A5B9 /* test_utils.c */, 726EE9E11E9427B40017A5B9 /* test_utils.h */, 726EE9E21E946B320017A5B9 /* systemx.c */, 726EE9E31E946B320017A5B9 /* systemx.h */, + 098AF3B7226A510E00F9BA42 /* copyfile_test.entitlements */, ); path = copyfile_test; sourceTree = ""; @@ -229,6 +236,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 098AF3B622692BF300F9BA42 /* stat_test.c in Sources */, 721D4F081EA95290000F0555 /* xattr_flags.c in Sources */, 721D4F071EA95283000F0555 /* copyfile.c in Sources */, 726EE9DB1E9423E50017A5B9 /* main.c in Sources */, @@ -272,6 +280,7 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_ENTITLEMENTS = copyfile_test/copyfile_test.entitlements; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; diff --git a/copyfile_private.h b/copyfile_private.h index 0111e0d..2d5417f 100644 --- a/copyfile_private.h +++ b/copyfile_private.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 Apple, Inc. All rights reserved. + * Copyright (c) 2013-19 Apple, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * @@ -33,6 +33,11 @@ /* * File flags that are not preserved when copying stat information. */ -#define COPYFILE_OMIT_FLAGS (UF_TRACKED | SF_RESTRICTED) +#define COPYFILE_OMIT_FLAGS (UF_TRACKED | SF_RESTRICTED | SF_NOUNLINK | UF_DATAVAULT) + +/* + * File flags that are not removed when replacing an existing file. + */ +#define COPYFILE_PRESERVE_FLAGS (SF_RESTRICTED | SF_NOUNLINK | UF_DATAVAULT) #endif /* _COPYFILE_PRIVATE_H */ diff --git a/copyfile_test/copyfile_test.entitlements b/copyfile_test/copyfile_test.entitlements new file mode 100644 index 0000000000000000000000000000000000000000..f5b1b1859ca04964d032f80f7bcf5bd698dee6bc GIT binary patch literal 90 zcmYc)$jK}&F)+Bu$P_Q2k({5amsn7cld4ygpI?%bT3oDGT#{dun4YSaoL^9xmYI_p ZUy@o}!pXsH#sCJ4j1ZcE8A`(_a{&B-6x#p* literal 0 HcmV?d00001 diff --git a/copyfile_test/main.c b/copyfile_test/main.c index 616dbe3..338b17c 100644 --- a/copyfile_test/main.c +++ b/copyfile_test/main.c @@ -12,6 +12,7 @@ #include #include "sparse_test.h" +#include "stat_test.h" #include "test_utils.h" #define DISK_IMAGE_SIZE_MB 512 @@ -53,6 +54,7 @@ int main(__unused int argc, __unused const char * argv[]) { failed |= do_sparse_test(TEST_DIR, stb.f_bsize); failed |= do_sparse_recursive_test(TEST_DIR, stb.f_bsize); failed |= do_fcopyfile_offset_test(TEST_DIR, stb.f_bsize); + failed |= do_preserve_dst_flags_test(TEST_DIR, stb.f_bsize); // Cleanup the disk image we ran our tests on. if (USING_DISK_IMAGE) { diff --git a/copyfile_test/sparse_test.c b/copyfile_test/sparse_test.c index ccced99..fc126d7 100644 --- a/copyfile_test/sparse_test.c +++ b/copyfile_test/sparse_test.c @@ -17,11 +17,6 @@ #include "test_utils.h" #include "systemx.h" -#define OPEN_FLAGS O_CREAT|O_TRUNC|O_RDWR -#define OPEN_PERM 0666 -#define MKDIR_PERM 0777 -#define NAME_MOD 999 - /* * Copy the file pointed to by src_fd (and orig_name) to copy_name, * using copyfile()/fcopyfile() and COPYFILE_DATA. If do_sparse, also pass COPYFILE_DATA_SPARSE. @@ -69,18 +64,18 @@ static bool test_copy(int src_fd, char* orig_name, char* copy_name, bool do_spar // Next, verify fcopyfile(). // Make an fd for the destination. - assert_with_errno((copy_fd = open(copy_name, OPEN_FLAGS, OPEN_PERM)) > 0); + assert_with_errno((copy_fd = open(copy_name, DEFAULT_OPEN_FLAGS, DEFAULT_OPEN_PERM)) > 0); // Call fcopyfile(). assert_with_errno((cpf_state = copyfile_state_alloc()) != NULL); assert_no_err(fcopyfile(src_fd, copy_fd, cpf_state, flags)); - // 1. The copy is a sparse file (if start_off is a multiple of the block size). + // 1. The copy is a sparse file (if start_off is 0). // 2. The copyfile_state_t for the copy returns that all bytes were copied. assert_no_err(fstat(src_fd, &orig_sb)); assert_no_err(fstat(copy_fd, ©_sb)); result &= verify_copy_sizes(&orig_sb, ©_sb, cpf_state, - start_off % copy_sb.st_blksize ? false : do_sparse, start_off); + start_off > 0 ? false : do_sparse, start_off); // 3. The copy and the source have identical contents. if (start_off == 0) { @@ -217,13 +212,13 @@ bool do_sparse_test(const char* apfs_test_directory, size_t block_size) { sub_test_success = false; // Make new names for this file and its copies. - test_file_id = rand() % NAME_MOD; + test_file_id = rand() % DEFAULT_NAME_MOD; create_test_file_name(apfs_test_directory, "sparse", test_file_id, out_name); create_test_file_name(apfs_test_directory, "copy_sparse", test_file_id, sparse_copy_name); create_test_file_name(apfs_test_directory, "copy_full", test_file_id, full_copy_name); // Create the test file. - fd = open(out_name, OPEN_FLAGS, OPEN_PERM); + fd = open(out_name, DEFAULT_OPEN_FLAGS, DEFAULT_OPEN_PERM); assert_with_errno(fd >= 0); // Write to the test file, making it sparse. @@ -277,19 +272,19 @@ bool do_sparse_recursive_test(const char *apfs_test_directory, size_t block_size assert_with_errno(snprintf(exterior_dir_src, BSIZE_B, "%s/recursive_src", apfs_test_directory) > 0); assert_with_errno(snprintf(interior_dir_src, BSIZE_B, "%s/interior", exterior_dir_src) > 0); - assert_no_err(mkdir(exterior_dir_src, MKDIR_PERM)); - assert_no_err(mkdir(interior_dir_src, MKDIR_PERM)); + assert_no_err(mkdir(exterior_dir_src, DEFAULT_MKDIR_PERM)); + assert_no_err(mkdir(interior_dir_src, DEFAULT_MKDIR_PERM)); - test_file_id = rand() % NAME_MOD; + test_file_id = rand() % DEFAULT_NAME_MOD; create_test_file_name(exterior_dir_src, "exterior_sparse_file", test_file_id, exterior_file_src); create_test_file_name(interior_dir_src, "interior_sparse_file", test_file_id, interior_file_src); // Create the actual test files. - exterior_file_src_fd = open(exterior_file_src, OPEN_FLAGS, OPEN_PERM); + exterior_file_src_fd = open(exterior_file_src, DEFAULT_OPEN_FLAGS, DEFAULT_OPEN_PERM); assert_with_errno(exterior_file_src_fd >= 0); write_start_and_end_holes(exterior_file_src_fd, block_size); - interior_file_src_fd = open(interior_file_src, OPEN_FLAGS, OPEN_PERM); + interior_file_src_fd = open(interior_file_src, DEFAULT_OPEN_FLAGS, DEFAULT_OPEN_PERM); assert_with_errno(interior_file_src_fd >= 0); write_middle_hole(interior_file_src_fd, block_size); @@ -344,20 +339,20 @@ bool do_fcopyfile_offset_test(const char *apfs_test_directory, size_t block_size printf("START [fcopyfile_offset]\n"); // Make new names for this file and its copies. - test_file_id = rand() % NAME_MOD; + test_file_id = rand() % DEFAULT_NAME_MOD; create_test_file_name(apfs_test_directory, "foff_sparse", test_file_id, out_name); create_test_file_name(apfs_test_directory, "foff_copy_sparse", test_file_id, sparse_copy_name); create_test_file_name(apfs_test_directory, "foff_copy_full", test_file_id, full_copy_name); // Create the test file. - src_fd = open(out_name, OPEN_FLAGS, OPEN_PERM); + src_fd = open(out_name, DEFAULT_OPEN_FLAGS, DEFAULT_OPEN_PERM); assert_with_errno(src_fd >= 0); // This writes 5 * block_size bytes. assert_with_errno(lseek(src_fd, write_middle_hole(src_fd, block_size), SEEK_SET) == 0); // Create a sparse copy using fcopyfile(). - sparse_copy_fd = open(sparse_copy_name, OPEN_FLAGS, OPEN_PERM); + sparse_copy_fd = open(sparse_copy_name, DEFAULT_OPEN_FLAGS, DEFAULT_OPEN_PERM); assert_with_errno(sparse_copy_fd >= 0); // Seek the sparse copy to a non-zero offset. @@ -373,7 +368,7 @@ bool do_fcopyfile_offset_test(const char *apfs_test_directory, size_t block_size // Now, repeat the same procedure with a full copy. assert_with_errno(lseek(src_fd, 0, SEEK_SET) == 0); - full_copy_fd = open(full_copy_name, OPEN_FLAGS, OPEN_PERM); + full_copy_fd = open(full_copy_name, DEFAULT_OPEN_FLAGS, DEFAULT_OPEN_PERM); assert_with_errno(full_copy_name >= 0); assert_with_errno(lseek(full_copy_fd, block_size, SEEK_SET) == (off_t) block_size); diff --git a/copyfile_test/sparse_test.h b/copyfile_test/sparse_test.h index 414007e..089b280 100644 --- a/copyfile_test/sparse_test.h +++ b/copyfile_test/sparse_test.h @@ -6,6 +6,9 @@ #ifndef sparse_test_h #define sparse_test_h +#include +#include + bool do_sparse_test(const char *apfs_test_directory, size_t block_size); bool do_sparse_recursive_test(const char *apfs_test_directory, size_t block_size); bool do_fcopyfile_offset_test(const char *apfs_test_directory, size_t block_size); diff --git a/copyfile_test/stat_test.c b/copyfile_test/stat_test.c new file mode 100644 index 0000000..0890544 --- /dev/null +++ b/copyfile_test/stat_test.c @@ -0,0 +1,149 @@ +// +// stat_test.c +// copyfile_test +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../copyfile.h" +#include "stat_test.h" +#include "test_utils.h" + +#define STORAGE_CLASS "copyfile_test" +#define SPECIAL_DIR_NAME "special_dir/" +#define REGULAR_DIR_NAME "regular_dir" +#define TEST_FILE_NAME "almighty_tallest" + +typedef int (*special_mkdir_func)(const char *, mode_t, const char *); + +static bool test_special_dir_with_flag(const char *source_directory, const char *test_directory, + special_mkdir_func create_func, uint32_t flag_to_test) { + char special_dir[BSIZE_B] = {0}, regular_dir[BSIZE_B] = {0}, test_file[BSIZE_B] = {0}; + struct stat sb = {0}; + bool success = true; + + // The plan here is as follows: + // + // (1) Create a special directory using 'create_func', and verify that its bsdflags + // have `flag_to_test` set. + // + // (2) Copy the contents of `source_directory` into that special directory, + // and verify that the directory and, optionally, a well-known file inside it + // have `flag_to_test` set. + // + // (3) Copy the contents of the special directory into a directory that is not a child + // of our special directory (a 'regular' directory), and verify that the directory and + // a well-known file inside it do *NOT* have `flag_to_test` set. + + // Create path names. + assert_with_errno(snprintf(special_dir, BSIZE_B, "%s/" SPECIAL_DIR_NAME, test_directory) > 0); + assert_with_errno(snprintf(regular_dir, BSIZE_B, "%s/" REGULAR_DIR_NAME, test_directory) > 0); + assert_with_errno(snprintf(test_file, BSIZE_B, "%s/" TEST_FILE_NAME, special_dir) > 0); + + // Create our regular directory. + assert_no_err(mkdir(regular_dir, DEFAULT_MKDIR_PERM)); + + // Create our special directory. + assert_no_err(create_func(special_dir, DEFAULT_MKDIR_PERM, STORAGE_CLASS)); + + // (1) Make sure the special directory has the specified bit set. + assert_no_err(stat(special_dir, &sb)); + assert(sb.st_flags & flag_to_test); + + // Now, copy the source directory's into the special directory. + assert_no_err(copyfile(source_directory, special_dir, NULL, COPYFILE_ALL|COPYFILE_RECURSIVE)); + + // (2) Make sure that the resulting folder (and optionally, its well-known subfile) + // have the specified bit set. + assert_no_err(stat(special_dir, &sb)); + success &= verify_st_flags(&sb, flag_to_test); + + if (flag_to_test != SF_NOUNLINK) { + assert_no_err(stat(test_file, &sb)); + success &= verify_st_flags(&sb, flag_to_test); + } + + // Finally, copy the contents of the special directory into our regular directory. + // Since at least one of the files in this directory will have a rootless xattr, + // which cannot be copied here, we do not attempt to copy extended attributes here. + assert_no_err(copyfile(special_dir, regular_dir, NULL, + COPYFILE_DATA|COPYFILE_SECURITY|COPYFILE_RECURSIVE)); + + // (3) Make sure that the regular directory (and optionally, its well-known subfile) + // do *NOT* have the specified bit set. + assert_no_err(stat(regular_dir, &sb)); + success &= ((sb.st_flags & flag_to_test) == 0); + + if (flag_to_test != SF_NOUNLINK) { + // Rebuild the path to the subfile, as our original path is relative + // to the special directory. + memset(test_file, 0, BSIZE_B); + assert_with_errno(snprintf(test_file, BSIZE_B, "%s/" TEST_FILE_NAME, regular_dir) > 0); + + assert_no_err(stat(test_file, &sb)); + success &= verify_st_flags(&sb, 0); + } + + // Clean up after the test. + assert_no_err(removefile(special_dir, NULL, REMOVEFILE_RECURSIVE)); + assert_no_err(removefile(regular_dir, NULL, REMOVEFILE_RECURSIVE)); + + return success; +} + +bool do_preserve_dst_flags_test(const char *test_directory, __unused size_t block_size) { + int interior_file_src_fd, test_file_id; + char exterior_dir_src[BSIZE_B] = {0}, interior_file_src[BSIZE_B] = {0}; + uid_t euid = geteuid(); + bool success = true; + + printf("START [preserve_dst_flags]\n"); + + // Construct our source layout. + assert_with_errno(snprintf(exterior_dir_src, BSIZE_B, "%s/" TEST_FILE_NAME, test_directory) > 0); + + assert_no_err(mkdir(exterior_dir_src, DEFAULT_MKDIR_PERM)); + + test_file_id = rand() % DEFAULT_NAME_MOD; + create_test_file_name(exterior_dir_src, TEST_FILE_NAME, test_file_id, interior_file_src); + + // Create our interior test file. + interior_file_src_fd = open(interior_file_src, DEFAULT_OPEN_FLAGS, DEFAULT_OPEN_PERM); + assert_with_errno(interior_file_src_fd >= 0); + assert_no_err(close(interior_file_src_fd)); // file only needs to exist + + if (euid == 0) { + // Try to copy our directory into a restricted environment. + success &= test_special_dir_with_flag(exterior_dir_src, test_directory, + rootless_mkdir_restricted, SF_RESTRICTED); + + // Make sure SF_NOUNLINK works as well. + success &= test_special_dir_with_flag(exterior_dir_src, test_directory, + rootless_mkdir_nounlink, SF_NOUNLINK); + } else { + printf("Skipping SF_RESTRICTED and SF_NOUNLINK tests, because we are not root.\n"); + } + + // Try to copy our directory into a datavault. + success &= test_special_dir_with_flag(exterior_dir_src, test_directory, + rootless_mkdir_datavault, UF_DATAVAULT); + + if (success) { + printf("PASS [preserve_dst_flags]\n"); + } else { + printf("FAIL [preserve_dst_flags]\n"); + } + + (void)removefile(exterior_dir_src, NULL, REMOVEFILE_RECURSIVE); + + return success ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/copyfile_test/stat_test.h b/copyfile_test/stat_test.h new file mode 100644 index 0000000..ab5c3c5 --- /dev/null +++ b/copyfile_test/stat_test.h @@ -0,0 +1,14 @@ +// +// stat_test.h +// copyfile_test +// + +#ifndef stat_test_h +#define stat_test_h + +#include +#include + +bool do_preserve_dst_flags_test(const char *test_directory, size_t block_size); + +#endif /* stat_test_h */ diff --git a/copyfile_test/test_utils.c b/copyfile_test/test_utils.c index 97c9efe..95741b1 100644 --- a/copyfile_test/test_utils.c +++ b/copyfile_test/test_utils.c @@ -15,6 +15,17 @@ #include "test_utils.h" #include "systemx.h" +bool verify_st_flags(struct stat *sb, uint32_t flags_to_expect) { + // Verify that sb's flags include flags_to_expect. + if (((sb->st_flags & flags_to_expect)) != flags_to_expect) { + printf("st_flags (%u) do not include expected flags (%u)\n", + sb->st_flags, flags_to_expect); + return false; + } + + return true; +} + bool verify_fd_contents(int orig_fd, off_t orig_pos, int copy_fd, off_t copy_pos, size_t length) { // Read *length* contents of the two fds and make sure they compare as equal. // Don't alter the position of either fd. @@ -39,7 +50,8 @@ bool verify_fd_contents(int orig_fd, off_t orig_pos, int copy_fd, off_t copy_pos for (size_t bad_off = 0; bad_off < length; bad_off++) { if (orig_contents[bad_off] != copy_contents[bad_off]) { printf("first mismatch is at offset %zu, original 0x%llx COPY 0x%llx\n", - bad_off, orig_contents[bad_off], copy_contents[bad_off]); + bad_off, (unsigned long long)orig_contents[bad_off], + (unsigned long long)copy_contents[bad_off]); break; } } @@ -68,14 +80,14 @@ bool verify_copy_sizes(struct stat *orig_sb, struct stat *copy_sb, copyfile_stat // If requested, verify that the copy is a sparse file. if (do_sparse) { if (orig_sb->st_size - src_start != copy_sb->st_size) { - printf("original size - offset (%zd) != copy size (%zd)\n", + printf("original size - offset (%lld) != copy size (%lld)\n", orig_sb->st_size - src_start, copy_sb->st_size); result = false; } - blocks_offset = src_start / orig_sb->st_blksize; + blocks_offset = src_start / S_BLKSIZE; if (orig_sb->st_blocks - blocks_offset < copy_sb->st_blocks) { - printf("original blocks - offset (%zd) < copy blocks (%zd)\n", + printf("original blocks - offset (%lld) < copy blocks (%lld)\n", orig_sb->st_blocks - blocks_offset, copy_sb->st_blocks); result = false; } @@ -85,7 +97,7 @@ bool verify_copy_sizes(struct stat *orig_sb, struct stat *copy_sb, copyfile_stat if (cpf_state) { assert_no_err(copyfile_state_get(cpf_state, COPYFILE_STATE_COPIED, &cpf_bytes_copied)); if (orig_sb->st_size - src_start != cpf_bytes_copied) { - printf("original size - start (%zd) != copied bytes (%zd)\n", + printf("original size - start (%lld) != copied bytes (%lld)\n", orig_sb->st_size - src_start, cpf_bytes_copied); result = false; } diff --git a/copyfile_test/test_utils.h b/copyfile_test/test_utils.h index a8fabf8..4cd7faa 100644 --- a/copyfile_test/test_utils.h +++ b/copyfile_test/test_utils.h @@ -18,6 +18,11 @@ #define BSIZE_B 128 #define MAX_DISK_IMAGE_SIZE_MB 1024 +#define DEFAULT_NAME_MOD 999 +#define DEFAULT_OPEN_FLAGS O_CREAT|O_TRUNC|O_RDWR +#define DEFAULT_OPEN_PERM 0666 +#define DEFAULT_MKDIR_PERM 0777 + #define DISK_IMAGE_PATH "/tmp/copyfile_sparse.sparseimage" #define VOLUME_NAME "apfs_sparse" #define DEFAULT_FSTYPE "JHFS+" @@ -30,6 +35,7 @@ #define DIFF_PATH "/usr/bin/diff" // Test routine helpers. +bool verify_st_flags(struct stat *sb, uint32_t flags_to_expect); bool verify_fd_contents(int orig_fd, off_t orig_pos, int copy_fd, off_t copy_pos, size_t length); bool verify_copy_contents(const char *orig_name, const char *copy_name); bool verify_copy_sizes(struct stat *orig_sb, struct stat *copy_sb, copyfile_state_t cpf_state, diff --git a/xattr_flags.h b/xattr_flags.h index 032b6ad..dc7290a 100644 --- a/xattr_flags.h +++ b/xattr_flags.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 Apple, Inc. All rights reserved. + * Copyright (c) 2013-19 Apple, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * diff --git a/xattr_name_with_flags.3 b/xattr_name_with_flags.3 index 0840430..35afc2a 100644 --- a/xattr_name_with_flags.3 +++ b/xattr_name_with_flags.3 @@ -1,7 +1,7 @@ .\" .\" Copyright (c) 2013 Apple Computer, Inc. All rights reserved. .\" -.Dd December 21, 2016 +.Dd October 9, 2018 .Dt XATTR_NAME_WITH_FLAGS 3 .Os .Sh NAME @@ -80,7 +80,7 @@ Indicates that the intent is to share, or export, the object. For example, saving as an attachment in an email message, or placing in a public folder. Sensitive information should probably not be preserved in this case. .It Dv XATTR_OPERATION_INTENT_SYNC -Indicates that the intent is to sync the object to a service like iCloud. +Indicates that the intent is to sync the object to a service like iCloud Drive. .El .Sh FLAGS Various flags are defined by the type @@ -101,9 +101,8 @@ This indicates that the extended attribute should never be copied from a source object to a destination, no matter what the given intent is. .It Dv XATTR_FLAG_SYNCABLE This indicates that the extended attribute should be copied when the file -is synced on services like iCloud. Sync services tends to want the metadata -synced to be kept to a bare minimum, and may enforce additional restrictions -on the acceptable size and number of extended attributes. +is synced on services like iCloud Drive. Sync services may enforce additional +restrictions on the acceptable size and number of extended attributes. .El .Sh EXAMPLE The following example is a simple function that, given an extended attribute diff --git a/xattr_properties.h b/xattr_properties.h index 21d9a29..9427203 100644 --- a/xattr_properties.h +++ b/xattr_properties.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 Apple, Inc. All rights reserved. + * Copyright (c) 2013-19 Apple, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * @@ -89,40 +89,6 @@ typedef uint64_t CopyOperationProperties_t; */ #define kCopyOperationPropertyNeverPreserve ((CopyOperationProperties_t)0x0004) -#if 0 -/* - * These are all going to be removed, and I don't believe anyone used them. - */ -/* - * Given an extended attribute name, and a set of properties, return an - * allocated C string with the name. This will return NULL on error; - * errno may be set to ENOMEM if the new name cannot be allocated, or - * ENAMETOOLONG if the new name is longer than the maximum for EAs (127 UTF8 - * characters). The caller must deallocate the return value otherwise. - * - * If no properties are set, it returns a copy of the EA name. - * - * If the EA name is in the internal list, and the properties are the same as - * defined there, then it will also return an unmodified copy of the EA name. - */ -extern char *_xattrNameWithProperties(const char *, CopyOperationProperties_t) DEPRECATED_IN_MAC_OS_X_VERSION_10_10_AND_LATER; - -/* - * Given an extended attribute name, which may or may not have properties encoded - * as a suffix, return just the name of the attribute. E.g., com.example.mine#P - * would return "com.example.mine". The return value will be NULL on error; - * errno will be set to ENOMEM if it cannot be allocated. The caller must deallocate - * the return value. - */ -extern char *_xattrNameWithoutProperties(const char *) DEPRECATED_IN_MAC_OS_X_VERSION_10_10_AND_LATER; - -/* - * Given an EA name, return the properties. If the name is in the internal list, - * those properties will be returned. Unknown property encodings are ignored. - */ -extern CopyOperationProperties_t _xattrPropertiesFromName(const char *) DEPRECATED_IN_MAC_OS_X_VERSION_10_10_AND_LATER; -#endif /* 0 */ - __END_DECLS #endif /* _XATTR_PROPERTIES_H */ -- 2.47.2