]> git.saurik.com Git - apple/libc.git/blame_incremental - darwin/_dirhelper.c
Libc-825.24.tar.gz
[apple/libc.git] / darwin / _dirhelper.c
... / ...
CommitLineData
1/*
2 * Copyright (c) 2006, 2007, 2010 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 <mach/mach.h>
25#include <mach/mach_error.h>
26#include <servers/bootstrap.h>
27#include <unistd.h>
28#include <stdio.h>
29#include <stdlib.h>
30#include <stdbool.h>
31#include <membership.h>
32#include <pthread.h>
33#include <errno.h>
34#include <sys/types.h>
35#include <sys/stat.h>
36#include <sys/param.h>
37#include <uuid/uuid.h>
38#include <string.h>
39#include <libkern/OSByteOrder.h>
40#include <TargetConditionals.h>
41#include <xpc/xpc.h>
42#include <xpc/private.h>
43
44#include "dirhelper.h"
45#include "dirhelper_priv.h"
46
47#define BUCKETLEN 2
48
49#define MUTEX_LOCK(x) if(__is_threaded) pthread_mutex_lock(x)
50#define MUTEX_UNLOCK(x) if(__is_threaded) pthread_mutex_unlock(x)
51
52// Use 5 bits per character, to avoid uppercase and shell magic characters
53#define ENCODEBITS 5
54#define ENCODEDSIZE ((8 * UUID_UID_SIZE + ENCODEBITS - 1) / ENCODEBITS)
55#define MASK(x) ((1 << (x)) - 1)
56#define UUID_UID_SIZE (sizeof(uuid_t) + sizeof(uid_t))
57
58extern int __is_threaded;
59
60static const mode_t modes[] = {
61 0755, /* user */
62 0700, /* temp */
63 0700, /* cache */
64};
65
66static const char *subdirs[] = {
67 DIRHELPER_TOP_STR,
68 DIRHELPER_TEMP_STR,
69 DIRHELPER_CACHE_STR,
70};
71
72static pthread_once_t userdir_control = PTHREAD_ONCE_INIT;
73static char *userdir = NULL;
74
75// lower case letter (minus vowels), plus numbers and _, making
76// 32 characters.
77static const char encode[] = "0123456789_bcdfghjklmnpqrstvwxyz";
78
79static void
80encode_uuid_uid(const uuid_t uuid, uid_t uid, char *str)
81{
82 unsigned char buf[UUID_UID_SIZE + 1];
83 unsigned char *bp = buf;
84 int i;
85 unsigned int n;
86
87 memcpy(bp, uuid, sizeof(uuid_t));
88 uid = OSSwapHostToBigInt32(uid);
89 memcpy(bp + sizeof(uuid_t), &uid, sizeof(uid_t));
90 bp[UUID_UID_SIZE] = 0; // this ensures the last encoded byte will have trailing zeros
91 for(i = 0; i < ENCODEDSIZE; i++) {
92 // 5 bits has 8 states
93 switch(i % 8) {
94 case 0:
95 n = *bp++;
96 *str++ = encode[n >> 3];
97 break;
98 case 1:
99 n = ((n & MASK(3)) << 8) | *bp++;
100 *str++ = encode[n >> 6];
101 break;
102 case 2:
103 n &= MASK(6);
104 *str++ = encode[n >> 1];
105 break;
106 case 3:
107 n = ((n & MASK(1)) << 8) | *bp++;
108 *str++ = encode[n >> 4];
109 break;
110 case 4:
111 n = ((n & MASK(4)) << 8) | *bp++;
112 *str++ = encode[n >> 7];
113 break;
114 case 5:
115 n &= MASK(7);
116 *str++ = encode[n >> 2];
117 break;
118 case 6:
119 n = ((n & MASK(2)) << 8) | *bp++;
120 *str++ = encode[n >> 5];
121 break;
122 case 7:
123 *str++ = encode[n & MASK(5)];
124 break;
125 }
126 }
127 *str = 0;
128}
129
130char *
131__user_local_dirname(uid_t uid, dirhelper_which_t which, char *path, size_t pathlen)
132{
133#if TARGET_OS_IPHONE
134 char *tmpdir;
135#else
136 uuid_t uuid;
137 char str[ENCODEDSIZE + 1];
138#endif
139 int res;
140
141 if(which < 0 || which > DIRHELPER_USER_LOCAL_LAST) {
142 errno = EINVAL;
143 return NULL;
144 }
145
146#if TARGET_OS_IPHONE
147 /* We only support DIRHELPER_USER_LOCAL_TEMP on embedded.
148 * This interface really doesn't map from OSX to embedded,
149 * and clients of this interface will need to adapt when
150 * porting their applications to embedded.
151 * See: <rdar://problem/7515613>
152 */
153 if(which == DIRHELPER_USER_LOCAL_TEMP) {
154 tmpdir = getenv("TMPDIR");
155 if(!tmpdir) {
156 errno = EINVAL;
157 return NULL;
158 }
159 res = snprintf(path, pathlen, "%s", tmpdir);
160 } else {
161 errno = EINVAL;
162 return NULL;
163 }
164#else
165 res = mbr_uid_to_uuid(uid, uuid);
166 if(res != 0) {
167 errno = res;
168 return NULL;
169 }
170
171 //
172 // We partition the namespace so that we don't end up with too
173 // many users in a single directory. With 1024 buckets, we
174 // could scale to 1,000,000 users while keeping the average
175 // number of files in a single directory around 1000
176 //
177 encode_uuid_uid(uuid, uid, str);
178 res = snprintf(path, pathlen,
179 "%s%.*s/%s/%s",
180 VAR_FOLDERS_PATH, BUCKETLEN, str, str + BUCKETLEN, subdirs[which]);
181#endif
182 if(res >= pathlen) {
183 errno = EINVAL;
184 return NULL; /* buffer too small */
185 }
186 return path;
187}
188
189char *
190__user_local_mkdir_p(char *path)
191{
192 char *next;
193 int res;
194
195 next = path + strlen(VAR_FOLDERS_PATH);
196 while ((next = strchr(next, '/')) != NULL) {
197 *next = 0; // temporarily truncate
198 res = mkdir(path, 0755);
199 if (res != 0 && errno != EEXIST)
200 return NULL;
201 *next++ = '/'; // restore the slash and increment
202 }
203 return path;
204}
205
206static void userdir_allocate(void)
207{
208 userdir = calloc(PATH_MAX, sizeof(char));
209}
210
211/*
212 * 9407258: Invalidate the dirhelper cache (userdir) of the child after fork.
213 * There is a rare case when launchd will have userdir set, and child process
214 * will sometimes inherit this cached value.
215 */
216__private_extern__ void
217_dirhelper_fork_child(void)
218{
219 if(userdir) *userdir = 0;
220}
221
222__private_extern__ char *
223_dirhelper(dirhelper_which_t which, char *path, size_t pathlen)
224{
225 static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
226 struct stat sb;
227
228 if(which < 0 || which > DIRHELPER_USER_LOCAL_LAST) {
229 errno = EINVAL;
230 return NULL;
231 }
232
233 if (pthread_once(&userdir_control, userdir_allocate)
234 || !userdir) {
235 errno = ENOMEM;
236 return NULL;
237 }
238
239 if(!*userdir) {
240 MUTEX_LOCK(&lock);
241 if (!*userdir) {
242
243 if(__user_local_dirname(geteuid(), DIRHELPER_USER_LOCAL, userdir, PATH_MAX) == NULL) {
244 MUTEX_UNLOCK(&lock);
245 return NULL;
246 }
247 /*
248 * All dirhelper directories are now at the same level, so
249 * we need to remove the DIRHELPER_TOP_STR suffix to get the
250 * parent directory.
251 */
252 userdir[strlen(userdir) - (sizeof(DIRHELPER_TOP_STR) - 1)] = 0;
253 /*
254 * check if userdir exists, and if not, either do the work
255 * ourself if we are root, or call
256 * __dirhelper_create_user_local to create it (we have to
257 * check again afterwards).
258 */
259 if(stat(userdir, &sb) < 0) {
260 mach_port_t mp;
261
262 if(errno != ENOENT) { /* some unknown error */
263 *userdir = 0;
264 MUTEX_UNLOCK(&lock);
265 return NULL;
266 }
267 /*
268 * If we are root, lets do what dirhelper does for us.
269 */
270 if (geteuid() == 0) {
271 if (__user_local_mkdir_p(userdir) == NULL) {
272 *userdir = 0;
273 MUTEX_UNLOCK(&lock);
274 return NULL;
275 }
276 } else {
277 if(bootstrap_look_up(bootstrap_port, DIRHELPER_BOOTSTRAP_NAME, &mp) != KERN_SUCCESS) {
278 errno = EPERM;
279 server_error:
280 mach_port_deallocate(mach_task_self(), mp);
281 MUTEX_UNLOCK(&lock);
282 return NULL;
283 }
284 if(__dirhelper_create_user_local(mp) != KERN_SUCCESS) {
285 errno = EPERM;
286 goto server_error;
287 }
288 /* double check that the directory really got created */
289 if(stat(userdir, &sb) < 0) {
290 goto server_error;
291 }
292 mach_port_deallocate(mach_task_self(), mp);
293 }
294 }
295 }
296 MUTEX_UNLOCK(&lock);
297 }
298
299 if(pathlen < strlen(userdir) + strlen(subdirs[which]) + 1) {
300 errno = EINVAL;
301 return NULL; /* buffer too small */
302 }
303 strcpy(path, userdir);
304 strcat(path, subdirs[which]);
305
306 /*
307 * create the subdir with the appropriate permissions if it doesn't already
308 * exist. On OS X, if we're under App Sandbox, we rely on the subdir having
309 * been already created for us.
310 */
311#if !TARGET_OS_IPHONE
312 if (!_xpc_runtime_is_app_sandboxed())
313#endif
314 if(mkdir(path, modes[which]) != 0 && errno != EEXIST)
315 return NULL;
316
317#if !TARGET_OS_IPHONE
318 char *userdir_suffix = NULL;
319
320 if (_xpc_runtime_is_app_sandboxed()) {
321 /*
322 * if the subdir wasn't made for us, bail since we probably don't have
323 * permission to create it ourselves.
324 */
325 if(stat(path, &sb) < 0) {
326 errno = EPERM;
327 return NULL;
328 }
329
330 /*
331 * sandboxed applications get per-application directories named
332 * after the container
333 */
334 userdir_suffix = getenv(XPC_ENV_SANDBOX_CONTAINER_ID);
335 if (!userdir_suffix) {
336 errno = EINVAL;
337 return NULL;
338 }
339 } else
340 userdir_suffix = getenv(DIRHELPER_ENV_USER_DIR_SUFFIX);
341
342 if (userdir_suffix) {
343 /*
344 * suffix (usually container ID) doesn't end in a slash, so +2 is for slash and \0
345 */
346 if (pathlen < strlen(path) + strlen(userdir_suffix) + 2) {
347 errno = EINVAL;
348 return NULL; /* buffer too small */
349 }
350
351 strcat(path, userdir_suffix);
352 strcat(path, "/");
353
354 /*
355 * create suffix subdirectory with the appropriate permissions
356 * if it doesn't already exist.
357 */
358 if (mkdir(path, modes[which]) != 0 && errno != EEXIST)
359 return NULL;
360
361 /*
362 * update TMPDIR if necessary
363 */
364 if (which == DIRHELPER_USER_LOCAL_TEMP) {
365 char *tmpdir = getenv("TMPDIR");
366 if (!tmpdir || strncmp(tmpdir, path, strlen(path)))
367 setenv("TMPDIR", path, 1);
368 }
369 }
370#endif
371
372 return path;
373}