#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 <TargetConditionals.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,
0700,
0700,
};
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; 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)
{
#if TARGET_OS_EMBEDDED
char *tmpdir;
#else
uuid_t uuid;
char str[ENCODEDSIZE + 1];
#endif
int res;
if(which < 0 || which > DIRHELPER_USER_LOCAL_LAST) {
errno = EINVAL;
return NULL;
}
#if TARGET_OS_EMBEDDED
tmpdir = getenv("TMPDIR");
if(!tmpdir) {
errno = EINVAL;
return NULL;
}
res = snprintf(path, pathlen, "%s/%s", tmpdir, subdirs[which]);
#else
res = mbr_uid_to_uuid(uid, uuid);
if(res != 0) {
errno = res;
return NULL;
}
encode_uuid_uid(uuid, uid, str);
res = snprintf(path, pathlen,
"%s%.*s/%s/%s",
VAR_FOLDERS_PATH, BUCKETLEN, str, str, subdirs[which]);
#endif
if(res >= pathlen) {
errno = EINVAL;
return NULL;
}
return path;
}
char *
__user_local_mkdir_p(char *path)
{
char *next;
int res;
next = path + strlen(VAR_FOLDERS_PATH);
while ((next = strchr(next, '/')) != NULL) {
*next = 0; res = mkdir(path, 0755);
if (res != 0 && errno != EEXIST)
return NULL;
*next++ = '/'; }
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) {
if(__user_local_dirname(geteuid(), DIRHELPER_USER_LOCAL, userdir, sizeof(userdir)) == NULL) {
MUTEX_UNLOCK(&lock);
return NULL;
}
if(stat(userdir, &sb) < 0) {
mach_port_t mp;
if(errno != ENOENT) {
*userdir = 0;
MUTEX_UNLOCK(&lock);
return NULL;
}
if (geteuid() == 0) {
if (__user_local_mkdir_p(userdir) == NULL) {
*userdir = 0;
MUTEX_UNLOCK(&lock);
return NULL;
}
} else {
if(bootstrap_look_up(bootstrap_port, DIRHELPER_BOOTSTRAP_NAME, &mp) != KERN_SUCCESS) {
errno = EPERM;
server_error:
mach_port_deallocate(mach_task_self(), mp);
MUTEX_UNLOCK(&lock);
return NULL;
}
if(__dirhelper_create_user_local(mp) != KERN_SUCCESS) {
errno = EPERM;
goto server_error;
}
if(stat(userdir, &sb) < 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;
}
strcpy(path, userdir);
strcat(path, subdirs[which]);
if(which != DIRHELPER_USER_LOCAL) {
res = mkdir(path, modes[which]);
if(res != 0 && errno != EEXIST)
return NULL;
}
return path;
}