1 /*
2   Simple DirectMedia Layer
3   Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
4 
5   This software is provided 'as-is', without any express or implied
6   warranty.  In no event will the authors be held liable for any damages
7   arising from the use of this software.
8 
9   Permission is granted to anyone to use this software for any purpose,
10   including commercial applications, and to alter it and redistribute it
11   freely, subject to the following restrictions:
12 
13   1. The origin of this software must not be misrepresented; you must not
14      claim that you wrote the original software. If you use this software
15      in a product, an acknowledgment in the product documentation would be
16      appreciated but is not required.
17   2. Altered source versions must be plainly marked as such, and must not be
18      misrepresented as being the original software.
19   3. This notice may not be removed or altered from any source distribution.
20 */
21 #include "../../SDL_internal.h"
22 
23 #ifdef SDL_FILESYSTEM_UNIX
24 
25 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
26 /* System dependent filesystem routines                                */
27 
28 #include <errno.h>
29 #include <stdio.h>
30 #include <unistd.h>
31 #include <stdlib.h>
32 #include <sys/stat.h>
33 #include <sys/types.h>
34 #include <limits.h>
35 #include <fcntl.h>
36 
37 #if defined(__FREEBSD__) || defined(__OPENBSD__)
38 #include <sys/sysctl.h>
39 #endif
40 
41 #include "SDL_error.h"
42 #include "SDL_stdinc.h"
43 #include "SDL_filesystem.h"
44 #include "SDL_rwops.h"
45 
46 /* QNX's /proc/self/exefile is a text file and not a symlink. */
47 #if !defined(__QNXNTO__)
48 static char *
readSymLink(const char * path)49 readSymLink(const char *path)
50 {
51     char *retval = NULL;
52     ssize_t len = 64;
53     ssize_t rc = -1;
54 
55     while (1)
56     {
57         char *ptr = (char *) SDL_realloc(retval, (size_t) len);
58         if (ptr == NULL) {
59             SDL_OutOfMemory();
60             break;
61         }
62 
63         retval = ptr;
64 
65         rc = readlink(path, retval, len);
66         if (rc == -1) {
67             break;  /* not a symlink, i/o error, etc. */
68         } else if (rc < len) {
69             retval[rc] = '\0';  /* readlink doesn't null-terminate. */
70             return retval;  /* we're good to go. */
71         }
72 
73         len *= 2;  /* grow buffer, try again. */
74     }
75 
76     SDL_free(retval);
77     return NULL;
78 }
79 #endif
80 
81 char *
SDL_GetBasePath(void)82 SDL_GetBasePath(void)
83 {
84     char *retval = NULL;
85 
86 #if defined(__FREEBSD__)
87     char fullpath[PATH_MAX];
88     size_t buflen = sizeof (fullpath);
89     const int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 };
90     if (sysctl(mib, SDL_arraysize(mib), fullpath, &buflen, NULL, 0) != -1) {
91         retval = SDL_strdup(fullpath);
92         if (!retval) {
93             SDL_OutOfMemory();
94             return NULL;
95         }
96     }
97 #endif
98 #if defined(__OPENBSD__)
99     char **retvalargs;
100     size_t len;
101     const int mib[] = { CTL_KERN, KERN_PROC_ARGS, getpid(), KERN_PROC_ARGV };
102     if (sysctl(mib, 4, NULL, &len, NULL, 0) != -1) {
103         retvalargs = SDL_malloc(len);
104         if (!retvalargs) {
105             SDL_OutOfMemory();
106             return NULL;
107         }
108         sysctl(mib, 4, retvalargs, &len, NULL, 0);
109         retval = SDL_malloc(PATH_MAX + 1);
110         if (retval)
111             realpath(retvalargs[0], retval);
112 
113         SDL_free(retvalargs);
114     }
115 #endif
116 #if defined(__SOLARIS__)
117     const char *path = getexecname();
118     if ((path != NULL) && (path[0] == '/')) { /* must be absolute path... */
119         retval = SDL_strdup(path);
120         if (!retval) {
121             SDL_OutOfMemory();
122             return NULL;
123         }
124     }
125 #endif
126 
127     /* is a Linux-style /proc filesystem available? */
128     if (!retval && (access("/proc", F_OK) == 0)) {
129         /* !!! FIXME: after 2.0.6 ships, let's delete this code and just
130                       use the /proc/%llu version. There's no reason to have
131                       two copies of this plus all the #ifdefs. --ryan. */
132 #if defined(__FREEBSD__)
133         retval = readSymLink("/proc/curproc/file");
134 #elif defined(__NETBSD__)
135         retval = readSymLink("/proc/curproc/exe");
136 #elif defined(__QNXNTO__)
137         retval = SDL_LoadFile("/proc/self/exefile", NULL);
138 #else
139         retval = readSymLink("/proc/self/exe");  /* linux. */
140         if (retval == NULL) {
141             /* older kernels don't have /proc/self ... try PID version... */
142             char path[64];
143             const int rc = (int) SDL_snprintf(path, sizeof(path),
144                                               "/proc/%llu/exe",
145                                               (unsigned long long) getpid());
146             if ( (rc > 0) && (rc < sizeof(path)) ) {
147                 retval = readSymLink(path);
148             }
149         }
150 #endif
151     }
152 
153     /* If we had access to argv[0] here, we could check it for a path,
154         or troll through $PATH looking for it, too. */
155 
156     if (retval != NULL) { /* chop off filename. */
157         char *ptr = SDL_strrchr(retval, '/');
158         if (ptr != NULL) {
159             *(ptr+1) = '\0';
160         } else {  /* shouldn't happen, but just in case... */
161             SDL_free(retval);
162             retval = NULL;
163         }
164     }
165 
166     if (retval != NULL) {
167         /* try to shrink buffer... */
168         char *ptr = (char *) SDL_realloc(retval, strlen(retval) + 1);
169         if (ptr != NULL)
170             retval = ptr;  /* oh well if it failed. */
171     }
172 
173     return retval;
174 }
175 
176 char *
SDL_GetPrefPath(const char * org,const char * app)177 SDL_GetPrefPath(const char *org, const char *app)
178 {
179     /*
180      * We use XDG's base directory spec, even if you're not on Linux.
181      *  This isn't strictly correct, but the results are relatively sane
182      *  in any case.
183      *
184      * http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
185      */
186     const char *envr = SDL_getenv("XDG_DATA_HOME");
187     const char *append;
188     char *retval = NULL;
189     char *ptr = NULL;
190     size_t len = 0;
191 
192     if (!app) {
193         SDL_InvalidParamError("app");
194         return NULL;
195     }
196     if (!org) {
197         org = "";
198     }
199 
200     if (!envr) {
201         /* You end up with "$HOME/.local/share/Game Name 2" */
202         envr = SDL_getenv("HOME");
203         if (!envr) {
204             /* we could take heroic measures with /etc/passwd, but oh well. */
205             SDL_SetError("neither XDG_DATA_HOME nor HOME environment is set");
206             return NULL;
207         }
208         append = "/.local/share/";
209     } else {
210         append = "/";
211     }
212 
213     len = SDL_strlen(envr);
214     if (envr[len - 1] == '/')
215         append += 1;
216 
217     len += SDL_strlen(append) + SDL_strlen(org) + SDL_strlen(app) + 3;
218     retval = (char *) SDL_malloc(len);
219     if (!retval) {
220         SDL_OutOfMemory();
221         return NULL;
222     }
223 
224     if (*org) {
225         SDL_snprintf(retval, len, "%s%s%s/%s/", envr, append, org, app);
226     } else {
227         SDL_snprintf(retval, len, "%s%s%s/", envr, append, app);
228     }
229 
230     for (ptr = retval+1; *ptr; ptr++) {
231         if (*ptr == '/') {
232             *ptr = '\0';
233             if (mkdir(retval, 0700) != 0 && errno != EEXIST)
234                 goto error;
235             *ptr = '/';
236         }
237     }
238     if (mkdir(retval, 0700) != 0 && errno != EEXIST) {
239 error:
240         SDL_SetError("Couldn't create directory '%s': '%s'", retval, strerror(errno));
241         SDL_free(retval);
242         return NULL;
243     }
244 
245     return retval;
246 }
247 
248 #endif /* SDL_FILESYSTEM_UNIX */
249 
250 /* vi: set ts=4 sw=4 expandtab: */
251