Implement the new 'darling' executable

This commit is contained in:
Sergey Bugaev 2016-12-29 18:13:47 +03:00
parent 4922fc9028
commit 9f0a3d5400
4 changed files with 554 additions and 162 deletions

View File

@ -164,7 +164,7 @@ Congratulations, you have just compiled and run your own Hello world application
### AppKit
AppKit is still highly expiramental and incomplete, but to work on it you need to configure CMake with `-DFRAMEWORK_APPKIT=1` and install some additional packages.
AppKit is still highly expiramental and incomplete, but to work on it you need to configure CMake with `-DFRAMEWORK_APPKIT=1` and install some additional packages.
Ubuntu 16.04:
```

View File

@ -38,94 +38,230 @@ along with Darling. If not, see <http://www.gnu.org/licenses/>.
#include "darling-config.h"
const char* DARLING_INIT_COMM = "darling-init";
uid_t g_originalUid;
char *prefix;
uid_t g_originalUid, g_originalGid;
int main(int argc, const char** argv)
{
const char* dprefix;
int pidInit;
pid_t pidInit, pidChild;
char path[4096];
int wstatus;
if (argc <= 1)
{
showHelp(argv[0]);
return 1;
}
/*if (geteuid() != 0)
if (geteuid() != 0)
{
missingSetuidRoot();
return 1;
}*/
}
if (loadKernelModule())
return 1;
// Temporarily drop root privileges
g_originalUid = getuid();
seteuid(getuid());
setegid(getgid());
dprefix = getenv("DPREFIX");
if (!dprefix)
dprefix = defaultPrefixPath();
if (!dprefix)
g_originalGid = getgid();
prefix = getenv("DPREFIX");
if (!prefix)
prefix = defaultPrefixPath();
if (!prefix)
return 1;
checkPrefixOwner(dprefix);
pidInit = getInitProcess(dprefix);
setenv("DPREFIX", prefix, 0);
if (!checkPrefixDir())
setupPrefix();
checkPrefixOwner();
pidInit = getInitProcess();
// If prefix's init is not running, start it up
if (pidInit == 0)
{
char* opts;
createDir(dprefix);
setupWorkdir(dprefix);
// Since overlay cannot be mounted inside user namespaces, we have to setup a new mount namespace
// and do the mount while we can be root
//seteuid(0);
if (unshare(CLONE_NEWNS) != 0)
{
fprintf(stderr, "Cannot unshare(CLONE_NEWNS): %s\n", strerror(errno));
return 1;
}
// Because IDIOTIC systemd marks / as MS_SHARED and we would inherit this into the overlay mount,
// causing it not to be unmounted once the init process dies.
if (mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL) != 0)
{
fprintf(stderr, "Cannot remount / as private: %s\n", strerror(errno));
return 1;
}
opts = (char*) malloc(strlen(dprefix)*2 + sizeof(LIBEXEC_PATH) + 50);
sprintf(opts, "lowerdir=%s,upperdir=%s,workdir=%s.workdir", LIBEXEC_PATH, dprefix, dprefix);
// Mount overlay onto our prefix
if (mount("overlay", dprefix, "overlay", 0, opts) != 0)
{
fprintf(stderr, "Cannot mount overlay: %s\n", strerror(errno));
return 1;
}
free(opts);
//seteuid(getuid());
pidInit = spawnInitProcess(dprefix);
setupWorkdir();
pidInit = spawnInitProcess();
putInitPid(pidInit);
}
// TODO: Spawn the executable in the namespace, wait for it and return its exit code
unloadKernelModule();
if (strcmp(argv[1], "shell") != 0)
{
const char *argv_child[argc + 1];
argv_child[0] = "dyld";
for (int i = 1; i < argc; i++)
argv_child[i] = argv[i];
argv_child[argc] = NULL;
pidChild = spawnChild(pidInit, "/usr/local/bin/dyld", argv_child);
}
else
{
// Spawn the shell
snprintf(path, sizeof(path), "%s/bin/bash", prefix);
if (argc > 2)
{
size_t total_len = 0;
for (int i = 2; i < argc; i++)
total_len += strlen(argv[i]);
char buffer[total_len + argc];
char *to = buffer;
for (int i = 2; i < argc; i++)
to = stpcpy(stpcpy(to, argv[i]), " ");
// Overwrite the last whitespace
*(to - 1) = '\0';
pidChild = spawnChild(pidInit, "/usr/local/bin/dyld",
(const char *[5]) {"dyld", path, "-c", buffer, NULL});
}
else
pidChild = spawnChild(pidInit, "/usr/local/bin/dyld",
(const char *[3]) {"dyld", path, NULL});
}
// Drop the privileges so that we can be killed, etc by the user
seteuid(g_originalUid);
waitpid(pidChild, &wstatus, 0);
// Should we unloadKernelModule() here? Others may be still using it
if (WIFEXITED(wstatus))
return WEXITSTATUS(wstatus);
if (WIFSIGNALED(wstatus))
return WTERMSIG(wstatus);
return 0;
}
pid_t spawnChild(int pidInit, const char *path, const char *const argv[])
{
int fdNS;
pid_t pidChild;
char pathNS[4096], curPath[4096];
if (getcwd(curPath, sizeof(curPath)) == NULL)
{
fprintf(stderr, "Cannot get current directory: %s\n", strerror(errno));
exit(1);
}
snprintf(pathNS, sizeof(pathNS), "/proc/%d/ns/pid", pidInit);
fdNS = open(pathNS, O_RDONLY);
if (fdNS < 0)
{
fprintf(stderr, "Cannot open PID namespace file: %s\n", strerror(errno));
exit(1);
}
// Calling setns() with a PID namespace doesn't move our process into it,
// but our child process will be spawned inside the namespace
if (setns(fdNS, CLONE_NEWPID) != 0)
{
fprintf(stderr, "Cannot join PID namespace: %s\n", strerror(errno));
exit(1);
}
close(fdNS);
pidChild = fork();
if (pidChild < 0)
{
fprintf(stderr, "Cannot spawn a child process: %s\n", strerror(errno));
exit(1);
}
if (pidChild == 0)
{
// This is the child process
// We still have the outside PIDs in /proc
snprintf(pathNS, sizeof(pathNS), "/proc/%d/ns/mnt", pidInit);
fdNS = open(pathNS, O_RDONLY);
if (fdNS < 0)
{
fprintf(stderr, "Cannot open mount namespace file: %s\n", strerror(errno));
exit(1);
}
if (setns(fdNS, CLONE_NEWNS) != 0)
{
fprintf(stderr, "Cannot join mount namespace: %s\n", strerror(errno));
exit(1);
}
close(fdNS);
snprintf(pathNS, sizeof(pathNS), "/proc/%d/ns/user", pidInit);
fdNS = open(pathNS, O_RDONLY);
if (fdNS < 0)
{
fprintf(stderr, "Cannot open user namespace file: %s\n", strerror(errno));
exit(1);
}
setresuid(g_originalUid, g_originalUid, g_originalUid);
setresgid(g_originalGid, g_originalGid, g_originalGid);
if (setns(fdNS, CLONE_NEWUSER) != 0)
{
fprintf(stderr, "Cannot join user namespace: %s\n", strerror(errno));
exit(1);
}
close(fdNS);
setupChild(curPath);
execv(path, (char * const *) argv);
fprintf(stderr, "Cannot exec the target program: %s\n", strerror(errno));
exit(1);
}
return pidChild;
}
void setupChild(const char *curPath)
{
char buffer1[4096];
char buffer2[4096];
setenv("PATH", "/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin", 1);
sscanf(getenv("HOME"), "/home/%4096s", buffer1);
snprintf(buffer2, sizeof(buffer2), "/Users/%s", buffer1);
setenv("HOME", buffer2, 1);
if (sscanf(curPath, "/home/%4096s", buffer1) == 1)
{
// We're currently inside our home directory
snprintf(buffer2, sizeof(buffer2), "/Users/%s", buffer1);
setenv("PWD", buffer2, 1);
snprintf(buffer2, sizeof(buffer2), "%s/Users/%s", prefix, buffer1);
chdir(buffer2);
}
else
{
snprintf(buffer2, sizeof(buffer2), "/system-root%s", curPath);
setenv("PWD", buffer2, 1);
snprintf(buffer2, sizeof(buffer2), "%s/system-root%s", prefix, curPath);
chdir(buffer2);
}
}
void showHelp(const char* argv0)
{
fprintf(stderr, "This is Darling, translation layer for macOS software.\n\n");
fprintf(stderr, "Copyright (C) 2012-2016 Lubos Dolezel\n\n");
fprintf(stderr, "Usage: %s program-path [arguments...]\n\n", argv0);
fprintf(stderr, "Usage:\n");
fprintf(stderr, "\t%s program-path [arguments...]\n", argv0);
fprintf(stderr, "\t%s shell [arguments...]\n", argv0);
fprintf(stderr, "\n");
fprintf(stderr, "Environment variables:\n"
"DPREFIX - specifies the location of Darling prefix, defaults to ~/.darling\n");
}
@ -134,80 +270,184 @@ void missingSetuidRoot(void)
{
char path[4096];
int len;
len = readlink("/proc/self/exe", path, sizeof(path)-1);
if (len < 0)
strcpy(path, "darling");
else
path[len] = '\0';
fprintf(stderr, "Sorry, the `%s' binary is not setuid root, which is mandatory.\n", path);
fprintf(stderr, "Darling needs this in order to create mount and PID namespaces and to perform mounts.\n");
}
int spawnInitProcess(const char* prefix)
pid_t spawnInitProcess(void)
{
char* childStack;
int pid;
char uidmap[100];
childStack = (char*) malloc(16*1024);
childStack += 16*1024;
//setuid(0);
pid = clone(darlingPreInit, childStack, /*CLONE_NEWPID |*/ CLONE_NEWUSER, (void*) prefix);
if (pid <= 0)
pid_t pid;
int pipefd[2];
char idmap[100];
char buffer[1];
FILE *file;
if (pipe(pipefd) == -1)
{
fprintf(stderr, "Cannot clone() to create darling-init: %s\n", strerror(errno));
fprintf(stderr, "Cannot create a pipe for synchronization: %s\n", strerror(errno));
exit(1);
}
sprintf(uidmap, "/proc/%d/uid_map", pid);
FILE* file = fopen(uidmap, "w");
if (unshare(CLONE_NEWPID) != 0)
{
fprintf(stderr, "Cannot unshare pid namespace to create darling-init: %s\n", strerror(errno));
exit(1);
}
pid = fork();
if (pid < 0)
{
fprintf(stderr, "Cannot fork() to create darling-init: %s\n", strerror(errno));
exit(1);
}
if (pid == 0)
{
// The child
char *opts;
// Since overlay cannot be mounted inside user namespaces, we have to setup a new mount namespace
// and do the mount while we can be root
if (unshare(CLONE_NEWNS) != 0)
{
fprintf(stderr, "Cannot unshare mount namespace: %s\n", strerror(errno));
exit(1);
}
// Because systemd marks / as MS_SHARED and we would inherit this into the overlay mount,
// causing it not to be unmounted once the init process dies.
if (mount(NULL, "/", NULL, MS_REC | MS_SLAVE, NULL) != 0)
{
fprintf(stderr, "Cannot remount / as slave: %s\n", strerror(errno));
exit(1);
}
opts = (char*) malloc(strlen(prefix)*2 + sizeof(LIBEXEC_PATH) + 50);
sprintf(opts, "lowerdir=%s,upperdir=%s,workdir=%s.workdir", LIBEXEC_PATH, prefix, prefix);
// Mount overlay onto our prefix
if (mount("overlay", prefix, "overlay", 0, opts) != 0)
{
fprintf(stderr, "Cannot mount overlay: %s\n", strerror(errno));
exit(1);
}
free(opts);
// Drop the privileges
setresuid(g_originalUid, g_originalUid, g_originalUid);
setresgid(g_originalGid, g_originalGid, g_originalGid);
prctl(PR_SET_DUMPABLE, 1, 0, 0, 0);
prctl(PR_SET_NAME, DARLING_INIT_COMM, 0, 0);
if (unshare(CLONE_NEWUSER) != 0)
{
fprintf(stderr, "Cannot unshare user namespace: %s\n", strerror(errno));
exit(1);
}
// Tell the parent we're ready for it to set up UID/GID mappings
write(pipefd[1], buffer, 1);
close(pipefd[1]);
// And wait for it to do it
read(pipefd[0], buffer, 1);
close(pipefd[0]);
darlingPreInit();
// Never returns
}
// Wait for the child to drop UID/GIDs and unshare stuff
read(pipefd[0], buffer, 1);
close(pipefd[0]);
snprintf(idmap, sizeof(idmap), "/proc/%d/uid_map", pid);
file = fopen(idmap, "w");
if (file != NULL)
{
fprintf(file, "0 %d 1\n", getuid()); // all users map to our user on the outside
fprintf(file, "0 %d 1\n", g_originalUid); // all users map to our user on the outside
fclose(file);
}
else
{
fprintf(stderr, "Failure setting uid_map in init process\n");
fprintf(stderr, "Cannot set uid_map for the init process: %s\n", strerror(errno));
}
seteuid(g_originalUid);
setuid(g_originalUid);
snprintf(idmap, sizeof(idmap), "/proc/%d/gid_map", pid);
file = fopen(idmap, "w");
if (file != NULL)
{
fprintf(file, "0 %d 1\n", g_originalGid); // all groups map to our group on the outside
fclose(file);
}
else
{
fprintf(stderr, "Cannot set gid_map for the init process: %s\n", strerror(errno));
}
// Resume the child
write(pipefd[1], buffer, 1);
close(pipefd[1]);
return pid;
}
int darlingPreInit(void* arg)
void putInitPid(pid_t pidInit)
{
const char pidFile[] = "/.init.pid";
char* pidPath;
FILE *fp;
pidPath = (char*) alloca(strlen(prefix) + sizeof(pidFile));
strcpy(pidPath, prefix);
strcat(pidPath, pidFile);
seteuid(g_originalUid);
setegid(g_originalGid);
fp = fopen(pidPath, "w");
seteuid(0);
setegid(0);
if (fp == NULL)
{
fprintf(stderr, "Cannot write out PID of the init process: %s\n", strerror(errno));
return;
}
fprintf(fp, "%d", (int) pidInit);
fclose(fp);
}
void darlingPreInit(void)
{
const char* prefix = (const char*) arg;
prctl(PR_SET_NAME, DARLING_INIT_COMM, 0, 0);
// Wait until the parent sets our uid_map
while (getuid() == 65534);
// TODO: Run /usr/libexec/makewhatis
// TODO: this is where we will exec() launchd in future.
// Instead, we just reap zombies.
while (1)
{
int status, sig;
sigset_t chld;
sigemptyset(&chld);
sigaddset(&chld, SIGCHLD);
sigwait(&chld, &sig);
while (waitpid(-1, &status, 0) != -1);
}
return 0;
}
char* defaultPrefixPath(void)
@ -215,24 +455,24 @@ char* defaultPrefixPath(void)
const char defaultPath[] = "/.darling";
const char* home = getenv("HOME");
char* buf;
if (!home)
{
fprintf(stderr, "Cannot detect your home directory!\n");
return NULL;
}
buf = (char*) malloc(strlen(home) + sizeof(defaultPath));
strcpy(buf, home);
strcat(buf, defaultPath);
return buf;
}
void createDir(const char* path)
{
struct stat st;
if (stat(path, &st) == 0)
{
if (!S_ISDIR(st.st_mode))
@ -253,56 +493,140 @@ void createDir(const char* path)
}
else
{
fprintf(stderr, "Problem accessing %s: %s\n", path, strerror(errno));
fprintf(stderr, "Cannot access %s: %s\n", path, strerror(errno));
exit(1);
}
}
}
void setupWorkdir(const char* prefix)
void setupWorkdir()
{
char* workdir;
const char suffix[] = ".workdir";
size_t len;
len = strlen(prefix);
workdir = (char*) alloca(len + sizeof(suffix));
strcpy(workdir, prefix);
// Remove trailing /
while (workdir[len-1] == '/')
len--;
workdir[len] = '\0';
strcat(workdir, suffix);
createDir(workdir);
}
int getInitProcess(const char* prefix)
int checkPrefixDir()
{
struct stat st;
if (stat(prefix, &st) == 0)
{
if (!S_ISDIR(st.st_mode))
{
fprintf(stderr, "%s is a file. Remove the file.\n", prefix);
exit(1);
}
return 1; // OK
}
if (errno == ENOENT)
return 0; // not found
fprintf(stderr, "Cannot access %s: %s\n", prefix, strerror(errno));
exit(1);
}
void setupPrefix()
{
char path[4096];
fprintf(stderr, "Setting up a new Darling prefix at %s\n", prefix);
seteuid(g_originalUid);
setegid(g_originalGid);
createDir(prefix);
snprintf(path, sizeof(path), "%s/system-root", prefix);
if (symlink("/", path) != 0)
{
fprintf(stderr, "Cannot symlink %s: %s\n", path, strerror(errno));
exit(1);
}
snprintf(path, sizeof(path), "%s/dev", prefix);
if (symlink("system-root/dev", path) != 0)
{
fprintf(stderr, "Cannot symlink %s: %s\n", path, strerror(errno));
exit(1);
}
snprintf(path, sizeof(path), "%s/tmp", prefix);
if (symlink("system-root/tmp", path) != 0)
{
fprintf(stderr, "Cannot symlink %s: %s\n", path, strerror(errno));
exit(1);
}
snprintf(path, sizeof(path), "%s/Users", prefix);
if (symlink("system-root/home", path) != 0)
{
fprintf(stderr, "Cannot symlink %s: %s\n", path, strerror(errno));
exit(1);
}
snprintf(path, sizeof(path), "%s/Volumes", prefix);
createDir(path);
snprintf(path, sizeof(path), "%s/Applications", prefix);
createDir(path);
snprintf(path, sizeof(path), "%s/var", prefix);
createDir(path);
snprintf(path, sizeof(path), "%s/var/root", prefix);
createDir(path);
snprintf(path, sizeof(path), "%s/var/run", prefix);
createDir(path);
snprintf(path, sizeof(path), "%s/var/run/syslog", prefix);
if (symlink("system-root/dev/log", path) != 0)
{
fprintf(stderr, "Cannot symlink %s: %s\n", path, strerror(errno));
exit(1);
}
seteuid(0);
setegid(0);
}
pid_t getInitProcess()
{
const char pidFile[] = "/.init.pid";
char* pidPath;
int fd, rd, pid;
char pidBuf[10];
char exeBuf[17], procBuf[100];
pid_t pid;
int pid_i;
FILE *fp;
char procBuf[100];
char *exeBuf, *statusBuf;
int uidMatch = 0, gidMatch = 0;
pidPath = (char*) alloca(strlen(prefix) + sizeof(pidFile));
strcpy(pidPath, prefix);
strcat(pidPath, pidFile);
fd = open(pidPath, O_RDONLY);
if (fd == -1)
fp = fopen(pidPath, "r");
if (fp == NULL)
return 0;
rd = read(fd, pidBuf, sizeof(pidBuf)-1);
close(fd);
if (rd <= 0)
if (fscanf(fp, "%d", &pid_i) != 1)
{
fclose(fp);
unlink(pidPath);
return 0;
pidBuf[rd-1] = '\0';
pid = atoi(pidBuf);
}
fclose(fp);
pid = (pid_t) pid_i;
// Does the process exist?
if (kill(pid, 0) == -1)
@ -310,42 +634,86 @@ int getInitProcess(const char* prefix)
unlink(pidPath);
return 0;
}
// Is it actually an init process?
sprintf(procBuf, "/proc/%d/comm", pid);
fd = open(procBuf, O_RDONLY);
if (fd == -1)
snprintf(procBuf, sizeof(procBuf), "/proc/%d/comm", pid);
fp = fopen(procBuf, "r");
if (fp == NULL)
{
unlink(pidPath);
return 0;
}
rd = read(fd, exeBuf, sizeof(exeBuf)-1);
close(fd);
if (rd <= 0)
if (fscanf(fp, "%ms", &exeBuf) != 1)
{
fclose(fp);
unlink(pidPath);
return 0;
}
exeBuf[rd] = '\0';
fclose(fp);
if (strcmp(exeBuf, DARLING_INIT_COMM) != 0)
{
unlink(pidPath);
return 0;
}
free(exeBuf);
// Is it owned by the current user?
if (g_originalUid != 0)
{
snprintf(procBuf, sizeof(procBuf), "/proc/%d/status", pid);
fp = fopen(procBuf, "r");
if (fp == NULL)
{
unlink(pidPath);
return 0;
}
while (1)
{
statusBuf = NULL;
size_t len;
if (getline(&statusBuf, &len, fp) == -1)
break;
int rid, eid, sid, fid;
if (sscanf(statusBuf, "Uid: %d %d %d %d", &rid, &eid, &sid, &fid) == 4)
{
uidMatch = 1;
uidMatch &= rid == g_originalUid;
uidMatch &= eid == g_originalUid;
uidMatch &= sid == g_originalUid;
uidMatch &= fid == g_originalUid;
}
if (sscanf(statusBuf, "Gid: %d %d %d %d", &rid, &eid, &sid, &fid) == 4)
{
gidMatch = 1;
gidMatch &= rid == g_originalGid;
gidMatch &= eid == g_originalGid;
gidMatch &= sid == g_originalGid;
gidMatch &= fid == g_originalGid;
}
free(statusBuf);
}
fclose(fp);
if (!uidMatch || !gidMatch)
{
unlink(pidPath);
return 0;
}
}
return pid;
}
void checkPrefixOwner(const char* prefix)
void checkPrefixOwner()
{
struct stat st;
if (stat(prefix, &st) == 0)
{
if (st.st_uid != getuid())
if (g_originalUid != 0 && st.st_uid != g_originalUid)
{
fprintf(stderr, "You do not own the prefix directory.\n");
exit(1);
@ -368,6 +736,7 @@ int isModuleLoaded()
if ((fp = fopen("/proc/modules", "r")) == NULL)
{
fprintf(stderr, "Failure opening /proc/modules: %s\n", strerror(errno));
fclose(fp);
return 0;
}
@ -375,9 +744,13 @@ int isModuleLoaded()
{
read = getline(&line, &len, fp);
if (read > 0 && strstr(line, "darling_mach") != NULL)
{
fclose(fp);
return 1;
}
}
fclose(fp);
return 0;
}
@ -391,7 +764,7 @@ int loadKernelModule()
return 0;
uname(&name);
sprintf(path, "/lib/modules/%s/kernel/misc/darling-mach.ko", name.release);
snprintf(path, sizeof(path), "/lib/modules/%s/kernel/misc/darling-mach.ko", name.release);
if (access(path, F_OK))
{
fprintf(stderr, "Cannot find kernel module at %s: %s\n", path, strerror(errno));

View File

@ -17,6 +17,8 @@ You should have received a copy of the GNU General Public License
along with Darling. If not, see <http://www.gnu.org/licenses/>.
*/
#include <sys/types.h>
#ifndef _DARLING_H_
#define _DARLING_H_
@ -27,20 +29,37 @@ void missingSetuidRoot(void);
// Returns ~/.darling with ~ expanded
char* defaultPrefixPath(void);
void setupWorkdir(const char* prefix);
void setupWorkdir(void);
void setupPrefix(void);
int checkPrefixDir(void);
// Creates the given directory, exit()ing if not possible
void createDir(const char* path);
// Return the PID of the init process in prefix (in our namespace)
// Spawn the specified proceess inside the namespaces that PID 1 is in
// Returns the PID of the child
// exit()s on error
pid_t spawnChild(int pidInit, const char *path, const char *const argv[]);
// Set up some environment variables
// As well as the working directory
// Called in the child process
void setupChild(const char *curPath);
// Returns the PID of the init process in prefix (in our namespace)
// Returns 0 if no init is running
int getInitProcess(const char* prefix);
pid_t getInitProcess(void);
int spawnInitProcess(const char* prefix);
pid_t spawnInitProcess(void);
int darlingPreInit(void* arg);
void putInitPid(pid_t pidInit);
void checkPrefixOwner(const char* prefix);
void darlingPreInit(void);
void checkPrefixOwner(void);
int isModuleLoaded(void);

View File

@ -29,25 +29,26 @@ along with Darling. If not, see <http://www.gnu.org/licenses/>.
static std::string GetUserLibrary()
{
const char* home;
const char *home, *prefix;
std::stringstream ss;
std::string path;
prefix = getenv("DPREFIX");
home = getenv("HOME");
if (!home)
if (!prefix || !home)
return std::string(); // give up on this user
ss << home << '/' << "Library" << '/';
ss << prefix << home << '/' << "Library" << '/';
return ss.str();
}
bool HasUserDirectoryStructure()
{
std::string path = GetUserLibrary();
if (path.empty())
return true; // give up on this user
if (::access(path.c_str(), F_OK) == -1)
return false;
else
@ -90,25 +91,24 @@ void SetupUserDirectoryStructure()
"Spelling",
"Voices",
};
std::string path = GetUserLibrary();
if (path.empty())
return;
std::cerr << "Darling: Creating Library structure at " << path << std::endl;
if (::mkdir(path.c_str(), 0777) == -1)
std::cerr << "Darling: Cannot mkdir(" << path << "): " << strerror(errno) << std::endl;
for (const char* dir : dirs)
{
std::string s = path;
s += dir;
if (::mkdir(s.c_str(), 0777) == -1)
std::cerr << "Darling: Cannot mkdir(" << s << "): " << strerror(errno) << std::endl;
}
}