#include "SDL_internal.h"
#ifdef SDL_FILESYSTEM_UNIX
#include "../SDL_sysfilesystem.h"
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#if defined(SDL_PLATFORM_FREEBSD) || defined(SDL_PLATFORM_OPENBSD)
#include <sys/sysctl.h>
#endif
static char *readSymLink(const char *path)
{
char *result = NULL;
ssize_t len = 64;
ssize_t rc = -1;
while (1) {
char *ptr = (char *)SDL_realloc(result, (size_t)len);
if (!ptr) {
break;
}
result = ptr;
rc = readlink(path, result, len);
if (rc == -1) {
break; } else if (rc < len) {
result[rc] = '\0'; return result; }
len *= 2; }
SDL_free(result);
return NULL;
}
#ifdef SDL_PLATFORM_OPENBSD
static char *search_path_for_binary(const char *bin)
{
const char *envr_real = SDL_getenv("PATH");
char *envr;
size_t alloc_size;
char *exe = NULL;
char *start;
char *ptr;
if (!envr_real) {
SDL_SetError("No $PATH set");
return NULL;
}
start = envr = SDL_strdup(envr_real);
if (!envr) {
return NULL;
}
SDL_assert(bin != NULL);
alloc_size = SDL_strlen(bin) + SDL_strlen(envr) + 2;
exe = (char *)SDL_malloc(alloc_size);
do {
ptr = SDL_strchr(start, ':'); if (ptr != start) {
if (ptr) {
*ptr = '\0';
}
SDL_snprintf(exe, alloc_size, "%s%s%s", start, (ptr && (ptr[-1] == '/')) ? "" : "/", bin);
if (access(exe, X_OK) == 0) { SDL_free(envr);
return exe;
}
}
start = ptr + 1; } while (ptr);
SDL_free(envr);
SDL_free(exe);
SDL_SetError("Process not found in $PATH");
return NULL; }
#endif
char *SDL_SYS_GetBasePath(void)
{
char *result = NULL;
#ifdef SDL_PLATFORM_FREEBSD
char fullpath[PATH_MAX];
size_t buflen = sizeof(fullpath);
const int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 };
if (sysctl(mib, SDL_arraysize(mib), fullpath, &buflen, NULL, 0) != -1) {
result = SDL_strdup(fullpath);
if (!result) {
return NULL;
}
}
#endif
#ifdef SDL_PLATFORM_OPENBSD
char **cmdline;
size_t len;
const int mib[] = { CTL_KERN, KERN_PROC_ARGS, getpid(), KERN_PROC_ARGV };
if (sysctl(mib, 4, NULL, &len, NULL, 0) != -1) {
char *exe, *pwddst;
char *realpathbuf = (char *)SDL_malloc(PATH_MAX + 1);
if (!realpathbuf) {
return NULL;
}
cmdline = SDL_malloc(len);
if (!cmdline) {
SDL_free(realpathbuf);
return NULL;
}
sysctl(mib, 4, cmdline, &len, NULL, 0);
exe = cmdline[0];
pwddst = NULL;
if (SDL_strchr(exe, '/') == NULL) { exe = search_path_for_binary(cmdline[0]);
} else {
if (exe && *exe == '.') {
const char *pwd = SDL_getenv("PWD");
if (pwd && *pwd) {
SDL_asprintf(&pwddst, "%s/%s", pwd, exe);
}
}
}
if (exe) {
if (!pwddst) {
if (realpath(exe, realpathbuf) != NULL) {
result = realpathbuf;
}
} else {
if (realpath(pwddst, realpathbuf) != NULL) {
result = realpathbuf;
}
SDL_free(pwddst);
}
if (exe != cmdline[0]) {
SDL_free(exe);
}
}
if (!result) {
SDL_free(realpathbuf);
}
SDL_free(cmdline);
}
#endif
if (!result && (access("/proc", F_OK) == 0)) {
#ifdef SDL_PLATFORM_FREEBSD
result = readSymLink("/proc/curproc/file");
#elif defined(SDL_PLATFORM_NETBSD)
result = readSymLink("/proc/curproc/exe");
#elif defined(SDL_PLATFORM_SOLARIS)
result = readSymLink("/proc/self/path/a.out");
#else
result = readSymLink("/proc/self/exe"); if (!result) {
char path[64];
const int rc = SDL_snprintf(path, sizeof(path),
"/proc/%llu/exe",
(unsigned long long)getpid());
if ((rc > 0) && (rc < sizeof(path))) {
result = readSymLink(path);
}
}
#endif
}
#ifdef SDL_PLATFORM_SOLARIS
if (!result) {
const char *path = getexecname();
if ((path) && (path[0] == '/')) { result = SDL_strdup(path);
if (!result) {
return NULL;
}
}
}
#endif
if (result) { char *ptr = SDL_strrchr(result, '/');
if (ptr) {
*(ptr + 1) = '\0';
} else { SDL_free(result);
result = NULL;
}
}
if (result) {
char *ptr = (char *)SDL_realloc(result, SDL_strlen(result) + 1);
if (ptr) {
result = ptr; }
}
return result;
}
char *SDL_SYS_GetPrefPath(const char *org, const char *app)
{
const char *envr = SDL_getenv("XDG_DATA_HOME");
const char *append;
char *result = NULL;
char *ptr = NULL;
if (!envr) {
envr = SDL_getenv("HOME");
if (!envr) {
SDL_SetError("neither XDG_DATA_HOME nor HOME environment is set");
return NULL;
}
append = "/.local/share/";
} else {
append = "/";
}
size_t len = SDL_strlen(envr);
if (envr[len - 1] == '/') {
append += 1;
}
len += SDL_strlen(append) + SDL_strlen(org) + SDL_strlen(app) + 3;
result = (char *)SDL_malloc(len);
if (!result) {
return NULL;
}
if (*org) {
(void)SDL_snprintf(result, len, "%s%s%s/%s/", envr, append, org, app);
} else {
(void)SDL_snprintf(result, len, "%s%s%s/", envr, append, app);
}
for (ptr = result + 1; *ptr; ptr++) {
if (*ptr == '/') {
*ptr = '\0';
if (mkdir(result, 0700) != 0 && errno != EEXIST) {
goto error;
}
*ptr = '/';
}
}
if (mkdir(result, 0700) != 0 && errno != EEXIST) {
error:
SDL_SetError("Couldn't create directory '%s': '%s'", result, strerror(errno));
SDL_free(result);
return NULL;
}
return result;
}
static char *xdg_user_dir_lookup_with_fallback (const char *type, const char *fallback)
{
FILE *file;
const char *home_dir, *config_home;
char *config_file;
char buffer[512];
char *user_dir;
char *p, *d;
int len;
int relative;
size_t l;
home_dir = SDL_getenv("HOME");
if (!home_dir)
goto error;
config_home = SDL_getenv("XDG_CONFIG_HOME");
if (!config_home || config_home[0] == 0)
{
l = SDL_strlen (home_dir) + SDL_strlen ("/.config/user-dirs.dirs") + 1;
config_file = (char *)SDL_malloc (l);
if (!config_file)
goto error;
SDL_strlcpy (config_file, home_dir, l);
SDL_strlcat (config_file, "/.config/user-dirs.dirs", l);
}
else
{
l = SDL_strlen (config_home) + SDL_strlen ("/user-dirs.dirs") + 1;
config_file = (char *)SDL_malloc (l);
if (!config_file)
goto error;
SDL_strlcpy (config_file, config_home, l);
SDL_strlcat (config_file, "/user-dirs.dirs", l);
}
file = fopen (config_file, "r");
SDL_free (config_file);
if (!file)
goto error;
user_dir = NULL;
while (fgets (buffer, sizeof (buffer), file))
{
len = SDL_strlen (buffer);
if (len > 0 && buffer[len-1] == '\n')
buffer[len-1] = 0;
p = buffer;
while (*p == ' ' || *p == '\t')
p++;
if (SDL_strncmp (p, "XDG_", 4) != 0)
continue;
p += 4;
if (SDL_strncmp (p, type, SDL_strlen (type)) != 0)
continue;
p += SDL_strlen (type);
if (SDL_strncmp (p, "_DIR", 4) != 0)
continue;
p += 4;
while (*p == ' ' || *p == '\t')
p++;
if (*p != '=')
continue;
p++;
while (*p == ' ' || *p == '\t')
p++;
if (*p != '"')
continue;
p++;
relative = 0;
if (SDL_strncmp (p, "$HOME/", 6) == 0)
{
p += 6;
relative = 1;
}
else if (*p != '/')
continue;
SDL_free (user_dir);
if (relative)
{
l = SDL_strlen (home_dir) + 1 + SDL_strlen (p) + 1;
user_dir = (char *)SDL_malloc (l);
if (!user_dir)
goto error2;
SDL_strlcpy (user_dir, home_dir, l);
SDL_strlcat (user_dir, "/", l);
}
else
{
user_dir = (char *)SDL_malloc (SDL_strlen (p) + 1);
if (!user_dir)
goto error2;
*user_dir = 0;
}
d = user_dir + SDL_strlen (user_dir);
while (*p && *p != '"')
{
if ((*p == '\\') && (*(p+1) != 0))
p++;
*d++ = *p++;
}
*d = 0;
}
error2:
fclose (file);
if (user_dir)
return user_dir;
error:
if (fallback)
return SDL_strdup (fallback);
return NULL;
}
static char *xdg_user_dir_lookup (const char *type)
{
const char *home_dir;
char *dir, *user_dir;
dir = xdg_user_dir_lookup_with_fallback(type, NULL);
if (dir)
return dir;
home_dir = SDL_getenv("HOME");
if (!home_dir)
return NULL;
if (SDL_strcmp(type, "DESKTOP") == 0) {
size_t length = SDL_strlen(home_dir) + SDL_strlen("/Desktop") + 1;
user_dir = (char *)SDL_malloc(length);
if (!user_dir)
return NULL;
SDL_strlcpy(user_dir, home_dir, length);
SDL_strlcat(user_dir, "/Desktop", length);
return user_dir;
}
return NULL;
}
char *SDL_SYS_GetUserFolder(SDL_Folder folder)
{
const char *param = NULL;
char *result;
char *newresult;
switch(folder) {
case SDL_FOLDER_HOME:
param = SDL_getenv("HOME");
if (!param) {
SDL_SetError("No $HOME environment variable available");
return NULL;
}
result = SDL_strdup(param);
goto append_slash;
case SDL_FOLDER_DESKTOP:
param = "DESKTOP";
break;
case SDL_FOLDER_DOCUMENTS:
param = "DOCUMENTS";
break;
case SDL_FOLDER_DOWNLOADS:
param = "DOWNLOAD";
break;
case SDL_FOLDER_MUSIC:
param = "MUSIC";
break;
case SDL_FOLDER_PICTURES:
param = "PICTURES";
break;
case SDL_FOLDER_PUBLICSHARE:
param = "PUBLICSHARE";
break;
case SDL_FOLDER_SAVEDGAMES:
SDL_SetError("Saved Games folder unavailable on XDG");
return NULL;
case SDL_FOLDER_SCREENSHOTS:
SDL_SetError("Screenshots folder unavailable on XDG");
return NULL;
case SDL_FOLDER_TEMPLATES:
param = "TEMPLATES";
break;
case SDL_FOLDER_VIDEOS:
param = "VIDEOS";
break;
default:
SDL_SetError("Invalid SDL_Folder: %d", (int) folder);
return NULL;
}
if (!param) {
SDL_SetError("No corresponding XDG user directory");
return NULL;
}
result = xdg_user_dir_lookup(param);
if (!result) {
SDL_SetError("XDG directory not available");
return NULL;
}
append_slash:
newresult = (char *) SDL_realloc(result, SDL_strlen(result) + 2);
if (!newresult) {
SDL_free(result);
return NULL;
}
result = newresult;
SDL_strlcat(result, "/", SDL_strlen(result) + 2);
return result;
}
#endif