]> git.saurik.com Git - apple/libc.git/blobdiff - darwin/_dirhelper.c
Libc-498.tar.gz
[apple/libc.git] / darwin / _dirhelper.c
diff --git a/darwin/_dirhelper.c b/darwin/_dirhelper.c
new file mode 100644 (file)
index 0000000..c28a0cc
--- /dev/null
@@ -0,0 +1,212 @@
+/*
+ * Copyright (c) 2006, 2007 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 <mach/mach.h>
+#include <mach/mach_error.h>
+#include <servers/bootstrap.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <membership.h>
+#include <pthread.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+#include <uuid/uuid.h>
+#include <string.h>
+#include <libkern/OSByteOrder.h>
+
+#include "dirhelper.h"
+#include "dirhelper_priv.h"
+
+#define BUCKETLEN      2
+
+#define MUTEX_LOCK(x)  if(__is_threaded) pthread_mutex_lock(x)
+#define MUTEX_UNLOCK(x)        if(__is_threaded) pthread_mutex_unlock(x)
+
+#define ENCODEBITS     6
+#define ENCODEDSIZE    ((8 * UUID_UID_SIZE + ENCODEBITS - 1) / ENCODEBITS)
+#define UUID_UID_SIZE  (sizeof(uuid_t) + sizeof(uid_t))
+
+extern int __is_threaded;
+
+static const mode_t modes[] = {
+    0,         /* unused */
+    0700,      /* temp */
+    0700,      /* cache */
+};
+
+static const char *subdirs[] = {
+    DIRHELPER_TOP_STR,
+    DIRHELPER_TEMP_STR,
+    DIRHELPER_CACHE_STR,
+};
+
+static const char encode[] = "+-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+
+static void
+encode_uuid_uid(uuid_t uuid, uid_t uid, char *str)
+{
+    unsigned char buf[UUID_UID_SIZE + 1];
+    unsigned char *bp = buf;
+    int i = 0;
+    unsigned int n;
+
+    memcpy(bp, uuid, sizeof(uuid_t));
+    uid = OSSwapHostToBigInt32(uid);
+    memcpy(bp + sizeof(uuid_t), &uid, sizeof(uid_t));
+    bp[UUID_UID_SIZE] = 0; // this ensures the last encoded byte will have trailing zeros
+    while(i < ENCODEDSIZE) {
+       switch(i % 4) {
+       case 0:
+           n = *bp++;
+           *str++ = encode[n >> 2];
+           break;
+       case 1:
+           n = ((n & 0x3) << 8) | *bp++;
+           *str++ = encode[n >> 4];
+           break;
+       case 2:
+           n = ((n & 0xf) << 8) | *bp++;
+           *str++ = encode[n >> 6];
+           break;
+       case 3:
+           *str++ = encode[n & 0x3f];
+           break;
+       }
+       i++;
+    }
+    *str = 0;
+}
+
+char *
+__user_local_dirname(uid_t uid, dirhelper_which_t which, char *path, size_t pathlen)
+{
+    uuid_t uuid;
+    char str[ENCODEDSIZE + 1];
+    int res;
+
+    if(which < 0 || which > DIRHELPER_USER_LOCAL_LAST) {
+       errno = EINVAL;
+       return NULL;
+    }
+
+    res = mbr_uid_to_uuid(uid, uuid);
+    if(res != 0) {
+        errno = res;
+        return NULL;
+    }
+    
+    //
+    // We partition the namespace so that we don't end up with too
+    // many users in a single directory.  With 4096 buckets, we
+    // could scale to 1,000,000 users while keeping the average
+    // number of files in a single directory below 250
+    //
+    encode_uuid_uid(uuid, uid, str);
+    res = snprintf(path, pathlen,
+       "%s%.*s/%s/%s",
+       VAR_FOLDERS_PATH, BUCKETLEN, str, str, subdirs[which]);
+    if(res >= pathlen) {
+       errno = EINVAL;
+       return NULL; /* buffer too small */
+    }
+    return path;
+}
+
+__private_extern__ char *
+_dirhelper(dirhelper_which_t which, char *path, size_t pathlen)
+{
+    static char userdir[PATH_MAX];
+    static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
+    int res;
+    struct stat sb;
+
+    if(which < 0 || which > DIRHELPER_USER_LOCAL_LAST) {
+       errno = EINVAL;
+       return NULL;
+    }
+
+    if(!*userdir) {
+       MUTEX_LOCK(&lock);
+       if(!*userdir) {
+           mach_port_t mp;
+
+           if(bootstrap_look_up(bootstrap_port, DIRHELPER_BOOTSTRAP_NAME, &mp) != KERN_SUCCESS) {
+               errno = EPERM;
+               MUTEX_UNLOCK(&lock);
+               return NULL;
+           }
+           if(__user_local_dirname(geteuid(), DIRHELPER_USER_LOCAL, userdir, sizeof(userdir)) == NULL) {
+server_error:
+               mach_port_deallocate(mach_task_self(), mp);
+               MUTEX_UNLOCK(&lock);
+               return NULL;
+           }
+           /*
+            * check if userdir exists, and if not, call
+            * __dirhelper_create_user_local to create it
+            * (we have to check again afterwards).
+            */
+           if(stat(userdir, &sb) < 0) {
+               if(errno != ENOENT) { /* some unknown error */
+                   *userdir = 0;
+                   goto server_error;
+               }
+               if(__dirhelper_create_user_local(mp) != KERN_SUCCESS) {
+                   errno = EPERM;
+                   *userdir = 0;
+                   goto server_error;
+               }
+               /* double check that the directory really got created */
+               if(stat(userdir, &sb) < 0) {
+                   *userdir = 0;
+                   goto server_error;
+               }
+           }
+           mach_port_deallocate(mach_task_self(), mp);
+       }
+       MUTEX_UNLOCK(&lock);
+    }
+    
+    if(pathlen < strlen(userdir) + strlen(subdirs[which]) + 1) {
+       errno = EINVAL;
+       return NULL; /* buffer too small */
+    }
+    strcpy(path, userdir);
+    strcat(path, subdirs[which]);
+
+    /*
+     * now for subdirectories, create it with the appropriate permissions
+     * if it doesn't already exist.
+     */
+    if(which != DIRHELPER_USER_LOCAL) {
+       res = mkdir(path, modes[which]);
+       if(res != 0 && errno != EEXIST)
+           return NULL;
+    }
+
+    return path;
+}