]>
Commit | Line | Data |
---|---|---|
11f767b3 A |
1 | /* |
2 | * Copyright (c) 2013 Apple, Inc. All rights reserved. | |
3 | * | |
4 | * @APPLE_LICENSE_HEADER_START@ | |
5 | * | |
6 | * This file contains Original Code and/or Modifications of Original Code | |
7 | * as defined in and that are subject to the Apple Public Source License | |
8 | * Version 2.0 (the 'License'). You may not use this file except in | |
9 | * compliance with the License. Please obtain a copy of the License at | |
10 | * http://www.opensource.apple.com/apsl/ and read it before using this | |
11 | * file. | |
12 | * | |
13 | * The Original Code and all software distributed under the License are | |
14 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER | |
15 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, | |
16 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, | |
17 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. | |
18 | * Please see the License for the specific language governing rights and | |
19 | * limitations under the License. | |
20 | * | |
21 | * @APPLE_LICENSE_HEADER_END@ | |
22 | */ | |
23 | ||
24 | #include <stdio.h> | |
25 | #include <stdlib.h> | |
26 | #include <string.h> | |
27 | #include <unistd.h> | |
28 | #include <err.h> | |
29 | #include <errno.h> | |
30 | #include <sys/types.h> | |
31 | #include <sys/xattr.h> | |
618b37c8 A |
32 | #include <dispatch/dispatch.h> |
33 | #include <xpc/private.h> | |
11f767b3 | 34 | |
618b37c8 A |
35 | #include <xattr_flags.h> |
36 | ||
37 | #define FLAG_DELIM_CHAR '#' | |
38 | #define FLAG_DELIM_STR "#" | |
11f767b3 A |
39 | |
40 | /* | |
41 | * Some default propeteries for EAs we know about internally. | |
42 | */ | |
43 | struct defaultList { | |
44 | const char *eaName; | |
45 | const char *propList; | |
46 | int flags; // See below | |
47 | }; | |
48 | ||
49 | #define propFlagsPrefix 0x0001 // The name is a prefix, so only look at that part | |
50 | ||
618b37c8 A |
51 | static const struct defaultList *defaultPropertyTable = NULL; |
52 | ||
11f767b3 | 53 | static const struct defaultList |
618b37c8 A |
54 | defaultUnboxedPropertyTable[] = { |
55 | { "com.apple.quarantine", "PCS", 0 }, // not public | |
56 | { "com.apple.TextEncoding", "CS", 0 }, // Content-dependent, public | |
57 | { "com.apple.metadata:", "PS", propFlagsPrefix }, // Don't export, keep for copy & safe save | |
58 | { "com.apple.security.", "S", propFlagsPrefix }, | |
59 | { XATTR_RESOURCEFORK_NAME, "PCS", 0 }, // Don't keep for safe save | |
60 | { XATTR_FINDERINFO_NAME, "PCS", 0 }, // Same as ResourceFork | |
11f767b3 A |
61 | { 0, 0, 0 }, |
62 | }; | |
63 | ||
618b37c8 A |
64 | static const struct defaultList |
65 | defaultSandboxedPropertyTable[] = { | |
66 | { "com.apple.quarantine", "PCS", 0 }, // not public | |
67 | { "com.apple.TextEncoding", "CS", 0 }, // Content-dependent, public | |
68 | { "com.apple.metadata:", "PS", propFlagsPrefix }, // Don't export, keep for copy & safe save | |
69 | { "com.apple.security.", "N", propFlagsPrefix }, | |
70 | { XATTR_RESOURCEFORK_NAME, "PCS", 0 }, // Don't keep for safe save | |
71 | { XATTR_FINDERINFO_NAME, "PCS", 0 }, // Same as ResourceFork | |
72 | { 0, 0, 0 }, | |
73 | }; | |
74 | ||
11f767b3 A |
75 | /* |
76 | * The property lists on an EA are set by having a suffix character, | |
77 | * and then a list of characters. In general, we're choosing upper-case | |
78 | * to indicate the property is set, and lower-case to indicate it's to be | |
79 | * cleared. | |
80 | */ | |
81 | struct propertyListMapping { | |
82 | char enable; // Character to enable | |
83 | char disable; // Character to disable -- usually lower-case of enable | |
618b37c8 | 84 | xattr_operation_intent_t value; |
11f767b3 A |
85 | }; |
86 | static const struct propertyListMapping | |
87 | PropertyListMapTable[] = { | |
618b37c8 A |
88 | { 'C', 'c', XATTR_FLAG_CONTENT_DEPENDENT }, |
89 | { 'P', 'p', XATTR_FLAG_NO_EXPORT }, | |
90 | { 'N', 'n', XATTR_FLAG_NEVER_PRESERVE }, | |
91 | { 'S', 's', XATTR_FLAG_SYNCABLE }, | |
11f767b3 A |
92 | { 0, 0, 0 }, |
93 | }; | |
94 | ||
95 | /* | |
96 | * Given a converted property list (that is, converted to the | |
618b37c8 | 97 | * xattr_operation_intent_t type), and an intent, determine if |
11f767b3 A |
98 | * it should be preserved or not. |
99 | * | |
100 | * I've chosen to use a block instead of a simple mask on the belief | |
101 | * that the question may be moderately complex. If it ends up not being | |
102 | * so, then this can simply be turned into a mask of which bits to check | |
103 | * as being exclusionary. | |
104 | */ | |
105 | static const struct divineIntent { | |
618b37c8 A |
106 | xattr_operation_intent_t intent; |
107 | int (^checker)(xattr_flags_t); | |
11f767b3 | 108 | } intentTable[] = { |
618b37c8 A |
109 | { XATTR_OPERATION_INTENT_COPY, ^(xattr_flags_t flags) { |
110 | if (flags & XATTR_FLAG_NEVER_PRESERVE) | |
11f767b3 A |
111 | return 0; |
112 | return 1; | |
113 | } }, | |
618b37c8 A |
114 | { XATTR_OPERATION_INTENT_SAVE, ^(xattr_flags_t flags) { |
115 | if (flags & (XATTR_FLAG_CONTENT_DEPENDENT | XATTR_FLAG_NEVER_PRESERVE)) | |
11f767b3 A |
116 | return 0; |
117 | return 1; | |
118 | } }, | |
618b37c8 A |
119 | { XATTR_OPERATION_INTENT_SHARE, ^(xattr_flags_t flags) { |
120 | if ((flags & (XATTR_FLAG_NO_EXPORT | XATTR_FLAG_NEVER_PRESERVE)) != 0) | |
11f767b3 A |
121 | return 0; |
122 | return 1; | |
123 | } }, | |
618b37c8 A |
124 | { XATTR_OPERATION_INTENT_SYNC, ^(xattr_flags_t flags) { |
125 | return (flags & (XATTR_FLAG_SYNCABLE | XATTR_FLAG_NEVER_PRESERVE)) == XATTR_FLAG_SYNCABLE; | |
126 | } }, | |
11f767b3 A |
127 | { 0, 0 }, |
128 | }; | |
129 | ||
130 | ||
131 | /* | |
132 | * If an EA name is in the default list, find it, and return the property | |
133 | * list string for it. | |
134 | */ | |
135 | static const char * | |
136 | nameInDefaultList(const char *eaname) | |
137 | { | |
138 | const struct defaultList *retval; | |
618b37c8 A |
139 | static dispatch_once_t onceToken; |
140 | ||
141 | dispatch_once(&onceToken, ^{ | |
142 | if (_xpc_runtime_is_app_sandboxed()) { | |
143 | defaultPropertyTable = defaultSandboxedPropertyTable; | |
144 | } else { | |
145 | defaultPropertyTable = defaultUnboxedPropertyTable; | |
146 | } | |
147 | }); | |
11f767b3 A |
148 | |
149 | for (retval = defaultPropertyTable; retval->eaName; retval++) { | |
150 | if ((retval->flags & propFlagsPrefix) != 0 && | |
151 | strncmp(retval->eaName, eaname, strlen(retval->eaName)) == 0) | |
152 | return retval->propList; | |
153 | if (strcmp(retval->eaName, eaname) == 0) | |
154 | return retval->propList; | |
155 | } | |
156 | return NULL; | |
157 | } | |
158 | ||
159 | /* | |
160 | * Given an EA name, see if it has a property list in it, and | |
161 | * return a pointer to it. All this is doing is looking for | |
162 | * the delimiter, and returning the string after that. Returns | |
163 | * NULL if the delimiter isn't found. Note that an empty string | |
164 | * is a valid property list, as far as we're concerned. | |
165 | */ | |
166 | static const char * | |
167 | findPropertyList(const char *eaname) | |
168 | { | |
169 | const char *ptr = strrchr(eaname, '#'); | |
170 | if (ptr) | |
171 | return ptr+1; | |
172 | return NULL; | |
173 | } | |
174 | ||
175 | /* | |
176 | * Convert a property list string (e.g., "pCd") into a | |
618b37c8 | 177 | * xattr_operation_intent_t type. |
11f767b3 | 178 | */ |
618b37c8 | 179 | static xattr_operation_intent_t |
11f767b3 A |
180 | stringToProperties(const char *proplist) |
181 | { | |
618b37c8 | 182 | xattr_operation_intent_t retval = 0; |
11f767b3 A |
183 | const char *ptr; |
184 | ||
185 | // A switch would be more efficient, but less generic. | |
186 | for (ptr = proplist; *ptr; ptr++) { | |
187 | const struct propertyListMapping *mapPtr; | |
188 | for (mapPtr = PropertyListMapTable; mapPtr->enable; mapPtr++) { | |
189 | if (*ptr == mapPtr->enable) { | |
190 | retval |= mapPtr->value; | |
191 | } else if (*ptr == mapPtr->disable) { | |
192 | retval &= ~mapPtr->value; | |
193 | } | |
194 | } | |
195 | } | |
196 | return retval; | |
197 | } | |
198 | ||
199 | /* | |
200 | * Given an EA name (e.g., "com.apple.lfs.hfs.test"), and a | |
618b37c8 | 201 | * xattr_operation_intent_t value (it's currently an integral value, so |
11f767b3 A |
202 | * just a bitmask), cycle through the list of known properties, and return |
203 | * a string with the EA name, and the property list appended. E.g., we | |
204 | * might return "com.apple.lfs.hfs.test#pD". | |
205 | * | |
206 | * The tricky part of this funciton is that it will not append any letters | |
207 | * if the value is only the default properites. In that case, it will copy | |
208 | * the EA name, and return that. | |
209 | * | |
210 | * It returns NULL if there was an error. The two errors right now are | |
211 | * no memory (strdup failed), in which case it will set errno to ENOMEM; and | |
212 | * the resulting EA name is longer than XATTR_MAXNAMELEN, in which case it | |
213 | * sets errno to ENAMETOOLONG. | |
214 | * | |
215 | * (Note that it also uses ENAMETOOLONG if the buffer it's trying to set | |
216 | * gets too large. I honestly can't see how that would happen, but it's there | |
217 | * for sanity checking. That would require having more than 64 bits to use.) | |
218 | */ | |
219 | char * | |
618b37c8 | 220 | xattr_name_with_flags(const char *orig, xattr_flags_t propList) |
11f767b3 A |
221 | { |
222 | char *retval = NULL; | |
223 | char suffix[66] = { 0 }; // 66: uint64_t for property types, plus '#', plus NUL | |
224 | char *cur = suffix; | |
225 | const struct propertyListMapping *mapPtr; | |
226 | ||
227 | *cur++ = '#'; | |
228 | for (mapPtr = PropertyListMapTable; mapPtr->enable; mapPtr++) { | |
229 | if ((propList & mapPtr->value) != 0) { | |
230 | *cur++ = mapPtr->enable; | |
231 | } | |
232 | if (cur >= (suffix + sizeof(suffix))) { | |
233 | errno = ENAMETOOLONG; | |
234 | return NULL; | |
235 | } | |
236 | ||
237 | } | |
238 | ||
239 | ||
240 | if (cur == suffix + 1) { | |
241 | // No changes made | |
242 | retval = strdup(orig); | |
243 | if (retval == NULL) | |
244 | errno = ENOMEM; | |
245 | } else { | |
246 | const char *defaultEntry = NULL; | |
247 | if ((defaultEntry = nameInDefaultList(orig)) != NULL && | |
248 | strcmp(defaultEntry, suffix + 1) == 0) { | |
249 | // Just use the name passed in | |
250 | retval = strdup(orig); | |
251 | } else { | |
252 | asprintf(&retval, "%s%s", orig, suffix); | |
253 | } | |
254 | if (retval == NULL) { | |
255 | errno = ENOMEM; | |
256 | } else { | |
257 | if (strlen(retval) > XATTR_MAXNAMELEN) { | |
258 | free(retval); | |
259 | retval = NULL; | |
260 | errno = ENAMETOOLONG; | |
261 | } | |
262 | } | |
263 | } | |
264 | return retval; | |
265 | } | |
266 | ||
618b37c8 A |
267 | char * |
268 | xattr_name_without_flags(const char *eaname) | |
269 | { | |
270 | char *retval = NULL; | |
271 | char *tmp; | |
272 | ||
273 | if ((tmp = strrchr(eaname, FLAG_DELIM_CHAR)) == NULL) { | |
274 | retval = strdup(eaname); | |
275 | } else { | |
276 | retval = calloc(tmp - eaname + 1, 1); | |
277 | if (retval) { | |
278 | strlcpy(retval, eaname, tmp - eaname + 1); | |
279 | } | |
280 | } | |
281 | if (retval == NULL) { | |
282 | errno = ENOMEM; | |
283 | } | |
284 | return retval; | |
285 | } | |
286 | ||
287 | int | |
288 | xattr_intent_with_flags(xattr_operation_intent_t intent, xattr_flags_t flags) | |
289 | { | |
290 | const struct divineIntent *ip; | |
291 | ||
292 | for (ip = intentTable; ip->intent; ip++) { | |
293 | if (ip->intent == intent) { | |
294 | return ip->checker(flags); | |
295 | } | |
296 | } | |
297 | if ((flags & XATTR_FLAG_NEVER_PRESERVE) != 0) | |
298 | return 0; // Special case, don't try to copy this one | |
299 | ||
300 | return 1; // Default | |
301 | } | |
302 | ||
303 | xattr_flags_t | |
304 | xattr_flags_from_name(const char *eaname) | |
11f767b3 | 305 | { |
618b37c8 | 306 | xattr_flags_t retval = 0; |
11f767b3 A |
307 | const char *propList; |
308 | ||
309 | propList = findPropertyList(eaname); | |
310 | if (propList == NULL) { | |
618b37c8 | 311 | propList = nameInDefaultList(eaname); |
11f767b3 A |
312 | } |
313 | if (propList != NULL) { | |
314 | retval = stringToProperties(propList); | |
315 | } | |
316 | ||
317 | return retval; | |
318 | } | |
319 | ||
320 | /* | |
321 | * Indicate whether an EA should be preserved, when using the | |
322 | * given intent. | |
323 | * | |
324 | * This returns 0 if it should not be preserved, and 1 if it should. | |
325 | * | |
326 | * It simply looks through the tables we have above, and compares the | |
618b37c8 | 327 | * xattr_operation_intent_t for the EA with the intent. If the |
11f767b3 A |
328 | * EA doesn't have any properties, and it's not on the default list, the |
329 | * default is to preserve it. | |
330 | */ | |
331 | ||
332 | int | |
618b37c8 | 333 | xattr_preserve_for_intent(const char *eaname, xattr_operation_intent_t intent) |
11f767b3 | 334 | { |
618b37c8 | 335 | xattr_flags_t flags = xattr_flags_from_name(eaname); |
11f767b3 | 336 | |
618b37c8 | 337 | return xattr_intent_with_flags(intent, flags); |
11f767b3 | 338 | } |
618b37c8 A |
339 | |
340 | #include "xattr_properties.h" |