comparison src/ljpath.c @ 0:c84446dfb3f5

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