1 | /* |
2 | Simple DirectMedia Layer |
3 | Copyright (C) 1997-2021 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 * |
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 * |
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 = 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 * |
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 | |