Update Source To copyfile-173.40.2

This commit is contained in:
Thomas A 2023-01-29 21:59:54 -08:00
parent eca07904e0
commit e2b53baa1c
15 changed files with 847 additions and 116 deletions

View File

@ -125,13 +125,15 @@ file had extended attributes but no ACLs, the return value would be
.It Dv COPYFILE_PACK
Serialize the
.Va from
file. The
file.
The
.Va to
file is an AppleDouble-format file.
.It Dv COPYFILE_UNPACK
Unserialize the
.Va from
file. The
file.
The
.Va from
file is an AppleDouble-format file; the
.Va to
@ -168,7 +170,8 @@ file. (This is only applicable for the
.Fn copyfile
function.) No error is returned if
.Xr remove 3
fails. Note that
fails.
Note that
.Xr remove 3
removes a symbolic link itself, not the
target of the link.
@ -185,8 +188,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. Since this flag implies COPYFILE_NOFOLLOW_SRC, symbolic links themselves will
be cloned instead of their targets.
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.)
@ -197,9 +200,9 @@ 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. 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.
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
function.)
@ -216,16 +219,19 @@ if sparse copying cannot be performed for any reason; otherwise, an error is ret
This is a convenience macro, equivalent to
.Dv (COPYFILE_NOFOLLOW_DST | COPYFILE_NOFOLLOW_SRC) .
.It Dv COPYFILE_RUN_IN_PLACE
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.
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.
.It Dv COPYFILE_PRESERVE_DST_TRACKED
Preserve the UF_TRACKED flag at
.Va to
when copying metadata, regardless of whether
.Va from
has it set. This flag is used in conjunction with COPYFILE_STAT, or COPYFILE_CLONE (for its fallback case).
has it set.
This flag is used in conjunction with COPYFILE_STAT, or COPYFILE_CLONE (for its fallback case).
.El
.Pp
Copying files into a directory is supported. If
Copying files into a directory is supported.
If
.Va to
is a directory,
.Va from
@ -269,7 +275,8 @@ parameters are pointers to
.It Dv COPYFILE_STATE_SRC_FILENAME
.It Dv COPYFILE_STATE_DST_FILENAME
Get or set the filename associated with the source (or destination)
file. If it has not been initialized yet, the value will be
file.
If it has not been initialized yet, the value will be
.Dv NULL .
For
.Fn copyfile_state_set ,
@ -385,7 +392,8 @@ entered. (That is, none of the filesystem objects contained
within the directory have been copied yet.)
.It Dv COPYFILE_RECURSE_DIR_CLEANUP
The object being copied is a directory, and all of the
objects contained have been copied. At this stage, the destination directory
objects contained have been copied.
At this stage, the destination directory
being copied will have any extra permissions that were added to
allow the copying will be removed.
.It Dv COPYFILE_RECURSE_ERROR
@ -403,15 +411,15 @@ The second argument to the call-back function will indicate
the stage of the copy, and will be one of the following values:
.Bl -tag -width COPYFILE_FINISH
.It Dv COPYFILE_START
Before copying has begun. The third
parameter will be a newly-created
Before copying has begun.
The third parameter will be a newly-created
.Vt copyfile_state_t
object with the call-back function and context pre-loaded.
.It Dv COPYFILE_FINISH
After copying has successfully finished.
.It Dv COPYFILE_ERR
Indicates an error has happened at some stage. If the
first argument to the call-back function is
Indicates an error has happened at some stage.
If the first argument to the call-back function is
.Dv COPYFILE_RECURSE_ERROR ,
then an error occurred while processing the source hierarchy;
otherwise, it will indicate what type of object was being copied,
@ -437,13 +445,14 @@ values:
The copy will continue as expected.
.It Dv COPYFILE_SKIP
This object will be skipped, and the next object will
be processed. (Note that, when entering a directory.
returning
be processed.
(Note that, when entering a directory, returning
.Dv COPYFILE_SKIP
from the call-back function will prevent the contents
of the directory from being copied.)
.It Dv COPYFILE_QUIT
The entire copy is aborted at this stage. Any filesystem
The entire copy is aborted at this stage.
Any filesystem
objects created up to this point will remain.
.Fn copyfile
will return -1, but
@ -474,11 +483,13 @@ Note that recursive cloning is also supported with the
.Dv COPYFILE_CLONE
flag (but not the
.Dv COPYFILE_CLONE_FORCE
flag). A recursive clone operation invokes
flag).
A recursive clone operation invokes
.Fn copyfile
with
.Dv COPYFILE_CLONE
on every entry found in the source file-system object. Because
on every entry found in the source file-system object.
Because
.Fn copyfile
does not allow the cloning of directories, a recursive clone will
instead copy any directory it finds (while cloning its contents).
@ -503,10 +514,12 @@ 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
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
) 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
@ -518,9 +531,11 @@ and
.Fn fcopyfile
will also use a callback to report data (e.g.,
.Dv COPYFILE_DATA )
progress. If given, the callback will be invoked on each
progress.
If given, the callback will be invoked on each
.Xr write 2
call. The first argument to the callback function will be
call.
The first argument to the callback function will be
.Dv COPYFILE_COPY_DATA .
The second argument will either be
.Dv COPYFILE_PROGRESS
@ -560,7 +575,8 @@ when finished with each individual attribute.
may be called for all of the extended attributes, before
the first callback with
.Dv COPYFILE_PROGRESS
is invoked.) Any attribute skipped by returning
is invoked.)
Any attribute skipped by returning
.Dv COPYFILE_SKIP
from the
.Dv COPYFILE_START
@ -621,6 +637,28 @@ changes while the copy is occurring, the results are undefined.
does not reset the seek position for either source or destination.
This can result in the destination file being a different size
than the source file.
.Sh EXAMPLES
.Bd -literal -offset indent
/* Initialize a state variable */
copyfile_state_t s;
s = copyfile_state_alloc();
/* Copy the data and extended attributes of one file to another */
copyfile("/tmp/f1", "/tmp/f2", s, COPYFILE_DATA | COPYFILE_XATTR);
/* Convert a file to an AppleDouble file for serialization */
copyfile("/tmp/f2", "/tmp/tmpfile", NULL, COPYFILE_ALL | COPYFILE_PACK);
/* Release the state variable */
copyfile_state_free(s);
/* A more complex way to call copyfile() */
s = copyfile_state_alloc();
copyfile_state_set(s, COPYFILE_STATE_SRC_FILENAME, "/tmp/foo");
/* One of src or dst must be set... rest can come from the state */
copyfile(NULL, "/tmp/bar", s, COPYFILE_ALL);
/* Now copy the same source file to another destination file */
copyfile(NULL, "/tmp/car", s, COPYFILE_ALL);
copyfile_state_free(s);
/* Remove extended attributes from a file */
copyfile("/dev/null", "/tmp/bar", NULL, COPYFILE_XATTR);
.Ed
.Sh ERRORS
.Fn copyfile
and
@ -688,40 +726,18 @@ parameter.
In addition, both functions may set
.Dv errno
via an underlying library or system call.
.Sh EXAMPLES
.Bd -literal -offset indent
/* Initialize a state variable */
copyfile_state_t s;
s = copyfile_state_alloc();
/* Copy the data and extended attributes of one file to another */
copyfile("/tmp/f1", "/tmp/f2", s, COPYFILE_DATA | COPYFILE_XATTR);
/* Convert a file to an AppleDouble file for serialization */
copyfile("/tmp/f2", "/tmp/tmpfile", NULL, COPYFILE_ALL | COPYFILE_PACK);
/* Release the state variable */
copyfile_state_free(s);
/* A more complex way to call copyfile() */
s = copyfile_state_alloc();
copyfile_state_set(s, COPYFILE_STATE_SRC_FILENAME, "/tmp/foo");
/* One of src or dst must be set... rest can come from the state */
copyfile(NULL, "/tmp/bar", s, COPYFILE_ALL);
/* Now copy the same source file to another destination file */
copyfile(NULL, "/tmp/car", s, COPYFILE_ALL);
copyfile_state_free(s);
/* Remove extended attributes from a file */
copyfile("/dev/null", "/tmp/bar", NULL, COPYFILE_XATTR);
.Ed
.Sh SEE ALSO
.Xr listxattr 2 ,
.Xr getxattr 2 ,
.Xr listxattr 2 ,
.Xr setxattr 2 ,
.Xr acl 3
.Sh HISTORY
The
.Fn copyfile
API was introduced in Mac OS X 10.5.
.Sh BUGS
Both
.Fn copyfile
functions lack a way to set the input or output block size.
.Pp
Recursive copies do not honor hard links.
.Sh HISTORY
The
.Fn copyfile
API was introduced in Mac OS X 10.5.

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2004-2019 Apple, Inc. All rights reserved.
* Copyright (c) 2004-2020 Apple, Inc. All rights reserved.
*
* @APPLE_LICENSE_HEADER_START@
*
@ -202,6 +202,41 @@ does_copy_protection(int fd)
return ((sfs.f_flags & MNT_CPROTECT) == MNT_CPROTECT);
}
static bool
path_does_copy_protection(const char *path)
{
struct statfs sfs;
if (statfs(path, &sfs) == -1) {
char parent_path[MAXPATHLEN];
if (errno != ENOENT)
return false;
// If the path doesn't exist,
// try to get its parent path and re-attempt the statfs().
if (dirname_r(path, parent_path) == NULL)
return false;
if (statfs(parent_path, &sfs) == -1)
return false;
}
return ((sfs.f_flags & MNT_CPROTECT) == MNT_CPROTECT);
}
static int
do_copy_protected_open(const char *path, int flags, int class, int dpflags, int mode)
{
// The passed-in protection class is meaningful, so use open_dprotected_np().
if (path_does_copy_protection(path)) {
return open_dprotected_np(path, flags, class, dpflags, mode);
}
// Fall-back to regular open().
return open(path, flags, mode);
}
static void
sort_xattrname_list(void *start, size_t length)
{
@ -1077,6 +1112,74 @@ static int copyfile_clone(copyfile_state_t state)
return ret;
}
/*
* Check if two provided paths are identical,
* and if we're able to determine that, return true.
*/
static bool copyfile_paths_identical(const char *src, const char *dst)
{
struct attrlist attrs;
struct statfs sfs;
struct stat src_sb, dst_sb;
char volroot[MAXPATHLEN + 1];
struct {
uint32_t length;
vol_capabilities_attr_t volAttrs;
} volattrs;
char *real_src_path = NULL, *real_dst_path = NULL;
// Common case: the destination does not exist.
if ((stat(dst, &dst_sb) == -1) || (stat(src, &src_sb) == -1))
return false;
// If both files exist, then we next try to check file IDs.
// This requires that the underlying filesystem support persistent file IDs.
if (statfs(src, &sfs) == -1)
return false;
strlcpy(volroot, sfs.f_mntonname, sizeof(volroot));
memset(&attrs, 0, sizeof(attrs));
attrs.bitmapcount = ATTR_BIT_MAP_COUNT;
attrs.volattr = ATTR_VOL_CAPABILITIES;
if (getattrlist(volroot, &attrs, &volattrs, sizeof(volattrs), 0) == -1)
return false;
// If the underlying devices are not the same, then the files are not the same.
if (src_sb.st_dev != dst_sb.st_dev)
return false;
if ((volattrs.volAttrs.capabilities[VOL_CAPABILITIES_FORMAT] & VOL_CAP_FMT_PERSISTENTOBJECTIDS) &&
(volattrs.volAttrs.valid[VOL_CAPABILITIES_FORMAT] & VOL_CAP_FMT_PERSISTENTOBJECTIDS)) {
// The underlying source filesystem supports persistent file IDs,
// so if our two files have the same file ID on the same device,
// they are identical.
return (src_sb.st_ino == dst_sb.st_ino);
}
// Finally, if we don't support persistent file ID's,
// we fall back to path comparisons.
real_src_path = realpath(src, NULL);
if (real_src_path == NULL)
goto exit;
real_dst_path = realpath(dst, NULL);
if (real_dst_path == NULL)
goto exit;
if (strncasecmp(src, dst, MAXPATHLEN) == 0)
return true;
exit:
if (real_src_path)
free(real_src_path);
if (real_dst_path)
free(real_dst_path);
return false;
}
/*
* the original copyfile() routine; this copies a source file to a destination
* file. Note that because we need to set the names in the state variable, this
@ -1134,6 +1237,20 @@ int copyfile(const char *src, const char *dst, copyfile_state_t state, copyfile_
COPYFILE_SET_FNAME(src, s);
COPYFILE_SET_FNAME(dst, s);
if (!(s->flags & COPYFILE_CHECK)) {
// We have no work to do if `src` and `dst` point to the same place.
if (copyfile_paths_identical(src, dst)) {
// ...but return an error if requested to do so.
if (s->flags & COPYFILE_EXCL) {
s->err = EEXIST;
goto error_exit;
}
ret = 0;
goto exit;
}
}
if (s->flags & COPYFILE_RECURSIVE) {
ret = copytree(s);
goto exit;
@ -1678,7 +1795,7 @@ static int copyfile_unset_acl(copyfile_state_t s)
*/
static int copyfile_open(copyfile_state_t s)
{
int oflags = O_EXCL | O_CREAT | O_WRONLY;
int oflags = O_EXCL | O_CREAT;
int islnk = 0, isdir = 0, isreg = 0;
int osrc = 0, dsrc = 0;
int prot_class = PROTECTION_CLASS_DEFAULT;
@ -1749,6 +1866,26 @@ static int copyfile_open(copyfile_state_t s)
if (s->dst && s->dst_fd == -2)
{
/*
* Per <rdar://60074298>, only open files for writing if we expect
* to modify the file's content. This avoids undesirable side effects
* of O_WRONLY when we're only modifying metadata/attributes.
*
* The calls needed by COPYFILE_METADATA (e.g. fchown(), fchmod(),
* fchflags(), futimes(), fsetattrlist(), f{set,remove}xattr(), and
* acl_set_fd()) are safe to use with O_RDONLY descriptors. They
* operate on the underlying vnode_t, as path-based variants do.
* These usually only need write permissions in st_mode or ACLs.
*/
const copyfile_flags_t writable_flags = (COPYFILE_DATA | COPYFILE_DATA_SPARSE);
if (COPYFILE_PACK & s->flags) {
oflags |= O_WRONLY; // always writes file content
} else if (COPYFILE_UNPACK & s->flags) {
oflags |= O_RDONLY; // only updates metadata
} else {
oflags |= (writable_flags & s->flags) ? O_WRONLY : O_RDONLY;
}
/*
* COPYFILE_UNLINK tells us to try removing the destination
* before we create it. We don't care if the file doesn't
@ -1841,7 +1978,8 @@ static int copyfile_open(copyfile_state_t s)
return -1;
}
set_cprot_explicit = 1;
} else while((s->dst_fd = open_dprotected_np(s->dst, oflags | dsrc, prot_class, 0, s->sb.st_mode | S_IWUSR)) < 0)
} else while((s->dst_fd = do_copy_protected_open(s->dst, oflags | dsrc, prot_class,
0, s->sb.st_mode | S_IWUSR)) < 0)
{
/*
* We set S_IWUSR because fsetxattr does not -- at the time this comment
@ -2053,10 +2191,13 @@ static int copyfile_data_sparse(copyfile_state_t s, size_t input_blk_size, size_
if (!(s->flags & COPYFILE_DATA_SPARSE)) {
// Don't attempt this unless the right flags are passed.
return ENOTSUP;
} else if (src_size <= 0) {
} else if (src_size < 0) {
// The file size of our source is invalid; there's nothing to copy.
errno = EINVAL;
goto error_exit;
} else if (src_size == 0) {
// This is a zero-length file: no work to do.
goto exit;
}
// Since a major underlying filesystem requires that holes are block-aligned,
@ -2761,6 +2902,8 @@ static int copyfile_stat(copyfile_state_t s)
return 0;
}
#define MAX_GETXATTR_RETRIES 3
#define MAX_XATTR_BUFFER_SIZE (32 * 1024 * 1024) // 32 MiB
/*
* Similar to copyfile_security() in some ways; this
* routine copies the extended attributes from the source,
@ -2774,31 +2917,53 @@ static int copyfile_stat(copyfile_state_t s)
static int copyfile_xattr(copyfile_state_t s)
{
char *name;
char *namebuf, *end;
char *namebuf = NULL;
char *end;
ssize_t xa_size;
void *xa_dataptr;
ssize_t bufsize = 4096;
ssize_t xa_bufsize = 4096;
ssize_t namebuf_size = 0;
ssize_t asize;
ssize_t nsize;
ssize_t list_size;
int ret = 0;
int look_for_decmpea = 0;
int tries_left = MAX_GETXATTR_RETRIES;
/* delete EAs on destination */
if ((nsize = flistxattr(s->dst_fd, 0, 0, 0)) > 0)
dst_restart:
if ((list_size = flistxattr(s->dst_fd, 0, 0, 0)) > 0)
{
if ((namebuf = (char *) malloc(nsize)) == NULL)
return -1;
else
nsize = flistxattr(s->dst_fd, namebuf, nsize, 0);
/* this is always true on the first call: no buffer yet (namebuf_size == 0) */
if (list_size > namebuf_size) {
if (list_size > MAX_XATTR_BUFFER_SIZE) {
copyfile_warn("destination's xattr list size (%zu) exceeds the threshold (%d); trying to allocate", list_size, MAX_XATTR_BUFFER_SIZE);
}
namebuf_size = list_size;
void *tdptr = namebuf;
if (nsize > 0) {
if ((namebuf =
(void *) realloc((void *) namebuf, namebuf_size)) == NULL)
{
if (tdptr) {
free(tdptr);
}
return -1;
}
}
list_size = flistxattr(s->dst_fd, namebuf, namebuf_size, 0);
if ((list_size < 0) && (errno == ERANGE) && (tries_left > 0)) {
/* `namebuf` is too small - try again */
tries_left--;
goto dst_restart;
} else if (list_size > 0) {
/*
* With this, end points to the last byte of the allocated buffer
* This *should* be NUL, from flistxattr, but if it's not, we can
* set it anyway -- it'll result in a truncated name, which then
* shouldn't match when we get them later.
*/
end = namebuf + nsize - 1;
end = namebuf + list_size - 1;
if (*end != 0)
*end = 0;
for (name = namebuf; name <= end; name += strlen(name) + 1) {
@ -2809,15 +2974,24 @@ static int copyfile_xattr(copyfile_state_t s)
fremovexattr(s->dst_fd, name,0);
}
}
free(namebuf);
} else
if (nsize < 0)
{
if (errno == ENOTSUP || errno == EPERM)
return 0;
else
return -1;
}
else if (list_size < 0)
{
if (namebuf) {
free(namebuf);
}
if (errno == ENOTSUP || errno == EPERM) {
return 0;
} else {
return -1;
}
}
if (namebuf) {
free(namebuf);
}
namebuf = NULL;
namebuf_size = 0;
tries_left = MAX_GETXATTR_RETRIES;
#ifdef DECMPFS_XATTR_NAME
if ((s->flags & COPYFILE_DATA) &&
@ -2829,24 +3003,50 @@ static int copyfile_xattr(copyfile_state_t s)
#endif
/* get name list of EAs on source */
if ((nsize = flistxattr(s->src_fd, 0, 0, look_for_decmpea)) < 0)
src_restart:
if ((list_size = flistxattr(s->src_fd, 0, 0, look_for_decmpea)) <= 0)
{
if (errno == ENOTSUP || errno == EPERM)
if (namebuf) {
free(namebuf);
}
if (list_size == 0) {
return 0;
else
} else if (errno == ENOTSUP || errno == EPERM) {
return 0;
} else {
return -1;
} else
if (nsize == 0)
return 0;
}
}
if ((namebuf = (char *) malloc(nsize)) == NULL)
return -1;
else
nsize = flistxattr(s->src_fd, namebuf, nsize, look_for_decmpea);
/* this is always true on the first call: no buffer yet (namebuf_size == 0) */
if (list_size > namebuf_size) {
if (list_size > MAX_XATTR_BUFFER_SIZE) {
copyfile_warn("source's xattr list size (%zu) exceeds the threshold (%d); trying to allocate", list_size, MAX_XATTR_BUFFER_SIZE);
}
namebuf_size = list_size;
void *tdptr = namebuf;
if ((namebuf =
(void *) realloc((void *) namebuf, namebuf_size)) == NULL)
{
if (tdptr) {
free(tdptr);
}
return -1;
}
}
if (nsize <= 0) {
free(namebuf);
return (int)nsize;
list_size = flistxattr(s->src_fd, namebuf, namebuf_size, look_for_decmpea);
if ((list_size < 0) && (errno == ERANGE) && (tries_left > 0)) {
/* `namebuf` is too small - try again */
tries_left--;
goto src_restart;
} else if (list_size <= 0) {
if (namebuf) {
free(namebuf);
}
return (int)list_size;
}
/*
@ -2855,11 +3055,11 @@ static int copyfile_xattr(copyfile_state_t s)
* set it anyway -- it'll result in a truncated name, which then
* shouldn't match when we get them later.
*/
end = namebuf + nsize - 1;
end = namebuf + list_size - 1;
if (*end != 0)
*end = 0;
if ((xa_dataptr = (void *) malloc(bufsize)) == NULL) {
if ((xa_dataptr = (void *) malloc(xa_bufsize)) == NULL) {
free(namebuf);
return -1;
}
@ -2875,17 +3075,22 @@ static int copyfile_xattr(copyfile_state_t s)
if (strncmp(name, XATTR_QUARANTINE_NAME, end - name) == 0)
continue;
tries_left = MAX_GETXATTR_RETRIES;
get_restart:
if ((xa_size = fgetxattr(s->src_fd, name, 0, 0, 0, look_for_decmpea)) < 0)
{
continue;
}
if (xa_size > bufsize)
if (xa_size > xa_bufsize)
{
if (xa_size > MAX_XATTR_BUFFER_SIZE) {
copyfile_warn("xattr named %s has size (%zu), which exceeds the threshold (%d); trying to allocate", name, list_size, MAX_XATTR_BUFFER_SIZE);
}
void *tdptr = xa_dataptr;
bufsize = xa_size;
xa_bufsize = xa_size;
if ((xa_dataptr =
(void *) realloc((void *) xa_dataptr, bufsize)) == NULL)
(void *) realloc((void *) xa_dataptr, xa_bufsize)) == NULL)
{
free(tdptr);
ret = -1;
@ -2893,8 +3098,13 @@ static int copyfile_xattr(copyfile_state_t s)
}
}
if ((asize = fgetxattr(s->src_fd, name, xa_dataptr, xa_size, 0, look_for_decmpea)) < 0)
if ((asize = fgetxattr(s->src_fd, name, xa_dataptr, xa_bufsize, 0, look_for_decmpea)) < 0)
{
if ((errno == ERANGE) && (tries_left > 0)) {
/* `xa_dataptr` is too small - try again */
tries_left--;
goto get_restart;
}
continue;
}

View File

@ -7,7 +7,9 @@
objects = {
/* Begin PBXBuildFile section */
096213F7239827D0005847FC /* identical_test.c in Sources */ = {isa = PBXBuildFile; fileRef = 096213F6239827D0005847FC /* identical_test.c */; };
098AF3B622692BF300F9BA42 /* stat_test.c in Sources */ = {isa = PBXBuildFile; fileRef = 098AF3B522692BF300F9BA42 /* stat_test.c */; };
3EF9FA5F2418B6BA003B43E8 /* readonly_fd_test.c in Sources */ = {isa = PBXBuildFile; fileRef = 3EF9FA5E2418B6BA003B43E8 /* readonly_fd_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 */; };
@ -18,6 +20,7 @@
72B4C0F41676C47D00C13E05 /* copyfile_private.h in Headers */ = {isa = PBXBuildFile; fileRef = 72B4C0F31676C47D00C13E05 /* copyfile_private.h */; settings = {ATTRIBUTES = (Private, ); }; };
72EAA3B016A72F4500833E98 /* xattr_flags.h in Headers */ = {isa = PBXBuildFile; fileRef = 72EAA3AF16A72F4500833E98 /* xattr_flags.h */; settings = {ATTRIBUTES = (Public, ); }; };
86EF9F0A1834018C00AAB3F3 /* xattr_properties.h in Headers */ = {isa = PBXBuildFile; fileRef = 86EF9F091834018C00AAB3F3 /* xattr_properties.h */; settings = {ATTRIBUTES = (Private, ); }; };
D11048A22455AE7900E8F465 /* xattr_test.c in Sources */ = {isa = PBXBuildFile; fileRef = D11048A12455AE7900E8F465 /* xattr_test.c */; };
FCCE17C3135A658F002CEE6D /* copyfile.c in Sources */ = {isa = PBXBuildFile; fileRef = FCCE17C1135A658F002CEE6D /* copyfile.c */; };
FCCE17C4135A658F002CEE6D /* copyfile.h in Headers */ = {isa = PBXBuildFile; fileRef = FCCE17C2135A658F002CEE6D /* copyfile.h */; settings = {ATTRIBUTES = (Public, ); }; };
/* End PBXBuildFile section */
@ -35,9 +38,13 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
096213F5239827D0005847FC /* identical_test.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = identical_test.h; sourceTree = "<group>"; };
096213F6239827D0005847FC /* identical_test.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = identical_test.c; sourceTree = "<group>"; };
098AF3B422692BF300F9BA42 /* stat_test.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = stat_test.h; sourceTree = "<group>"; };
098AF3B522692BF300F9BA42 /* stat_test.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = stat_test.c; sourceTree = "<group>"; };
098AF3B7226A510E00F9BA42 /* copyfile_test.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = copyfile_test.entitlements; sourceTree = "<group>"; };
3EF9FA5D2418B6BA003B43E8 /* readonly_fd_test.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = readonly_fd_test.h; sourceTree = "<group>"; };
3EF9FA5E2418B6BA003B43E8 /* readonly_fd_test.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = readonly_fd_test.c; sourceTree = "<group>"; };
3F1EFD4C185C4EB400D1C970 /* copyfile.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = copyfile.xcconfig; path = xcodescripts/copyfile.xcconfig; sourceTree = "<group>"; };
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 = "<group>"; };
@ -53,6 +60,8 @@
72EAA3AF16A72F4500833E98 /* xattr_flags.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = xattr_flags.h; sourceTree = "<group>"; };
861E1C14180F0AF900E65B9A /* xattr_name_with_flags.3 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = xattr_name_with_flags.3; sourceTree = "<group>"; };
86EF9F091834018C00AAB3F3 /* xattr_properties.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = xattr_properties.h; sourceTree = "<group>"; };
D11048A02455AE7900E8F465 /* xattr_test.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = xattr_test.h; sourceTree = "<group>"; };
D11048A12455AE7900E8F465 /* xattr_test.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = xattr_test.c; sourceTree = "<group>"; };
FCCE17BB135A6444002CEE6D /* libcopyfile.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libcopyfile.dylib; sourceTree = BUILT_PRODUCTS_DIR; };
FCCE17C0135A658F002CEE6D /* copyfile.3 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = copyfile.3; sourceTree = "<group>"; };
FCCE17C1135A658F002CEE6D /* copyfile.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = copyfile.c; sourceTree = "<group>"; };
@ -89,6 +98,10 @@
isa = PBXGroup;
children = (
726EE9DA1E9423E50017A5B9 /* main.c */,
096213F6239827D0005847FC /* identical_test.c */,
096213F5239827D0005847FC /* identical_test.h */,
3EF9FA5E2418B6BA003B43E8 /* readonly_fd_test.c */,
3EF9FA5D2418B6BA003B43E8 /* readonly_fd_test.h */,
726EE9DE1E9425160017A5B9 /* sparse_test.c */,
726EE9DF1E9425160017A5B9 /* sparse_test.h */,
098AF3B522692BF300F9BA42 /* stat_test.c */,
@ -98,6 +111,8 @@
726EE9E21E946B320017A5B9 /* systemx.c */,
726EE9E31E946B320017A5B9 /* systemx.h */,
098AF3B7226A510E00F9BA42 /* copyfile_test.entitlements */,
D11048A02455AE7900E8F465 /* xattr_test.h */,
D11048A12455AE7900E8F465 /* xattr_test.c */,
);
path = copyfile_test;
sourceTree = "<group>";
@ -201,6 +216,7 @@
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
English,
en,
);
mainGroup = FCCE17AB135A5FFB002CEE6D;
@ -238,9 +254,12 @@
files = (
098AF3B622692BF300F9BA42 /* stat_test.c in Sources */,
721D4F081EA95290000F0555 /* xattr_flags.c in Sources */,
3EF9FA5F2418B6BA003B43E8 /* readonly_fd_test.c in Sources */,
D11048A22455AE7900E8F465 /* xattr_test.c in Sources */,
721D4F071EA95283000F0555 /* copyfile.c in Sources */,
726EE9DB1E9423E50017A5B9 /* main.c in Sources */,
726EE9E61E946D590017A5B9 /* test_utils.c in Sources */,
096213F7239827D0005847FC /* identical_test.c in Sources */,
726EE9E41E946B320017A5B9 /* systemx.c in Sources */,
726EE9E01E9425160017A5B9 /* sparse_test.c in Sources */,
);
@ -366,6 +385,7 @@
"-ldispatch",
"-lxpc",
);
OTHER_TAPI_FLAGS = "-umbrella System";
SDKROOT = macosx.internal;
"SIM_SUFFIX[sdk=iphonesimulator*]" = _sim;
SUPPORTS_TEXT_BASED_API = YES;

View File

@ -0,0 +1,119 @@
//
// identical_test.c
// copyfile_test
//
#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <removefile.h>
#include <sys/fcntl.h>
#include <sys/stat.h>
#include <sys/xattr.h>
#include "../copyfile.h"
#include "identical_test.h"
#include "test_utils.h"
#define REGULAR_FILE_NAME "regular_file"
#define REGULAR_DIR_NAME "regular_dir"
#define DUMMY_XATTR_NAME "dummy_xattr"
#define DUMMY_XATTR_DATA "drell"
#define TEST_FILE_DATA "krogan"
static bool verify_src_dst_identical(const char *apfs_test_directory, __unused size_t block_size) {
char regular_file[BSIZE_B] = {0}, folder[BSIZE_B] = {0}, file_inside_folder[BSIZE_B] = {0};
int regular_file_fd, file_inside_folder_fd;
bool success = true;
// The idea here is to verify that copyfile(file1, file1) returns success
// without doing anything.
// There are a few wrinkles - COPYFILE_CHECK still needs to work on these files,
// and we need to make sure that our identity check works on filesystems without
// persistent object identifiers. The first we can easily verify but the second
// is not tested here. Nor are negative tests included (there are an infinite
// number of those, so we rely on the other tests to verify that behavior).
// Create path names.
assert_with_errno(snprintf(regular_file, BSIZE_B, "%s/" REGULAR_FILE_NAME, apfs_test_directory) > 0);
assert_with_errno(snprintf(folder, BSIZE_B, "%s/" REGULAR_DIR_NAME, apfs_test_directory) > 0);
assert_with_errno(snprintf(file_inside_folder, BSIZE_B, "%s/" REGULAR_FILE_NAME, folder) > 0);
// First, verify copyfile(file1, file1),
// where file1 is a regular file.
// Create our regular file.
regular_file_fd = open(regular_file, DEFAULT_OPEN_FLAGS, DEFAULT_OPEN_PERM);
assert_with_errno(regular_file_fd >= 0);
// Write some data to the test file so that we can verify it is not empty.
assert(write(regular_file_fd, TEST_FILE_DATA, sizeof(TEST_FILE_DATA)) > 0);
// Verify copyfile(file1, file1) does nothing.
assert_no_err(copyfile(regular_file, regular_file, NULL, COPYFILE_ALL));
success = success && verify_contents_with_buf(regular_file_fd, 0, (const char *)TEST_FILE_DATA, sizeof(TEST_FILE_DATA));
// Verify copyfile(file1, file1, COPYFILE_EXCL) returns an error.
assert(copyfile(regular_file, regular_file, NULL, COPYFILE_ALL|COPYFILE_EXCL) == -1);
assert(errno == EEXIST);
// Write an dummy xattr to the file to verify COPYFILE_CHECK.
assert_no_err(fsetxattr(regular_file_fd, DUMMY_XATTR_NAME, DUMMY_XATTR_DATA, sizeof(DUMMY_XATTR_DATA), 0, XATTR_CREATE));
// Verify copyfile(file1, file1, ..., COPYFILE_CHECK) works.
assert_no_err(copyfile(regular_file, regular_file, NULL, COPYFILE_CHECK) == COPYFILE_XATTR);
// Now, verify that copyfile(dir1, dir1, COPYFILE_RECURSIVE)
// also returns early. Do this by making sure the contents of a file inside the directory
// do not change after copyfile(COPYFILE_RECURSIVE).
// Create our directory.
assert_no_err(mkdir(folder, DEFAULT_MKDIR_PERM));
// Create a regular file inside that directory.
file_inside_folder_fd = open(file_inside_folder, DEFAULT_OPEN_FLAGS, DEFAULT_OPEN_PERM);
assert_with_errno(file_inside_folder_fd >= 0);
// Write some data to the interior file so that we can verify it is not empty.
assert(write(file_inside_folder_fd, (const char *)TEST_FILE_DATA, sizeof(TEST_FILE_DATA)) > 0);
// Verify copyfile(dir1, dir1, ... COPYFILE_RECURSIVE).
assert_no_err(copyfile(folder, folder, NULL, COPYFILE_RECURSIVE));
success = success && verify_contents_with_buf(file_inside_folder_fd, 0, TEST_FILE_DATA, sizeof(TEST_FILE_DATA));
// Post-test cleanup.
assert_no_err(close(file_inside_folder_fd));
assert_no_err(close(regular_file_fd));
(void)removefile(folder, NULL, REMOVEFILE_RECURSIVE);
(void)removefile(regular_file, NULL, 0);
return success;
}
bool do_src_dst_identical_test(const char *apfs_test_directory, __unused size_t block_size) {
char test_dir[BSIZE_B] = {0};
int test_folder_id;
bool success = true;
printf("START [identical]\n");
// Get ready for the test.
test_folder_id = rand() % DEFAULT_NAME_MOD;
create_test_file_name(apfs_test_directory, "identical", test_folder_id, test_dir);
assert_no_err(mkdir(test_dir, DEFAULT_MKDIR_PERM));
success = verify_src_dst_identical(test_dir, block_size);
if (success) {
printf("PASS [identical]\n");
} else {
printf("FAIL [identical]\n");
}
(void)removefile(test_dir, NULL, REMOVEFILE_RECURSIVE);
return success ? EXIT_SUCCESS : EXIT_FAILURE;
}

View File

@ -0,0 +1,14 @@
//
// identical_test.h
// copyfile_test
//
#ifndef identical_test_h
#define identical_test_h
#include <stdbool.h>
#include <stdlib.h>
bool do_src_dst_identical_test(const char *apfs_test_directory, size_t block_size);
#endif /* identical_test_h */

View File

@ -11,8 +11,11 @@
#include <sys/stat.h>
#include <removefile.h>
#include "identical_test.h"
#include "readonly_fd_test.h"
#include "sparse_test.h"
#include "stat_test.h"
#include "xattr_test.h"
#include "test_utils.h"
#define DISK_IMAGE_SIZE_MB 512
@ -51,11 +54,14 @@ int main(__unused int argc, __unused const char * argv[]) {
// Run our tests.
sranddev();
failed |= do_readonly_fd_test(TEST_DIR, stb.f_bsize);
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);
failed |= do_preserve_dst_tracked_test(TEST_DIR, stb.f_bsize);
failed |= do_src_dst_identical_test(TEST_DIR, stb.f_bsize);
failed |= do_xattr_test(TEST_DIR, stb.f_bsize);
// Cleanup the disk image we ran our tests on.
if (USING_DISK_IMAGE) {

View File

@ -0,0 +1,124 @@
//
// Copyright (c) 2020 Apple Inc. All rights reserved.
//
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <paths.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/acl.h>
#include <sys/attr.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/xattr.h>
#include <unistd.h>
#include "readonly_fd_test.h"
#include "test_utils.h"
static
bool test_readonly_fd_metadata(const char *basedir)
{
char filename[] = ".readonly-ops-XXXXXX";
bool created = false;
bool success = true;
int dirfd = -1;
int tmpfd = -1;
int fd = -1;
acl_t acl = NULL;
static const char test_name[] = "readonly_fd_metadata";
printf("START [%s]\n", test_name);
assert_with_errno((dirfd = open(basedir, O_RDONLY | O_DIRECTORY)) != -1);
assert_with_errno((tmpfd = mkstempsat_np(dirfd, filename, 0)) != -1);
created = true;
assert_with_errno((fd = openat(dirfd, filename, O_RDONLY)) != -1);
close(tmpfd);
tmpfd = -1;
// confirm that writes are disallowed
const char data[] = "failure";
assert(write(fd, data, sizeof(data) - 1) == -1);
// check fchown()
const uid_t uid = geteuid();
const gid_t gid = getegid();
assert_no_err(fchown(fd, uid, gid));
// check fchmod()
assert_no_err(fchmod(fd, 0644));
assert_no_err(fchmod(fd, 0600));
// check fchflags()
assert_no_err(fchflags(fd, UF_HIDDEN));
// check setting timestamps with fsetattrlist
const time_t mtime = 978307200;
const time_t atime = mtime + 1;
struct timeval matimes_usec[] = {{mtime, 0}, {atime, 0}};
assert_no_err(futimes(fd, matimes_usec));
struct attrlist attrlist = {
.bitmapcount = ATTR_BIT_MAP_COUNT,
.commonattr = ATTR_CMN_MODTIME | ATTR_CMN_ACCTIME,
};
struct {
struct timespec mtime;
struct timespec atime;
} matimes_nsec = {{mtime, 0}, {atime, 0}};
assert_no_err(fsetattrlist(fd, &attrlist, &matimes_nsec, sizeof(matimes_nsec), 0));
// check adding and removing xattrs
static const char key[] = "local.test-xattr";
static const char value[] = "local.test-xattr.value";
assert_no_err(fsetxattr(fd, key, value, sizeof(value)-1, 0, 0));
assert_no_err(fremovexattr(fd, key, 0));
// check setting ACLs
assert_with_errno((acl = acl_init(1)) != NULL);
assert_no_err(acl_set_fd(fd, acl));
// log pass/fail before cleanup
if (success) {
printf("PASS [%s]\n", test_name);
} else {
printf("FAIL [%s]\n", test_name);
}
// clean up resources
if (acl) {
acl_free(acl);
}
if (fd != -1) {
close(fd);
}
if (tmpfd != -1) {
close(tmpfd);
}
if (created) {
unlinkat(dirfd, filename, 0);
}
if (dirfd != -1) {
close(dirfd);
}
return success;
}
bool do_readonly_fd_test(const char *apfs_test_directory, size_t block_size __unused)
{
// These tests verify the underlying calls needed for COPYFILE_METADATA
// operations are safe with O_RDONLY file descriptors. If this fails,
// expect <rdar://60074298> to cause many other copyfile() failures.
bool success = true;
success = success && test_readonly_fd_metadata(apfs_test_directory);
return !success; // caller expects nonzero to mean failure
}

View File

@ -0,0 +1,13 @@
//
// Copyright (c) 2020 Apple Inc. All rights reserved.
//
#ifndef readonly_fd_test_h
#define readonly_fd_test_h
#include <stdbool.h>
#include <stddef.h>
bool do_readonly_fd_test(const char *apfs_test_directory, size_t block_size);
#endif /* readonly_fd_test_h */

View File

@ -182,12 +182,16 @@ static off_t write_diff_adj_holes(int fd, off_t block_size) {
return 0;
}
static off_t write_nothing(__unused int fd, __unused off_t block_size) {
return 0;
}
typedef struct {
creator_func func; // pointer to function to create a sparse file
const char * name; // null terminated string
} sparse_test_func;
#define NUM_TEST_FUNCTIONS 10
#define NUM_TEST_FUNCTIONS 11
sparse_test_func test_functions[NUM_TEST_FUNCTIONS] = {
{write_start_and_end_holes, "start_and_end_holes"},
{write_middle_hole, "middle_hole"},
@ -198,7 +202,8 @@ sparse_test_func test_functions[NUM_TEST_FUNCTIONS] = {
{write_no_sparse, "no_sparse"},
{write_sparse_odd_offset, "write_sparse_odd_offset"},
{write_sparse_bs_offset, "write_sparse_bs_offset"},
{write_diff_adj_holes, "write_diff_adj_holes"}
{write_diff_adj_holes, "write_diff_adj_holes"},
{write_nothing, "write_nothing"},
};
bool do_sparse_test(const char* apfs_test_directory, size_t block_size) {

View File

@ -174,8 +174,8 @@ bool do_preserve_dst_tracked_test(const char *test_directory, __unused size_t bl
assert_no_err(copyfile(file_src, file_dst, NULL, COPYFILE_DATA|COPYFILE_STAT|COPYFILE_PRESERVE_DST_TRACKED));
assert_no_err(stat(file_dst, &dst_stb));
success &= (dst_stb.st_size == src_fsize);
success &= (dst_stb.st_flags & UF_TRACKED);
success = success && (dst_stb.st_size == src_fsize);
success = success && (dst_stb.st_flags & UF_TRACKED);
if (success) {
printf("PASS [preserve_dst_tracked]\n");
} else {

View File

@ -11,10 +11,78 @@
#include <unistd.h>
#include <sys/fcntl.h>
#include <sys/stat.h>
#include <sys/xattr.h>
#include "test_utils.h"
#include "systemx.h"
static bool verify_xattr_content(int fd, const char *xattr_name, const char *expected, ssize_t size) {
// Verify that the file referenced by `fd` has an xattr named `xattr_name`
// of size `size` and with contents equal to `expected`.
char *actual = NULL;
bool equal;
assert(fd > 0 && xattr_name && expected);
assert_with_errno(actual = malloc(size));
assert_with_errno(fgetxattr(fd, xattr_name, actual, size, 0, 0) == size);
equal = (memcmp(actual, expected, size) == 0);
if (!equal) {
printf("xattr %s: content does not match expected\n", xattr_name);
}
free(actual);
return equal;
}
bool verify_fd_xattr_contents(int orig_fd, int copy_fd) {
// Verify that both fd's have the same xattrs.
// We do so by first verifying that `flistxattr()` returns the same size
// for both, and then validating that `copy_fd` has each of the xattrs
// that `orig_fd` has.
char *namebuf = NULL, *xa_buf = NULL, *name, *end;
ssize_t orig_size, copy_size, xa_size;
bool equal = true;
assert((orig_fd > 0) && (copy_fd > 0));
orig_size = flistxattr(orig_fd, 0, 0, XATTR_SHOWCOMPRESSION);
copy_size = flistxattr(copy_fd, 0, 0, XATTR_SHOWCOMPRESSION);
if (orig_size != copy_size) {
printf("xattrlist size: orig_size(%zu) != (%zu)copy_size\n", orig_size, copy_size);
return false;
}
if (orig_size == 0) {
return true;
}
assert_with_errno(namebuf = malloc(orig_size));
assert_with_errno(flistxattr(orig_fd, namebuf, orig_size, 0) == orig_size);
end = namebuf + orig_size - 1;
if (*end != 0) {
*end = 0;
}
for (name = namebuf; name <= end; name += strlen(name) + 1) {
xa_size = fgetxattr(orig_fd, name, 0, 0, 0, 0);
assert(xa_size >= 0);
assert_with_errno(xa_buf = malloc(xa_size));
assert_with_errno(fgetxattr(orig_fd, name, xa_buf, xa_size, 0, 0) == xa_size);
equal = equal && verify_xattr_content(copy_fd, name, xa_buf, xa_size);
free(xa_buf);
if (!equal) {
break;
}
}
free(namebuf);
return equal;
}
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) {
@ -26,6 +94,37 @@ bool verify_st_flags(struct stat *sb, uint32_t flags_to_expect) {
return true;
}
bool verify_contents_with_buf(int orig_fd, off_t orig_pos, const char *expected, size_t length)
{
// Read *length* bytes from a file descriptor at a specified position
// and assert that they match *length* bytes from expected.
char orig_contents[length];
bool equal;
assert(orig_fd > 0 && orig_pos >= 0);
memset(orig_contents, 0, length);
errno = 0;
ssize_t pread_res = pread(orig_fd, orig_contents, length, orig_pos);
assert_with_errno(pread_res == (off_t) length);
equal = (memcmp(orig_contents, expected, length) == 0);
if (!equal) {
printf("fd (%lld - %lld) did not match expected contents\n", orig_pos, orig_pos + length);
// Find the first non-matching byte and print it out.
for (size_t bad_off = 0; bad_off < length; bad_off++) {
if (orig_contents[bad_off] != expected[bad_off]) {
printf("first mismatch is at offset %zu, original 0x%llx expected 0x%llx\n",
bad_off, (unsigned long long)orig_contents[bad_off],
(unsigned long long)expected[bad_off]);
break;
}
}
}
return equal;
}
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.
@ -33,6 +132,7 @@ bool verify_fd_contents(int orig_fd, off_t orig_pos, int copy_fd, off_t copy_pos
bool equal;
assert(orig_fd > 0 && copy_fd > 0);
assert(orig_pos >= 0);
memset(orig_contents, 0, length);
memset(copy_contents, 0, length);
@ -119,7 +219,7 @@ int create_hole_in_fd(int fd, off_t offset, off_t length) {
void create_test_file_name(const char *dir, const char *postfix, int id, char *string_out) {
// Make a name for this new file and put it in out_name, which should be BSIZE_B bytes.
// Make a name for this new file and put it in string_out, which should be BSIZE_B bytes.
assert_with_errno(snprintf(string_out, BSIZE_B, "%s/testfile-%d.%s", dir, id, postfix) > 0);
}

View File

@ -35,7 +35,9 @@
#define DIFF_PATH "/usr/bin/diff"
// Test routine helpers.
bool verify_fd_xattr_contents(int orig_fd, int copy_fd);
bool verify_st_flags(struct stat *sb, uint32_t flags_to_expect);
bool verify_contents_with_buf(int orig_fd, off_t orig_pos, const char *expected, size_t length);
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,

View File

@ -0,0 +1,84 @@
//
// xattr_test.c
// copyfile_test
//
#include <unistd.h>
#include <removefile.h>
#include <sys/fcntl.h>
#include <sys/stat.h>
#include <sys/xattr.h>
#include "xattr_test.h"
#include "test_utils.h"
#define SRC_FILE_NAME "src_file"
#define DST_FILE_NAME "dst_file"
#define SMALL_XATTR_NAME "small_xattr"
#define SMALL_XATTR_DATA "drell"
#define BIG_XATTR_NAME "big_xattr"
#define BIG_XATTR_SIZE (20 * 1024 * 1024) // 20MiB
#define DEFAULT_CHAR_MOD 256
static bool copy_and_verify_xattr_contents(const char *src_file, const char *dst_file, int src_file_fd, int dst_file_fd) {
assert_no_err(copyfile(src_file, dst_file, NULL, COPYFILE_XATTR));
return verify_fd_xattr_contents(src_file_fd, dst_file_fd);
}
bool do_xattr_test(const char *apfs_test_directory, __unused size_t block_size) {
char test_dir[BSIZE_B] = {0};
char src_file[BSIZE_B] = {0}, dst_file[BSIZE_B] = {0};
char *big_xattr_data = NULL, buf[4096] = {0};
int test_folder_id;
int src_file_fd, dst_file_fd;
bool success = true;
printf("START [xattr]\n");
// Get ready for the test.
test_folder_id = rand() % DEFAULT_NAME_MOD;
create_test_file_name(apfs_test_directory, "xattr", test_folder_id, test_dir);
assert_no_err(mkdir(test_dir, DEFAULT_MKDIR_PERM));
// Create path names.
assert_with_errno(snprintf(src_file, BSIZE_B, "%s/" SRC_FILE_NAME, test_dir) > 0);
assert_with_errno(snprintf(dst_file, BSIZE_B, "%s/" DST_FILE_NAME, test_dir) > 0);
// Create our files.
src_file_fd = open(src_file, DEFAULT_OPEN_FLAGS, DEFAULT_OPEN_PERM);
assert_with_errno(src_file_fd >= 0);
dst_file_fd = open(dst_file, DEFAULT_OPEN_FLAGS, DEFAULT_OPEN_PERM);
assert_with_errno(dst_file_fd >= 0);
// Sanity check - empty copy
success = success && copy_and_verify_xattr_contents(src_file, dst_file, src_file_fd, dst_file_fd);
// Write a small xattr to the source file.
assert_no_err(fsetxattr(src_file_fd, SMALL_XATTR_NAME, SMALL_XATTR_DATA, sizeof(SMALL_XATTR_DATA), 0, XATTR_CREATE));
success = success && copy_and_verify_xattr_contents(src_file, dst_file, src_file_fd, dst_file_fd);
// Create big xattr data
assert_with_errno(big_xattr_data = malloc(BIG_XATTR_SIZE));
for (int i = 0; i * sizeof(buf) < BIG_XATTR_SIZE; i++) {
memset(buf, rand() % DEFAULT_CHAR_MOD, sizeof(buf));
memcpy(big_xattr_data + (i * sizeof(buf)), buf, sizeof(buf));
}
// Write a big xattr to the source file.
assert_no_err(fsetxattr(src_file_fd, BIG_XATTR_NAME, big_xattr_data, BIG_XATTR_SIZE, 0, XATTR_CREATE));
success = success && copy_and_verify_xattr_contents(src_file, dst_file, src_file_fd, dst_file_fd);
if (success) {
printf("PASS [xattr]\n");
} else {
printf("FAIL [xattr]\n");
}
free(big_xattr_data);
(void)removefile(test_dir, NULL, REMOVEFILE_RECURSIVE);
return success ? EXIT_SUCCESS : EXIT_FAILURE;
}

View File

@ -0,0 +1,14 @@
//
// xattr_test.h
// copyfile_test
//
#ifndef xattr_test_h
#define xattr_test_h
#include <stdbool.h>
bool do_xattr_test(const char *apfs_test_directory, size_t block_size);
#endif /* xattr_test_h */

View File

@ -7,6 +7,7 @@
.Sh NAME
.Nm xattr_preserve_for_intent , xattr_name_with_flags , xattr_name_without_flags ,
.Nm xattr_flags_from_name , xattr_intent_with_flags
.Nd obtain properties related to extended attributes, for use in copying
.Sh LIBRARY
.Lb libc
.Sh SYNOPSIS
@ -23,7 +24,8 @@
.Fn xattr_intent_with_flags "xattr_operation_intent_t" "xattr_flags_t"
.Sh DESCRIPTION
These functions are used in conjunction with copying extended attributes from
one file to another. Various types of copying (an "intent") check flags to
one file to another.
Various types of copying (an "intent") check flags to
determine which is allowed or not.
.Pp
The
@ -32,7 +34,8 @@ function returns an extended attribute name with the appropriate flags encoded
as a string; the
.Fn xattr_name_without_flags
undoes this, giving the name of the extended attribute without the flags
encoding. The slight inverse of that is
encoding.
The slight inverse of that is
.Fn xattr_flags_from_name ,
which will return the flags encoded in a name.
.Pp
@ -62,30 +65,30 @@ named extended attribute should be preserved during a copy for
the given intent.
.Sh INTENT
The type
.Dt xattr_operation_intent_t
is an integral type, which is used to indicate what the intent for the operation
is. The following intent values are defined:
.Vt xattr_operation_intent_t
is an integral type, which is used to indicate what the intent for the operation is.
The following intent values are defined:
.Bl -tag -width XATTR_OPERATION_INTENT_SHARE
.It Dv XATTR_OPERATION_INTENT_COPY
Indicates that the intent is to simply copy from the source to the destination.
E.g., with cp. Most extended attributes should generally be preserved in this
case.
E.g., with cp.
Most extended attributes should generally be preserved in this case.
.It Dv XATTR_OPERATION_INTENT_SAVE
Indicates that intent is to perform a save (perhaps as in a "safe save").
This differs from a copy in that the content may be changing; the destination
may be over-writing or replacing the source, and some extended attributes should
not be preserved during this process.
.It Dv XATTR_OPERATION_INTENT_SHARE
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.
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 Drive.
.El
.Sh FLAGS
Various flags are defined by the type
.Dt xattr_flags_t ;
the currently-defined values for this are
.Vt xattr_flags_t ;
the currently-defined values for this are:
.Bl -tag -width XATTR_FLAG_CONTENT_DEPENDENT
.It Dv XATTR_FLAG_NO_EXPORT
This indicates that the extended attribute should not be exported, or shared.
@ -94,15 +97,16 @@ This is used with
.It Dv XATTR_FLAG_CONTENT_DEPENDENT
This indicates that the extended attribute is tied to the contents of the
file (or vice versa), such that it should be re-created when the contents
are changed. A checksum, for example, should not be copied, and would thus
be marked with this flag.
are changed.
A checksum, for example, should not be copied, and would thus be marked with this flag.
.It Dv XATTR_FLAG_NEVER_PRESERVE
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 Drive. Sync services 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