From fca18fb211df8e354cd3859b9b8aab0587542115 Mon Sep 17 00:00:00 2001 From: Lubos Dolezel Date: Tue, 3 Nov 2015 21:30:04 +0100 Subject: [PATCH] Added libsystem_copyfile --- src/CMakeLists.txt | 1 + src/copyfile/APPLE_LICENSE | 335 +++ src/copyfile/CMakeLists.txt | 29 + src/copyfile/Kernel | 1 + src/copyfile/copyfile.3 | 588 ++++ src/copyfile/copyfile.c | 4156 ++++++++++++++++++++++++++ src/copyfile/copyfile.h | 123 + src/copyfile/copyfile_private.h | 38 + src/copyfile/xattr_flags.c | 340 +++ src/copyfile/xattr_flags.h | 149 + src/copyfile/xattr_name_with_flags.3 | 123 + src/copyfile/xattr_properties.h | 128 + src/libsystem/CMakeLists.txt | 2 +- 13 files changed, 6012 insertions(+), 1 deletion(-) create mode 100644 src/copyfile/APPLE_LICENSE create mode 100644 src/copyfile/CMakeLists.txt create mode 120000 src/copyfile/Kernel create mode 100644 src/copyfile/copyfile.3 create mode 100644 src/copyfile/copyfile.c create mode 100644 src/copyfile/copyfile.h create mode 100644 src/copyfile/copyfile_private.h create mode 100644 src/copyfile/xattr_flags.c create mode 100644 src/copyfile/xattr_flags.h create mode 100644 src/copyfile/xattr_name_with_flags.3 create mode 100644 src/copyfile/xattr_properties.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 90f5d8900..f6753a180 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -57,6 +57,7 @@ include_directories("${CMAKE_CURRENT_SOURCE_DIR}/duct/include") add_subdirectory(libc) add_subdirectory(libm) add_subdirectory(libgcc) +add_subdirectory(copyfile) add_subdirectory(libinfo) add_subdirectory(libmalloc) add_subdirectory(libunwind) diff --git a/src/copyfile/APPLE_LICENSE b/src/copyfile/APPLE_LICENSE new file mode 100644 index 000000000..ab463a237 --- /dev/null +++ b/src/copyfile/APPLE_LICENSE @@ -0,0 +1,335 @@ +APPLE PUBLIC SOURCE LICENSE +Version 2.0 - August 6, 2003 + +Please read this License carefully before downloading this software. By +downloading or using this software, you are agreeing to be bound by the terms +of this License. If you do not or cannot agree to the terms of this License, +please do not download or use the software. + +Apple Note: In January 2007, Apple changed its corporate name from "Apple +Computer, Inc." to "Apple Inc." This change has been reflected below and +copyright years updated, but no other changes have been made to the APSL 2.0. + +1. General; Definitions. This License applies to any program or other +work which Apple Inc. ("Apple") makes publicly available and which contains a +notice placed by Apple identifying such program or work as "Original Code" and +stating that it is subject to the terms of this Apple Public Source License +version 2.0 ("License"). As used in this License: + +1.1 "Applicable Patent Rights" mean: (a) in the case where Apple is the +grantor of rights, (i) claims of patents that are now or hereafter acquired, +owned by or assigned to Apple and (ii) that cover subject matter contained in +the Original Code, but only to the extent necessary to use, reproduce and/or +distribute the Original Code without infringement; and (b) in the case where +You are the grantor of rights, (i) claims of patents that are now or hereafter +acquired, owned by or assigned to You and (ii) that cover subject matter in +Your Modifications, taken alone or in combination with Original Code. + +1.2 "Contributor" means any person or entity that creates or contributes to +the creation of Modifications. + +1.3 "Covered Code" means the Original Code, Modifications, the combination +of Original Code and any Modifications, and/or any respective portions thereof. + +1.4 "Externally Deploy" means: (a) to sublicense, distribute or otherwise +make Covered Code available, directly or indirectly, to anyone other than You; +and/or (b) to use Covered Code, alone or as part of a Larger Work, in any way +to provide a service, including but not limited to delivery of content, through +electronic communication with a client other than You. + +1.5 "Larger Work" means a work which combines Covered Code or portions +thereof with code not governed by the terms of this License. + +1.6 "Modifications" mean any addition to, deletion from, and/or change to, +the substance and/or structure of the Original Code, any previous +Modifications, the combination of Original Code and any previous Modifications, +and/or any respective portions thereof. When code is released as a series of +files, a Modification is: (a) any addition to or deletion from the contents of +a file containing Covered Code; and/or (b) any new file or other representation +of computer program statements that contains any part of Covered Code. + +1.7 "Original Code" means (a) the Source Code of a program or other work as +originally made available by Apple under this License, including the Source +Code of any updates or upgrades to such programs or works made available by +Apple under this License, and that has been expressly identified by Apple as +such in the header file(s) of such work; and (b) the object code compiled from +such Source Code and originally made available by Apple under this License + +1.8 "Source Code" means the human readable form of a program or other work +that is suitable for making modifications to it, including all modules it +contains, plus any associated interface definition files, scripts used to +control compilation and installation of an executable (object code). + +1.9 "You" or "Your" means an individual or a legal entity exercising rights +under this License. For legal entities, "You" or "Your" includes any entity +which controls, is controlled by, or is under common control with, You, where +"control" means (a) the power, direct or indirect, to cause the direction or +management of such entity, whether by contract or otherwise, or (b) ownership +of fifty percent (50%) or more of the outstanding shares or beneficial +ownership of such entity. + +2. Permitted Uses; Conditions & Restrictions. Subject to the terms and +conditions of this License, Apple hereby grants You, effective on the date You +accept this License and download the Original Code, a world-wide, royalty-free, +non-exclusive license, to the extent of Apple's Applicable Patent Rights and +copyrights covering the Original Code, to do the following: + +2.1 Unmodified Code. You may use, reproduce, display, perform, internally +distribute within Your organization, and Externally Deploy verbatim, unmodified +copies of the Original Code, for commercial or non-commercial purposes, +provided that in each instance: + +(a) You must retain and reproduce in all copies of Original Code the +copyright and other proprietary notices and disclaimers of Apple as they appear +in the Original Code, and keep intact all notices in the Original Code that +refer to this License; and + +(b) You must include a copy of this License with every copy of Source Code +of Covered Code and documentation You distribute or Externally Deploy, and You +may not offer or impose any terms on such Source Code that alter or restrict +this License or the recipients' rights hereunder, except as permitted under +Section 6. + +2.2 Modified Code. You may modify Covered Code and use, reproduce, +display, perform, internally distribute within Your organization, and +Externally Deploy Your Modifications and Covered Code, for commercial or +non-commercial purposes, provided that in each instance You also meet all of +these conditions: + +(a) You must satisfy all the conditions of Section 2.1 with respect to the +Source Code of the Covered Code; + +(b) You must duplicate, to the extent it does not already exist, the notice +in Exhibit A in each file of the Source Code of all Your Modifications, and +cause the modified files to carry prominent notices stating that You changed +the files and the date of any change; and + +(c) If You Externally Deploy Your Modifications, You must make Source Code +of all Your Externally Deployed Modifications either available to those to whom +You have Externally Deployed Your Modifications, or publicly available. Source +Code of Your Externally Deployed Modifications must be released under the terms +set forth in this License, including the license grants set forth in Section 3 +below, for as long as you Externally Deploy the Covered Code or twelve (12) +months from the date of initial External Deployment, whichever is longer. You +should preferably distribute the Source Code of Your Externally Deployed +Modifications electronically (e.g. download from a web site). + +2.3 Distribution of Executable Versions. In addition, if You Externally +Deploy Covered Code (Original Code and/or Modifications) in object code, +executable form only, You must include a prominent notice, in the code itself +as well as in related documentation, stating that Source Code of the Covered +Code is available under the terms of this License with information on how and +where to obtain such Source Code. + +2.4 Third Party Rights. You expressly acknowledge and agree that although +Apple and each Contributor grants the licenses to their respective portions of +the Covered Code set forth herein, no assurances are provided by Apple or any +Contributor that the Covered Code does not infringe the patent or other +intellectual property rights of any other entity. Apple and each Contributor +disclaim any liability to You for claims brought by any other entity based on +infringement of intellectual property rights or otherwise. As a condition to +exercising the rights and licenses granted hereunder, You hereby assume sole +responsibility to secure any other intellectual property rights needed, if any. +For example, if a third party patent license is required to allow You to +distribute the Covered Code, it is Your responsibility to acquire that license +before distributing the Covered Code. + +3. Your Grants. In consideration of, and as a condition to, the licenses +granted to You under this License, You hereby grant to any person or entity +receiving or distributing Covered Code under this License a non-exclusive, +royalty-free, perpetual, irrevocable license, under Your Applicable Patent +Rights and other intellectual property rights (other than patent) owned or +controlled by You, to use, reproduce, display, perform, modify, sublicense, +distribute and Externally Deploy Your Modifications of the same scope and +extent as Apple's licenses under Sections 2.1 and 2.2 above. + +4. Larger Works. You may create a Larger Work by combining Covered Code +with other code not governed by the terms of this License and distribute the +Larger Work as a single product. In each such instance, You must make sure the +requirements of this License are fulfilled for the Covered Code or any portion +thereof. + +5. Limitations on Patent License. Except as expressly stated in Section +2, no other patent rights, express or implied, are granted by Apple herein. +Modifications and/or Larger Works may require additional patent licenses from +Apple which Apple may grant in its sole discretion. + +6. Additional Terms. You may choose to offer, and to charge a fee for, +warranty, support, indemnity or liability obligations and/or other rights +consistent with the scope of the license granted herein ("Additional Terms") to +one or more recipients of Covered Code. However, You may do so only on Your own +behalf and as Your sole responsibility, and not on behalf of Apple or any +Contributor. You must obtain the recipient's agreement that any such Additional +Terms are offered by You alone, and You hereby agree to indemnify, defend and +hold Apple and every Contributor harmless for any liability incurred by or +claims asserted against Apple or such Contributor by reason of any such +Additional Terms. + +7. Versions of the License. Apple may publish revised and/or new versions +of this License from time to time. Each version will be given a distinguishing +version number. Once Original Code has been published under a particular +version of this License, You may continue to use it under the terms of that +version. You may also choose to use such Original Code under the terms of any +subsequent version of this License published by Apple. No one other than Apple +has the right to modify the terms applicable to Covered Code created under this +License. + +8. NO WARRANTY OR SUPPORT. The Covered Code may contain in whole or in +part pre-release, untested, or not fully tested works. The Covered Code may +contain errors that could cause failures or loss of data, and may be incomplete +or contain inaccuracies. You expressly acknowledge and agree that use of the +Covered Code, or any portion thereof, is at Your sole and entire risk. THE +COVERED CODE IS PROVIDED "AS IS" AND WITHOUT WARRANTY, UPGRADES OR SUPPORT OF +ANY KIND AND APPLE AND APPLE'S LICENSOR(S) (COLLECTIVELY REFERRED TO AS "APPLE" +FOR THE PURPOSES OF SECTIONS 8 AND 9) AND ALL CONTRIBUTORS EXPRESSLY DISCLAIM +ALL WARRANTIES AND/OR CONDITIONS, EXPRESS OR IMPLIED, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES AND/OR CONDITIONS OF MERCHANTABILITY, OF +SATISFACTORY QUALITY, OF FITNESS FOR A PARTICULAR PURPOSE, OF ACCURACY, OF +QUIET ENJOYMENT, AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. APPLE AND EACH +CONTRIBUTOR DOES NOT WARRANT AGAINST INTERFERENCE WITH YOUR ENJOYMENT OF THE +COVERED CODE, THAT THE FUNCTIONS CONTAINED IN THE COVERED CODE WILL MEET YOUR +REQUIREMENTS, THAT THE OPERATION OF THE COVERED CODE WILL BE UNINTERRUPTED OR +ERROR-FREE, OR THAT DEFECTS IN THE COVERED CODE WILL BE CORRECTED. NO ORAL OR +WRITTEN INFORMATION OR ADVICE GIVEN BY APPLE, AN APPLE AUTHORIZED +REPRESENTATIVE OR ANY CONTRIBUTOR SHALL CREATE A WARRANTY. You acknowledge +that the Covered Code is not intended for use in the operation of nuclear +facilities, aircraft navigation, communication systems, or air traffic control +machines in which case the failure of the Covered Code could lead to death, +personal injury, or severe physical or environmental damage. + +9. LIMITATION OF LIABILITY. TO THE EXTENT NOT PROHIBITED BY LAW, IN NO +EVENT SHALL APPLE OR ANY CONTRIBUTOR BE LIABLE FOR ANY INCIDENTAL, SPECIAL, +INDIRECT OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR RELATING TO THIS LICENSE OR +YOUR USE OR INABILITY TO USE THE COVERED CODE, OR ANY PORTION THEREOF, WHETHER +UNDER A THEORY OF CONTRACT, WARRANTY, TORT (INCLUDING NEGLIGENCE), PRODUCTS +LIABILITY OR OTHERWISE, EVEN IF APPLE OR SUCH CONTRIBUTOR HAS BEEN ADVISED OF +THE POSSIBILITY OF SUCH DAMAGES AND NOTWITHSTANDING THE FAILURE OF ESSENTIAL +PURPOSE OF ANY REMEDY. SOME JURISDICTIONS DO NOT ALLOW THE LIMITATION OF +LIABILITY OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THIS LIMITATION MAY NOT +APPLY TO YOU. In no event shall Apple's total liability to You for all damages +(other than as may be required by applicable law) under this License exceed the +amount of fifty dollars ($50.00). + +10. Trademarks. This License does not grant any rights to use the +trademarks or trade names "Apple", "Mac", "Mac OS", "QuickTime", "QuickTime +Streaming Server" or any other trademarks, service marks, logos or trade names +belonging to Apple (collectively "Apple Marks") or to any trademark, service +mark, logo or trade name belonging to any Contributor. You agree not to use +any Apple Marks in or as part of the name of products derived from the Original +Code or to endorse or promote products derived from the Original Code other +than as expressly permitted by and in strict compliance at all times with +Apple's third party trademark usage guidelines which are posted at +http://www.apple.com/legal/guidelinesfor3rdparties.html. + +11. Ownership. Subject to the licenses granted under this License, each +Contributor retains all rights, title and interest in and to any Modifications +made by such Contributor. Apple retains all rights, title and interest in and +to the Original Code and any Modifications made by or on behalf of Apple +("Apple Modifications"), and such Apple Modifications will not be automatically +subject to this License. Apple may, at its sole discretion, choose to license +such Apple Modifications under this License, or on different terms from those +contained in this License or may choose not to license them at all. + +12. Termination. + +12.1 Termination. This License and the rights granted hereunder will +terminate: + +(a) automatically without notice from Apple if You fail to comply with any +term(s) of this License and fail to cure such breach within 30 days of becoming +aware of such breach; +(b) immediately in the event of the circumstances described in Section +13.5(b); or +(c) automatically without notice from Apple if You, at any time during the +term of this License, commence an action for patent infringement against Apple; +provided that Apple did not first commence an action for patent infringement +against You in that instance. + +12.2 Effect of Termination. Upon termination, You agree to immediately stop +any further use, reproduction, modification, sublicensing and distribution of +the Covered Code. All sublicenses to the Covered Code which have been properly +granted prior to termination shall survive any termination of this License. +Provisions which, by their nature, should remain in effect beyond the +termination of this License shall survive, including but not limited to +Sections 3, 5, 8, 9, 10, 11, 12.2 and 13. No party will be liable to any other +for compensation, indemnity or damages of any sort solely as a result of +terminating this License in accordance with its terms, and termination of this +License will be without prejudice to any other right or remedy of any party. + +13. Miscellaneous. + +13.1 Government End Users. The Covered Code is a "commercial item" as +defined in FAR 2.101. Government software and technical data rights in the +Covered Code include only those rights customarily provided to the public as +defined in this License. This customary commercial license in technical data +and software is provided in accordance with FAR 12.211 (Technical Data) and +12.212 (Computer Software) and, for Department of Defense purchases, DFAR +252.227-7015 (Technical Data -- Commercial Items) and 227.7202-3 (Rights in +Commercial Computer Software or Computer Software Documentation). Accordingly, +all U.S. Government End Users acquire Covered Code with only those rights set +forth herein. + +13.2 Relationship of Parties. This License will not be construed as +creating an agency, partnership, joint venture or any other form of legal +association between or among You, Apple or any Contributor, and You will not +represent to the contrary, whether expressly, by implication, appearance or +otherwise. + +13.3 Independent Development. Nothing in this License will impair Apple's +right to acquire, license, develop, have others develop for it, market and/or +distribute technology or products that perform the same or similar functions +as, or otherwise compete with, Modifications, Larger Works, technology or +products that You may develop, produce, market or distribute. + +13.4 Waiver; Construction. Failure by Apple or any Contributor to enforce +any provision of this License will not be deemed a waiver of future enforcement +of that or any other provision. Any law or regulation which provides that the +language of a contract shall be construed against the drafter will not apply to +this License. + +13.5 Severability. (a) If for any reason a court of competent jurisdiction +finds any provision of this License, or portion thereof, to be unenforceable, +that provision of the License will be enforced to the maximum extent +permissible so as to effect the economic benefits and intent of the parties, +and the remainder of this License will continue in full force and effect. (b) +Notwithstanding the foregoing, if applicable law prohibits or restricts You +from fully and/or specifically complying with Sections 2 and/or 3 or prevents +the enforceability of either of those Sections, this License will immediately +terminate and You must immediately discontinue any use of the Covered Code and +destroy all copies of it that are in your possession or control. + +13.6 Dispute Resolution. Any litigation or other dispute resolution between +You and Apple relating to this License shall take place in the Northern +District of California, and You and Apple hereby consent to the personal +jurisdiction of, and venue in, the state and federal courts within that +District with respect to this License. The application of the United Nations +Convention on Contracts for the International Sale of Goods is expressly +excluded. + +13.7 Entire Agreement; Governing Law. This License constitutes the entire +agreement between the parties with respect to the subject matter hereof. This +License shall be governed by the laws of the United States and the State of +California, except that body of California law concerning conflicts of law. + +Where You are located in the province of Quebec, Canada, the following clause +applies: The parties hereby confirm that they have requested that this License +and all related documents be drafted in English. Les parties ont exigé que le +présent contrat et tous les documents connexes soient rédigés en anglais. + +EXHIBIT A. + +"Portions Copyright (c) 1999-2007 Apple Inc. All Rights Reserved. + +This file contains Original Code and/or Modifications of Original Code as +defined in and that are subject to the Apple Public Source License Version 2.0 +(the 'License'). You may not use this file except in compliance with the +License. Please obtain a copy of the License at +http://www.opensource.apple.com/apsl/ and read it before using this file. + +The Original Code and all software distributed under the License are +distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS +OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT +LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the License for the +specific language governing rights and limitations under the License." + diff --git a/src/copyfile/CMakeLists.txt b/src/copyfile/CMakeLists.txt new file mode 100644 index 000000000..901e76857 --- /dev/null +++ b/src/copyfile/CMakeLists.txt @@ -0,0 +1,29 @@ +project(copyfile) + +cmake_minimum_required(VERSION 2.4.0) + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -nostdinc -fblocks") +set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -nostdlib -Wl,--version-script=${DARLING_TOP_DIRECTORY}/darwin.map") + +include_directories(${DARLING_TOP_DIRECTORY}/platform-include) +include_directories(${DARLING_TOP_DIRECTORY}/src/libinfo/membership.subproj/) +include_directories(${DARLING_TOP_DIRECTORY}/src/external/libdispatch) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) + +add_definitions(-DTARGET_OS_MAC=1) +add_definitions(-DHAVE_STDINT_H=1) +add_definitions(-D__APPLE__ -D__DYNAMIC__) + +set(copyfile_sources + copyfile.c + # xattr_flags.c # Doesn't build yet (XPC) +) + +SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib${SUFFIX}/darling") +SET(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) +SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) + +add_library(system_copyfile SHARED ${copyfile_sources}) +target_link_libraries(system_copyfile system_kernel system_c) + +install(TARGETS system_copyfile DESTINATION lib${SUFFIX}/darling) diff --git a/src/copyfile/Kernel b/src/copyfile/Kernel new file mode 120000 index 000000000..15e9cd465 --- /dev/null +++ b/src/copyfile/Kernel @@ -0,0 +1 @@ +../../kernel-include/ \ No newline at end of file diff --git a/src/copyfile/copyfile.3 b/src/copyfile/copyfile.3 new file mode 100644 index 000000000..e7e94e3c2 --- /dev/null +++ b/src/copyfile/copyfile.3 @@ -0,0 +1,588 @@ +.\" +.\" Copyright (c) 2002 Apple Computer, Inc. All rights reserved. +.\" +.Dd April 27, 2006 +.Dt COPYFILE 3 +.Os +.Sh NAME +.Nm copyfile , fcopyfile , +.Nm copyfile_state_alloc , copyfile_state_free , +.Nm copyfile_state_get , copyfile_state_set +.Nd copy a file +.Sh LIBRARY +.Lb libc +.Sh SYNOPSIS +.In copyfile.h +.Ft int +.Fn copyfile "const char *from" "const char *to" "copyfile_state_t state" "copyfile_flags_t flags" +.Ft int +.Fn fcopyfile "int from" "int to" "copyfile_state_t state" "copyfile_flags_t flags" +.Ft copyfile_state_t +.Fn copyfile_state_alloc "void" +.Ft int +.Fn copyfile_state_free "copyfile_state_t state" +.Ft int +.Fn copyfile_state_get "copyfile_state_t state" "uint32_t flag" "void * dst" +.Ft int +.Fn copyfile_state_set "copyfile_state_t state" "uint32_t flag" "const void * src" +.Ft typedef int +.Fn (*copyfile_callback_t) "int what" "int stage" "copyfile_state_t state" "const char * src" "const char * dst" "void * ctx" +.Sh DESCRIPTION +These functions are used to copy a file's data and/or metadata. (Metadata +consists of permissions, extended attributes, access control lists, and so +forth.) +.Pp +The +.Fn copyfile_state_alloc +function initializes a +.Vt copyfile_state_t +object (which is an opaque data type). +This object can be passed to +.Fn copyfile +and +.Fn fcopyfile ; +.Fn copyfile_state_get +and +.Fn copyfile_state_set +can be used to manipulate the state (see below). +The +.Fn copyfile_state_free +function is used to deallocate the object and its contents. +.Pp +The +.Fn copyfile +function can copy the named +.Va from +file to the named +.Va to +file; the +.Fn fcopyfile +function does the same, but using the file descriptors of already-opened +files. +If the +.Va state +parameter is the return value from +.Fn copyfile_state_alloc , +then +.Fn copyfile +and +.Fn fcopyfile +will use the information from the state object; if it is +.Dv NULL , +then both functions will work normally, but less control will be available to the caller. +The +.Va flags +parameter controls which contents are copied: +.Bl -tag -width COPYFILE_XATTR +.It Dv COPYFILE_ACL +Copy the source file's access control lists. +.It Dv COPYFILE_STAT +Copy the source file's POSIX information (mode, modification time, etc.). +.It Dv COPYFILE_XATTR +Copy the source file's extended attributes. +.It Dv COPYFILE_DATA +Copy the source file's data. +.El +.Pp +These values may be or'd together; several convenience macros are provided: +.Bl -tag -width COPYFILE_SECURITY +.It Dv COPYFILE_SECURITY +Copy the source file's POSIX and ACL information; equivalent to +.Dv (COPYFILE_STAT|COPYFILE_ACL) . +.It Dv COPYFILE_METADATA +Copy the metadata; equivalent to +.Dv (COPYFILE_SECURITY|COPYFILE_XATTR) . +.It Dv COPYFILE_ALL +Copy the entire file; equivalent to +.Dv (COPYFILE_METADATA|COPYFILE_DATA) . +.El +.Pp +The +.Fn copyfile +and +.Fn fcopyfile +functions can also have their behavior modified by the following flags: +.Bl -tag -width COPYFILE_NOFOLLOW_SRC +.It Dv COPYFILE_RECURSIVE +Causes +.Fn copyfile +to recursively copy a hierarchy. +This flag is not used by +.Fn fcopyfile ; +see below for more information. +.It Dv COPYFILE_CHECK +Return a bitmask (corresponding to the +.Va flags +argument) indicating which contents would be copied; no data are actually +copied. (E.g., if +.Va flags +was set to +.Dv COPYFILE_CHECK|COPYFILE_METADATA , +and the +.Va from +file had extended attributes but no ACLs, the return value would be +.Dv COPYFILE_XATTR .) +.It Dv COPYFILE_PACK +Serialize the +.Va from +file. The +.Va to +file is an AppleDouble-format file. +.It Dv COPYFILE_UNPACK +Unserialize the +.Va from +file. The +.Va from +file is an AppleDouble-format file; the +.Va to +file will have the extended attributes, ACLs, resource fork, and +FinderInfo data from the +.Va to +file, regardless of the +.Va flags +argument passed in. +.It Dv COPYFILE_EXCL +Fail if the +.Va to +file already exists. (This is only applicable for the +.Fn copyfile +function.) +.It Dv COPYFILE_NOFOLLOW_SRC +Do not follow the +.Va from +file, if it is a symbolic link. (This is only applicable for the +.Fn copyfile +function.) +.It Dv COPYFILE_NOFOLLOW_DST +Do not follow the +.Va to +file, if it is a symbolic link. (This is only applicable for the +.Fn copyfile +function.) +.It Dv COPYFILE_MOVE +Unlink (using +.Xr remove 3 ) +the +.Fa from +file. (This is only applicable for the +.Fn copyfile +function.) No error is returned if +.Xr remove 3 +fails. Note that +.Xr remove 3 +removes a symbolic link itself, not the +target of the link. +.It Dv COPYFILE_UNLINK +Unlink the +.Va to +file before starting. (This is only applicable for the +.Fn copyfile +function.) +.It Dv COPYFILE_NOFOLLOW +This is a convenience macro, equivalent to +.Dv (COPYFILE_NOFOLLOW_DST|COPYFILE_NOFOLLOW_SRC) . +.El +.Pp +The +.Fn copyfile_state_get +and +.Fn copyfile_state_set +functions can be used to manipulate the +.Ft copyfile_state_t +object returned by +.Fn copyfile_state_alloc . +In both functions, the +.Va dst +parameter's type depends on the +.Va flag +parameter that is passed in. +.Bl -tag -width COPYFILE_STATE_DST_FILENAME +.It Dv COPYFILE_STATE_SRC_FD +.It Dv COPYFILE_STATE_DST_FD +Get or set the file descriptor associated with the source (or destination) +file. +If this has not been initialized yet, the value will be -2. +The +.Va dst +(for +.Fn copyfile_state_get ) +and +.Va src +(for +.Fn copyfile_state_set ) +parameters are pointers to +.Vt int . +.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 +.Dv NULL . +For +.Fn copyfile_state_set , +the +.Va src +parameter is a pointer to a C string +(i.e., +.Vt char* ); +.Fn copyfile_state_set +makes a private copy of this string. +For +.Fn copyfile_state_get +function, the +.Va dst +parameter is a pointer to a pointer to a C string +(i.e., +.Vt char** ); +the returned value is a pointer to the +.Va state 's +copy, and must not be modified or released. +.It Dv COPYFILE_STATE_STATUS_CB +Get or set the callback status function (currently +only used for recursive copies; see below for details). +The +.Va src +parameter is a pointer to a function of type +.Vt copyfile_callback_t +(see above). +.It Dv COPYFILE_STATE_STATUS_CTX +Get or set the context parameter for the status +call-back function (see below for details). +The +.Va src +parameter is a +.Vt void\ * . +.It Dv COPYFILE_STATE_QUARANTINE +Get or set the quarantine information with the source file. +The +.Va src +parameter is a pointer to an opaque +object (type +.Vt void\ * +). +.It Dv COPYFILE_STATE_COPIED +Get the number of data bytes copied so far. +(Only valid for +.Fn copyfile_state_get ; +see below for more details about callbacks.) +The +.Va dst +parameter is a pointer to +.Vt off_t +(type +.Vt off_t\ * ). +.It Dv COPYFILE_STATE_XATTRNAME +Get the name of the extended attribute during a callback +for +.Dv COPYFILE_COPY_XATTR +(see below for details). This field cannot be set, +and may be +.Dv NULL . +.El +.Sh Recursive Copies +When given the +.Dv COPYFILE_RECURSIVE +flag, +.Fn copyfile +(but not +.Fn fcopyfile ) +will use the +.Xr fts 3 +functions to recursively descend into the source file-system object. +It then calls +.Fn copyfile +on each of the entries it finds that way. +If a call-back function is given (using +.Fn copyfile_state_set +and +.Dv COPYFILE_STATE_STATUS_CB ), +the call-back function will be called four times for each directory +object, and twice for all other objects. (Each directory will +be examined twice, once on entry -- before copying each of the +objects contained in the directory -- and once on exit -- after +copying each object contained in the directory, in order to perform +some final cleanup.) +.Pp +The call-back function will have one of the following values +as the first argument, indicating what is being copied: +.Bl -tag -width COPYFILE_RECURSE_DIR_CLEANUP +.It Dv COPYFILE_RECURSE_FILE +The object being copied is a file (or, rather, +something other than a directory). +.It Dv COPYFILE_RECURSE_DIR +The object being copied is a directory, and is being +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 +being copied will have any extra permissions that were added to +allow the copying will be removed. +.It Dv COPYFILE_RECURSE_ERROR +There was an error in processing an element of the source hierarchy; +this happens when +.Xr fts 3 +returns an error or unknown file type. +(Currently, the second argument to the call-back function will always +be +.Dv COPYFILE_ERR +in this case.) +.El +.Pp +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 +.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 +.Dv COPYFILE_RECURSE_ERROR , +then an error occurred while processing the source hierarchy; +otherwise, it will indicate what type of object was being copied, +and +.Dv errno +will be set to indicate the error. +.El +.Pp +The fourth and fifth +parameters are the source and destination paths that +are to be copied (or have been copied, or failed to copy, depending on +the second argument). +.Pp +The last argument to the call-back function will be the value +set by +.Dv COPYFILE_STATE_STATUS_CTX , +if any. +.Pp +The call-back function is required to return one of the following +values: +.Bl -tag -width COPYFILE_CONTINUE +.It Dv COPYFILE_CONTINUE +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 +.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 +objects created up to this point will remain. +.Fn copyfile +will return -1, but +.Dv errno +will be unmodified. +.El +.Pp +The call-back function must always return one of the values listed +above; if not, the results are undefined. +.Pp +The call-back function will be called twice for each object +(and an additional two times for directory cleanup); the first +call will have a +.Ar stage +parameter of +.Dv COPYFILE_START ; +the second time, that value will be either +.Dv COPYFILE_FINISH +or +.Dv COPYFILE_ERR +to indicate a successful completion, or an error during +processing. +In the event of an error, the +.Dv errno +value will be set appropriately. +.Pp +The +.Dv COPYFILE_PACK , +.Dv COPYFILE_UNPACK , +.Dv COPYFILE_MOVE , +and +.Dv COPYFILE_UNLINK +flags are not used during a recursive copy, and will result +in an error being returned. +.Sh Progress Callback +In addition to the recursive callbacks described above, +.Fn copyfile +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 +.Xr write 2 +call. The first argument to the callback function will be +.Dv COPYFILE_COPY_DATA . +The second argument will either be +.Dv COPYFILE_PROGRESS +(indicating that the write was successful), or +.Dv COPYFILE_ERR +(indicating that there was an error of some sort). +.Pp +The amount of data bytes copied so far can be retrieved using +.Fn copyfile_state_get , +with the +.Dv COPYFILE_STATE_COPIED +requestor (the argument type is a pointer to +.Vt off_t ). +.Pp +When copying extended attributes, the first argument to the +callback function will be +.Dv COPYFILE_COPY_XATTR . +The other arguments will be as described for +.Dv COPYFILE_COPY_DATA ; +the name of the extended attribute being copied may be +retrieved using +.Fn copyfile_state_get +and the parameter +.Dv COPYFILE_STATE_XATTRNAME . +When using +.Dv COPYFILE_PACK , +the callback may be called with +.Dv COPYFILE_START +for each of the extended attributes first, followed by +.Dv COPYFILE_PROGRESS +before getting and packing the data for each +individual attribute, and then +.Dv COPYFILE_FINISH +when finished with each individual attribute. +(That is, +.Dv COPYFILE_START +may be called for all of the extended attributes, before +the first callback with +.Dv COPYFILE_PROGRESS +is invoked.) Any attribute skipped by returning +.Dv COPYFILE_SKIP +from the +.Dv COPYFILE_START +callback will not be placed into the packed output file. +.Pp +The return value for the data callback must be one of +.Bl -tag -width COPYFILE_CONTINUE +.It Dv COPYFILE_CONTINUE +The copy will continue as expected. +(In the case of error, it will attempt to write the data again.) +.It Dv COPYFILE_SKIP +The data copy will be aborted, but without error. +.It Dv COPYFILE_QUIT +The data copy will be aborted; in the case of +.Dv COPYFILE_PROGRESS , +.Dv errno +will be set to +.Dv ECANCELED . +.El +.Pp +While the +.Va src +and +.Va dst +parameters will be passed in, they may be +.Dv NULL +in the case of +.Fn fcopyfile . +.Sh RETURN VALUES +Except when given the +.Dv COPYFILE_CHECK +flag, +.Fn copyfile +and +.Fn fcopyfile +return less than 0 on error, and 0 on success. +All of the other functions return 0 on success, and less than 0 +on error. +.Sh WARNING +Both +.Fn copyfile +and +.Fn fcopyfile +can copy symbolic links; there is a gap between when the source +link is examined and the actual copy is started, and this can +be a potential security risk, especially if the process has +elevated privileges. +.Pp +When performing a recursive copy, if the source hierarchy +changes while the copy is occurring, the results are undefined. +.Pp +.Fn fcopyfile +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 ERRORS +.Fn copyfile +and +.Fn fcopyfile +will fail if: +.Bl -tag -width Er +.It Bq Er EINVAL +An invalid flag was passed in with +.Dv COPYFILE_RECURSIVE . +.It Bq Er EINVAL +The +.Va from +or +.Va to +parameter to +.Fn copyfile +was a +.Dv NULL +pointer. +.It Bq Er EINVAL +The +.Va from +or +.Va to +parameter to +.Fn copyfile +was a negative number. +.It Bq Er ENOMEM +A memory allocation failed. +.It Bq Er ENOTSUP +The source file was not a directory, symbolic link, or regular file. +.It Bq Er ECANCELED +The copy was cancelled by callback. +.El +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 setxattr 2 , +.Xr acl 3 +.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. diff --git a/src/copyfile/copyfile.c b/src/copyfile/copyfile.c new file mode 100644 index 000000000..fe314fde5 --- /dev/null +++ b/src/copyfile/copyfile.c @@ -0,0 +1,4156 @@ +// Modified by Lubos Dolezel for Darling +/* + * Copyright (c) 2004-2010 Apple, Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef VOL_CAP_FMT_DECMPFS_COMPRESSION +# include +#endif + +#include +#if !TARGET_OS_IPHONE && !defined(DARLING) +#include + +#define XATTR_QUARANTINE_NAME qtn_xattr_name +#else /* TARGET_OS_IPHONE */ +#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; } +#define XATTR_QUARANTINE_NAME "figgledidiggledy" +#endif /* TARGET_OS_IPHONE */ + +#include "copyfile.h" +#include "copyfile_private.h" +#include "xattr_flags.h" + +enum cfInternalFlags { + cfDelayAce = 1 << 0, + cfMakeFileInvisible = 1 << 1, + cfSawDecmpEA = 1 << 2, +}; + +/* + * The state structure keeps track of + * the source filename, the destination filename, their + * associated file-descriptors, the stat infomration for the + * source file, the security information for the source file, + * the flags passed in for the copy, a pointer to place statistics + * (not currently implemented), debug flags, and a pointer to callbacks + * (not currently implemented). + */ +struct _copyfile_state +{ + char *src; + char *dst; + int src_fd; + int dst_fd; + struct stat sb; + filesec_t fsec; + copyfile_flags_t flags; + unsigned int internal_flags; + void *stats; + uint32_t debug; + copyfile_callback_t statuscb; + void *ctx; + qtn_file_t qinfo; /* Quarantine information -- probably NULL */ + filesec_t original_fsec; + filesec_t permissive_fsec; + off_t totalCopied; + int err; + char *xattr_name; + xattr_operation_intent_t copyIntent; +}; + +struct acl_entry { + u_int32_t ae_magic; +#define _ACL_ENTRY_MAGIC 0xac1ac101 + u_int32_t ae_tag; + guid_t ae_applicable; + u_int32_t ae_flags; + u_int32_t ae_perms; +}; + +#define PACE(ace) do { \ + struct acl_entry *__t = (struct acl_entry*)(ace); \ + fprintf(stderr, "%s(%d): " #ace " = { flags = %#x, perms = %#x }\n", __FUNCTION__, __LINE__, __t->ae_flags, __t->ae_perms); \ + } while (0) + +#define PACL(ace) \ + do { \ + ssize_t __l; char *__cp = acl_to_text(ace, &__l); \ + fprintf(stderr, "%s(%d): " #ace " = %s\n", __FUNCTION__, __LINE__, __cp ? __cp : "(null)"); \ + } while (0) + +static int +acl_compare_permset_np(acl_permset_t p1, acl_permset_t p2) +{ + struct pm { u_int32_t ap_perms; } *ps1, *ps2; + ps1 = (struct pm*) p1; + ps2 = (struct pm*) p2; + + return ((ps1->ap_perms == ps2->ap_perms) ? 1 : 0); +} + + +static int +doesdecmpfs(int fd) { +#ifdef DECMPFS_XATTR_NAME + int rv; + struct attrlist attrs; + char volroot[MAXPATHLEN + 1]; + struct statfs sfs; + struct { + uint32_t length; + vol_capabilities_attr_t volAttrs; + } volattrs; + + (void)fstatfs(fd, &sfs); + strlcpy(volroot, sfs.f_mntonname, sizeof(volroot)); + + memset(&attrs, 0, sizeof(attrs)); + attrs.bitmapcount = ATTR_BIT_MAP_COUNT; + attrs.volattr = ATTR_VOL_CAPABILITIES; + + rv = getattrlist(volroot, &attrs, &volattrs, sizeof(volattrs), 0); + + if (rv != -1 && + (volattrs.volAttrs.capabilities[VOL_CAPABILITIES_FORMAT] & VOL_CAP_FMT_DECMPFS_COMPRESSION) && + (volattrs.volAttrs.valid[VOL_CAPABILITIES_FORMAT] & VOL_CAP_FMT_DECMPFS_COMPRESSION)) { + return 1; + } +#endif + return 0; +} + + +static void +sort_xattrname_list(void *start, size_t length) +{ + char **ptrs = NULL; + int nel; + char *tmp; + int indx = 0; + + /* If it's not a proper C string at the end, don't do anything */ + if (((char*)start)[length] != 0) + return; + /* + * In order to sort the list of names, we need to + * make a list of pointers to strings. To do that, + * we need to run through the buffer, and find the + * beginnings of strings. + */ + nel = 10; // Most files don't have many EAs + ptrs = (char**)calloc(nel, sizeof(char*)); + + if (ptrs == NULL) + goto done; + +#ifdef DEBUG +{ + char *curPtr = start; + while (curPtr < (char*)start + length) { + printf("%s\n", curPtr); + curPtr += strlen(curPtr) + 1; + } +} +#endif + + tmp = ptrs[indx++] = (char*)start; + + while (tmp = memchr(tmp, 0, ((char*)start + length) - tmp)) { + if (indx == nel) { + nel += 10; + ptrs = realloc(ptrs, sizeof(char**) * nel); + if (ptrs == NULL) + goto done; + } + ptrs[indx++] = ++tmp; + } +#ifdef DEBUG + printf("Unsorted:\n"); + for (nel = 0; nel < indx-1; nel++) { + printf("\tEA %d = `%s'\n", nel, ptrs[nel]); + } +#endif + qsort_b(ptrs, indx-1, sizeof(char*), ^(const void *left, const void *right) { + int rv; + char *lstr = *(char**)left, *rstr = *(char**)right; + rv = strcmp(lstr, rstr); + return rv; + }); +#ifdef DEBUG + printf("Sorted:\n"); + for (nel = 0; nel < indx-1; nel++) { + printf("\tEA %d = `%s'\n", nel, ptrs[nel]); + } +#endif + /* + * Now that it's sorted, we need to make a copy, so we can + * move the strings around into the new order. Then we + * copy that on top of the old buffer, and we're done. + */ + char *copy = malloc(length); + if (copy) { + int i; + char *curPtr = copy; + + for (i = 0; i < indx-1; i++) { + size_t len = strlen(ptrs[i]); + memcpy(curPtr, ptrs[i], len+1); + curPtr += len+1; + } + memcpy(start, copy, length); + free(copy); + } + +done: + if (ptrs) + free(ptrs); + return; +} + +/* + * Internally, the process is broken into a series of + * private functions. + */ +static int copyfile_open (copyfile_state_t); +static int copyfile_close (copyfile_state_t); +static int copyfile_data (copyfile_state_t); +static int copyfile_stat (copyfile_state_t); +static int copyfile_security (copyfile_state_t); +static int copyfile_xattr (copyfile_state_t); +static int copyfile_pack (copyfile_state_t); +static int copyfile_unpack (copyfile_state_t); + +static copyfile_flags_t copyfile_check (copyfile_state_t); +static filesec_t copyfile_fix_perms(copyfile_state_t, filesec_t *); +static int copyfile_preamble(copyfile_state_t *s, copyfile_flags_t flags); +static int copyfile_internal(copyfile_state_t state, copyfile_flags_t flags); +static int copyfile_unset_posix_fsec(filesec_t); +static int copyfile_quarantine(copyfile_state_t); + +#define COPYFILE_DEBUG (1<<31) +#define COPYFILE_DEBUG_VAR "COPYFILE_DEBUG" + +#ifndef _COPYFILE_TEST +# define copyfile_warn(str, ...) syslog(LOG_WARNING, str ": %m", ## __VA_ARGS__) +# define copyfile_debug(d, str, ...) \ + do { \ + if (s && (d <= s->debug)) {\ + syslog(LOG_DEBUG, "%s:%d:%s() " str "\n", __FILE__, __LINE__ , __FUNCTION__, ## __VA_ARGS__); \ + } \ + } while (0) +#else +#define copyfile_warn(str, ...) \ + fprintf(stderr, "%s:%d:%s() " str ": %s\n", __FILE__, __LINE__ , __FUNCTION__, ## __VA_ARGS__, (errno) ? strerror(errno) : "") +# define copyfile_debug(d, str, ...) \ + do { \ + if (s && (d <= s->debug)) {\ + fprintf(stderr, "%s:%d:%s() " str "\n", __FILE__, __LINE__ , __FUNCTION__, ## __VA_ARGS__); \ + } \ + } while(0) +#endif + +#ifndef DARLING +static int copyfile_quarantine(copyfile_state_t s) +{ + int rv = 0; + if (s->qinfo == NULL) + { + int error; + s->qinfo = qtn_file_alloc(); + if (s->qinfo == NULL) + { + rv = -1; + goto done; + } + if ((error = qtn_file_init_with_fd(s->qinfo, s->src_fd)) != 0) + { + qtn_file_free(s->qinfo); + s->qinfo = NULL; + rv = -1; + goto done; + } + } +done: + return rv; +} +#else +static int copyfile_quarantine(copyfile_state_t s) { return 0; } +#endif + +static int +add_uberace(acl_t *acl) +{ + acl_entry_t entry; + acl_permset_t permset; + uuid_t qual; + + if (mbr_uid_to_uuid(getuid(), qual) != 0) + goto error_exit; + + /* + * First, we create an entry, and give it the special name + * of ACL_FIRST_ENTRY, thus guaranteeing it will be first. + * After that, we clear out all the permissions in it, and + * add three permissions: WRITE_DATA, WRITE_ATTRIBUTES, and + * WRITE_EXTATTRIBUTES. We put these into an ACE that allows + * the functionality, and put this into the ACL. + */ + if (acl_create_entry_np(acl, &entry, ACL_FIRST_ENTRY) == -1) + goto error_exit; + if (acl_get_permset(entry, &permset) == -1) { + copyfile_warn("acl_get_permset"); + goto error_exit; + } + if (acl_clear_perms(permset) == -1) { + copyfile_warn("acl_clear_permset"); + goto error_exit; + } + if (acl_add_perm(permset, ACL_WRITE_DATA) == -1) { + copyfile_warn("add ACL_WRITE_DATA"); + goto error_exit; + } + if (acl_add_perm(permset, ACL_WRITE_ATTRIBUTES) == -1) { + copyfile_warn("add ACL_WRITE_ATTRIBUTES"); + goto error_exit; + } + if (acl_add_perm(permset, ACL_WRITE_EXTATTRIBUTES) == -1) { + copyfile_warn("add ACL_WRITE_EXTATTRIBUTES"); + goto error_exit; + } + if (acl_add_perm(permset, ACL_APPEND_DATA) == -1) { + copyfile_warn("add ACL_APPEND_DATA"); + goto error_exit; + } + if (acl_add_perm(permset, ACL_WRITE_SECURITY) == -1) { + copyfile_warn("add ACL_WRITE_SECURITY"); + goto error_exit; + } + if (acl_set_tag_type(entry, ACL_EXTENDED_ALLOW) == -1) { + copyfile_warn("set ACL_EXTENDED_ALLOW"); + goto error_exit; + } + + if(acl_set_permset(entry, permset) == -1) { + copyfile_warn("acl_set_permset"); + goto error_exit; + } + if(acl_set_qualifier(entry, qual) == -1) { + copyfile_warn("acl_set_qualifier"); + goto error_exit; + } + + return 0; +error_exit: + return -1; +} + +static int +is_uberace(acl_entry_t ace) +{ + int retval = 0; + acl_permset_t perms, tperms; + acl_t tacl; + acl_entry_t tentry; + acl_tag_t tag; + guid_t *qual = NULL; + uuid_t myuuid; + + // Who am I, and who is the ACE for? + mbr_uid_to_uuid(geteuid(), myuuid); + qual = (guid_t*)acl_get_qualifier(ace); + + // Need to create a temporary acl, so I can get the uberace template. + tacl = acl_init(1); + if (tacl == NULL) { + goto done; + } + add_uberace(&tacl); + if (acl_get_entry(tacl, ACL_FIRST_ENTRY, &tentry) != 0) { + goto done; + } + acl_get_permset(tentry, &tperms); + + // Now I need to get + acl_get_tag_type(ace, &tag); + acl_get_permset(ace, &perms); + + if (tag == ACL_EXTENDED_ALLOW && + (memcmp(qual, myuuid, sizeof(myuuid)) == 0) && + acl_compare_permset_np(tperms, perms)) + retval = 1; + +done: + + if (qual) + acl_free(qual); + + if (tacl) + acl_free(tacl); + + return retval; +} + +static void +remove_uberace(int fd, struct stat *sbuf) +{ + filesec_t fsec = NULL; + acl_t acl = NULL; + acl_entry_t entry; + struct stat sb; + + fsec = filesec_init(); + if (fsec == NULL) { + goto noacl; + } + + if (fstatx_np(fd, &sb, fsec) != 0) { + if (errno == ENOTSUP) + goto noacl; + goto done; + } + + if (filesec_get_property(fsec, FILESEC_ACL, &acl) != 0) { + goto done; + } + + if (acl_get_entry(acl, ACL_FIRST_ENTRY, &entry) == 0) { + if (is_uberace(entry)) + { + mode_t m = sbuf->st_mode & ~S_IFMT; + + if (acl_delete_entry(acl, entry) != 0 || + filesec_set_property(fsec, FILESEC_ACL, &acl) != 0 || + filesec_set_property(fsec, FILESEC_MODE, &m) != 0 || + fchmodx_np(fd, fsec) != 0) + goto noacl; + } + } + +done: + if (acl) + acl_free(acl); + if (fsec) + filesec_free(fsec); + return; + +noacl: + fchmod(fd, sbuf->st_mode & ~S_IFMT); + goto done; +} + +static void +reset_security(copyfile_state_t s) +{ + /* If we haven't reset the file security information + * (COPYFILE_SECURITY is not set in flags) + * restore back the permissions the file had originally + * + * One of the reasons this seems so complicated is that + * it is partially at odds with copyfile_security(). + * + * Simplisticly, we are simply trying to make sure we + * only copy what was requested, and that we don't stomp + * on what wasn't requested. + */ + +#ifdef COPYFILE_RECURSIVE + if (s->dst_fd > -1) { + struct stat sbuf; + + if (s->src_fd > -1 && (s->flags & COPYFILE_STAT)) + fstat(s->src_fd, &sbuf); + else + fstat(s->dst_fd, &sbuf); + + if (!(s->internal_flags & cfDelayAce)) + remove_uberace(s->dst_fd, &sbuf); + } +#else + if (s->permissive_fsec && (s->flags & COPYFILE_SECURITY) != COPYFILE_SECURITY) { + if (s->flags & COPYFILE_ACL) { + /* Just need to reset the BSD information -- mode, owner, group */ + (void)fchown(s->dst_fd, s->dst_sb.st_uid, s->dst_sb.st_gid); + (void)fchmod(s->dst_fd, s->dst_sb.st_mode); + } else { + /* + * flags is either COPYFILE_STAT, or neither; if it's + * neither, then we restore both ACL and POSIX permissions; + * if it's STAT, however, then we only want to restore the + * ACL (which may be empty). We do that by removing the + * POSIX information from the filesec object. + */ + if (s->flags & COPYFILE_STAT) { + copyfile_unset_posix_fsec(s->original_fsec); + } + if (fchmodx_np(s->dst_fd, s->original_fsec) < 0 && errno != ENOTSUP) + copyfile_warn("restoring security information"); + } + } + + if (s->permissive_fsec) { + filesec_free(s->permissive_fsec); + s->permissive_fsec = NULL; + } + + if (s->original_fsec) { + filesec_free(s->original_fsec); + s->original_fsec = NULL; + } +#endif + + return; +} + +/* + * copytree -- recursively copy a hierarchy. + * + * Unlike normal copyfile(), copytree() can copy an entire hierarchy. + * Care is taken to keep the ACLs set up correctly, in addition to the + * normal copying that is done. (When copying a hierarchy, we can't + * get rid of the "allow-all-writes" ACE on a directory until we're done + * copying the *contents* of the directory.) + * + * The other big difference from copyfile (for the moment) is that copytree() + * will use a call-back function to pass along information about what is + * about to be copied, and whether or not it succeeded. + * + * copytree() is called from copyfile() -- but copytree() itself then calls + * copyfile() to copy each individual object. + * + * XXX - no effort is made to handle overlapping hierarchies at the moment. + * + */ + +static int +copytree(copyfile_state_t s) +{ + char *slash; + int retval = 0; + int (*sfunc)(const char *, struct stat *); + copyfile_callback_t status = NULL; + char srcisdir = 0, dstisdir = 0, dstexists = 0; + struct stat sbuf; + char *src, *dst; + const char *dstpathsep = ""; +#ifdef NOTYET + char srcpath[PATH_MAX * 2 + 1], dstpath[PATH_MAX * 2 + 1]; +#endif + char *srcroot; + FTS *fts = NULL; + FTSENT *ftsent; + ssize_t offset = 0; + const char *paths[2] = { 0 }; + unsigned int flags = 0; + int fts_flags = FTS_NOCHDIR; + + if (s == NULL) { + errno = EINVAL; + retval = -1; + goto done; + } + if (s->flags & (COPYFILE_MOVE | COPYFILE_UNLINK | COPYFILE_CHECK | COPYFILE_PACK | COPYFILE_UNPACK)) { + errno = EINVAL; + retval = -1; + goto done; + } + + flags = s->flags & (COPYFILE_ALL | COPYFILE_NOFOLLOW | COPYFILE_VERBOSE); + + paths[0] = src = s->src; + dst = s->dst; + + if (src == NULL || dst == NULL) { + errno = EINVAL; + retval = -1; + goto done; + } + + sfunc = (flags & COPYFILE_NOFOLLOW_SRC) ? lstat : stat; + if ((sfunc)(src, &sbuf) == -1) { + retval = -1; + goto done; + } + if ((sbuf.st_mode & S_IFMT) == S_IFDIR) { + srcisdir = 1; + } + + sfunc = (flags & COPYFILE_NOFOLLOW_DST) ? lstat : stat; + if ((sfunc)(dst, &sbuf) == -1) { + if (errno != ENOENT) { + retval = -1; + goto done; + } + } else { + dstexists = 1; + if ((sbuf.st_mode & S_IFMT) == S_IFDIR) { + dstisdir = 1; + } + } + +#ifdef NOTYET + // This doesn't handle filesystem crossing and case sensitivity + // So there's got to be a better way + + if (realpath(src, srcpath) == NULL) { + retval = -1; + goto done; + } + + if (realpath(dst, dstpath) == NULL && + (errno == ENOENT && realpath(dirname(dst), dstpath) == NULL)) { + retval = -1; + goto done; + } + if (strstr(srcpath, dstpath) != NULL) { + errno = EINVAL; + retval = -1; + goto done; + } +#endif + srcroot = basename((char*)src); + if (srcroot == NULL) { + retval = -1; + goto done; + } + + /* + * To work on as well: + * We have a few cases when copying a hierarchy: + * 1) src is a non-directory, dst is a directory; + * 2) src is a non-directory, dst is a non-directory; + * 3) src is a non-directory, dst does not exist; + * 4) src is a directory, dst is a directory; + * 5) src is a directory, dst is a non-directory; + * 6) src is a directory, dst does not exist + * + * (1) copies src to dst/basename(src). + * (2) fails if COPYFILE_EXCLUSIVE is set, otherwise copies src to dst. + * (3) and (6) copy src to the name dst. + * (4) copies the contents of src to the contents of dst. + * (5) is an error. + */ + + if (dstisdir) { + // copy /path/to/src to /path/to/dst/src + // Append "/" and (fts_path - strlen(basename(src))) to dst? + dstpathsep = "/"; + slash = strrchr(src, '/'); + if (slash == NULL) + offset = 0; + else + offset = slash - src + 1; + } else { + // copy /path/to/src to /path/to/dst + // append (fts_path + strlen(src)) to dst? + dstpathsep = ""; + offset = strlen(src); + } + + if (s->flags | COPYFILE_NOFOLLOW_SRC) + fts_flags |= FTS_PHYSICAL; + else + fts_flags |= FTS_LOGICAL; + + fts = fts_open((char * const *)paths, fts_flags, NULL); + + status = s->statuscb; + while ((ftsent = fts_read(fts)) != NULL) { + int rv = 0; + char *dstfile = NULL; + int cmd = 0; + copyfile_state_t tstate = copyfile_state_alloc(); + if (tstate == NULL) { + errno = ENOMEM; + retval = -1; + break; + } + tstate->statuscb = s->statuscb; + tstate->ctx = s->ctx; + asprintf(&dstfile, "%s%s%s", dst, dstpathsep, ftsent->fts_path + offset); + if (dstfile == NULL) { + copyfile_state_free(tstate); + errno = ENOMEM; + retval = -1; + break; + } + switch (ftsent->fts_info) { + case FTS_D: + tstate->internal_flags |= cfDelayAce; + cmd = COPYFILE_RECURSE_DIR; + break; + case FTS_SL: + case FTS_SLNONE: + case FTS_DEFAULT: + case FTS_F: + cmd = COPYFILE_RECURSE_FILE; + break; + case FTS_DP: + cmd = COPYFILE_RECURSE_DIR_CLEANUP; + break; + case FTS_DNR: + case FTS_ERR: + case FTS_NS: + case FTS_NSOK: + default: + errno = ftsent->fts_errno; + if (status) { + rv = (*status)(COPYFILE_RECURSE_ERROR, COPYFILE_ERR, tstate, ftsent->fts_path, dstfile, s->ctx); + if (rv == COPYFILE_SKIP || rv == COPYFILE_CONTINUE) { + errno = 0; + goto skipit; + } + if (rv == COPYFILE_QUIT) { + retval = -1; + goto stopit; + } + } else { + retval = -1; + goto stopit; + } + case FTS_DOT: + goto skipit; + + } + + if (cmd == COPYFILE_RECURSE_DIR || cmd == COPYFILE_RECURSE_FILE) { + if (status) { + rv = (*status)(cmd, COPYFILE_START, tstate, ftsent->fts_path, dstfile, s->ctx); + if (rv == COPYFILE_SKIP) { + if (cmd == COPYFILE_RECURSE_DIR) { + rv = fts_set(fts, ftsent, FTS_SKIP); + if (rv == -1) { + rv = (*status)(0, COPYFILE_ERR, tstate, ftsent->fts_path, dstfile, s->ctx); + if (rv == COPYFILE_QUIT) + retval = -1; + } + } + goto skipit; + } + if (rv == COPYFILE_QUIT) { + retval = -1; errno = 0; + goto stopit; + } + } + int tmp_flags = (cmd == COPYFILE_RECURSE_DIR) ? (flags & ~COPYFILE_STAT) : flags; + rv = copyfile(ftsent->fts_path, dstfile, tstate, tmp_flags); + if (rv < 0) { + if (status) { + rv = (*status)(cmd, COPYFILE_ERR, tstate, ftsent->fts_path, dstfile, s->ctx); + if (rv == COPYFILE_QUIT) { + retval = -1; + goto stopit; + } else + rv = 0; + goto skipit; + } else { + retval = -1; + goto stopit; + } + } + if (status) { + rv = (*status)(cmd, COPYFILE_FINISH, tstate, ftsent->fts_path, dstfile, s->ctx); + if (rv == COPYFILE_QUIT) { + retval = -1; errno = 0; + goto stopit; + } + } + } else if (cmd == COPYFILE_RECURSE_DIR_CLEANUP) { + if (status) { + rv = (*status)(cmd, COPYFILE_START, tstate, ftsent->fts_path, dstfile, s->ctx); + if (rv == COPYFILE_QUIT) { + retval = -1; errno = 0; + goto stopit; + } else if (rv == COPYFILE_SKIP) { + rv = 0; + goto skipit; + } + } + rv = copyfile(ftsent->fts_path, dstfile, tstate, (flags & COPYFILE_NOFOLLOW) | COPYFILE_STAT); + if (rv < 0) { + if (status) { + rv = (*status)(COPYFILE_RECURSE_DIR_CLEANUP, COPYFILE_ERR, tstate, ftsent->fts_path, dstfile, s->ctx); + if (rv == COPYFILE_QUIT) { + retval = -1; + goto stopit; + } else if (rv == COPYFILE_SKIP || rv == COPYFILE_CONTINUE) { + if (rv == COPYFILE_CONTINUE) + errno = 0; + retval = 0; + goto skipit; + } + } else { + retval = -1; + goto stopit; + } + } else { + if (status) { + rv = (*status)(COPYFILE_RECURSE_DIR_CLEANUP, COPYFILE_FINISH, tstate, ftsent->fts_path, dstfile, s->ctx); + if (rv == COPYFILE_QUIT) { + retval = -1; errno = 0; + goto stopit; + } + } + } + + rv = 0; + } +skipit: +stopit: + copyfile_state_free(tstate); + free(dstfile); + if (retval == -1) + break; + } + +done: + if (fts) + fts_close(fts); + + return retval; +} + +/* + * fcopyfile() is used to copy a source file descriptor to a destination file + * descriptor. This allows an application to figure out how it wants to open + * the files (doing various security checks, perhaps), and then just pass in + * the file descriptors. + */ +int fcopyfile(int src_fd, int dst_fd, copyfile_state_t state, copyfile_flags_t flags) +{ + int ret = 0; + copyfile_state_t s = state; + struct stat dst_sb; + + if (src_fd < 0 || dst_fd < 0) + { + errno = EINVAL; + return -1; + } + + if (copyfile_preamble(&s, flags) < 0) + return -1; + + copyfile_debug(2, "set src_fd <- %d", src_fd); + if (s->src_fd == -2 && src_fd > -1) + { + s->src_fd = src_fd; + if (fstatx_np(s->src_fd, &s->sb, s->fsec) != 0) + { + if (errno == ENOTSUP || errno == EPERM) + fstat(s->src_fd, &s->sb); + else + { + copyfile_warn("fstatx_np on src fd %d", s->src_fd); + return -1; + } + } + } + + /* prevent copying on unsupported types */ + switch (s->sb.st_mode & S_IFMT) + { + case S_IFLNK: + case S_IFDIR: + case S_IFREG: + break; + default: + errno = ENOTSUP; + return -1; + } + + copyfile_debug(2, "set dst_fd <- %d", dst_fd); + if (s->dst_fd == -2 && dst_fd > -1) + s->dst_fd = dst_fd; + + (void)fstat(s->dst_fd, &dst_sb); + (void)fchmod(s->dst_fd, (dst_sb.st_mode & ~S_IFMT) | (S_IRUSR | S_IWUSR)); + + (void)copyfile_quarantine(s); + + ret = copyfile_internal(s, flags); + + if (ret >= 0 && !(s->flags & COPYFILE_STAT)) + { + (void)fchmod(s->dst_fd, dst_sb.st_mode & ~S_IFMT); + } + + if (s->err) { + errno = s->err; + s->err = 0; + } + if (state == NULL) { + int t = errno; + copyfile_state_free(s); + errno = t; + } + + return ret; + +} + +/* + * 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 + * is not just the same as opening the two files, and then calling fcopyfile(). + * Oh, if only life were that simple! + */ +int copyfile(const char *src, const char *dst, copyfile_state_t state, copyfile_flags_t flags) +{ + int ret = 0; + int createdst = 0; + copyfile_state_t s = state; + struct stat dst_sb; + + if (src == NULL && dst == NULL) + { + errno = EINVAL; + return -1; + } + + if (copyfile_preamble(&s, flags) < 0) + { + return -1; + } + +/* + * This macro is... well, it's not the worst thing you can do with cpp, not + * by a long shot. Essentially, we are setting the filename (src or dst) + * in the state structure; since the structure may not have been cleared out + * before being used again, we do some of the cleanup here: if the given + * filename (e.g., src) is set, and state->src is not equal to that, then + * we need to check to see if the file descriptor had been opened, and if so, + * close it. After that, we set state->src to be a copy of the given filename, + * releasing the old copy if necessary. + */ +#define COPYFILE_SET_FNAME(NAME, S) \ + do { \ + if (NAME != NULL) { \ + if (S->NAME != NULL && strncmp(NAME, S->NAME, MAXPATHLEN)) { \ + copyfile_debug(2, "replacing string %s (%s) -> (%s)", #NAME, NAME, S->NAME);\ + if (S->NAME##_fd != -2 && S->NAME##_fd > -1) { \ + copyfile_debug(4, "closing %s fd: %d", #NAME, S->NAME##_fd); \ + close(S->NAME##_fd); \ + S->NAME##_fd = -2; \ + } \ + } \ + if (S->NAME) { \ + free(S->NAME); \ + S->NAME = NULL; \ + } \ + if ((NAME) && (S->NAME = strdup(NAME)) == NULL) \ + return -1; \ + } \ + } while (0) + + COPYFILE_SET_FNAME(src, s); + COPYFILE_SET_FNAME(dst, s); + + if (s->flags & COPYFILE_RECURSIVE) { + ret = copytree(s); + goto exit; + } + + /* + * Get a copy of the source file's security settings + */ + if (s->original_fsec) { + filesec_free(s->original_fsec); + s->original_fsec = NULL; + } + if ((s->original_fsec = filesec_init()) == NULL) + goto error_exit; + + if ((s->flags & COPYFILE_NOFOLLOW_DST) && lstat(s->dst, &dst_sb) == 0 && + ((dst_sb.st_mode & S_IFMT) == S_IFLNK)) { + if (s->permissive_fsec) + free(s->permissive_fsec); + s->permissive_fsec = NULL; + } else if(statx_np(s->dst, &dst_sb, s->original_fsec) == 0) + { + /* + * copyfile_fix_perms() will make a copy of the permission set, + * and insert at the beginning an ACE that ensures we can write + * to the file and set attributes. + */ + + if((s->permissive_fsec = copyfile_fix_perms(s, &s->original_fsec)) != NULL) + { + /* + * Set the permissions for the destination to our copy. + * We should get ENOTSUP from any filesystem that simply + * doesn't support it. + */ + if (chmodx_np(s->dst, s->permissive_fsec) < 0 && errno != ENOTSUP) + { + copyfile_warn("setting security information"); + filesec_free(s->permissive_fsec); + s->permissive_fsec = NULL; + } + } + } else if (errno == ENOENT) { + createdst = 1; + } + + /* + * If COPYFILE_CHECK is set in flags, then all we are going to do + * is see what kinds of things WOULD have been copied (see + * copyfile_check() below). We return that value. + */ + if (COPYFILE_CHECK & flags) + { + ret = copyfile_check(s); + goto exit; + } else if ((ret = copyfile_open(s)) < 0) + goto error_exit; + + (void)fcntl(s->src_fd, F_NOCACHE, 1); + (void)fcntl(s->dst_fd, F_NOCACHE, 1); +#ifdef F_SINGLE_WRITER + (void)fcntl(s->dst_fd, F_SINGLE_WRITER, 1); +#endif + + ret = copyfile_internal(s, flags); + if (ret == -1) + goto error_exit; + +#ifdef COPYFILE_RECURSIVE + if (!(flags & COPYFILE_STAT)) { + if (!createdst) + { + /* Just need to reset the BSD information -- mode, owner, group */ + (void)fchown(s->dst_fd, dst_sb.st_uid, dst_sb.st_gid); + (void)fchmod(s->dst_fd, dst_sb.st_mode); + } + } +#endif + + reset_security(s); + + if (s->src && (flags & COPYFILE_MOVE)) + (void)remove(s->src); + +exit: + if (state == NULL) { + int t = errno; + copyfile_state_free(s); + errno = t; + } + + return ret; + +error_exit: + ret = -1; + if (s->err) { + errno = s->err; + s->err = 0; + } + goto exit; +} + +/* + * Shared prelude to the {f,}copyfile(). This initializes the + * state variable, if necessary, and also checks for both debugging + * and disabling environment variables. + */ +static int copyfile_preamble(copyfile_state_t *state, copyfile_flags_t flags) +{ + copyfile_state_t s; + + if (*state == NULL) + { + if ((*state = copyfile_state_alloc()) == NULL) + return -1; + } + + s = *state; + + if (COPYFILE_DEBUG & flags) + { + char *e; + if ((e = getenv(COPYFILE_DEBUG_VAR))) + { + errno = 0; + s->debug = (uint32_t)strtol(e, NULL, 0); + + /* clamp s->debug to 1 if the environment variable is not parsable */ + if (s->debug == 0 && errno != 0) + s->debug = 1; + } + copyfile_debug(2, "debug value set to: %d", s->debug); + } + +#if 0 + /* Temporarily disabled */ + if (getenv(COPYFILE_DISABLE_VAR) != NULL) + { + copyfile_debug(1, "copyfile disabled"); + return 2; + } +#endif + copyfile_debug(2, "setting flags: %d", s->flags); + s->flags = flags; + + return 0; +} + +/* + * The guts of {f,}copyfile(). + * This looks through the flags in a particular order, and calls the + * associated functions. + */ +static int copyfile_internal(copyfile_state_t s, copyfile_flags_t flags) +{ + int ret = 0; + + if (s->dst_fd < 0 || s->src_fd < 0) + { + copyfile_debug(1, "file descriptors not open (src: %d, dst: %d)", s->src_fd, s->dst_fd); + s->err = EINVAL; + return -1; + } + + /* + * COPYFILE_PACK causes us to create an Apple Double version of the + * source file, and puts it into the destination file. See + * copyfile_pack() below for all the gory details. + */ + if (COPYFILE_PACK & flags) + { + if ((ret = copyfile_pack(s)) < 0) + { + if (s->dst) unlink(s->dst); + goto exit; + } + goto exit; + } + + /* + * COPYFILE_UNPACK is the undoing of COPYFILE_PACK, obviously. + * The goal there is to take an Apple Double file, and turn it + * into a normal file (with data fork, resource fork, modes, + * extended attributes, ACLs, etc.). + */ + if (COPYFILE_UNPACK & flags) + { + if ((ret = copyfile_unpack(s)) < 0) + goto error_exit; + goto exit; + } + + /* + * If we have quarantine info set, we attempt + * to apply it to dst_fd. We don't care if + * it fails, not yet anyway. + */ + if (s->qinfo) { + int qr = qtn_file_apply_to_fd(s->qinfo, s->dst_fd); + if (qr != 0) { + if (s->statuscb) { + int rv; + + s->xattr_name = (char*)XATTR_QUARANTINE_NAME; + rv = (*s->statuscb)(COPYFILE_COPY_XATTR, COPYFILE_ERR, s, s->src, s->dst, s->ctx); + s->xattr_name = NULL; + if (rv == COPYFILE_QUIT) { + s->err = errno = (qr < 0 ? ENOTSUP : qr); + ret = -1; + goto exit; + } + } else { + s->err = errno = (qr < 0 ? ENOTSUP : qr); + ret = -1; + goto exit; + } + } + } + + /* + * COPYFILE_XATTR tells us to copy the extended attributes; + * this is seperate from the extended security (aka ACLs), + * however. If we succeed in this, we continue to the next + * stage; if we fail, we return with an error value. Note + * that we fail if the errno is ENOTSUP, but we don't print + * a warning in that case. + */ + if (COPYFILE_XATTR & flags) + { + if ((ret = copyfile_xattr(s)) < 0) + { + if (errno != ENOTSUP && errno != EPERM) + copyfile_warn("error processing extended attributes"); + goto exit; + } + } + + /* + * Simialr to above, this tells us whether or not to copy + * the non-meta data portion of the file. We attempt to + * remove (via unlink) the destination file if we fail. + */ + if (COPYFILE_DATA & flags) + { + if ((ret = copyfile_data(s)) < 0) + { + copyfile_warn("error processing data"); + if (s->dst && unlink(s->dst)) + copyfile_warn("%s: remove", s->src ? s->src : "(null src)"); + goto exit; + } + } + + /* + * COPYFILE_SECURITY requests that we copy the security, both + * extended and mundane (that is, ACLs and POSIX). + */ + if (COPYFILE_SECURITY & flags) + { + if ((ret = copyfile_security(s)) < 0) + { + copyfile_warn("error processing security information"); + goto exit; + } + } + + if (COPYFILE_STAT & flags) + { + if ((ret = copyfile_stat(s)) < 0) + { + copyfile_warn("error processing POSIX information"); + goto exit; + } + } + +exit: + return ret; + +error_exit: + ret = -1; + goto exit; +} + +/* + * A publicly-visible routine, copyfile_state_alloc() sets up the state variable. + */ +copyfile_state_t copyfile_state_alloc(void) +{ + copyfile_state_t s = (copyfile_state_t) calloc(1, sizeof(struct _copyfile_state)); + + if (s != NULL) + { + s->src_fd = -2; + s->dst_fd = -2; + if (s->fsec) { + filesec_free(s->fsec); + s->fsec = NULL; + } + s->fsec = filesec_init(); + } else + errno = ENOMEM; + + return s; +} + +/* + * copyfile_state_free() returns the memory allocated to the state structure. + * It also closes the file descriptors, if they've been opened. + */ +int copyfile_state_free(copyfile_state_t s) +{ + if (s != NULL) + { + if (s->fsec) + filesec_free(s->fsec); + + if (s->original_fsec) + filesec_free(s->original_fsec); + + if (s->permissive_fsec) + filesec_free(s->permissive_fsec); + + if (s->qinfo) + qtn_file_free(s->qinfo); + + if (copyfile_close(s) < 0) + { + copyfile_warn("error closing files"); + return -1; + } + if (s->xattr_name) + free(s->xattr_name); + if (s->dst) + free(s->dst); + if (s->src) + free(s->src); + free(s); + } + return 0; +} + +/* + * Should we worry if we can't close the source? NFS says we + * should, but it's pretty late for us at this point. + */ +static int copyfile_close(copyfile_state_t s) +{ + if (s->src && s->src_fd >= 0) + close(s->src_fd); + + if (s->dst && s->dst_fd >= 0) { + if (close(s->dst_fd)) + return -1; + } + + return 0; +} + +/* + * The purpose of this function is to set up a set of permissions + * (ACL and traditional) that lets us write to the file. In the + * case of ACLs, we do this by putting in a first entry that lets + * us write data, attributes, and extended attributes. In the case + * of traditional permissions, we set the S_IWUSR (user-write) + * bit. + */ +static filesec_t copyfile_fix_perms(copyfile_state_t s __unused, filesec_t *fsec) +{ + filesec_t ret_fsec = NULL; + mode_t mode; + acl_t acl = NULL; + + if ((ret_fsec = filesec_dup(*fsec)) == NULL) + goto error_exit; + + if (filesec_get_property(ret_fsec, FILESEC_ACL, &acl) == 0) + { +#ifdef COPYFILE_RECURSIVE + if (add_uberace(&acl)) + goto error_exit; +#else + acl_entry_t entry; + acl_permset_t permset; + uuid_t qual; + + if (mbr_uid_to_uuid(getuid(), qual) != 0) + goto error_exit; + + /* + * First, we create an entry, and give it the special name + * of ACL_FIRST_ENTRY, thus guaranteeing it will be first. + * After that, we clear out all the permissions in it, and + * add three permissions: WRITE_DATA, WRITE_ATTRIBUTES, and + * WRITE_EXTATTRIBUTES. We put these into an ACE that allows + * the functionality, and put this into the ACL. + */ + if (acl_create_entry_np(&acl, &entry, ACL_FIRST_ENTRY) == -1) + goto error_exit; + if (acl_get_permset(entry, &permset) == -1) + goto error_exit; + if (acl_clear_perms(permset) == -1) + goto error_exit; + if (acl_add_perm(permset, ACL_WRITE_DATA) == -1) + goto error_exit; + if (acl_add_perm(permset, ACL_WRITE_ATTRIBUTES) == -1) + goto error_exit; + if (acl_add_perm(permset, ACL_WRITE_EXTATTRIBUTES) == -1) + goto error_exit; + if (acl_set_tag_type(entry, ACL_EXTENDED_ALLOW) == -1) + goto error_exit; + + if(acl_set_permset(entry, permset) == -1) + goto error_exit; + if(acl_set_qualifier(entry, qual) == -1) + goto error_exit; +#endif + + if (filesec_set_property(ret_fsec, FILESEC_ACL, &acl) != 0) + goto error_exit; + } + + /* + * This is for the normal, mundane, POSIX permission model. + * We make sure that we can write to the file. + */ + if (filesec_get_property(ret_fsec, FILESEC_MODE, &mode) == 0) + { + if ((mode & (S_IWUSR | S_IRUSR)) != (S_IWUSR | S_IRUSR)) + { + mode |= S_IWUSR|S_IRUSR; + if (filesec_set_property(ret_fsec, FILESEC_MODE, &mode) != 0) + goto error_exit; + } + } + +exit: + if (acl) + acl_free(acl); + + return ret_fsec; + +error_exit: + if (ret_fsec) + { + filesec_free(ret_fsec); + ret_fsec = NULL; + } + goto exit; +} + +/* + * Used to clear out the BSD/POSIX security information from + * a filesec + */ +static int +copyfile_unset_posix_fsec(filesec_t fsec) +{ + (void)filesec_set_property(fsec, FILESEC_OWNER, _FILESEC_UNSET_PROPERTY); + (void)filesec_set_property(fsec, FILESEC_GROUP, _FILESEC_UNSET_PROPERTY); + (void)filesec_set_property(fsec, FILESEC_MODE, _FILESEC_UNSET_PROPERTY); + return 0; +} + +/* + * Used to remove acl information from a filesec_t + * Unsetting the acl alone in Tiger was insufficient + */ +static int copyfile_unset_acl(copyfile_state_t s) +{ + int ret = 0; + if (filesec_set_property(s->fsec, FILESEC_ACL, NULL) == -1) + { + copyfile_debug(5, "unsetting acl attribute on %s", s->dst ? s->dst : "(null dst)"); + ++ret; + } + if (filesec_set_property(s->fsec, FILESEC_UUID, NULL) == -1) + { + copyfile_debug(5, "unsetting uuid attribute on %s", s->dst ? s->dst : "(null dst)"); + ++ret; + } + if (filesec_set_property(s->fsec, FILESEC_GRPUUID, NULL) == -1) + { + copyfile_debug(5, "unsetting group uuid attribute on %s", s->dst ? s->dst : "(null dst)"); + ++ret; + } + return ret; +} + +/* + * copyfile_open() does what one expects: it opens up the files + * given in the state structure, if they're not already open. + * It also does some type validation, to ensure that we only + * handle file types we know about. + */ +static int copyfile_open(copyfile_state_t s) +{ + int oflags = O_EXCL | O_CREAT | O_WRONLY; + int islnk = 0, isdir = 0; + int osrc = 0, dsrc = 0; + + if (s->src && s->src_fd == -2) + { + if ((COPYFILE_NOFOLLOW_SRC & s->flags ? lstatx_np : statx_np) + (s->src, &s->sb, s->fsec)) + { + copyfile_warn("stat on %s", s->src); + return -1; + } + + /* prevent copying on unsupported types */ + switch (s->sb.st_mode & S_IFMT) + { + case S_IFLNK: + islnk = 1; + if ((size_t)s->sb.st_size > SIZE_T_MAX) { + s->err = ENOMEM; /* too big for us to copy */ + return -1; + } + osrc = O_SYMLINK; + break; + case S_IFDIR: + isdir = 1; + break; + case S_IFREG: + break; + default: + if (!(strcmp(s->src, "/dev/null") == 0 && (s->flags & COPYFILE_METADATA))) { + s->err = ENOTSUP; + return -1; + } + } + /* + * If we're packing, then we are actually + * creating a file, no matter what the source + * was. + */ + if (s->flags & COPYFILE_PACK) { + /* + * O_SYMLINK and O_NOFOLLOW are not compatible options: + * if the file is a symlink, and O_NOFOLLOW is specified, + * open will return ELOOP, whether or not O_SYMLINK is set. + * However, we know whether or not it was a symlink from + * the stat above (although there is a potentiaal for a race + * condition here, but it will err on the side of returning + * ELOOP from open). + */ + if (!islnk) + osrc = (s->flags & COPYFILE_NOFOLLOW_SRC) ? O_NOFOLLOW : 0; + isdir = islnk = 0; + } + + if ((s->src_fd = open(s->src, O_RDONLY | osrc , 0)) < 0) + { + copyfile_warn("open on %s", s->src); + return -1; + } else + copyfile_debug(2, "open successful on source (%s)", s->src); + + (void)copyfile_quarantine(s); + } + + if (s->dst && s->dst_fd == -2) + { + /* + * COPYFILE_UNLINK tells us to try removing the destination + * before we create it. We don't care if the file doesn't + * exist, so we ignore ENOENT. + */ + if (COPYFILE_UNLINK & s->flags) + { + if (remove(s->dst) < 0 && errno != ENOENT) + { + copyfile_warn("%s: remove", s->dst); + return -1; + } + } + + if (s->flags & COPYFILE_NOFOLLOW_DST) { + struct stat st; + + dsrc = O_NOFOLLOW; + if (lstat(s->dst, &st) != -1) { + if ((st.st_mode & S_IFMT) == S_IFLNK) + dsrc = O_SYMLINK; + } + } + + if (islnk) { + size_t sz = (size_t)s->sb.st_size + 1; + char *bp; + + bp = calloc(1, sz); + if (bp == NULL) { + copyfile_warn("cannot allocate %zd bytes", sz); + return -1; + } + if (readlink(s->src, bp, sz-1) == -1) { + copyfile_warn("cannot readlink %s", s->src); + free(bp); + return -1; + } + if (symlink(bp, s->dst) == -1) { + if (errno != EEXIST || (s->flags & COPYFILE_EXCL)) { + copyfile_warn("Cannot make symlink %s", s->dst); + free(bp); + return -1; + } + } + free(bp); + s->dst_fd = open(s->dst, O_RDONLY | O_SYMLINK); + if (s->dst_fd == -1) { + copyfile_warn("Cannot open symlink %s for reading", s->dst); + return -1; + } + } else if (isdir) { + mode_t mode; + mode = (s->sb.st_mode & ~S_IFMT) | S_IRWXU; + + if (mkdir(s->dst, mode) == -1) { + if (errno != EEXIST || (s->flags & COPYFILE_EXCL)) { + copyfile_warn("Cannot make directory %s", s->dst); + return -1; + } + } + s->dst_fd = open(s->dst, O_RDONLY | dsrc); + if (s->dst_fd == -1) { + copyfile_warn("Cannot open directory %s for reading", s->dst); + return -1; + } + } else while((s->dst_fd = open(s->dst, oflags | dsrc, s->sb.st_mode | S_IWUSR)) < 0) + { + /* + * We set S_IWUSR because fsetxattr does not -- at the time this comment + * was written -- allow one to set an extended attribute on a file descriptor + * for a read-only file, even if the file descriptor is opened for writing. + * This will only matter if the file does not already exist. + */ + switch(errno) + { + case EEXIST: + copyfile_debug(3, "open failed, retrying (%s)", s->dst); + if (s->flags & COPYFILE_EXCL) + break; + oflags = oflags & ~O_CREAT; + if (s->flags & (COPYFILE_PACK | COPYFILE_DATA)) + { + copyfile_debug(4, "truncating existing file (%s)", s->dst); + oflags |= O_TRUNC; + } + continue; + case EACCES: + if(chmod(s->dst, (s->sb.st_mode | S_IWUSR) & ~S_IFMT) == 0) + continue; + else { + /* + * If we're trying to write to a directory to which we don't + * have access, the create above would have failed, but chmod + * here would have given us ENOENT. But the real error is + * still one of access, so we change the errno we're reporting. + * This could cause confusion with a race condition. + */ + + if (errno == ENOENT) + errno = EACCES; + break; + } + case EISDIR: + copyfile_debug(3, "open failed because it is a directory (%s)", s->dst); + if (((s->flags & COPYFILE_EXCL) || + (!isdir && (s->flags & COPYFILE_DATA))) + && !(s->flags & COPYFILE_UNPACK)) + break; + oflags = (oflags & ~(O_WRONLY|O_CREAT|O_TRUNC)) | O_RDONLY; + continue; + } + copyfile_warn("open on %s", s->dst); + return -1; + } + copyfile_debug(2, "open successful on destination (%s)", s->dst); + } + + if (s->dst_fd < 0 || s->src_fd < 0) + { + copyfile_debug(1, "file descriptors not open (src: %d, dst: %d)", + s->src_fd, s->dst_fd); + s->err = EINVAL; + return -1; + } + return 0; +} + + +/* + * copyfile_check(), as described above, essentially tells you + * what you'd have to copy, if you wanted it to copy the things + * you asked it to copy. + * In other words, if you pass in COPYFILE_ALL, and the file in + * question had no extended attributes but did have an ACL, you'd + * get back COPYFILE_ACL. + */ +static copyfile_flags_t copyfile_check(copyfile_state_t s) +{ + acl_t acl = NULL; + copyfile_flags_t ret = 0; + int nofollow = (s->flags & COPYFILE_NOFOLLOW_SRC); + qtn_file_t qinfo; + + if (!s->src) + { + s->err = EINVAL; + return -1; + } + + /* check EAs */ + if (COPYFILE_XATTR & s->flags) + if (listxattr(s->src, 0, 0, nofollow ? XATTR_NOFOLLOW : 0) > 0) + { + ret |= COPYFILE_XATTR; + } + + if (COPYFILE_ACL & s->flags) + { + (COPYFILE_NOFOLLOW_SRC & s->flags ? lstatx_np : statx_np) + (s->src, &s->sb, s->fsec); + + if (filesec_get_property(s->fsec, FILESEC_ACL, &acl) == 0) + ret |= COPYFILE_ACL; + } + + copyfile_debug(2, "check result: %d (%s)", ret, s->src); + + if (acl) + acl_free(acl); + + if (s->qinfo) { + /* If the state has had quarantine info set already, we use that */ + ret |= ((s->flags & COPYFILE_XATTR) ? COPYFILE_XATTR : COPYFILE_ACL); + } else { + qinfo = qtn_file_alloc(); + /* + * For quarantine information, we need to see if the source file + * has any. Since it may be a symlink, however, and we may, or + * not be following, *and* there's no qtn* routine which can optionally + * follow or not follow a symlink, we need to instead work around + * this limitation. + */ + if (qinfo) { + int fd; + int qret = 0; + struct stat sbuf; + + /* + * If we care about not following symlinks, *and* the file exists + * (which is to say, lstat doesn't return an error), *and* the file + * is a symlink, then we open it up (with O_SYMLINK), and use + * qtn_file_init_with_fd(); if none of that is true, however, then + * we can simply use qtn_file_init_with_path(). + */ + if (nofollow + && lstat(s->src, &sbuf) == 0 + && ((sbuf.st_mode & S_IFMT) == S_IFLNK)) { + fd = open(s->src, O_RDONLY | O_SYMLINK); + if (fd != -1) { + if (!qtn_file_init_with_fd(qinfo, fd)) { + qret |= ((s->flags & COPYFILE_XATTR) ? COPYFILE_XATTR : COPYFILE_ACL); + } + close(fd); + } + } else { + if (!qtn_file_init_with_path(qinfo, s->src)) { + qret |= ((s->flags & COPYFILE_XATTR) ? COPYFILE_XATTR : COPYFILE_ACL); + } + } + qtn_file_free(qinfo); + ret |= qret; + } + } + return ret; +} + +/* + * Attempt to copy the data section of a file. Using blockisize + * is not necessarily the fastest -- it might be desirable to + * specify a blocksize, somehow. But it's a size that should be + * guaranteed to work. + */ +static int copyfile_data(copyfile_state_t s) +{ + size_t blen; + char *bp = 0; + ssize_t nread; + int ret = 0; + size_t iBlocksize = 0; + size_t oBlocksize = 0; + const size_t onegig = 1 << 30; + struct statfs sfs; + copyfile_callback_t status = s->statuscb; + + /* Unless it's a normal file, we don't copy. For now, anyway */ + if ((s->sb.st_mode & S_IFMT) != S_IFREG) + return 0; + +#ifdef VOL_CAP_FMT_DECMPFS_COMPRESSION + if (s->internal_flags & cfSawDecmpEA) { + if (s->sb.st_flags & UF_COMPRESSED) { + if ((s->flags & COPYFILE_STAT) == 0) { + if (fchflags(s->dst_fd, UF_COMPRESSED) == 0) { + goto exit; + } + } + } + } +#endif + + if (fstatfs(s->src_fd, &sfs) == -1) { + iBlocksize = s->sb.st_blksize; + } else { + iBlocksize = sfs.f_iosize; + } + + /* Work-around for 6453525, limit blocksize to 1G */ + if (iBlocksize > onegig) { + iBlocksize = onegig; + } + + if ((bp = malloc(iBlocksize)) == NULL) + return -1; + + if (fstatfs(s->dst_fd, &sfs) == -1 || sfs.f_iosize == 0) { + oBlocksize = iBlocksize; + } else { + oBlocksize = sfs.f_iosize; + if (oBlocksize > onegig) + oBlocksize = onegig; + } + + blen = iBlocksize; + + s->totalCopied = 0; +/* If supported, do preallocation for Xsan / HFS volumes */ +#ifdef F_PREALLOCATE + { + fstore_t fst; + + fst.fst_flags = 0; + fst.fst_posmode = F_PEOFPOSMODE; + fst.fst_offset = 0; + fst.fst_length = s->sb.st_size; + /* Ignore errors; this is merely advisory. */ + (void)fcntl(s->dst_fd, F_PREALLOCATE, &fst); + } +#endif + + while ((nread = read(s->src_fd, bp, blen)) > 0) + { + ssize_t nwritten; + size_t left = nread; + void *ptr = bp; + int loop = 0; + + while (left > 0) { + nwritten = write(s->dst_fd, ptr, MIN(left, oBlocksize)); + switch (nwritten) { + case 0: + if (++loop > 5) { + copyfile_warn("writing to output %d times resulted in 0 bytes written", loop); + ret = -1; + s->err = EAGAIN; + goto exit; + } + break; + case -1: + copyfile_warn("writing to output file got error"); + if (status) { + int rv = (*status)(COPYFILE_COPY_DATA, COPYFILE_ERR, s, s->src, s->dst, s->ctx); + if (rv == COPYFILE_SKIP) { // Skip the data copy + ret = 0; + goto exit; + } + if (rv == COPYFILE_CONTINUE) { // Retry the write + errno = 0; + continue; + } + } + ret = -1; + goto exit; + default: + left -= nwritten; + ptr = ((char*)ptr) + nwritten; + loop = 0; + break; + } + s->totalCopied += nwritten; + if (status) { + int rv = (*status)(COPYFILE_COPY_DATA, COPYFILE_PROGRESS, s, s->src, s->dst, s->ctx); + if (rv == COPYFILE_QUIT) { + ret = -1; s->err = errno = ECANCELED; + goto exit; + } + } + } + } + if (nread < 0) + { + copyfile_warn("reading from %s", s->src ? s->src : "(null src)"); + ret = -1; + goto exit; + } + + if (ftruncate(s->dst_fd, s->totalCopied) < 0) + { + ret = -1; + goto exit; + } + +exit: + if (ret == -1) + { + s->err = errno; + } + free(bp); + return ret; +} + +/* + * copyfile_security() will copy the ACL set, and the + * POSIX set. Complexities come when dealing with + * inheritied permissions, and when dealing with both + * POSIX and ACL permissions. + */ +static int copyfile_security(copyfile_state_t s) +{ + int copied = 0; + struct stat sb; + acl_t acl_src = NULL, acl_tmp = NULL, acl_dst = NULL; + int ret = 0; + filesec_t tmp_fsec = NULL; + filesec_t fsec_dst = filesec_init(); + + if (fsec_dst == NULL) + return -1; + + + if (COPYFILE_ACL & s->flags) + { + if (filesec_get_property(s->fsec, FILESEC_ACL, &acl_src)) + { + if (errno == ENOENT) + acl_src = NULL; + else + goto error_exit; + } + +/* grab the destination acl + cannot assume it's empty due to inheritance +*/ + if(fstatx_np(s->dst_fd, &sb, fsec_dst)) + goto error_exit; + + if (filesec_get_property(fsec_dst, FILESEC_ACL, &acl_dst)) + { + if (errno == ENOENT) + acl_dst = NULL; + else + goto error_exit; + } + + if (acl_src == NULL && acl_dst == NULL) + goto no_acl; + + acl_tmp = acl_init(4); + if (acl_tmp == NULL) + goto error_exit; + + if (acl_src) { + acl_entry_t ace = NULL; + acl_entry_t tmp = NULL; + for (copied = 0; + acl_get_entry(acl_src, + ace == NULL ? ACL_FIRST_ENTRY : ACL_NEXT_ENTRY, + &ace) == 0;) + { + acl_flagset_t flags = { 0 }; + acl_get_flagset_np(ace, &flags); + if (!acl_get_flag_np(flags, ACL_ENTRY_INHERITED)) + { + if ((ret = acl_create_entry(&acl_tmp, &tmp)) == -1) + goto error_exit; + + if ((ret = acl_copy_entry(tmp, ace)) == -1) + goto error_exit; + + copyfile_debug(2, "copied acl entry from %s to %s", + s->src ? s->src : "(null src)", + s->dst ? s->dst : "(null tmp)"); + copied++; + } + } + } + if (acl_dst) { + acl_entry_t ace = NULL; + acl_entry_t tmp = NULL; + acl_flagset_t flags = { 0 }; + for (copied = 0;acl_get_entry(acl_dst, + ace == NULL ? ACL_FIRST_ENTRY : ACL_NEXT_ENTRY, + &ace) == 0;) + { + acl_get_flagset_np(ace, &flags); + if (acl_get_flag_np(flags, ACL_ENTRY_INHERITED)) + { + if ((ret = acl_create_entry(&acl_tmp, &tmp)) == -1) + goto error_exit; + + if ((ret = acl_copy_entry(tmp, ace)) == -1) + goto error_exit; + + copyfile_debug(2, "copied acl entry from %s to %s", + s->src ? s->src : "(null dst)", + s->dst ? s->dst : "(null tmp)"); + copied++; + } + } + } + if (!filesec_set_property(s->fsec, FILESEC_ACL, &acl_tmp)) + { + copyfile_debug(3, "altered acl"); + } + } +no_acl: + /* + * The following code is attempting to ensure that only the requested + * security information gets copied over to the destination file. + * We essentially have four cases: COPYFILE_ACL, COPYFILE_STAT, + * COPYFILE_(STAT|ACL), and none (in which case, we wouldn't be in + * this function). + * + * If we have both flags, we copy everything; if we have ACL but not STAT, + * we remove the POSIX information from the filesec object, and apply the + * ACL; if we have STAT but not ACL, then we just use fchmod(), and ignore + * the extended version. + */ + tmp_fsec = filesec_dup(s->fsec); + if (tmp_fsec == NULL) { + goto error_exit; + } + + switch (COPYFILE_SECURITY & s->flags) { + case COPYFILE_ACL: + copyfile_unset_posix_fsec(tmp_fsec); + /* FALLTHROUGH */ + case COPYFILE_ACL | COPYFILE_STAT: + if (fchmodx_np(s->dst_fd, tmp_fsec) < 0) { + acl_t acl = NULL; + /* + * The call could have failed for a number of reasons, since + * it does a number of things: it changes the mode of the file, + * sets the owner and group, and applies an ACL (if one exists). + * The typical failure is going to be trying to set the group of + * the destination file to match the source file, when the process + * doesn't have permission to put files in that group. We try to + * work around this by breaking the steps out and doing them + * discretely. We don't care if the fchown fails, but we do care + * if the mode or ACL can't be set. For historical reasons, we + * simply log those failures, however. + * + * Big warning here: we may NOT have COPYFILE_STAT set, since + * we fell-through from COPYFILE_ACL. So check for the fchmod. + */ + +#define NS(x) ((x) ? (x) : "(null string)") + if ((s->flags & COPYFILE_STAT) && + fchmod(s->dst_fd, s->sb.st_mode) == -1) { + copyfile_warn("could not change mode of destination file %s to match source file %s", NS(s->dst), NS(s->src)); + } + (void)fchown(s->dst_fd, s->sb.st_uid, s->sb.st_gid); + if (filesec_get_property(tmp_fsec, FILESEC_ACL, &acl) == 0) { + if (acl_set_fd(s->dst_fd, acl) == -1) { + copyfile_warn("could not apply acl to destination file %s from source file %s", NS(s->dst), NS(s->src)); + } + acl_free(acl); + } + } +#undef NS + break; + case COPYFILE_STAT: + (void)fchmod(s->dst_fd, s->sb.st_mode); + break; + } + filesec_free(tmp_fsec); +exit: + filesec_free(fsec_dst); + if (acl_src) acl_free(acl_src); + if (acl_dst) acl_free(acl_dst); + if (acl_tmp) acl_free(acl_tmp); + + return ret; + +error_exit: + ret = -1; +goto exit; + +} + +/* + * Attempt to set the destination file's stat information -- including + * flags and time-related fields -- to the source's. + */ +static int copyfile_stat(copyfile_state_t s) +{ + struct timeval tval[2]; + unsigned int added_flags = 0, dst_flags = 0; + struct stat dst_sb; + + /* + * NFS doesn't support chflags; ignore errors as a result, since + * we don't return failure for this. + */ + if (s->internal_flags & cfMakeFileInvisible) + 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. + */ + if (fstat(s->dst_fd, &dst_sb)) + return -1; + if (dst_sb.st_flags & SF_RESTRICTED) + added_flags |= SF_RESTRICTED; + + /* 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); + + tval[0].tv_sec = s->sb.st_atime; + tval[1].tv_sec = s->sb.st_mtime; + tval[0].tv_usec = tval[1].tv_usec = 0; + (void)futimes(s->dst_fd, tval); + + return 0; +} + +/* + * Similar to copyfile_security() in some ways; this + * routine copies the extended attributes from the source, + * and sets them on the destination. + * The procedure is pretty simple, even if it is verbose: + * for each named attribute on the destination, get its name, and + * remove it. We should have none after that. + * For each named attribute on the source, get its name, get its + * data, and set it on the destination. + */ +static int copyfile_xattr(copyfile_state_t s) +{ + char *name; + char *namebuf, *end; + ssize_t xa_size; + void *xa_dataptr; + ssize_t bufsize = 4096; + ssize_t asize; + ssize_t nsize; + int ret = 0; + int look_for_decmpea = 0; + + /* delete EAs on destination */ + if ((nsize = 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); + + if (nsize > 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; + if (*end != 0) + *end = 0; + for (name = namebuf; name <= end; name += strlen(name) + 1) { + /* If the quarantine information shows up as an EA, we skip over it */ + if (strncmp(name, XATTR_QUARANTINE_NAME, end - name) == 0) { + continue; + } + fremovexattr(s->dst_fd, name,0); + } + } + free(namebuf); + } else + if (nsize < 0) + { + if (errno == ENOTSUP || errno == EPERM) + return 0; + else + return -1; + } + +#ifdef DECMPFS_XATTR_NAME + if ((s->flags & COPYFILE_DATA) && + (s->sb.st_flags & UF_COMPRESSED) && + doesdecmpfs(s->src_fd) && + doesdecmpfs(s->dst_fd)) { + look_for_decmpea = XATTR_SHOWCOMPRESSION; + } +#endif + + /* get name list of EAs on source */ + if ((nsize = flistxattr(s->src_fd, 0, 0, look_for_decmpea)) < 0) + { + 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); + + if (nsize <= 0) { + free(namebuf); + return (int)nsize; + } + + /* + * 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; + if (*end != 0) + *end = 0; + + if ((xa_dataptr = (void *) malloc(bufsize)) == NULL) { + free(namebuf); + return -1; + } + + for (name = namebuf; name <= end; name += strlen(name) + 1) + { + if (s->xattr_name) { + free(s->xattr_name); + s->xattr_name = NULL; + } + + /* If the quarantine information shows up as an EA, we skip over it */ + if (strncmp(name, XATTR_QUARANTINE_NAME, end - name) == 0) + continue; + + if ((xa_size = fgetxattr(s->src_fd, name, 0, 0, 0, look_for_decmpea)) < 0) + { + continue; + } + + if (xa_size > bufsize) + { + void *tdptr = xa_dataptr; + bufsize = xa_size; + if ((xa_dataptr = + (void *) realloc((void *) xa_dataptr, bufsize)) == NULL) + { + free(tdptr); + ret = -1; + continue; + } + } + + if ((asize = fgetxattr(s->src_fd, name, xa_dataptr, xa_size, 0, look_for_decmpea)) < 0) + { + continue; + } + + if (xa_size != asize) + xa_size = asize; + +#ifdef DECMPFS_XATTR_NAME + if (strncmp(name, DECMPFS_XATTR_NAME, end-name) == 0) + { + decmpfs_disk_header *hdr = xa_dataptr; + + /* + * If the EA has the decmpfs name, but is too + * small, or doesn't have the right magic number, + * or isn't the right type, we'll just skip it. + * This means it won't end up in the destination + * file, and data copy will happen normally. + */ + if ((size_t)xa_size < sizeof(decmpfs_disk_header)) { + continue; + } + if (OSSwapLittleToHostInt32(hdr->compression_magic) != DECMPFS_MAGIC) { + continue; + } + /* + * From AppleFSCompression documentation: + * "It is incumbent on the aware copy engine to identify + * the type of compression being used, and to perform an + * unaware copy of any file it does not recognize." + * + * Compression Types are defined in: + * "AppleFSCompression/Common/compressorCommon.h" + * + * Unfortunately, they don't provide a way to dynamically + * determine what possible compression_type values exist, + * so we have to update this every time a new compression_type + * is added (Types 7->10 were added in Yosemite) + * + * Ubiquity faulting file compression type 0x80000001 are + * deprecated as of Yosemite, per rdar://17714998 don't copy the + * decmpfs xattr on these files, zero byte files are safer + * than a fault nobody knows how to handle. + */ + switch (OSSwapLittleToHostInt32(hdr->compression_type)) { + case 3: /* zlib-compressed data in xattr */ + case 4: /* 64k chunked zlib-compressed data in resource fork */ + + case 7: /* LZVN-compressed data in xattr */ + case 8: /* 64k chunked LZVN-compressed data in resource fork */ + + case 9: /* uncompressed data in xattr (similar to but not identical to CMP_Type1) */ + case 10: /* 64k chunked uncompressed data in resource fork */ + + /* valid compression type, we want to copy. */ + break; + + case 5: /* specifies de-dup within the generation store. Don't copy decmpfs xattr. */ + copyfile_debug(3, "compression_type <5> on attribute com.apple.decmpfs for src file %s is not copied.", + s->src ? s->src : "(null string)"); + continue; + + case 6: /* unused */ + case 0x80000001: /* faulting files are deprecated, don't copy decmpfs xattr */ + default: + copyfile_warn("Invalid compression_type <%d> on attribute %s for src file %s", + OSSwapLittleToHostInt32(hdr->compression_type), name, s->src ? s->src : "(null string)"); + continue; + } + s->internal_flags |= cfSawDecmpEA; + } +#endif + + // If we have a copy intention stated, and the EA is to be ignored, we ignore it + if (s->copyIntent + && xattr_preserve_for_intent(name, s->copyIntent) == 0) + continue; + + s->xattr_name = strdup(name); + + if (s->statuscb) { + int rv; + rv = (*s->statuscb)(COPYFILE_COPY_XATTR, COPYFILE_START, s, s->src, s->dst, s->ctx); + if (rv == COPYFILE_QUIT) { + s->err = ECANCELED; + goto out; + } else if (rv == COPYFILE_SKIP) { + continue; + } + } + if (fsetxattr(s->dst_fd, name, xa_dataptr, xa_size, 0, look_for_decmpea) < 0) + { + if (s->statuscb) + { + int rv; + if (s->xattr_name == NULL) + s->xattr_name = strdup(name); + rv = (*s->statuscb)(COPYFILE_COPY_XATTR, COPYFILE_ERR, s, s->src, s->dst, s->ctx); + if (rv == COPYFILE_QUIT) + { + s->err = ECANCELED; + ret = -1; + goto out; + } + } + else + { + ret = -1; + copyfile_warn("could not set attributes %s on destination file descriptor: %s", name, strerror(errno)); + continue; + } + } + if (s->statuscb) { + int rv; + if (s->xattr_name == NULL) + s->xattr_name = strdup(name); + + rv = (*s->statuscb)(COPYFILE_COPY_XATTR, COPYFILE_FINISH, s, s->src, s->dst, s->ctx); + if (rv == COPYFILE_QUIT) { + s->err = ECANCELED; + goto out; + } + } + } +out: + if (namebuf) + free(namebuf); + free((void *) xa_dataptr); + if (s->xattr_name) { + free(s->xattr_name); + s->xattr_name = NULL; + } + return ret; +} + +/* + * API interface into getting data from the opaque data type. + */ +int copyfile_state_get(copyfile_state_t s, uint32_t flag, void *ret) +{ + if (ret == NULL) + { + errno = EFAULT; + return -1; + } + + switch(flag) + { + case COPYFILE_STATE_SRC_FD: + *(int*)ret = s->src_fd; + break; + case COPYFILE_STATE_DST_FD: + *(int*)ret = s->dst_fd; + break; + case COPYFILE_STATE_SRC_FILENAME: + *(char**)ret = s->src; + break; + case COPYFILE_STATE_DST_FILENAME: + *(char**)ret = s->dst; + break; + case COPYFILE_STATE_QUARANTINE: + *(qtn_file_t*)ret = s->qinfo; + break; +#if 0 + case COPYFILE_STATE_STATS: + ret = s->stats.global; + break; + case COPYFILE_STATE_PROGRESS_CB: + ret = s->callbacks.progress; + break; +#endif +#ifdef COPYFILE_STATE_STATUS_CB + case COPYFILE_STATE_STATUS_CB: + *(copyfile_callback_t*)ret = s->statuscb; + break; + case COPYFILE_STATE_STATUS_CTX: + *(void**)ret = s->ctx; + break; + case COPYFILE_STATE_COPIED: + *(off_t*)ret = s->totalCopied; + break; +#endif +#ifdef COPYFILE_STATE_XATTRNAME + case COPYFILE_STATE_XATTRNAME: + *(char**)ret = s->xattr_name; + break; +#endif +#ifdef COPYFILE_STATE_INTENT + case COPYFILE_STATE_INTENT: + *(xattr_operation_intent_t*)ret = s->copyIntent; + break; +#endif + default: + errno = EINVAL; + ret = NULL; + return -1; + } + return 0; +} + +/* + * Public API for setting state data (remember that the state is + * an opaque data type). + */ +int copyfile_state_set(copyfile_state_t s, uint32_t flag, const void * thing) +{ +#define copyfile_set_string(DST, SRC) \ + do { \ + if (SRC != NULL) { \ + DST = strdup((char *)SRC); \ + } else { \ + if (DST != NULL) { \ + free(DST); \ + } \ + DST = NULL; \ + } \ + } while (0) + + if (thing == NULL) + { + errno = EFAULT; + return -1; + } + + switch(flag) + { + case COPYFILE_STATE_SRC_FD: + s->src_fd = *(int*)thing; + break; + case COPYFILE_STATE_DST_FD: + s->dst_fd = *(int*)thing; + break; + case COPYFILE_STATE_SRC_FILENAME: + copyfile_set_string(s->src, thing); + break; + case COPYFILE_STATE_DST_FILENAME: + copyfile_set_string(s->dst, thing); + break; + case COPYFILE_STATE_QUARANTINE: + if (s->qinfo) + { + qtn_file_free(s->qinfo); + s->qinfo = NULL; + } + if (*(qtn_file_t*)thing) + s->qinfo = qtn_file_clone(*(qtn_file_t*)thing); + break; +#if 0 + case COPYFILE_STATE_STATS: + s->stats.global = thing; + break; + case COPYFILE_STATE_PROGRESS_CB: + s->callbacks.progress = thing; + break; +#endif +#ifdef COPYFILE_STATE_STATUS_CB + case COPYFILE_STATE_STATUS_CB: + s->statuscb = (copyfile_callback_t)thing; + break; + case COPYFILE_STATE_STATUS_CTX: + s->ctx = (void*)thing; + break; +#endif +#ifdef COPYFILE_STATE_INTENT + case COPYFILE_STATE_INTENT: + s->copyIntent = *(xattr_operation_intent_t*)thing; + break; +#endif + default: + errno = EINVAL; + return -1; + } + return 0; +#undef copyfile_set_string +} + + +/* + * Make this a standalone program for testing purposes by + * defining _COPYFILE_TEST. + */ +#ifdef _COPYFILE_TEST +#define COPYFILE_OPTION(x) { #x, COPYFILE_ ## x }, + +struct {char *s; int v;} opts[] = { + COPYFILE_OPTION(ACL) + COPYFILE_OPTION(STAT) + COPYFILE_OPTION(XATTR) + COPYFILE_OPTION(DATA) + COPYFILE_OPTION(SECURITY) + COPYFILE_OPTION(METADATA) + COPYFILE_OPTION(ALL) + COPYFILE_OPTION(NOFOLLOW_SRC) + COPYFILE_OPTION(NOFOLLOW_DST) + COPYFILE_OPTION(NOFOLLOW) + COPYFILE_OPTION(EXCL) + COPYFILE_OPTION(MOVE) + COPYFILE_OPTION(UNLINK) + COPYFILE_OPTION(PACK) + COPYFILE_OPTION(UNPACK) + COPYFILE_OPTION(CHECK) + COPYFILE_OPTION(VERBOSE) + COPYFILE_OPTION(DEBUG) + {NULL, 0} +}; + +int main(int c, char *v[]) +{ + int i; + int flags = 0; + + if (c < 3) + errx(1, "insufficient arguments"); + + while(c-- > 3) + { + for (i = 0; opts[i].s != NULL; ++i) + { + if (strcasecmp(opts[i].s, v[c]) == 0) + { + printf("option %d: %s <- %d\n", c, opts[i].s, opts[i].v); + flags |= opts[i].v; + break; + } + } + } + + return copyfile(v[1], v[2], NULL, flags); +} +#endif +/* + * Apple Double Create + * + * Create an Apple Double "._" file from a file's extented attributes + * + * Copyright (c) 2004 Apple Computer, Inc. All rights reserved. + */ + + +#define offsetof(type, member) ((size_t)(&((type *)0)->member)) + +#define XATTR_MAXATTRLEN (16*1024*1024) + + +/* + Typical "._" AppleDouble Header File layout: + ------------------------------------------------------------ + MAGIC 0x00051607 + VERSION 0x00020000 + FILLER 0 + COUNT 2 + .-- AD ENTRY[0] Finder Info Entry (must be first) + .--+-- AD ENTRY[1] Resource Fork Entry (must be last) + | '-> FINDER INFO + | ///////////// Fixed Size Data (32 bytes) + | EXT ATTR HDR + | ///////////// + | ATTR ENTRY[0] --. + | ATTR ENTRY[1] --+--. + | ATTR ENTRY[2] --+--+--. + | ... | | | + | ATTR ENTRY[N] --+--+--+--. + | ATTR DATA 0 <-' | | | + | //////////// | | | + | ATTR DATA 1 <----' | | + | ///////////// | | + | ATTR DATA 2 <-------' | + | ///////////// | + | ... | + | ATTR DATA N <----------' + | ///////////// + | Attribute Free Space + | + '----> RESOURCE FORK + ///////////// Variable Sized Data + ///////////// + ///////////// + ///////////// + ///////////// + ///////////// + ... + ///////////// + + ------------------------------------------------------------ + + NOTE: The EXT ATTR HDR, ATTR ENTRY's and ATTR DATA's are + stored as part of the Finder Info. The length in the Finder + Info AppleDouble entry includes the length of the extended + attribute header, attribute entries, and attribute data. +*/ + + +/* + * On Disk Data Structures + * + * Note: Motorola 68K alignment and big-endian. + * + * See RFC 1740 for additional information about the AppleDouble file format. + * + */ + +#define ADH_MAGIC 0x00051607 +#define ADH_VERSION 0x00020000 +#define ADH_MACOSX "Mac OS X " + +/* + * AppleDouble Entry ID's + */ +#define AD_DATA 1 /* Data fork */ +#define AD_RESOURCE 2 /* Resource fork */ +#define AD_REALNAME 3 /* File's name on home file system */ +#define AD_COMMENT 4 /* Standard Mac comment */ +#define AD_ICONBW 5 /* Mac black & white icon */ +#define AD_ICONCOLOR 6 /* Mac color icon */ +#define AD_UNUSED 7 /* Not used */ +#define AD_FILEDATES 8 /* File dates; create, modify, etc */ +#define AD_FINDERINFO 9 /* Mac Finder info & extended info */ +#define AD_MACINFO 10 /* Mac file info, attributes, etc */ +#define AD_PRODOSINFO 11 /* Pro-DOS file info, attrib., etc */ +#define AD_MSDOSINFO 12 /* MS-DOS file info, attributes, etc */ +#define AD_AFPNAME 13 /* Short name on AFP server */ +#define AD_AFPINFO 14 /* AFP file info, attrib., etc */ +#define AD_AFPDIRID 15 /* AFP directory ID */ +#define AD_ATTRIBUTES AD_FINDERINFO + + +#define ATTR_FILE_PREFIX "._" +#define ATTR_HDR_MAGIC 0x41545452 /* 'ATTR' */ + +#define ATTR_BUF_SIZE 4096 /* default size of the attr file and how much we'll grow by */ + +/* Implementation Limits */ +#define ATTR_MAX_SIZE (16*1024*1024) /* 16 megabyte maximum attribute data size */ +#define ATTR_MAX_NAME_LEN 128 +#define ATTR_MAX_HDR_SIZE (65536+18) + +/* + * Note: ATTR_MAX_HDR_SIZE is the largest attribute header + * size supported (including the attribute entries). All of + * the attribute entries must reside within this limit. + */ + + +#define FINDERINFOSIZE 32 + +typedef struct apple_double_entry +{ + u_int32_t type; /* entry type: see list, 0 invalid */ + u_int32_t offset; /* entry data offset from the beginning of the file. */ + u_int32_t length; /* entry data length in bytes. */ +} __attribute__((aligned(2), packed)) apple_double_entry_t; + + +typedef struct apple_double_header +{ + u_int32_t magic; /* == ADH_MAGIC */ + u_int32_t version; /* format version: 2 = 0x00020000 */ + u_int32_t filler[4]; + u_int16_t numEntries; /* number of entries which follow */ + apple_double_entry_t entries[2]; /* 'finfo' & 'rsrc' always exist */ + u_int8_t finfo[FINDERINFOSIZE]; /* Must start with Finder Info (32 bytes) */ + u_int8_t pad[2]; /* get better alignment inside attr_header */ +} __attribute__((aligned(2), packed)) apple_double_header_t; + + +/* Entries are aligned on 4 byte boundaries */ +typedef struct attr_entry +{ + u_int32_t offset; /* file offset to data */ + u_int32_t length; /* size of attribute data */ + u_int16_t flags; + u_int8_t namelen; /* length of name including NULL termination char */ + u_int8_t name[1]; /* NULL-terminated UTF-8 name (up to 128 bytes max) */ +} __attribute__((aligned(2), packed)) attr_entry_t; + + + +/* Header + entries must fit into 64K */ +typedef struct attr_header +{ + apple_double_header_t appledouble; + u_int32_t magic; /* == ATTR_HDR_MAGIC */ + u_int32_t debug_tag; /* for debugging == file id of owning file */ + u_int32_t total_size; /* total size of attribute header + entries + data */ + u_int32_t data_start; /* file offset to attribute data area */ + u_int32_t data_length; /* length of attribute data area */ + u_int32_t reserved[3]; + u_int16_t flags; + u_int16_t num_attrs; +} __attribute__((aligned(2), packed)) attr_header_t; + +/* Empty Resource Fork Header */ +/* This comes by way of xnu's vfs_xattr.c */ +typedef struct rsrcfork_header { + u_int32_t fh_DataOffset; + u_int32_t fh_MapOffset; + u_int32_t fh_DataLength; + u_int32_t fh_MapLength; + u_int8_t systemData[112]; + u_int8_t appData[128]; + u_int32_t mh_DataOffset; + u_int32_t mh_MapOffset; + u_int32_t mh_DataLength; + u_int32_t mh_MapLength; + u_int32_t mh_Next; + u_int16_t mh_RefNum; + u_int8_t mh_Attr; + u_int8_t mh_InMemoryAttr; + u_int16_t mh_Types; + u_int16_t mh_Names; + u_int16_t typeCount; +} __attribute__((aligned(2), packed)) rsrcfork_header_t; +#define RF_FIRST_RESOURCE 256 +#define RF_NULL_MAP_LENGTH 30 +#define RF_EMPTY_TAG "This resource fork intentionally left blank " + +static const rsrcfork_header_t empty_rsrcfork_header = { + OSSwapHostToBigInt32(RF_FIRST_RESOURCE), // fh_DataOffset + OSSwapHostToBigInt32(RF_FIRST_RESOURCE), // fh_MapOffset + 0, // fh_DataLength + OSSwapHostToBigInt32(RF_NULL_MAP_LENGTH), // fh_MapLength + { RF_EMPTY_TAG, }, // systemData + { 0 }, // appData + OSSwapHostToBigInt32(RF_FIRST_RESOURCE), // mh_DataOffset + OSSwapHostToBigInt32(RF_FIRST_RESOURCE), // mh_MapOffset + 0, // mh_DataLength + OSSwapHostToBigInt32(RF_NULL_MAP_LENGTH), // mh_MapLength + 0, // mh_Next + 0, // mh_RefNum + 0, // mh_Attr + 0, // mh_InMemoryAttr + OSSwapHostToBigInt16(RF_NULL_MAP_LENGTH - 2), // mh_Types + OSSwapHostToBigInt16(RF_NULL_MAP_LENGTH), // mh_Names + OSSwapHostToBigInt16(-1), // typeCount +}; + +#define SWAP16(x) OSSwapBigToHostInt16(x) +#define SWAP32(x) OSSwapBigToHostInt32(x) +#define SWAP64(x) OSSwapBigToHostInt64(x) + +#define ATTR_ALIGN 3L /* Use four-byte alignment */ + +#define ATTR_ENTRY_LENGTH(namelen) \ + ((sizeof(attr_entry_t) - 1 + (namelen) + ATTR_ALIGN) & (~ATTR_ALIGN)) + +#define ATTR_NEXT(ae) \ + (attr_entry_t *)((u_int8_t *)(ae) + ATTR_ENTRY_LENGTH((ae)->namelen)) + +#define XATTR_SECURITY_NAME "com.apple.acl.text" + +/* + * Endian swap Apple Double header + */ +static void +swap_adhdr(apple_double_header_t *adh) +{ +#if BYTE_ORDER == LITTLE_ENDIAN + int count; + int i; + + count = (adh->magic == ADH_MAGIC) ? adh->numEntries : SWAP16(adh->numEntries); + + adh->magic = SWAP32 (adh->magic); + adh->version = SWAP32 (adh->version); + adh->numEntries = SWAP16 (adh->numEntries); + + for (i = 0; i < count; i++) + { + adh->entries[i].type = SWAP32 (adh->entries[i].type); + adh->entries[i].offset = SWAP32 (adh->entries[i].offset); + adh->entries[i].length = SWAP32 (adh->entries[i].length); + } +#else + (void)adh; +#endif +} + +/* + * Endian swap a single attr_entry_t + */ +static void +swap_attrhdr_entry(attr_entry_t *ae) +{ +#if BYTE_ORDER == LITTLE_ENDIAN + ae->offset = SWAP32 (ae->offset); + ae->length = SWAP32 (ae->length); + ae->flags = SWAP16 (ae->flags); +#else + (void)ae; +#endif +} + +/* + * For a validated/endian swapped attr_header_t* + * ah, endian swap all of the entries. + */ +static void +swap_attrhdr_entries(attr_header_t *ah) +{ +#if BYTE_ORDER == LITTLE_ENDIAN + int i; + int count; + attr_entry_t *entry; + attr_entry_t *next; + + /* If we're in copyfile_pack, num_args is native endian, + * if we're in _unpack, num_args is big endian. Use + * the magic number to test for endianess. + */ + count = (ah->magic == ATTR_HDR_MAGIC) ? ah->num_attrs : SWAP16(ah->num_attrs); + + entry = (attr_entry_t *)(&ah[1]); + for (i = 0; i < count; i++) { + next = ATTR_NEXT(entry); + swap_attrhdr_entry(entry); + entry = next; + } +#else + (void)ah; +#endif +} + +/* + * Endian swap extended attributes header + */ +static void +swap_attrhdr(attr_header_t *ah) +{ +#if BYTE_ORDER == LITTLE_ENDIAN + ah->magic = SWAP32 (ah->magic); + ah->debug_tag = SWAP32 (ah->debug_tag); + ah->total_size = SWAP32 (ah->total_size); + ah->data_start = SWAP32 (ah->data_start); + ah->data_length = SWAP32 (ah->data_length); + ah->flags = SWAP16 (ah->flags); + ah->num_attrs = SWAP16 (ah->num_attrs); +#else + (void)ah; +#endif +} + +static const u_int32_t emptyfinfo[8] = {0}; + +/* + * Given an Apple Double file in src, turn it into a + * normal file (possibly with multiple forks, EAs, and + * ACLs) in dst. + */ +static int copyfile_unpack(copyfile_state_t s) +{ + ssize_t bytes; + void * buffer, * endptr, * dataptr = NULL; + apple_double_header_t *adhdr; + ssize_t hdrsize; + int error = 0; + + if (s->sb.st_size < ATTR_MAX_HDR_SIZE) + hdrsize = (ssize_t)s->sb.st_size; + else + hdrsize = ATTR_MAX_HDR_SIZE; + + buffer = calloc(1, hdrsize); + if (buffer == NULL) { + copyfile_debug(1, "copyfile_unpack: calloc(1, %zu) returned NULL", hdrsize); + error = -1; + goto exit; + } else + endptr = (char*)buffer + hdrsize; + + bytes = pread(s->src_fd, buffer, hdrsize, 0); + + if (bytes < 0) + { + copyfile_debug(1, "pread returned: %zd", bytes); + error = -1; + goto exit; + } + if (bytes < hdrsize) + { + copyfile_debug(1, + "pread couldn't read entire header: %d of %d", + (int)bytes, (int)s->sb.st_size); + error = -1; + goto exit; + } + adhdr = (apple_double_header_t *)buffer; + + /* + * Check for Apple Double file. + */ + if ((size_t)bytes < sizeof(apple_double_header_t) - 2 || + SWAP32(adhdr->magic) != ADH_MAGIC || + SWAP32(adhdr->version) != ADH_VERSION || + SWAP16(adhdr->numEntries) != 2 || + SWAP32(adhdr->entries[0].type) != AD_FINDERINFO) + { + if (COPYFILE_VERBOSE & s->flags) + copyfile_warn("Not a valid Apple Double header"); + error = -1; + goto exit; + } + swap_adhdr(adhdr); + + /* + * Remove any extended attributes on the target. + */ + + if ((bytes = flistxattr(s->dst_fd, 0, 0, 0)) > 0) + { + char *namebuf, *name; + + if ((namebuf = (char*) malloc(bytes)) == NULL) + { + s->err = ENOMEM; + goto exit; + } + bytes = flistxattr(s->dst_fd, namebuf, bytes, 0); + + if (bytes > 0) + for (name = namebuf; name < namebuf + bytes; name += strlen(name) + 1) + (void)fremovexattr(s->dst_fd, name, 0); + + free(namebuf); + } + else if (bytes < 0) + { + if (errno != ENOTSUP && errno != EPERM) + goto exit; + } + + /* + * Extract the extended attributes. + * + * >>> WARNING <<< + * This assumes that the data is already in memory (not + * the case when there are lots of attributes or one of + * the attributes is very large. + */ + if (adhdr->entries[0].length > FINDERINFOSIZE) + { + attr_header_t *attrhdr; + attr_entry_t *entry; + int count; + int i; + + if ((size_t)hdrsize < sizeof(attr_header_t)) { + copyfile_warn("bad attribute header: %zu < %zu", hdrsize, sizeof(attr_header_t)); + error = -1; + goto exit; + } + + attrhdr = (attr_header_t *)buffer; + swap_attrhdr(attrhdr); + if (attrhdr->magic != ATTR_HDR_MAGIC) + { + if (COPYFILE_VERBOSE & s->flags) + copyfile_warn("bad attribute header"); + error = -1; + goto exit; + } + count = attrhdr->num_attrs; + entry = (attr_entry_t *)&attrhdr[1]; + + for (i = 0; i < count; i++) + { + /* + * First we do some simple sanity checking. + * +) See if entry is within the buffer's range; + * + * +) Check the attribute name length; if it's longer than the + * maximum, we truncate it down. (We could error out as well; + * I'm not sure which is the better way to go here.) + * + * +) If, given the name length, it goes beyond the end of + * the buffer, error out. + * + * +) If the last byte isn't a NUL, make it a NUL. (Since we + * truncated the name length above, we truncate the name here.) + * + * +) If entry->offset is so large that it causes dataptr to + * go beyond the end of the buffer -- or, worse, so large that + * it wraps around! -- we error out. + * + * +) If entry->length would cause the entry to go beyond the + * end of the buffer (or, worse, wrap around to before it), + * *or* if the length is larger than the hdrsize, we error out. + * (An explanation of that: what we're checking for there is + * the small range of values such that offset+length would cause + * it to go beyond endptr, and then wrap around past buffer. We + * care about this because we are passing entry->length down to + * fgetxattr() below, and an erroneously large value could cause + * problems there. By making sure that it's less than hdrsize, + * which has already been sanity-checked above, we're safe. + * That may mean that the check against < buffer is unnecessary.) + */ + if ((void*)entry >= endptr || (void*)entry < buffer) { + if (COPYFILE_VERBOSE & s->flags) + copyfile_warn("Incomplete or corrupt attribute entry"); + error = -1; + s->err = EINVAL; + goto exit; + } + + if (((char*)entry + sizeof(*entry)) > (char*)endptr) { + if (COPYFILE_VERBOSE & s->flags) + copyfile_warn("Incomplete or corrupt attribute entry"); + error = -1; + s->err = EINVAL; + goto exit; + } + + /* + * Endian swap the entry we're looking at. Previously + * we did this swap as part of swap_attrhdr, but that + * allowed a maliciously constructed file to overrun + * our allocation. Instead do the swap after we've verified + * the entry struct is within the buffer's range. + */ + swap_attrhdr_entry(entry); + + if (entry->namelen < 2) { + if (COPYFILE_VERBOSE & s->flags) + copyfile_warn("Corrupt attribute entry (only %d bytes)", entry->namelen); + error = -1; + s->err = EINVAL; + goto exit; + } + + if (entry->namelen > XATTR_MAXNAMELEN + 1) { + if (COPYFILE_VERBOSE & s->flags) + copyfile_warn("Corrupt attribute entry (name length is %d bytes)", entry->namelen); + error = -1; + s->err = EINVAL; + goto exit; + } + + if ((void*)(entry->name + entry->namelen) > endptr) { + if (COPYFILE_VERBOSE & s->flags) + copyfile_warn("Incomplete or corrupt attribute entry"); + error = -1; + s->err = EINVAL; + goto exit; + } + + /* Because namelen includes the NUL, we check one byte back */ + if (entry->name[entry->namelen-1] != 0) { + if (COPYFILE_VERBOSE & s->flags) + copyfile_warn("Corrupt attribute entry (name is not NUL-terminated)"); + error = -1; + s->err = EINVAL; + goto exit; + } + + copyfile_debug(3, "extracting \"%s\" (%d bytes) at offset %u", + entry->name, entry->length, entry->offset); + +#if 0 + dataptr = (char *)attrhdr + entry->offset; + + if (dataptr > endptr || dataptr < buffer) { + copyfile_debug(1, "Entry %d overflows: offset = %u", i, entry->offset); + error = -1; + s->err = EINVAL; /* Invalid buffer */ + goto exit; + } + + if (((char*)dataptr + entry->length) > (char*)endptr || + (((char*)dataptr + entry->length) < (char*)buffer) || + (entry->length > (size_t)hdrsize)) { + if (COPYFILE_VERBOSE & s->flags) + copyfile_warn("Incomplete or corrupt attribute entry"); + copyfile_debug(1, "Entry %d length overflows: offset = %u, length = %u", + i, entry->offset, entry->length); + error = -1; + s->err = EINVAL; /* Invalid buffer */ + goto exit; + } + +#else + dataptr = malloc(entry->length); + if (dataptr == NULL) { + copyfile_debug(1, "no memory for %u bytes\n", entry->length); + error = -1; + s->err = ENOMEM; + goto exit; + } + if (pread(s->src_fd, dataptr, entry->length, entry->offset) != (ssize_t)entry->length) { + copyfile_debug(1, "failed to read %u bytes at offset %u\n", entry->length, entry->offset); + error = -1; + s->err = EINVAL; + goto exit; + } +#endif + + if (strcmp((char*)entry->name, XATTR_QUARANTINE_NAME) == 0) + { + qtn_file_t tqinfo = NULL; + + if (s->qinfo == NULL) + { + tqinfo = qtn_file_alloc(); + if (tqinfo) + { + int x; + if ((x = qtn_file_init_with_data(tqinfo, dataptr, entry->length)) != 0) + { + copyfile_warn("qtn_file_init_with_data failed: %s", qtn_error(x)); + qtn_file_free(tqinfo); + tqinfo = NULL; + } + } + } + else + { + tqinfo = s->qinfo; + } + if (tqinfo) + { + int x; + x = qtn_file_apply_to_fd(tqinfo, s->dst_fd); + if (x != 0) { + copyfile_warn("qtn_file_apply_to_fd failed: %s", qtn_error(x)); + if (s->statuscb) { + int rv; + s->xattr_name = (char*)XATTR_QUARANTINE_NAME; + rv = (*s->statuscb)(COPYFILE_COPY_XATTR, COPYFILE_ERR, s, s->src, s->dst, s->ctx); + s->xattr_name = NULL; + if (rv == COPYFILE_QUIT) { + error = s->err = x < 0 ? ENOTSUP : errno; + goto exit; + } + } else { + error = s->err = x < 0 ? ENOTSUP : errno; + goto exit; + } + } + } + if (tqinfo && !s->qinfo) + { + qtn_file_free(tqinfo); + } + } + /* Look for ACL data */ + else if (strcmp((char*)entry->name, XATTR_SECURITY_NAME) == 0) + { + acl_t acl; + struct stat sb; + int retry = 1; + char *tcp = dataptr; + + if (entry->length == 0) { + /* Not sure how we got here, but we had one case + * where it was 0. In a normal EA, we can have a 0-byte + * payload. That means nothing in this case, so we'll + * simply skip the EA. + */ + error = 0; + goto acl_done; + } + /* + * acl_from_text() requires a NUL-terminated string. The ACL EA, + * however, may not be NUL-terminated. So in that case, we need to + * copy it to a +1 sized buffer, to ensure it's got a terminated string. + */ + if (tcp[entry->length - 1] != 0) { + char *tmpstr = malloc(entry->length + 1); + if (tmpstr == NULL) { + error = -1; + goto exit; + } + strlcpy(tmpstr, tcp, entry->length + 1); + acl = acl_from_text(tmpstr); + free(tmpstr); + } else { + acl = acl_from_text(tcp); + } + + if (acl != NULL) + { + filesec_t fsec_tmp; + + if ((fsec_tmp = filesec_init()) == NULL) + error = -1; + else if((error = fstatx_np(s->dst_fd, &sb, fsec_tmp)) < 0) + error = -1; + else if (filesec_set_property(fsec_tmp, FILESEC_ACL, &acl) < 0) + error = -1; + else { + while (fchmodx_np(s->dst_fd, fsec_tmp) < 0) + { + if (errno == ENOTSUP) + { + if (retry && !copyfile_unset_acl(s)) + { + retry = 0; + continue; + } + } + copyfile_warn("setting security information"); + error = -1; + break; + } + } + acl_free(acl); + filesec_free(fsec_tmp); + +acl_done: + if (error == -1) + goto exit; + } + } + /* And, finally, everything else */ + else + { + if (s->copyIntent || + xattr_preserve_for_intent((char*)entry->name, s->copyIntent) == 1) { + if (s->statuscb) { + int rv; + s->xattr_name = strdup((char*)entry->name); + s->totalCopied = 0; + rv = (*s->statuscb)(COPYFILE_COPY_XATTR, COPYFILE_START, s, s->src, s->dst, s->ctx); + if (s->xattr_name) { + free(s->xattr_name); + s->xattr_name = NULL; + } + if (rv == COPYFILE_QUIT) { + s->err = ECANCELED; + error = -1; + goto exit; + } + } + if (fsetxattr(s->dst_fd, (char *)entry->name, dataptr, entry->length, 0, 0) == -1) { + if (COPYFILE_VERBOSE & s->flags) + copyfile_warn("error %d setting attribute %s", errno, entry->name); + if (s->statuscb) { + int rv; + + s->xattr_name = strdup((char*)entry->name); + rv = (s->statuscb)(COPYFILE_COPY_XATTR, COPYFILE_ERR, s, s->src, s->dst, s->ctx); + if (s->xattr_name) { + free(s->xattr_name); + s->xattr_name = NULL; + } + if (rv == COPYFILE_QUIT) { + error = -1; + goto exit; + } + } else { + error = -1; + goto exit; + } + } else if (s->statuscb) { + int rv; + s->xattr_name = strdup((char*)entry->name); + s->totalCopied = entry->length; + rv = (*s->statuscb)(COPYFILE_COPY_XATTR, COPYFILE_FINISH, s, s->src, s->dst, s->ctx); + if (s->xattr_name) { + free(s->xattr_name); + s->xattr_name = NULL; + } + if (rv == COPYFILE_QUIT) { + error = -1; + s->err = ECANCELED; + goto exit; + } + } + } + } + if (dataptr) { + free(dataptr); + dataptr = NULL; + } + entry = ATTR_NEXT(entry); + } + } + + /* + * Extract the Finder Info. + */ + if (adhdr->entries[0].offset > (hdrsize - sizeof(emptyfinfo))) { + error = -1; + goto exit; + } + + if (bcmp((u_int8_t*)buffer + adhdr->entries[0].offset, emptyfinfo, sizeof(emptyfinfo)) != 0) + { + uint16_t *fFlags; + uint8_t *newFinfo; + enum { kFinderInvisibleMask = 1 << 14 }; + + newFinfo = (u_int8_t*)buffer + adhdr->entries[0].offset; + fFlags = (uint16_t*)&newFinfo[8]; + copyfile_debug(3, " extracting \"%s\" (32 bytes)", XATTR_FINDERINFO_NAME); + if (s->statuscb) { + int rv; + s->xattr_name = (char*)XATTR_FINDERINFO_NAME; + rv = (*s->statuscb)(COPYFILE_COPY_XATTR, COPYFILE_START, s, s->src, s->dst, s->ctx); + s->xattr_name = NULL; + if (rv == COPYFILE_QUIT) { + error = -1; + s->err = ECANCELED; + goto exit; + } else if (rv == COPYFILE_SKIP) { + goto skip_fi; + } + } + error = fsetxattr(s->dst_fd, XATTR_FINDERINFO_NAME, (u_int8_t*)buffer + adhdr->entries[0].offset, sizeof(emptyfinfo), 0, 0); + if (error) { + if (s->statuscb) { + int rv; + s->xattr_name = (char *)XATTR_FINDERINFO_NAME; + rv = (*s->statuscb)(COPYFILE_COPY_XATTR, COPYFILE_ERR, s, s->src, s->dst, s->ctx); + s->xattr_name = NULL; + if (rv == COPYFILE_QUIT) { + error = -1; + s->err = ECANCELED; + goto exit; + } + } + goto exit; + } else if (s->statuscb) { + int rv; + s->xattr_name = (char *)XATTR_FINDERINFO_NAME; + rv = (*s->statuscb)(COPYFILE_COPY_XATTR, COPYFILE_FINISH, s, s->src, s->dst, s->ctx); + s->xattr_name = NULL; + if (rv == COPYFILE_QUIT) { + error = -1; + s->err = ECANCELED; + goto exit; + } + } + if (SWAP16(*fFlags) & kFinderInvisibleMask) + s->internal_flags |= cfMakeFileInvisible; + } +skip_fi: + + /* + * Extract the Resource Fork. + */ + if (adhdr->entries[1].type == AD_RESOURCE && + adhdr->entries[1].length > 0) + { + void * rsrcforkdata = NULL; + size_t length; + off_t offset; + struct stat sb; + struct timeval tval[2]; + + length = adhdr->entries[1].length; + offset = adhdr->entries[1].offset; + rsrcforkdata = malloc(length); + + if (rsrcforkdata == NULL) { + copyfile_debug(1, "could not allocate %zu bytes for rsrcforkdata", + length); + error = -1; + goto bad; + } + + if (fstat(s->dst_fd, &sb) < 0) + { + copyfile_debug(1, "couldn't stat destination file"); + error = -1; + goto bad; + } + + bytes = pread(s->src_fd, rsrcforkdata, length, offset); + if (bytes < (ssize_t)length) + { + if (bytes == -1) + { + copyfile_debug(1, "couldn't read resource fork"); + } + else + { + copyfile_debug(1, + "couldn't read resource fork (only read %d bytes of %d)", + (int)bytes, (int)length); + } + error = -1; + goto bad; + } + if (s->statuscb) { + int rv; + s->xattr_name = (char *)XATTR_RESOURCEFORK_NAME; + rv = (*s->statuscb)(COPYFILE_COPY_XATTR, COPYFILE_START, s, s->src, s->dst, s->ctx); + s->xattr_name = NULL; + if (rv == COPYFILE_QUIT) { + error = -1; + s->err = ECANCELED; + if (rsrcforkdata) + free(rsrcforkdata); + goto exit; + } else if (rv == COPYFILE_SKIP) { + goto bad; + } + } + error = fsetxattr(s->dst_fd, XATTR_RESOURCEFORK_NAME, rsrcforkdata, bytes, 0, 0); + if (error) + { + /* + * For filesystems that do not natively support named attributes, + * the kernel creates an AppleDouble file that -- for compatabilty + * reasons -- has a resource fork containing nothing but a rsrcfork_header_t + * structure that says there are no resources. So, if fsetxattr has + * failed, and the resource fork is that empty structure, *and* the + * target file is a directory, then we do nothing with it. + */ + if ((bytes == sizeof(rsrcfork_header_t)) && + ((sb.st_mode & S_IFMT) == S_IFDIR) && + (memcmp(rsrcforkdata, &empty_rsrcfork_header, bytes) == 0)) { + copyfile_debug(2, "not setting empty resource fork on directory"); + error = errno = 0; + goto bad; + } + if (s->statuscb) { + int rv; + s->xattr_name = (char *)XATTR_RESOURCEFORK_NAME; + rv = (*s->statuscb)(COPYFILE_COPY_XATTR, COPYFILE_ERR, s, s->src, s->dst, s->ctx); + s->xattr_name = NULL; + if (rv == COPYFILE_CONTINUE) { + error = errno = 0; + goto bad; + } + } + copyfile_debug(1, "error %d setting resource fork attribute", error); + error = -1; + goto bad; + } else if (s->statuscb) { + int rv; + s->xattr_name = (char *)XATTR_RESOURCEFORK_NAME; + rv = (*s->statuscb)(COPYFILE_COPY_XATTR, COPYFILE_FINISH, s, s->src, s->dst, s->ctx); + s->xattr_name = NULL; + if (rv == COPYFILE_QUIT) { + error = -1; + s->err = ECANCELED; + if (rsrcforkdata) + free(rsrcforkdata); + goto exit; + } + } + copyfile_debug(3, "extracting \"%s\" (%d bytes)", + XATTR_RESOURCEFORK_NAME, (int)length); + + if (!(s->flags & COPYFILE_STAT)) + { + tval[0].tv_sec = sb.st_atime; + tval[1].tv_sec = sb.st_mtime; + tval[0].tv_usec = tval[1].tv_usec = 0; + + if (futimes(s->dst_fd, tval)) + copyfile_warn("%s: set times", s->dst ? s->dst : "(null dst)"); + } +bad: + if (rsrcforkdata) + free(rsrcforkdata); + } + + if (COPYFILE_STAT & s->flags) + { + error = copyfile_stat(s); + } +exit: + if (buffer) free(buffer); + if (dataptr) free(dataptr); + return error; +} + +static int copyfile_pack_quarantine(copyfile_state_t s, void **buf, ssize_t *len) +{ + int ret = 0; + char qbuf[QTN_SERIALIZED_DATA_MAX]; + size_t qlen = sizeof(qbuf); + + if (s->qinfo == NULL) + { + ret = -1; + goto done; + } + + if (qtn_file_to_data(s->qinfo, qbuf, &qlen) != 0) + { + ret = -1; + goto done; + } + + *buf = malloc(qlen); + if (*buf) + { + memcpy(*buf, qbuf, qlen); + *len = qlen; + } +done: + return ret; +} + +static int copyfile_pack_acl(copyfile_state_t s, void **buf, ssize_t *len) +{ + int ret = 0; + acl_t acl = NULL; + char *acl_text; + + if (filesec_get_property(s->fsec, FILESEC_ACL, &acl) < 0) + { + if (errno != ENOENT) + { + ret = -1; + if (COPYFILE_VERBOSE & s->flags) + copyfile_warn("getting acl"); + } + *len = 0; + goto exit; + } + + if ((acl_text = acl_to_text(acl, len)) != NULL) + { + /* + * acl_to_text() doesn't include the NUL at the endo + * in it's count (*len). It does, however, promise to + * return a valid C string, so we need to up the count + * by 1. + */ + *len = *len + 1; + *buf = malloc(*len); + if (*buf) + memcpy(*buf, acl_text, *len); + else + *len = 0; + acl_free(acl_text); + } + copyfile_debug(2, "copied acl (%ld) %p", *len, *buf); +exit: + if (acl) + acl_free(acl); + return ret; +} + +static int copyfile_pack_rsrcfork(copyfile_state_t s, attr_header_t *filehdr) +{ + ssize_t datasize; + char *databuf = NULL; + int ret = 0; + +/* + * XXX + * do COPYFILE_COPY_XATTR here; no need to + * the work if we want to skip. + */ + + if (s->statuscb) + { + int rv; + + s->xattr_name = (char*)XATTR_RESOURCEFORK_NAME; + + rv = (*s->statuscb)(COPYFILE_COPY_XATTR, COPYFILE_START, s, s->src, s->dst, s->ctx); + s->xattr_name = NULL; + if (rv == COPYFILE_SKIP) { + ret = 0; + goto done; + } + if (rv == COPYFILE_QUIT) { + ret = -1; + s->err = ECANCELED; + goto done; + } + } + /* Get the resource fork size */ + if ((datasize = fgetxattr(s->src_fd, XATTR_RESOURCEFORK_NAME, NULL, 0, 0, 0)) < 0) + { + if (COPYFILE_VERBOSE & s->flags) + copyfile_warn("skipping attr \"%s\" due to error %d", XATTR_RESOURCEFORK_NAME, errno); + return -1; + } + + if (datasize > INT_MAX) { + s->err = EINVAL; + ret = -1; + goto done; + } + + if (s->statuscb) { + int rv; + s->xattr_name = (char*)XATTR_RESOURCEFORK_NAME; + + s->totalCopied = 0; + rv = (*s->statuscb)(COPYFILE_COPY_XATTR, COPYFILE_PROGRESS, s, s->src, s->dst, s->ctx); + s->xattr_name = NULL; + if (rv == COPYFILE_QUIT) { + s->err = ECANCELED; + ret = -1; + goto done; + } + } + if ((databuf = malloc(datasize)) == NULL) + { + copyfile_warn("malloc"); + ret = -1; + goto done; + } + + if (fgetxattr(s->src_fd, XATTR_RESOURCEFORK_NAME, databuf, datasize, 0, 0) != datasize) + { + if (COPYFILE_VERBOSE & s->flags) + copyfile_warn("couldn't read entire resource fork"); + ret = -1; + goto done; + } + + /* Write the resource fork to disk. */ + if (pwrite(s->dst_fd, databuf, datasize, filehdr->appledouble.entries[1].offset) != datasize) + { + if (COPYFILE_VERBOSE & s->flags) + copyfile_warn("couldn't write resource fork"); + } + if (s->statuscb) + { + int rv; + rv = (*s->statuscb)(COPYFILE_COPY_XATTR, COPYFILE_FINISH, s, s->src, s->dst, s->ctx); + if (rv == COPYFILE_QUIT) { + ret = -1; + goto done; + } + } + copyfile_debug(3, "copied %zd bytes of \"%s\" data @ offset 0x%08x", + datasize, XATTR_RESOURCEFORK_NAME, filehdr->appledouble.entries[1].offset); + filehdr->appledouble.entries[1].length = (u_int32_t)datasize; + +done: + if (ret == -1 && s->statuscb) + { + int rv; + rv = (*s->statuscb)(COPYFILE_COPY_XATTR, COPYFILE_ERR, s, s->src, s->dst, s->ctx); + if (rv == COPYFILE_CONTINUE) + ret = 0; + } + if (s->xattr_name) { + s->xattr_name = NULL; + } + if (databuf) + free(databuf); + +/* + * XXX + * Do status callback here + * If ret == -1, then error callback + */ + return ret; +} + +/* + * The opposite of copyfile_unpack(), obviously. + */ +static int copyfile_pack(copyfile_state_t s) +{ + char *attrnamebuf = NULL, *endnamebuf; + void *databuf = NULL; + attr_header_t *filehdr, *endfilehdr; + attr_entry_t *entry; + ssize_t listsize = 0; + char *nameptr; + size_t namelen; + size_t entrylen; + ssize_t datasize; + size_t offset = 0; + int hasrsrcfork = 0; + int error = 0; + int seenq = 0; // Have we seen any quarantine info already? + + filehdr = (attr_header_t *) calloc(1, ATTR_MAX_HDR_SIZE); + + if (filehdr == NULL) { + error = -1; + goto exit; + } else { + endfilehdr = (attr_header_t*)(((char*)filehdr) + ATTR_MAX_HDR_SIZE); + } + + attrnamebuf = calloc(1, ATTR_MAX_HDR_SIZE); + if (attrnamebuf == NULL) { + error = -1; + goto exit; + } else { + endnamebuf = ((char*)attrnamebuf) + ATTR_MAX_HDR_SIZE; + } + + /* + * Fill in the Apple Double Header defaults. + */ + filehdr->appledouble.magic = ADH_MAGIC; + 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].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].length = 0; + bcopy(ADH_MACOSX, filehdr->appledouble.filler, sizeof(filehdr->appledouble.filler)); + + /* + * Fill in the initial Attribute Header. + */ + filehdr->magic = ATTR_HDR_MAGIC; + filehdr->debug_tag = 0; + filehdr->data_start = (u_int32_t)sizeof(attr_header_t); + + /* + * Collect the attribute names. + */ + entry = (attr_entry_t *)((char *)filehdr + sizeof(attr_header_t)); + + /* + * Test if there are acls to copy + */ + if (COPYFILE_ACL & s->flags) + { + acl_t temp_acl = NULL; + if (filesec_get_property(s->fsec, FILESEC_ACL, &temp_acl) < 0) + { + copyfile_debug(2, "no acl entries found (errno = %d)", errno); + } else + { + offset = strlen(XATTR_SECURITY_NAME) + 1; + strcpy(attrnamebuf, XATTR_SECURITY_NAME); + endnamebuf = attrnamebuf + offset; + } + if (temp_acl) + acl_free(temp_acl); + } + + if (COPYFILE_XATTR & s->flags) + { + ssize_t left = ATTR_MAX_HDR_SIZE - offset; + if ((listsize = flistxattr(s->src_fd, attrnamebuf + offset, left, 0)) <= 0) + { + copyfile_debug(2, "no extended attributes found (%d)", errno); + } + if (listsize > left) + { + copyfile_debug(1, "extended attribute list too long"); + listsize = left; + } + + endnamebuf = attrnamebuf + offset + (listsize > 0 ? listsize : 0); + if (endnamebuf > (attrnamebuf + ATTR_MAX_HDR_SIZE)) { + error = -1; + goto exit; + } + + if (listsize > 0) + sort_xattrname_list(attrnamebuf, endnamebuf - attrnamebuf); + + for (nameptr = attrnamebuf; nameptr < endnamebuf; nameptr += namelen) + { + namelen = strlen(nameptr) + 1; + /* Skip over FinderInfo or Resource Fork names */ + if (strcmp(nameptr, XATTR_FINDERINFO_NAME) == 0 || + strcmp(nameptr, XATTR_RESOURCEFORK_NAME) == 0) { + continue; + } + if (strcmp(nameptr, XATTR_QUARANTINE_NAME) == 0) { + seenq = 1; + } + + /* The system should prevent this from happening, but... */ + if (namelen > XATTR_MAXNAMELEN + 1) { + namelen = XATTR_MAXNAMELEN + 1; + } + if (s->copyIntent && + xattr_preserve_for_intent(nameptr, s->copyIntent) == 0) { + // Skip it + size_t amt = endnamebuf - (nameptr + namelen); + memmove(nameptr, nameptr + namelen, amt); + endnamebuf -= namelen; + /* Set namelen to 0 so continue doesn't miss names */ + namelen = 0; + continue; + } + + if (s->statuscb) { + int rv; + char eaname[namelen]; + bcopy(nameptr, eaname, namelen); + eaname[namelen - 1] = 0; // Just to be sure! + s->xattr_name = eaname; + rv = (*s->statuscb)(COPYFILE_COPY_XATTR, COPYFILE_START, s, s->src, s->dst, s->ctx); + s->xattr_name = NULL; + if (rv == COPYFILE_QUIT) { + error = -1; + s->err = ECANCELED; + goto exit; + } else if (rv == COPYFILE_SKIP) { + size_t amt = endnamebuf - (nameptr + namelen); + memmove(nameptr, nameptr + namelen, amt); + endnamebuf -= namelen; + /* Set namelen to 0 so continue doesn't miss names */ + namelen = 0; + continue; + } + } + entry->namelen = namelen; + entry->flags = 0; + if (nameptr + namelen > endnamebuf) { + error = -1; + goto exit; + } + + bcopy(nameptr, &entry->name[0], namelen); + copyfile_debug(2, "copied name [%s]", entry->name); + + entrylen = ATTR_ENTRY_LENGTH(namelen); + entry = (attr_entry_t *)(((char *)entry) + entrylen); + + if ((void*)entry >= (void*)endfilehdr) { + error = -1; + goto exit; + } + + /* Update the attributes header. */ + filehdr->num_attrs++; + filehdr->data_start += (u_int32_t)entrylen; + } + } + + /* + * If we have any quarantine data, we always pack it. + * But if we've already got it in the EA list, don't put it in again. + */ + if (s->qinfo && !seenq) + { + ssize_t left = ATTR_MAX_HDR_SIZE - offset; + /* strlcpy returns number of bytes copied, but we need offset to point to the next byte */ + offset += strlcpy(attrnamebuf + offset, XATTR_QUARANTINE_NAME, left) + 1; + } + + seenq = 0; + /* + * Collect the attribute data. + */ + entry = (attr_entry_t *)((char *)filehdr + sizeof(attr_header_t)); + + for (nameptr = attrnamebuf; nameptr < endnamebuf; nameptr += namelen + 1) + { + namelen = strlen(nameptr); + + if (strcmp(nameptr, XATTR_SECURITY_NAME) == 0) + copyfile_pack_acl(s, &databuf, &datasize); + else if (s->qinfo && strcmp(nameptr, XATTR_QUARANTINE_NAME) == 0) + { + copyfile_pack_quarantine(s, &databuf, &datasize); + } + /* Check for Finder Info. */ + else if (strcmp(nameptr, XATTR_FINDERINFO_NAME) == 0) + { + if (s->statuscb) + { + int rv; + s->xattr_name = (char*)XATTR_FINDERINFO_NAME; + rv = (*s->statuscb)(COPYFILE_COPY_XATTR, COPYFILE_START, s, s->src, s->dst, s->ctx); + s->xattr_name = NULL; + if (rv == COPYFILE_QUIT) + { + s->xattr_name = NULL; + s->err = ECANCELED; + error = -1; + goto exit; + } + else if (rv == COPYFILE_SKIP) + { + s->xattr_name = NULL; + continue; + } + s->totalCopied = 0; + rv = (*s->statuscb)(COPYFILE_COPY_XATTR, COPYFILE_PROGRESS, s, s->src, s->dst, s->ctx); + s->xattr_name = NULL; + if (rv == COPYFILE_QUIT) + { + s->err = ECANCELED; + error = -1; + goto exit; + } + } + datasize = fgetxattr(s->src_fd, nameptr, (u_int8_t*)filehdr + filehdr->appledouble.entries[0].offset, 32, 0, 0); + if (datasize < 0) + { + if (s->statuscb) { + int rv; + s->xattr_name = strdup(nameptr); + rv = (*s->statuscb)(COPYFILE_COPY_XATTR, COPYFILE_ERR, s, s->src, s->dst, s->ctx); + if (s->xattr_name) { + free(s->xattr_name); + s->xattr_name = NULL; + } + if (rv == COPYFILE_QUIT) { + error = -1; + goto exit; + } + } + if (COPYFILE_VERBOSE & s->flags) + copyfile_warn("skipping attr \"%s\" due to error %d", nameptr, errno); + } else if (datasize != 32) + { + if (COPYFILE_VERBOSE & s->flags) + copyfile_warn("unexpected size (%ld) for \"%s\"", datasize, nameptr); + } else + { + if (COPYFILE_VERBOSE & s->flags) + copyfile_warn(" copied 32 bytes of \"%s\" data @ offset 0x%08x", + XATTR_FINDERINFO_NAME, filehdr->appledouble.entries[0].offset); + if (s->statuscb) { + int rv; + s->xattr_name = strdup(nameptr); + rv = (*s->statuscb)(COPYFILE_COPY_XATTR, COPYFILE_FINISH, s, s->src, s->dst, s->ctx); + if (s->xattr_name) { + free(s->xattr_name); + s->xattr_name = NULL; + } + if (rv == COPYFILE_QUIT) { + error = -1; + goto exit; + } + } + } + continue; /* finder info doesn't have an attribute entry */ + } + /* Check for Resource Fork. */ + else if (strcmp(nameptr, XATTR_RESOURCEFORK_NAME) == 0) + { + hasrsrcfork = 1; + continue; + } else + { + /* Just a normal attribute. */ + if (s->statuscb) + { + int rv; + s->xattr_name = strdup(nameptr); + s->totalCopied = 0; + rv = (*s->statuscb)(COPYFILE_COPY_XATTR, COPYFILE_PROGRESS, s, s->src, s->dst, s->ctx); + if (s->xattr_name) { + free(s->xattr_name); + s->xattr_name = NULL; + } + /* + * Due to the nature of the packed file, we can't skip at this point. + */ + if (rv == COPYFILE_QUIT) + { + s->err = ECANCELED; + error = -1; + goto exit; + } + } + datasize = fgetxattr(s->src_fd, nameptr, NULL, 0, 0, 0); + if (datasize == 0) + goto next; + if (datasize < 0) + { + if (COPYFILE_VERBOSE & s->flags) + copyfile_warn("skipping attr \"%s\" due to error %d", nameptr, errno); + if (s->statuscb) + { + int rv; + s->xattr_name = strdup(nameptr); + rv = (*s->statuscb)(COPYFILE_COPY_XATTR, COPYFILE_ERR, s, s->src, s->dst, s->ctx); + if (s->xattr_name) { + free(s->xattr_name); + s->xattr_name = NULL; + } + if (rv == COPYFILE_QUIT) + { + s->err = ECANCELED; + error = -1; + goto exit; + } + } + goto next; + } + if (datasize > XATTR_MAXATTRLEN) + { + if (COPYFILE_VERBOSE & s->flags) + copyfile_warn("skipping attr \"%s\" (too big)", nameptr); + goto next; + } + databuf = malloc(datasize); + if (databuf == NULL) { + error = -1; + continue; + } + datasize = fgetxattr(s->src_fd, nameptr, databuf, datasize, 0, 0); + if (s->statuscb) { + int rv; + s->xattr_name = strdup(nameptr); + rv = (*s->statuscb)(COPYFILE_COPY_XATTR, COPYFILE_FINISH, s, s->src, s->dst, s->ctx); + if (s->xattr_name) { + free(s->xattr_name); + s->xattr_name = NULL; + } + if (rv == COPYFILE_QUIT) { + s->err = ECANCELED; + error = -1; + goto exit; + } + } + } + + entry->length = (u_int32_t)datasize; + entry->offset = filehdr->data_start + filehdr->data_length; + + filehdr->data_length += (u_int32_t)datasize; +#if 0 + /* + * >>> WARNING <<< + * This assumes that the data is fits in memory (not + * the case when there are lots of attributes or one of + * the attributes is very large. + */ + if (entry->offset > ATTR_MAX_SIZE || + (entry->offset + datasize > ATTR_MAX_SIZE)) { + error = 1; + } else { + bcopy(databuf, (char*)filehdr + entry->offset, datasize); + } +#else + if (pwrite(s->dst_fd, databuf, datasize, entry->offset) != datasize) { + error = 1; + } +#endif + free(databuf); + + copyfile_debug(3, "copied %ld bytes of \"%s\" data @ offset 0x%08x", datasize, nameptr, entry->offset); +next: + /* bump to next entry */ + entrylen = ATTR_ENTRY_LENGTH(entry->namelen); + entry = (attr_entry_t *)((char *)entry + entrylen); + } + + /* Now we know where the resource fork data starts. */ + filehdr->appledouble.entries[1].offset = (filehdr->data_start + filehdr->data_length); + + /* We also know the size of the "Finder Info entry. */ + filehdr->appledouble.entries[0].length = + filehdr->appledouble.entries[1].offset - filehdr->appledouble.entries[0].offset; + + filehdr->total_size = filehdr->appledouble.entries[1].offset; + + /* Copy Resource Fork. */ + if (hasrsrcfork && (error = copyfile_pack_rsrcfork(s, filehdr))) + goto exit; + + /* Write the header to disk. */ + datasize = filehdr->data_start; + + swap_adhdr(&filehdr->appledouble); + swap_attrhdr(filehdr); + swap_attrhdr_entries(filehdr); + + if (pwrite(s->dst_fd, filehdr, datasize, 0) != datasize) + { + if (COPYFILE_VERBOSE & s->flags) + copyfile_warn("couldn't write file header"); + error = -1; + goto exit; + } +exit: + if (filehdr) free(filehdr); + if (attrnamebuf) free(attrnamebuf); + + if (error) + return error; + else + return copyfile_stat(s); +} diff --git a/src/copyfile/copyfile.h b/src/copyfile/copyfile.h new file mode 100644 index 000000000..91dfe87aa --- /dev/null +++ b/src/copyfile/copyfile.h @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2004-2010 Apple, Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ +#ifndef _COPYFILE_H_ /* version 0.1 */ +#define _COPYFILE_H_ + +/* + * This API facilitates the copying of files and their associated + * metadata. There are several open source projects that need + * modifications to support preserving extended attributes and ACLs + * and this API collapses several hundred lines of modifications into + * one or two calls. + */ + +/* private */ +#include +#include + +__BEGIN_DECLS +struct _copyfile_state; +typedef struct _copyfile_state * copyfile_state_t; +typedef uint32_t copyfile_flags_t; + +/* public */ + +/* receives: + * from path to source file system object + * to path to destination file system object + * state opaque blob for future extensibility + * Must be NULL in current implementation + * flags (described below) + * returns: + * int negative for error + */ + +int copyfile(const char *from, const char *to, copyfile_state_t state, copyfile_flags_t flags); +int fcopyfile(int from_fd, int to_fd, copyfile_state_t, copyfile_flags_t flags); + +int copyfile_state_free(copyfile_state_t); +copyfile_state_t copyfile_state_alloc(void); + + +int copyfile_state_get(copyfile_state_t s, uint32_t flag, void * dst); +int copyfile_state_set(copyfile_state_t s, uint32_t flag, const void * src); + +typedef int (*copyfile_callback_t)(int, int, copyfile_state_t, const char *, const char *, void *); + +#define COPYFILE_STATE_SRC_FD 1 +#define COPYFILE_STATE_SRC_FILENAME 2 +#define COPYFILE_STATE_DST_FD 3 +#define COPYFILE_STATE_DST_FILENAME 4 +#define COPYFILE_STATE_QUARANTINE 5 +#define COPYFILE_STATE_STATUS_CB 6 +#define COPYFILE_STATE_STATUS_CTX 7 +#define COPYFILE_STATE_COPIED 8 +#define COPYFILE_STATE_XATTRNAME 9 + + +#define COPYFILE_DISABLE_VAR "COPYFILE_DISABLE" + +/* flags for copyfile */ + +#define COPYFILE_ACL (1<<0) +#define COPYFILE_STAT (1<<1) +#define COPYFILE_XATTR (1<<2) +#define COPYFILE_DATA (1<<3) + +#define COPYFILE_SECURITY (COPYFILE_STAT | COPYFILE_ACL) +#define COPYFILE_METADATA (COPYFILE_SECURITY | COPYFILE_XATTR) +#define COPYFILE_ALL (COPYFILE_METADATA | COPYFILE_DATA) + +#define COPYFILE_RECURSIVE (1<<15) /* Descend into hierarchies */ +#define COPYFILE_CHECK (1<<16) /* return flags for xattr or acls if set */ +#define COPYFILE_EXCL (1<<17) /* fail if destination exists */ +#define COPYFILE_NOFOLLOW_SRC (1<<18) /* don't follow if source is a symlink */ +#define COPYFILE_NOFOLLOW_DST (1<<19) /* don't follow if dst is a symlink */ +#define COPYFILE_MOVE (1<<20) /* unlink src after copy */ +#define COPYFILE_UNLINK (1<<21) /* unlink dst before copy */ +#define COPYFILE_NOFOLLOW (COPYFILE_NOFOLLOW_SRC | COPYFILE_NOFOLLOW_DST) + +#define COPYFILE_PACK (1<<22) +#define COPYFILE_UNPACK (1<<23) + +#define COPYFILE_VERBOSE (1<<30) + +#define COPYFILE_RECURSE_ERROR 0 +#define COPYFILE_RECURSE_FILE 1 +#define COPYFILE_RECURSE_DIR 2 +#define COPYFILE_RECURSE_DIR_CLEANUP 3 +#define COPYFILE_COPY_DATA 4 +#define COPYFILE_COPY_XATTR 5 + +#define COPYFILE_START 1 +#define COPYFILE_FINISH 2 +#define COPYFILE_ERR 3 +#define COPYFILE_PROGRESS 4 + +#define COPYFILE_CONTINUE 0 +#define COPYFILE_SKIP 1 +#define COPYFILE_QUIT 2 + +__END_DECLS + +#endif /* _COPYFILE_H_ */ diff --git a/src/copyfile/copyfile_private.h b/src/copyfile/copyfile_private.h new file mode 100644 index 000000000..0111e0d75 --- /dev/null +++ b/src/copyfile/copyfile_private.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2013 Apple, Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#ifndef _COPYFILE_PRIVATE_H +# define _COPYFILE_PRIVATE_H + +/* + * Set (or get) the intent type; see xattr_properties.h for details. + * This command uses a pointer to CopyOperationIntent_t as the parameter. + */ +# define COPYFILE_STATE_INTENT 256 + +/* + * File flags that are not preserved when copying stat information. + */ +#define COPYFILE_OMIT_FLAGS (UF_TRACKED | SF_RESTRICTED) + +#endif /* _COPYFILE_PRIVATE_H */ diff --git a/src/copyfile/xattr_flags.c b/src/copyfile/xattr_flags.c new file mode 100644 index 000000000..8e8fd0512 --- /dev/null +++ b/src/copyfile/xattr_flags.c @@ -0,0 +1,340 @@ +/* + * Copyright (c) 2013 Apple, Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define FLAG_DELIM_CHAR '#' +#define FLAG_DELIM_STR "#" + +/* + * Some default propeteries for EAs we know about internally. + */ +struct defaultList { + const char *eaName; + const char *propList; + int flags; // See below +}; + +#define propFlagsPrefix 0x0001 // The name is a prefix, so only look at that part + +static const struct defaultList *defaultPropertyTable = NULL; + +static const struct defaultList +defaultUnboxedPropertyTable[] = { + { "com.apple.quarantine", "PCS", 0 }, // not public + { "com.apple.TextEncoding", "CS", 0 }, // Content-dependent, public + { "com.apple.metadata:", "PS", propFlagsPrefix }, // Don't export, keep for copy & safe save + { "com.apple.security.", "S", propFlagsPrefix }, + { XATTR_RESOURCEFORK_NAME, "PCS", 0 }, // Don't keep for safe save + { XATTR_FINDERINFO_NAME, "PCS", 0 }, // Same as ResourceFork + { 0, 0, 0 }, +}; + +static const struct defaultList +defaultSandboxedPropertyTable[] = { + { "com.apple.quarantine", "PCS", 0 }, // not public + { "com.apple.TextEncoding", "CS", 0 }, // Content-dependent, public + { "com.apple.metadata:", "PS", propFlagsPrefix }, // Don't export, keep for copy & safe save + { "com.apple.security.", "N", propFlagsPrefix }, + { XATTR_RESOURCEFORK_NAME, "PCS", 0 }, // Don't keep for safe save + { XATTR_FINDERINFO_NAME, "PCS", 0 }, // Same as ResourceFork + { 0, 0, 0 }, +}; + +/* + * The property lists on an EA are set by having a suffix character, + * and then a list of characters. In general, we're choosing upper-case + * to indicate the property is set, and lower-case to indicate it's to be + * cleared. + */ +struct propertyListMapping { + char enable; // Character to enable + char disable; // Character to disable -- usually lower-case of enable + xattr_operation_intent_t value; +}; +static const struct propertyListMapping +PropertyListMapTable[] = { + { 'C', 'c', XATTR_FLAG_CONTENT_DEPENDENT }, + { 'P', 'p', XATTR_FLAG_NO_EXPORT }, + { 'N', 'n', XATTR_FLAG_NEVER_PRESERVE }, + { 'S', 's', XATTR_FLAG_SYNCABLE }, + { 0, 0, 0 }, +}; + +/* + * Given a converted property list (that is, converted to the + * xattr_operation_intent_t type), and an intent, determine if + * it should be preserved or not. + * + * I've chosen to use a block instead of a simple mask on the belief + * that the question may be moderately complex. If it ends up not being + * so, then this can simply be turned into a mask of which bits to check + * as being exclusionary. + */ +static const struct divineIntent { + xattr_operation_intent_t intent; + int (^checker)(xattr_flags_t); +} intentTable[] = { + { XATTR_OPERATION_INTENT_COPY, ^(xattr_flags_t flags) { + if (flags & XATTR_FLAG_NEVER_PRESERVE) + return 0; + return 1; + } }, + { XATTR_OPERATION_INTENT_SAVE, ^(xattr_flags_t flags) { + if (flags & (XATTR_FLAG_CONTENT_DEPENDENT | XATTR_FLAG_NEVER_PRESERVE)) + return 0; + return 1; + } }, + { XATTR_OPERATION_INTENT_SHARE, ^(xattr_flags_t flags) { + if ((flags & (XATTR_FLAG_NO_EXPORT | XATTR_FLAG_NEVER_PRESERVE)) != 0) + return 0; + return 1; + } }, + { XATTR_OPERATION_INTENT_SYNC, ^(xattr_flags_t flags) { + return (flags & (XATTR_FLAG_SYNCABLE | XATTR_FLAG_NEVER_PRESERVE)) == XATTR_FLAG_SYNCABLE; + } }, + { 0, 0 }, +}; + + +/* + * If an EA name is in the default list, find it, and return the property + * list string for it. + */ +static const char * +nameInDefaultList(const char *eaname) +{ + const struct defaultList *retval; + static dispatch_once_t onceToken; + + dispatch_once(&onceToken, ^{ + if (_xpc_runtime_is_app_sandboxed()) { + defaultPropertyTable = defaultSandboxedPropertyTable; + } else { + defaultPropertyTable = defaultUnboxedPropertyTable; + } + }); + + for (retval = defaultPropertyTable; retval->eaName; retval++) { + if ((retval->flags & propFlagsPrefix) != 0 && + strncmp(retval->eaName, eaname, strlen(retval->eaName)) == 0) + return retval->propList; + if (strcmp(retval->eaName, eaname) == 0) + return retval->propList; + } + return NULL; +} + +/* + * Given an EA name, see if it has a property list in it, and + * return a pointer to it. All this is doing is looking for + * the delimiter, and returning the string after that. Returns + * NULL if the delimiter isn't found. Note that an empty string + * is a valid property list, as far as we're concerned. + */ +static const char * +findPropertyList(const char *eaname) +{ + const char *ptr = strrchr(eaname, '#'); + if (ptr) + return ptr+1; + return NULL; +} + +/* + * Convert a property list string (e.g., "pCd") into a + * xattr_operation_intent_t type. + */ +static xattr_operation_intent_t +stringToProperties(const char *proplist) +{ + xattr_operation_intent_t retval = 0; + const char *ptr; + + // A switch would be more efficient, but less generic. + for (ptr = proplist; *ptr; ptr++) { + const struct propertyListMapping *mapPtr; + for (mapPtr = PropertyListMapTable; mapPtr->enable; mapPtr++) { + if (*ptr == mapPtr->enable) { + retval |= mapPtr->value; + } else if (*ptr == mapPtr->disable) { + retval &= ~mapPtr->value; + } + } + } + return retval; +} + +/* + * Given an EA name (e.g., "com.apple.lfs.hfs.test"), and a + * xattr_operation_intent_t value (it's currently an integral value, so + * just a bitmask), cycle through the list of known properties, and return + * a string with the EA name, and the property list appended. E.g., we + * might return "com.apple.lfs.hfs.test#pD". + * + * The tricky part of this funciton is that it will not append any letters + * if the value is only the default properites. In that case, it will copy + * the EA name, and return that. + * + * It returns NULL if there was an error. The two errors right now are + * no memory (strdup failed), in which case it will set errno to ENOMEM; and + * the resulting EA name is longer than XATTR_MAXNAMELEN, in which case it + * sets errno to ENAMETOOLONG. + * + * (Note that it also uses ENAMETOOLONG if the buffer it's trying to set + * gets too large. I honestly can't see how that would happen, but it's there + * for sanity checking. That would require having more than 64 bits to use.) + */ +char * +xattr_name_with_flags(const char *orig, xattr_flags_t propList) +{ + char *retval = NULL; + char suffix[66] = { 0 }; // 66: uint64_t for property types, plus '#', plus NUL + char *cur = suffix; + const struct propertyListMapping *mapPtr; + + *cur++ = '#'; + for (mapPtr = PropertyListMapTable; mapPtr->enable; mapPtr++) { + if ((propList & mapPtr->value) != 0) { + *cur++ = mapPtr->enable; + } + if (cur >= (suffix + sizeof(suffix))) { + errno = ENAMETOOLONG; + return NULL; + } + + } + + + if (cur == suffix + 1) { + // No changes made + retval = strdup(orig); + if (retval == NULL) + errno = ENOMEM; + } else { + const char *defaultEntry = NULL; + if ((defaultEntry = nameInDefaultList(orig)) != NULL && + strcmp(defaultEntry, suffix + 1) == 0) { + // Just use the name passed in + retval = strdup(orig); + } else { + asprintf(&retval, "%s%s", orig, suffix); + } + if (retval == NULL) { + errno = ENOMEM; + } else { + if (strlen(retval) > XATTR_MAXNAMELEN) { + free(retval); + retval = NULL; + errno = ENAMETOOLONG; + } + } + } + return retval; +} + +char * +xattr_name_without_flags(const char *eaname) +{ + char *retval = NULL; + char *tmp; + + if ((tmp = strrchr(eaname, FLAG_DELIM_CHAR)) == NULL) { + retval = strdup(eaname); + } else { + retval = calloc(tmp - eaname + 1, 1); + if (retval) { + strlcpy(retval, eaname, tmp - eaname + 1); + } + } + if (retval == NULL) { + errno = ENOMEM; + } + return retval; +} + +int +xattr_intent_with_flags(xattr_operation_intent_t intent, xattr_flags_t flags) +{ + const struct divineIntent *ip; + + for (ip = intentTable; ip->intent; ip++) { + if (ip->intent == intent) { + return ip->checker(flags); + } + } + if ((flags & XATTR_FLAG_NEVER_PRESERVE) != 0) + return 0; // Special case, don't try to copy this one + + return 1; // Default +} + +xattr_flags_t +xattr_flags_from_name(const char *eaname) +{ + xattr_flags_t retval = 0; + const char *propList; + + propList = findPropertyList(eaname); + if (propList == NULL) { + propList = nameInDefaultList(eaname); + } + if (propList != NULL) { + retval = stringToProperties(propList); + } + + return retval; +} + +/* + * Indicate whether an EA should be preserved, when using the + * given intent. + * + * This returns 0 if it should not be preserved, and 1 if it should. + * + * It simply looks through the tables we have above, and compares the + * xattr_operation_intent_t for the EA with the intent. If the + * EA doesn't have any properties, and it's not on the default list, the + * default is to preserve it. + */ + +int +xattr_preserve_for_intent(const char *eaname, xattr_operation_intent_t intent) +{ + xattr_flags_t flags = xattr_flags_from_name(eaname); + + return xattr_intent_with_flags(intent, flags); +} + +#include "xattr_properties.h" diff --git a/src/copyfile/xattr_flags.h b/src/copyfile/xattr_flags.h new file mode 100644 index 000000000..032b6ad01 --- /dev/null +++ b/src/copyfile/xattr_flags.h @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2013 Apple, Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#ifndef _XATTR_FLAGS_H +#define _XATTR_FLAGS_H + +#include + +#include +#include + +__BEGIN_DECLS + +/* + * xattr_operation_intent_t is used to declare what the intent of the copy is. + * Not a bit-field (for now, at least). + * + * XATTR_OPERATION_INTENT_COPY indicates that the EA is attached to an object + * that is simply being copied. E.g., cp src dst + * + * XATTR_OPERATION_INTENT_SAVE indicates that the EA is attached to an object + * being saved; as in a "safe save," the destination is being replaced by + * the source, so the question is whether the EA should be applied to the + * destination, or generated anew. + * + * XATTR_OPERATION_INTENT_SHARE indicates that the EA is attached to an object that + * is being given out to other people. For example, saving to a public folder, + * or attaching to an email message. + * + * XATTR_OPERATION_INTENT_SYNC indicates that the EA is attached to an object that + * is being synced to other storages for the same user. For example synced to + * iCloud. + */ + +#define XATTR_OPERATION_INTENT_COPY 1 +#define XATTR_OPERATION_INTENT_SAVE 2 +#define XATTR_OPERATION_INTENT_SHARE 3 +#define XATTR_OPERATION_INTENT_SYNC 4 + +typedef unsigned int xattr_operation_intent_t; + +typedef uint64_t xattr_flags_t; + +/* + * Various properties used to determine how to handle the xattr during + * copying. The intent is that the default is reasonable for most xattrs. + */ + +/* + * XATTR_FLAG_NO_EXPORT + * Declare that the extended property should not be exported; this is + * deliberately a bit vague, but this is used by XATTR_OPERATION_INTENT_SHARE + * to indicate not to preserve the xattr. + */ +#define XATTR_FLAG_NO_EXPORT ((xattr_flags_t)0x0001) + +/* + * XATTR_FLAG_CONTENT_DEPENDENT + * Declares the extended attribute to be tied to the contents of the file (or + * vice versa), such that it should be re-created when the contents of the + * file change. Examples might include cryptographic keys, checksums, saved + * position or search information, and text encoding. + * + * This property causes the EA to be preserved for copy and share, but not for + * safe save. (In a safe save, the EA exists on the original, and will not + * be copied to the new version.) + */ +#define XATTR_FLAG_CONTENT_DEPENDENT ((xattr_flags_t)0x0002) + +/* + * XATTR_FLAG_NEVER_PRESERVE + * Declares that the extended attribute is never to be copied, for any + * intention type. + */ +#define XATTR_FLAG_NEVER_PRESERVE ((xattr_flags_t)0x0004) + +/* + * XATTR_FLAG_SYNCABLE + * Declares that the extended attribute is to be synced, used by the + * XATTR_OPERATION_ITENT_SYNC intention. Syncing tends to want to minimize the + * amount of metadata synced around, hence the default behavior is for the EA + * NOT to be synced, even if it would else be preserved for the + * XATTR_OPERATION_ITENT_COPY intention. + */ +#define XATTR_FLAG_SYNCABLE ((xattr_flags_t)0x0008) + +/* Given a named extended attribute, and a copy intent, should the EA be preserved? */ +extern int xattr_preserve_for_intent(const char *, xattr_operation_intent_t) __OSX_AVAILABLE_STARTING( __MAC_10_10, __IPHONE_8_0); + +/* + * 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 *xattr_name_with_flags(const char *, xattr_flags_t) __OSX_AVAILABLE_STARTING( __MAC_10_10, __IPHONE_8_0); + +/* + * 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 *xattr_name_without_flags(const char *) __OSX_AVAILABLE_STARTING( __MAC_10_10, __IPHONE_8_0); + +/* + * 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 xattr_flags_t xattr_flags_from_name(const char *) __OSX_AVAILABLE_STARTING( __MAC_10_10, __IPHONE_8_0); + +/* + * Given an xattr_operation_intent_t and an xattr_flags_t, return whether it should + * be preserved. The default (in case either flags or intent is 0, or unknown + * values) is to return 1; it only returns 0 if the flags and intent indicate it + * should not be preserved. + */ +extern int xattr_intent_with_flags(xattr_operation_intent_t, xattr_flags_t) __OSX_AVAILABLE_STARTING( __MAC_10_10, __IPHONE_8_0); + +__END_DECLS + +#endif /* _XATTR_FLAGS_H */ diff --git a/src/copyfile/xattr_name_with_flags.3 b/src/copyfile/xattr_name_with_flags.3 new file mode 100644 index 000000000..509f8b133 --- /dev/null +++ b/src/copyfile/xattr_name_with_flags.3 @@ -0,0 +1,123 @@ +.\" +.\" Copyright (c) 2013 Apple Computer, Inc. All rights reserved. +.\" +.Dd October 7, 2013 +.Dt XATTR_NAME_WITH_FLAGS 3 +.Os +.Sh NAME +.Nm xattr_preserve_for_intent , xattr_name_with_flags , xattr_name_without_flags , +.Nm xattr_flags_from_name , xattr_intent_with_flags +.Sh LIBRARY +.Lb libc +.Sh SYNOPSIS +.In xattr_properties.h +.Ft int +.Fn xattr_preserve_for_intent "const char *" "xattr_operation_intent_t" +.Ft char * +.Fn xattr_name_with_flags "const char *" "xattr_flags_t" +.Ft char * +.Fn xattr_name_without_flags "const char *" +.Ft xattr_flags_t +.Fn xattr_flags_from_name "const char *" +.Ft int +.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 +determine which is allowed or not. +.Pp +The +.Fn xattr_name_with_flags +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 +.Fn xattr_flags_from_name , +which will return the flags encoded in a name. +.Pp +The values returned by +.Fn xattr_name_with_flags +and +.Fn xattr_name_without_flags +are allocated using +.Xr malloc 3 , +and should be released by the caller, using +.Xr free 3 . +.Pp +These functions also have an internal table of pre-defined names, maintained +by the operating system. +.Pp +The function +.Fn xattr_intent_with_flags +will return 0 if the +.Ar flags +argument indicates it should not be preserved for the given +intent, or 1 if it should. +.Pp +The function +.Fn xattr_presere_for_intent +combines the functions above, and will return zero if the +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: +.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. +.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 som 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. +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. +.El +.Sh FLAGS +Various flags are defined by the type +.Dt 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. +This is used with +.Dv XATTR_OPERATION_INTENT_SHARE . +.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. +.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. 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. +.El +.Sh EXAMPLE +The following example is a simple function that, given an extended attribute +name and an operation intent, will return whether or not the extended attribute +should be copied. (This essentially does what +.Fn xattr_preserve_for_intent +does.) +.Bd -literal -offset indent +int +ShouldCopyEA(const char *eaName, xattr_operation_intent_t intent) +{ + xattr_flags_t flags = xattr_flags_from_name(eaName); + return xattr_intent_with_flags(intent, flags); +} +.Ed +.Sh HISTORY +These functions first appeared in Mac OS in 2013. diff --git a/src/copyfile/xattr_properties.h b/src/copyfile/xattr_properties.h new file mode 100644 index 000000000..21d9a29eb --- /dev/null +++ b/src/copyfile/xattr_properties.h @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2013 Apple, Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#ifndef _XATTR_PROPERTIES_H +#define _XATTR_PROPERTIES_H + +#include + +#include +#include + +__BEGIN_DECLS + +/* + * CopyOperationIntent_t is used to declare what the intent of the copy is. + * Not a bit-field (for now, at least). + * + * CopyOperationIntentCopy indicates that the EA is attached to an object + * that is simply being copied. E.g., cp src dst + * + * CopyOperationIntentSave indicates that the EA is attached to an object + * being saved; as in a "safe save," the destination is being replaced by + * the source, so the question is whether the EA should be applied to the + * destination, or generated anew. + * + * CopyOperationIntentShare indicates that the EA is attached to an object that + * is being given out to other people. For example, saving to a public folder, + * or attaching to an email message. + */ + +typedef enum { + CopyOperationIntentCopy = 1, + CopyOperationIntentSave, + CopyOperationIntentShare, +} CopyOperationIntent_t; + +typedef uint64_t CopyOperationProperties_t; + +/* + * Various properties used to determine how to handle the xattr during + * copying. The intent is that the default is reasonable for most xattrs. + */ + +/* + * kCopyOperationPropertyNoExport + * Declare that the extended property should not be exported; this is + * deliberately a bit vague, but this is used by CopyOperationIntentShare + * to indicate not to preserve the xattr. + */ +#define kCopyOperationPropertyNoExport ((CopyOperationProperties_t)0x0001) + +/* + * kCopyOperationPropertyContentDependent + * Declares the extended attribute to be tied to the contents of the file (or + * vice versa), such that it should be re-created when the contents of the + * file change. Examples might include cryptographic keys, checksums, saved + * position or search information, and text encoding. + * + * This property causes the EA to be preserved for copy and share, but not for + * safe save. (In a safe save, the EA exists on the original, and will not + * be copied to the new version.) + */ +#define kCopyOperationPropertyContentDependent ((CopyOperationProperties_t)0x0002) + +/* + * kCopyOperationPropertyNeverPreserve + * Declares that the extended attribute is never to be copied, for any + * intention type. + */ +#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 */ diff --git a/src/libsystem/CMakeLists.txt b/src/libsystem/CMakeLists.txt index f9f00a426..0c8b287a8 100644 --- a/src/libsystem/CMakeLists.txt +++ b/src/libsystem/CMakeLists.txt @@ -39,7 +39,7 @@ SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) add_library(system SHARED ${libsystem_sources}) target_link_libraries(system system_malloc system_c system_kernel keymgr system_m system_info system_notify unwind libdispatch_shared objc launch dyld - removefile) + removefile system_copyfile) install(TARGETS system DESTINATION lib${SUFFIX}/darling) -- 2.51.2