rev |
line source |
paulo@0
|
1 /*
|
paulo@0
|
2 ljpath - functions to support an application that can be
|
paulo@0
|
3 installed to either a read-write folder ("portable" config)
|
paulo@0
|
4 or to a read-only folder ("installed" config)
|
paulo@0
|
5
|
paulo@0
|
6 Copyright 2008 Damian Yerrick
|
paulo@0
|
7
|
paulo@0
|
8 Insert zlib license here.
|
paulo@0
|
9
|
paulo@0
|
10 */
|
paulo@0
|
11
|
paulo@0
|
12
|
paulo@0
|
13 #include <stdlib.h>
|
paulo@0
|
14 #include <string.h>
|
paulo@0
|
15 #include "ljpath.h"
|
paulo@0
|
16 #include <limits.h> // for PATH_MAX
|
paulo@0
|
17 #include <sys/stat.h> // for mkdir()
|
paulo@0
|
18 #include <errno.h>
|
paulo@0
|
19
|
paulo@0
|
20 #ifdef WIN32
|
paulo@0
|
21 #include <shlobj.h>
|
paulo@0
|
22 #include <direct.h>
|
paulo@0
|
23 #define USE_WINDOWS_APPDATA
|
paulo@0
|
24 #endif
|
paulo@0
|
25
|
paulo@0
|
26 // on the DS
|
paulo@0
|
27 #ifdef ARM9
|
paulo@0
|
28 #include <fat.h> // Chishm's libfat
|
paulo@0
|
29 static char hasFAT;
|
paulo@0
|
30 #include <unistd.h> // for MAXPATHLEN
|
paulo@0
|
31 #else
|
paulo@0
|
32 #define hasFAT 1
|
paulo@0
|
33 #endif
|
paulo@0
|
34
|
paulo@0
|
35 // http://forum.gbadev.org/viewtopic.php?p=147795#147795
|
paulo@0
|
36 #if !defined(PATH_MAX) && defined(MAXPATHLEN)
|
paulo@0
|
37 #define PATH_MAX MAXPATHLEN
|
paulo@0
|
38 #endif
|
paulo@0
|
39
|
paulo@0
|
40 static char readWriteFolder[PATH_MAX];
|
paulo@0
|
41 static char skinFolder[PATH_MAX];
|
paulo@0
|
42 static char readOnlyFolder[PATH_MAX];
|
paulo@0
|
43
|
paulo@0
|
44
|
paulo@0
|
45 #ifdef WIN32
|
paulo@0
|
46 static int ljmkdir(const char *path) {
|
paulo@0
|
47 //allegro_message("mkdir(\"%s\")\n", path);
|
paulo@0
|
48 return _mkdir(path);
|
paulo@0
|
49 }
|
paulo@0
|
50 #else
|
paulo@0
|
51 static int ljmkdir(const char *path) {
|
paulo@0
|
52 //allegro_message("mkdir(\"%s\")\n", path);
|
paulo@0
|
53 return mkdir(path, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
|
paulo@0
|
54 }
|
paulo@0
|
55 #endif
|
paulo@0
|
56
|
paulo@0
|
57 /**
|
paulo@0
|
58 * Wrapper around mkdir() that doesn't error if the
|
paulo@0
|
59 * directory already exists.
|
paulo@0
|
60 */
|
paulo@0
|
61 static int makeFolder(const char *path) {
|
paulo@0
|
62 if (ljmkdir(path) < 0) {
|
paulo@0
|
63 if (errno != EEXIST) {
|
paulo@0
|
64 return -1;
|
paulo@0
|
65 }
|
paulo@0
|
66 }
|
paulo@0
|
67 return 0;
|
paulo@0
|
68 }
|
paulo@0
|
69
|
paulo@0
|
70
|
paulo@0
|
71
|
paulo@0
|
72 /**
|
paulo@0
|
73 * Computes the name of the folder containing filename.
|
paulo@0
|
74 * @param dst destination to receive the folder name, of size PATH_MAX
|
paulo@0
|
75 * @param filename name of a file in this folder
|
paulo@0
|
76 */
|
paulo@0
|
77 static void ljpathSetFolder(char *dst, const char *filename) {
|
paulo@0
|
78 const char *lastPathSep = NULL;
|
paulo@0
|
79
|
paulo@0
|
80 // I considered strrchr, but it would need two passes:
|
paulo@0
|
81 // one for '/' and one for the drive-letter ':' under Windows.
|
paulo@0
|
82 for (const char *here = filename; *here != 0; ++here) {
|
paulo@0
|
83 if (*here == '/' || *here == '\\' || *here == ':') {
|
paulo@0
|
84 lastPathSep = here;
|
paulo@0
|
85 }
|
paulo@0
|
86 }
|
paulo@0
|
87 if (lastPathSep == NULL || lastPathSep - filename > PATH_MAX - 1) {
|
paulo@0
|
88 strcpy(dst, ".");
|
paulo@0
|
89 } else {
|
paulo@0
|
90 memcpy(dst, filename, lastPathSep - filename);
|
paulo@0
|
91 dst[lastPathSep - filename] = 0;
|
paulo@0
|
92 }
|
paulo@0
|
93 }
|
paulo@0
|
94
|
paulo@0
|
95 #if defined(USE_WINDOWS_APPDATA)
|
paulo@0
|
96 /**
|
paulo@0
|
97 * Retrieves the read/write path on a Windows system.
|
paulo@0
|
98 * SHGetFolderPath() is described in
|
paulo@0
|
99 * http://msdn.microsoft.com/library/default.asp?url=/library/en-us/shellcc/platform/shell/reference/functions/shgetfolderpath.asp
|
paulo@0
|
100 */
|
paulo@0
|
101 static void ljpathSetRWFolder(void) {
|
paulo@0
|
102 static const char suffix1[] = "/Pin Eight";
|
paulo@0
|
103 static const char suffix2[] = "/lockjaw";
|
paulo@0
|
104 char path[MAX_PATH + sizeof(suffix1) + sizeof(suffix2)] = "";
|
paulo@0
|
105
|
paulo@0
|
106 int rVal = SHGetFolderPath(
|
paulo@0
|
107 NULL, // owner window used with obscure dial-up functionality
|
paulo@0
|
108 CSIDL_APPDATA, // type of folder to retrieve
|
paulo@0
|
109 NULL, // user ID when impersonating another user
|
paulo@0
|
110 0, // get the current path, not the default path
|
paulo@0
|
111 path
|
paulo@0
|
112 );
|
paulo@0
|
113 if (rVal) {
|
paulo@0
|
114 return;
|
paulo@0
|
115 }
|
paulo@0
|
116
|
paulo@0
|
117 strcat(path, suffix1);
|
paulo@0
|
118 ljmkdir(path);
|
paulo@0
|
119 strcat(path, suffix2);
|
paulo@0
|
120 ljmkdir(path);
|
paulo@0
|
121 if (strlen(path) >= PATH_MAX) {
|
paulo@0
|
122 return;
|
paulo@0
|
123 }
|
paulo@0
|
124
|
paulo@0
|
125 strncpy(readWriteFolder, path, PATH_MAX - 1);
|
paulo@0
|
126 readWriteFolder[PATH_MAX - 1] = 0;
|
paulo@0
|
127 }
|
paulo@0
|
128 #else
|
paulo@0
|
129 /**
|
paulo@0
|
130 * Retrieves the read/write path on a UNIX system using the HOME
|
paulo@0
|
131 * environment variable.
|
paulo@0
|
132 */
|
paulo@0
|
133 static void ljpathSetRWFolder(void) {
|
paulo@0
|
134 static const char suffix[] = "/.lockjaw";
|
paulo@0
|
135 const char *value = getenv("HOME");
|
paulo@0
|
136 if (!value) {
|
paulo@0
|
137 return;
|
paulo@0
|
138 }
|
paulo@0
|
139
|
paulo@0
|
140 unsigned int len = strlen(value) + sizeof(suffix);
|
paulo@0
|
141 if (len > PATH_MAX) {
|
paulo@0
|
142 return;
|
paulo@0
|
143 }
|
paulo@0
|
144
|
paulo@0
|
145 strcpy(readWriteFolder, value);
|
paulo@0
|
146 strcat(readWriteFolder, suffix);
|
paulo@0
|
147 ljmkdir(readWriteFolder);
|
paulo@0
|
148 //allegro_message("readWriteFolder is now \"%s\"\n", skinFolder);
|
paulo@0
|
149 }
|
paulo@0
|
150 #endif
|
paulo@0
|
151
|
paulo@0
|
152 void ljpathSetSkinFolder(const char *filename) {
|
paulo@0
|
153 //allegro_message("ljpathSetFolder(skinFolder, \"%s\")\n", filename);
|
paulo@0
|
154 ljpathSetFolder(skinFolder, filename);
|
paulo@0
|
155 //allegro_message("skinFolder is now \"%s\"\n", skinFolder);
|
paulo@0
|
156 }
|
paulo@0
|
157
|
paulo@0
|
158 static int ljpathJoin(char *restrict dst,
|
paulo@0
|
159 const char *restrict folder,
|
paulo@0
|
160 const char *restrict filename) {
|
paulo@0
|
161 size_t len = 0;
|
paulo@0
|
162
|
paulo@0
|
163 //allegro_message("ljpathJoin(\"%s\", \"%s\")\n", folder, filename);
|
paulo@0
|
164
|
paulo@0
|
165 // If the path is not absolute, copy the folder and the
|
paulo@0
|
166 // path separator into the destination string.
|
paulo@0
|
167 if (filename[0] != '\\' && filename[0] != '\\'
|
paulo@0
|
168 && filename[1] != ':') {
|
paulo@0
|
169 while (len < PATH_MAX - 1 && *folder != 0) {
|
paulo@0
|
170 dst[len++] = *folder++;
|
paulo@0
|
171 }
|
paulo@0
|
172 /* The path separator '/' is standard under Linux and Mac OS X.
|
paulo@0
|
173 It also works on MS-DOS and Windows API, notwithstanding
|
paulo@0
|
174 certain Microsoft command-line utilities. */
|
paulo@0
|
175 dst[len++] = '/';
|
paulo@0
|
176 while (len < PATH_MAX - 1 && *filename != 0) {
|
paulo@0
|
177 dst[len++] = *filename++;
|
paulo@0
|
178 }
|
paulo@0
|
179 }
|
paulo@0
|
180
|
paulo@0
|
181 if (len < PATH_MAX - 1) {
|
paulo@0
|
182 dst[len] = 0;
|
paulo@0
|
183 //allegro_message("equals \"%s\"\n", dst);
|
paulo@0
|
184 return 1;
|
paulo@0
|
185 }
|
paulo@0
|
186 //allegro_message("failed\n");
|
paulo@0
|
187 return 0;
|
paulo@0
|
188 }
|
paulo@0
|
189
|
paulo@0
|
190 /**
|
paulo@0
|
191 * Determines whether a file exists and is readable.
|
paulo@0
|
192 * @param path the path of a file
|
paulo@0
|
193 * @return nonzero iff readable
|
paulo@0
|
194 */
|
paulo@0
|
195 static int fexists(const char *path) {
|
paulo@0
|
196 struct stat st;
|
paulo@0
|
197 if (!hasFAT) {
|
paulo@0
|
198 return 0;
|
paulo@0
|
199 }
|
paulo@0
|
200 int rval = stat(path, &st);
|
paulo@0
|
201 //allegro_message("stat(\"%s\") returned %d\n", path, rval);
|
paulo@0
|
202 return rval == 0;
|
paulo@0
|
203 }
|
paulo@0
|
204
|
paulo@0
|
205 int ljpathInit(const char *argv0) {
|
paulo@0
|
206 #ifdef ARM9
|
paulo@0
|
207 hasFAT = fatInitDefault();
|
paulo@0
|
208 if (hasFAT) {
|
paulo@0
|
209 if (makeFolder("/data") < 0 || makeFolder("/data/lockjaw") < 0) {
|
paulo@0
|
210 hasFAT = 0;
|
paulo@0
|
211 }
|
paulo@0
|
212 }
|
paulo@0
|
213 strcpy(readOnlyFolder, "/data/lockjaw");
|
paulo@0
|
214 strcpy(readWriteFolder, "/data/lockjaw");
|
paulo@0
|
215 strcpy(skinFolder, "/data/lockjaw");
|
paulo@0
|
216 return hasFAT;
|
paulo@0
|
217 #else
|
paulo@0
|
218 char path[PATH_MAX];
|
paulo@0
|
219 int installed;
|
paulo@0
|
220
|
paulo@0
|
221 //allegro_message("argv[0] = \"%s\"", argv0);
|
paulo@0
|
222 ljpathSetFolder(readOnlyFolder, argv0);
|
paulo@0
|
223 strcpy(readWriteFolder, ".");
|
paulo@0
|
224 strcpy(skinFolder, ".");
|
paulo@0
|
225
|
paulo@0
|
226 // determine whether "installed.ini" (an empty text file)
|
paulo@0
|
227 // exists
|
paulo@0
|
228 installed = ljpathJoin(path, readOnlyFolder, "installed.ini") >= 0
|
paulo@0
|
229 && fexists(path);
|
paulo@0
|
230 if (installed) {
|
paulo@0
|
231 ljpathSetRWFolder();
|
paulo@0
|
232 }
|
paulo@0
|
233 return installed;
|
paulo@0
|
234 #endif
|
paulo@0
|
235 }
|
paulo@0
|
236
|
paulo@0
|
237 /**
|
paulo@0
|
238 * Finds a file name for reading.
|
paulo@0
|
239 * @param dst buffer for path, of size PATH_MAX
|
paulo@0
|
240 * @param src name of file
|
paulo@0
|
241 * @return 0 if not found, nonzero if found
|
paulo@0
|
242 */
|
paulo@0
|
243 int ljpathFind_r(char *restrict dst, const char *restrict filename) {
|
paulo@0
|
244 if (!hasFAT) {
|
paulo@0
|
245 return 0;
|
paulo@0
|
246 }
|
paulo@0
|
247 if (ljpathJoin(dst, readWriteFolder, filename) >= 0
|
paulo@0
|
248 && fexists(dst)) {
|
paulo@0
|
249 return 1;
|
paulo@0
|
250 }
|
paulo@0
|
251 if (ljpathJoin(dst, skinFolder, filename) >= 0
|
paulo@0
|
252 && fexists(dst)) {
|
paulo@0
|
253 return 1;
|
paulo@0
|
254 }
|
paulo@0
|
255 if (ljpathJoin(dst, readOnlyFolder, filename) >= 0
|
paulo@0
|
256 && fexists(dst)) {
|
paulo@0
|
257 return 1;
|
paulo@0
|
258 }
|
paulo@0
|
259 return 0;
|
paulo@0
|
260 }
|
paulo@0
|
261
|
paulo@0
|
262 int ljpathFind_w(char *restrict dst, const char *restrict filename) {
|
paulo@0
|
263 return ljpathJoin(dst, readWriteFolder, filename);
|
paulo@0
|
264 }
|
paulo@0
|
265
|
paulo@0
|
266 FILE *ljfopen(const char *restrict filename, const char *restrict mode) {
|
paulo@0
|
267 char path[PATH_MAX];
|
paulo@0
|
268 FILE *fp;
|
paulo@0
|
269
|
paulo@0
|
270 if (!hasFAT) {
|
paulo@0
|
271 return NULL;
|
paulo@0
|
272 }
|
paulo@0
|
273
|
paulo@0
|
274 //allegro_message("ljfopen(\"%s\", \"%s\")\n", filename, mode);
|
paulo@0
|
275 if (mode[0] == 'r') {
|
paulo@0
|
276
|
paulo@0
|
277 // For read-only files, search all three paths.
|
paulo@0
|
278 if (ljpathFind_r(path, filename) >= 0
|
paulo@0
|
279 && (fp = fopen(path, mode)) != 0) {
|
paulo@0
|
280 //allegro_message("fopen(\"%s\", \"%s\") for reading\n", path, mode);
|
paulo@0
|
281 return fp;
|
paulo@0
|
282 }
|
paulo@0
|
283 } else {
|
paulo@0
|
284
|
paulo@0
|
285 // For read/write files, check only the read/write path.
|
paulo@0
|
286 if (ljpathFind_w(path, filename) >= 0
|
paulo@0
|
287 && (fp = fopen(path, mode)) != 0) {
|
paulo@0
|
288 //allegro_message("fopen(\"%s\", \"%s\") for writing\n", path, mode);
|
paulo@0
|
289 return fp;
|
paulo@0
|
290 }
|
paulo@0
|
291 }
|
paulo@0
|
292 return NULL;
|
paulo@0
|
293 }
|
paulo@0
|
294
|