]> git.saurik.com Git - apple/libc.git/blame - darwin/_dirhelper.c
Libc-763.13.tar.gz
[apple/libc.git] / darwin / _dirhelper.c
CommitLineData
224c7076 1/*
1f2f436a 2 * Copyright (c) 2006, 2007, 2010 Apple Inc. All rights reserved.
224c7076
A
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>
511daa4c 40#include <TargetConditionals.h>
1f2f436a
A
41#include <xpc/xpc.h>
42#include <xpc/private.h>
224c7076
A
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
1f2f436a
A
52// Use 5 bits per character, to avoid uppercase and shell magic characters
53#define ENCODEBITS 5
224c7076 54#define ENCODEDSIZE ((8 * UUID_UID_SIZE + ENCODEBITS - 1) / ENCODEBITS)
1f2f436a 55#define MASK(x) ((1 << (x)) - 1)
224c7076
A
56#define UUID_UID_SIZE (sizeof(uuid_t) + sizeof(uid_t))
57
58extern int __is_threaded;
59
60static const mode_t modes[] = {
1f2f436a 61 0755, /* user */
224c7076
A
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
1f2f436a
A
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";
224c7076
A
78
79static void
1f2f436a 80encode_uuid_uid(const uuid_t uuid, uid_t uid, char *str)
224c7076
A
81{
82 unsigned char buf[UUID_UID_SIZE + 1];
83 unsigned char *bp = buf;
1f2f436a 84 int i;
224c7076
A
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
1f2f436a
A
91 for(i = 0; i < ENCODEDSIZE; i++) {
92 // 5 bits has 8 states
93 switch(i % 8) {
224c7076
A
94 case 0:
95 n = *bp++;
1f2f436a 96 *str++ = encode[n >> 3];
224c7076
A
97 break;
98 case 1:
1f2f436a
A
99 n = ((n & MASK(3)) << 8) | *bp++;
100 *str++ = encode[n >> 6];
224c7076
A
101 break;
102 case 2:
1f2f436a
A
103 n &= MASK(6);
104 *str++ = encode[n >> 1];
224c7076
A
105 break;
106 case 3:
1f2f436a
A
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)];
224c7076
A
124 break;
125 }
224c7076
A
126 }
127 *str = 0;
128}
129
130char *
131__user_local_dirname(uid_t uid, dirhelper_which_t which, char *path, size_t pathlen)
132{
511daa4c
A
133#if TARGET_OS_EMBEDDED
134 char *tmpdir;
135#else
224c7076
A
136 uuid_t uuid;
137 char str[ENCODEDSIZE + 1];
511daa4c 138#endif
224c7076
A
139 int res;
140
141 if(which < 0 || which > DIRHELPER_USER_LOCAL_LAST) {
142 errno = EINVAL;
143 return NULL;
144 }
145
511daa4c 146#if TARGET_OS_EMBEDDED
1f2f436a
A
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 {
511daa4c
A
161 errno = EINVAL;
162 return NULL;
163 }
511daa4c 164#else
224c7076
A
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
1f2f436a 173 // many users in a single directory. With 1024 buckets, we
224c7076 174 // could scale to 1,000,000 users while keeping the average
1f2f436a 175 // number of files in a single directory around 1000
224c7076
A
176 //
177 encode_uuid_uid(uuid, uid, str);
178 res = snprintf(path, pathlen,
179 "%s%.*s/%s/%s",
1f2f436a 180 VAR_FOLDERS_PATH, BUCKETLEN, str, str + BUCKETLEN, subdirs[which]);
511daa4c 181#endif
224c7076
A
182 if(res >= pathlen) {
183 errno = EINVAL;
184 return NULL; /* buffer too small */
185 }
186 return path;
187}
188
34e8f829
A
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
1f2f436a
A
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
224c7076
A
222__private_extern__ char *
223_dirhelper(dirhelper_which_t which, char *path, size_t pathlen)
224{
224c7076 225 static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
224c7076
A
226 struct stat sb;
227
228 if(which < 0 || which > DIRHELPER_USER_LOCAL_LAST) {
229 errno = EINVAL;
230 return NULL;
231 }
232
1f2f436a
A
233 if (pthread_once(&userdir_control, userdir_allocate)
234 || !userdir) {
235 errno = ENOMEM;
236 return NULL;
237 }
238
224c7076
A
239 if(!*userdir) {
240 MUTEX_LOCK(&lock);
34e8f829
A
241 if (!*userdir) {
242
1f2f436a 243 if(__user_local_dirname(geteuid(), DIRHELPER_USER_LOCAL, userdir, PATH_MAX) == NULL) {
224c7076
A
244 MUTEX_UNLOCK(&lock);
245 return NULL;
246 }
1f2f436a
A
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;
224c7076 253 /*
34e8f829
A
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).
224c7076
A
258 */
259 if(stat(userdir, &sb) < 0) {
34e8f829
A
260 mach_port_t mp;
261
224c7076
A
262 if(errno != ENOENT) { /* some unknown error */
263 *userdir = 0;
34e8f829
A
264 MUTEX_UNLOCK(&lock);
265 return NULL;
224c7076 266 }
34e8f829
A
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);
224c7076
A
293 }
294 }
224c7076
A
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 * now for subdirectories, create it with the appropriate permissions
1f2f436a
A
308 * if it doesn't already exist. On OS X, if we're under App Sandbox, we
309 * rely on xpchelper having created the subdir for us.
224c7076 310 */
1f2f436a
A
311#if !TARGET_OS_EMBEDDED
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_EMBEDDED
318 if (_xpc_runtime_is_app_sandboxed()) {
319 /*
320 * if xpchelper didn't make the subdir for us, bail since we don't have
321 * permission to create it ourselves.
322 */
323 if(stat(path, &sb) < 0) {
324 errno = EPERM;
325 return NULL;
326 }
327
328 /*
329 * sandboxed applications get per-application directories named
330 * after the container
331 */
332 char *container_id = getenv(XPC_ENV_SANDBOX_CONTAINER_ID);
333 if(!container_id) {
334 errno = EINVAL;
335 return NULL;
336 }
337
338 /*
339 * container ID doesn't end in a slash, so +2 is for slash and \0
340 */
341 if (pathlen < strlen(path) + strlen(container_id) + 2) {
342 errno = EINVAL;
343 return NULL; /* buffer too small */
344 }
345
346 strcat(path, container_id);
347 strcat(path, "/");
348
349 /*
350 * create per-app subdirectory with the appropriate permissions
351 * if it doesn't already exist.
352 */
353 if(mkdir(path, modes[which]) != 0 && errno != EEXIST)
354 return NULL;
224c7076 355 }
1f2f436a 356#endif
224c7076
A
357
358 return path;
359}