SDL
618 строк · 16.1 Кб
1/*
2Simple DirectMedia Layer
3Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
4
5This software is provided 'as-is', without any express or implied
6warranty. In no event will the authors be held liable for any damages
7arising from the use of this software.
8
9Permission is granted to anyone to use this software for any purpose,
10including commercial applications, and to alter it and redistribute it
11freely, subject to the following restrictions:
12
131. The origin of this software must not be misrepresented; you must not
14claim that you wrote the original software. If you use this software
15in a product, an acknowledgment in the product documentation would be
16appreciated but is not required.
172. Altered source versions must be plainly marked as such, and must not be
18misrepresented as being the original software.
193. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"22
23#ifdef SDL_FILESYSTEM_UNIX24
25/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
26// System dependent filesystem routines
27
28#include "../SDL_sysfilesystem.h"29
30#include <stdio.h>31#include <sys/stat.h>32#include <sys/types.h>33#include <dirent.h>34#include <errno.h>35#include <fcntl.h>36#include <limits.h>37#include <stdlib.h>38#include <string.h>39#include <unistd.h>40
41#if defined(SDL_PLATFORM_FREEBSD) || defined(SDL_PLATFORM_OPENBSD)42#include <sys/sysctl.h>43#endif44
45static char *readSymLink(const char *path)46{
47char *result = NULL;48ssize_t len = 64;49ssize_t rc = -1;50
51while (1) {52char *ptr = (char *)SDL_realloc(result, (size_t)len);53if (!ptr) {54break;55}56
57result = ptr;58
59rc = readlink(path, result, len);60if (rc == -1) {61break; // not a symlink, i/o error, etc.62} else if (rc < len) {63result[rc] = '\0'; // readlink doesn't null-terminate.64return result; // we're good to go.65}66
67len *= 2; // grow buffer, try again.68}69
70SDL_free(result);71return NULL;72}
73
74#ifdef SDL_PLATFORM_OPENBSD75static char *search_path_for_binary(const char *bin)76{
77const char *envr_real = SDL_getenv("PATH");78char *envr;79size_t alloc_size;80char *exe = NULL;81char *start = envr;82char *ptr;83
84if (!envr_real) {85SDL_SetError("No $PATH set");86return NULL;87}88
89envr = SDL_strdup(envr_real);90if (!envr) {91return NULL;92}93
94SDL_assert(bin != NULL);95
96alloc_size = SDL_strlen(bin) + SDL_strlen(envr) + 2;97exe = (char *)SDL_malloc(alloc_size);98
99do {100ptr = SDL_strchr(start, ':'); // find next $PATH separator.101if (ptr != start) {102if (ptr) {103*ptr = '\0';104}105
106// build full binary path...107SDL_snprintf(exe, alloc_size, "%s%s%s", start, (ptr && (ptr[-1] == '/')) ? "" : "/", bin);108
109if (access(exe, X_OK) == 0) { // Exists as executable? We're done.110SDL_free(envr);111return exe;112}113}114start = ptr + 1; // start points to beginning of next element.115} while (ptr);116
117SDL_free(envr);118SDL_free(exe);119
120SDL_SetError("Process not found in $PATH");121return NULL; // doesn't exist in path.122}
123#endif124
125char *SDL_SYS_GetBasePath(void)126{
127char *result = NULL;128
129#ifdef SDL_PLATFORM_FREEBSD130char fullpath[PATH_MAX];131size_t buflen = sizeof(fullpath);132const int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 };133if (sysctl(mib, SDL_arraysize(mib), fullpath, &buflen, NULL, 0) != -1) {134result = SDL_strdup(fullpath);135if (!result) {136return NULL;137}138}139#endif140#ifdef SDL_PLATFORM_OPENBSD141// Please note that this will fail if the process was launched with a relative path and $PWD + the cwd have changed, or argv is altered. So don't do that. Or add a new sysctl to OpenBSD.142char **cmdline;143size_t len;144const int mib[] = { CTL_KERN, KERN_PROC_ARGS, getpid(), KERN_PROC_ARGV };145if (sysctl(mib, 4, NULL, &len, NULL, 0) != -1) {146char *exe, *pwddst;147char *realpathbuf = (char *)SDL_malloc(PATH_MAX + 1);148if (!realpathbuf) {149return NULL;150}151
152cmdline = SDL_malloc(len);153if (!cmdline) {154SDL_free(realpathbuf);155return NULL;156}157
158sysctl(mib, 4, cmdline, &len, NULL, 0);159
160exe = cmdline[0];161pwddst = NULL;162if (SDL_strchr(exe, '/') == NULL) { // not a relative or absolute path, check $PATH for it163exe = search_path_for_binary(cmdline[0]);164} else {165if (exe && *exe == '.') {166const char *pwd = SDL_getenv("PWD");167if (pwd && *pwd) {168SDL_asprintf(&pwddst, "%s/%s", pwd, exe);169}170}171}172
173if (exe) {174if (!pwddst) {175if (realpath(exe, realpathbuf) != NULL) {176result = realpathbuf;177}178} else {179if (realpath(pwddst, realpathbuf) != NULL) {180result = realpathbuf;181}182SDL_free(pwddst);183}184
185if (exe != cmdline[0]) {186SDL_free(exe);187}188}189
190if (!result) {191SDL_free(realpathbuf);192}193
194SDL_free(cmdline);195}196#endif197
198// is a Linux-style /proc filesystem available?199if (!result && (access("/proc", F_OK) == 0)) {200/* !!! FIXME: after 2.0.6 ships, let's delete this code and just201use the /proc/%llu version. There's no reason to have
202two copies of this plus all the #ifdefs. --ryan. */
203#ifdef SDL_PLATFORM_FREEBSD204result = readSymLink("/proc/curproc/file");205#elif defined(SDL_PLATFORM_NETBSD)206result = readSymLink("/proc/curproc/exe");207#elif defined(SDL_PLATFORM_SOLARIS)208result = readSymLink("/proc/self/path/a.out");209#else210result = readSymLink("/proc/self/exe"); // linux.211if (!result) {212// older kernels don't have /proc/self ... try PID version...213char path[64];214const int rc = SDL_snprintf(path, sizeof(path),215"/proc/%llu/exe",216(unsigned long long)getpid());217if ((rc > 0) && (rc < sizeof(path))) {218result = readSymLink(path);219}220}221#endif222}223
224#ifdef SDL_PLATFORM_SOLARIS // try this as a fallback if /proc didn't pan out225if (!result) {226const char *path = getexecname();227if ((path) && (path[0] == '/')) { // must be absolute path...228result = SDL_strdup(path);229if (!result) {230return NULL;231}232}233}234#endif235/* If we had access to argv[0] here, we could check it for a path,236or troll through $PATH looking for it, too. */
237
238if (result) { // chop off filename.239char *ptr = SDL_strrchr(result, '/');240if (ptr) {241*(ptr + 1) = '\0';242} else { // shouldn't happen, but just in case...243SDL_free(result);244result = NULL;245}246}247
248if (result) {249// try to shrink buffer...250char *ptr = (char *)SDL_realloc(result, SDL_strlen(result) + 1);251if (ptr) {252result = ptr; // oh well if it failed.253}254}255
256return result;257}
258
259char *SDL_SYS_GetPrefPath(const char *org, const char *app)260{
261/*262* We use XDG's base directory spec, even if you're not on Linux.
263* This isn't strictly correct, but the results are relatively sane
264* in any case.
265*
266* http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
267*/
268const char *envr = SDL_getenv("XDG_DATA_HOME");269const char *append;270char *result = NULL;271char *ptr = NULL;272size_t len = 0;273
274if (!app) {275SDL_InvalidParamError("app");276return NULL;277}278if (!org) {279org = "";280}281
282if (!envr) {283// You end up with "$HOME/.local/share/Game Name 2"284envr = SDL_getenv("HOME");285if (!envr) {286// we could take heroic measures with /etc/passwd, but oh well.287SDL_SetError("neither XDG_DATA_HOME nor HOME environment is set");288return NULL;289}290append = "/.local/share/";291} else {292append = "/";293}294
295len = SDL_strlen(envr);296if (envr[len - 1] == '/') {297append += 1;298}299
300len += SDL_strlen(append) + SDL_strlen(org) + SDL_strlen(app) + 3;301result = (char *)SDL_malloc(len);302if (!result) {303return NULL;304}305
306if (*org) {307(void)SDL_snprintf(result, len, "%s%s%s/%s/", envr, append, org, app);308} else {309(void)SDL_snprintf(result, len, "%s%s%s/", envr, append, app);310}311
312for (ptr = result + 1; *ptr; ptr++) {313if (*ptr == '/') {314*ptr = '\0';315if (mkdir(result, 0700) != 0 && errno != EEXIST) {316goto error;317}318*ptr = '/';319}320}321if (mkdir(result, 0700) != 0 && errno != EEXIST) {322error:323SDL_SetError("Couldn't create directory '%s': '%s'", result, strerror(errno));324SDL_free(result);325return NULL;326}327
328return result;329}
330
331/*
332The two functions below (prefixed with `xdg_`) have been copied from:
333https://gitlab.freedesktop.org/xdg/xdg-user-dirs/-/blob/master/xdg-user-dir-lookup.c
334and have been adapted to work with SDL. They are licensed under the following
335terms:
336
337Copyright (c) 2007 Red Hat, Inc.
338
339Permission is hereby granted, free of charge, to any person
340obtaining a copy of this software and associated documentation files
341(the "Software"), to deal in the Software without restriction,
342including without limitation the rights to use, copy, modify, merge,
343publish, distribute, sublicense, and/or sell copies of the Software,
344and to permit persons to whom the Software is furnished to do so,
345subject to the following conditions:
346
347The above copyright notice and this permission notice shall be
348included in all copies or substantial portions of the Software.
349
350THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
351EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
352MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
353NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
354BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
355ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
356CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
357SOFTWARE.
358*/
359static char *xdg_user_dir_lookup_with_fallback (const char *type, const char *fallback)360{
361FILE *file;362const char *home_dir, *config_home;363char *config_file;364char buffer[512];365char *user_dir;366char *p, *d;367int len;368int relative;369size_t l;370
371home_dir = SDL_getenv ("HOME");372
373if (!home_dir)374goto error;375
376config_home = SDL_getenv ("XDG_CONFIG_HOME");377if (!config_home || config_home[0] == 0)378{379l = SDL_strlen (home_dir) + SDL_strlen ("/.config/user-dirs.dirs") + 1;380config_file = (char*) SDL_malloc (l);381if (!config_file)382goto error;383
384SDL_strlcpy (config_file, home_dir, l);385SDL_strlcat (config_file, "/.config/user-dirs.dirs", l);386}387else388{389l = SDL_strlen (config_home) + SDL_strlen ("/user-dirs.dirs") + 1;390config_file = (char*) SDL_malloc (l);391if (!config_file)392goto error;393
394SDL_strlcpy (config_file, config_home, l);395SDL_strlcat (config_file, "/user-dirs.dirs", l);396}397
398file = fopen (config_file, "r");399SDL_free (config_file);400if (!file)401goto error;402
403user_dir = NULL;404while (fgets (buffer, sizeof (buffer), file))405{406// Remove newline at end407len = SDL_strlen (buffer);408if (len > 0 && buffer[len-1] == '\n')409buffer[len-1] = 0;410
411p = buffer;412while (*p == ' ' || *p == '\t')413p++;414
415if (SDL_strncmp (p, "XDG_", 4) != 0)416continue;417p += 4;418if (SDL_strncmp (p, type, SDL_strlen (type)) != 0)419continue;420p += SDL_strlen (type);421if (SDL_strncmp (p, "_DIR", 4) != 0)422continue;423p += 4;424
425while (*p == ' ' || *p == '\t')426p++;427
428if (*p != '=')429continue;430p++;431
432while (*p == ' ' || *p == '\t')433p++;434
435if (*p != '"')436continue;437p++;438
439relative = 0;440if (SDL_strncmp (p, "$HOME/", 6) == 0)441{442p += 6;443relative = 1;444}445else if (*p != '/')446continue;447
448SDL_free (user_dir);449if (relative)450{451l = SDL_strlen (home_dir) + 1 + SDL_strlen (p) + 1;452user_dir = (char*) SDL_malloc (l);453if (!user_dir)454goto error2;455
456SDL_strlcpy (user_dir, home_dir, l);457SDL_strlcat (user_dir, "/", l);458}459else460{461user_dir = (char*) SDL_malloc (SDL_strlen (p) + 1);462if (!user_dir)463goto error2;464
465*user_dir = 0;466}467
468d = user_dir + SDL_strlen (user_dir);469while (*p && *p != '"')470{471if ((*p == '\\') && (*(p+1) != 0))472p++;473*d++ = *p++;474}475*d = 0;476}477error2:478fclose (file);479
480if (user_dir)481return user_dir;482
483error:484if (fallback)485return SDL_strdup (fallback);486return NULL;487}
488
489static char *xdg_user_dir_lookup (const char *type)490{
491const char *home_dir;492char *dir, *user_dir;493
494dir = xdg_user_dir_lookup_with_fallback(type, NULL);495if (dir)496return dir;497
498home_dir = SDL_getenv("HOME");499
500if (!home_dir)501return NULL;502
503// Special case desktop for historical compatibility504if (SDL_strcmp(type, "DESKTOP") == 0) {505size_t length = SDL_strlen(home_dir) + SDL_strlen("/Desktop") + 1;506user_dir = (char*) SDL_malloc(length);507if (!user_dir)508return NULL;509
510SDL_strlcpy(user_dir, home_dir, length);511SDL_strlcat(user_dir, "/Desktop", length);512return user_dir;513}514
515return NULL;516}
517
518char *SDL_SYS_GetUserFolder(SDL_Folder folder)519{
520const char *param = NULL;521char *result;522char *newresult;523
524/* According to `man xdg-user-dir`, the possible values are:525DESKTOP
526DOWNLOAD
527TEMPLATES
528PUBLICSHARE
529DOCUMENTS
530MUSIC
531PICTURES
532VIDEOS
533*/
534switch(folder) {535case SDL_FOLDER_HOME:536param = SDL_getenv("HOME");537
538if (!param) {539SDL_SetError("No $HOME environment variable available");540return NULL;541}542
543result = SDL_strdup(param);544goto append_slash;545
546case SDL_FOLDER_DESKTOP:547param = "DESKTOP";548break;549
550case SDL_FOLDER_DOCUMENTS:551param = "DOCUMENTS";552break;553
554case SDL_FOLDER_DOWNLOADS:555param = "DOWNLOAD";556break;557
558case SDL_FOLDER_MUSIC:559param = "MUSIC";560break;561
562case SDL_FOLDER_PICTURES:563param = "PICTURES";564break;565
566case SDL_FOLDER_PUBLICSHARE:567param = "PUBLICSHARE";568break;569
570case SDL_FOLDER_SAVEDGAMES:571SDL_SetError("Saved Games folder unavailable on XDG");572return NULL;573
574case SDL_FOLDER_SCREENSHOTS:575SDL_SetError("Screenshots folder unavailable on XDG");576return NULL;577
578case SDL_FOLDER_TEMPLATES:579param = "TEMPLATES";580break;581
582case SDL_FOLDER_VIDEOS:583param = "VIDEOS";584break;585
586default:587SDL_SetError("Invalid SDL_Folder: %d", (int) folder);588return NULL;589}590
591/* param *should* to be set to something at this point, but just in case */592if (!param) {593SDL_SetError("No corresponding XDG user directory");594return NULL;595}596
597result = xdg_user_dir_lookup(param);598
599if (!result) {600SDL_SetError("XDG directory not available");601return NULL;602}603
604append_slash:605newresult = (char *) SDL_realloc(result, SDL_strlen(result) + 2);606
607if (!newresult) {608SDL_free(result);609return NULL;610}611
612result = newresult;613SDL_strlcat(result, "/", SDL_strlen(result) + 2);614
615return result;616}
617
618#endif // SDL_FILESYSTEM_UNIX619