paulo@0: /* paulo@0: ljpath - functions to support an application that can be paulo@0: installed to either a read-write folder ("portable" config) paulo@0: or to a read-only folder ("installed" config) paulo@0: paulo@0: Copyright 2008 Damian Yerrick paulo@0: paulo@0: Insert zlib license here. paulo@0: paulo@0: */ paulo@0: paulo@0: paulo@0: #include paulo@0: #include paulo@0: #include "ljpath.h" paulo@0: #include // for PATH_MAX paulo@0: #include // for mkdir() paulo@0: #include paulo@0: paulo@0: #ifdef WIN32 paulo@0: #include paulo@0: #include paulo@0: #define USE_WINDOWS_APPDATA paulo@0: #endif paulo@0: paulo@0: // on the DS paulo@0: #ifdef ARM9 paulo@0: #include // Chishm's libfat paulo@0: static char hasFAT; paulo@0: #include // for MAXPATHLEN paulo@0: #else paulo@0: #define hasFAT 1 paulo@0: #endif paulo@0: paulo@0: // http://forum.gbadev.org/viewtopic.php?p=147795#147795 paulo@0: #if !defined(PATH_MAX) && defined(MAXPATHLEN) paulo@0: #define PATH_MAX MAXPATHLEN paulo@0: #endif paulo@0: paulo@0: static char readWriteFolder[PATH_MAX]; paulo@0: static char skinFolder[PATH_MAX]; paulo@0: static char readOnlyFolder[PATH_MAX]; paulo@0: paulo@0: paulo@0: #ifdef WIN32 paulo@0: static int ljmkdir(const char *path) { paulo@0: //allegro_message("mkdir(\"%s\")\n", path); paulo@0: return _mkdir(path); paulo@0: } paulo@0: #else paulo@0: static int ljmkdir(const char *path) { paulo@0: //allegro_message("mkdir(\"%s\")\n", path); paulo@0: return mkdir(path, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); paulo@0: } paulo@0: #endif paulo@0: paulo@0: /** paulo@0: * Wrapper around mkdir() that doesn't error if the paulo@0: * directory already exists. paulo@0: */ paulo@0: static int makeFolder(const char *path) { paulo@0: if (ljmkdir(path) < 0) { paulo@0: if (errno != EEXIST) { paulo@0: return -1; paulo@0: } paulo@0: } paulo@0: return 0; paulo@0: } paulo@0: paulo@0: paulo@0: paulo@0: /** paulo@0: * Computes the name of the folder containing filename. paulo@0: * @param dst destination to receive the folder name, of size PATH_MAX paulo@0: * @param filename name of a file in this folder paulo@0: */ paulo@0: static void ljpathSetFolder(char *dst, const char *filename) { paulo@0: const char *lastPathSep = NULL; paulo@0: paulo@0: // I considered strrchr, but it would need two passes: paulo@0: // one for '/' and one for the drive-letter ':' under Windows. paulo@0: for (const char *here = filename; *here != 0; ++here) { paulo@0: if (*here == '/' || *here == '\\' || *here == ':') { paulo@0: lastPathSep = here; paulo@0: } paulo@0: } paulo@0: if (lastPathSep == NULL || lastPathSep - filename > PATH_MAX - 1) { paulo@0: strcpy(dst, "."); paulo@0: } else { paulo@0: memcpy(dst, filename, lastPathSep - filename); paulo@0: dst[lastPathSep - filename] = 0; paulo@0: } paulo@0: } paulo@0: paulo@0: #if defined(USE_WINDOWS_APPDATA) paulo@0: /** paulo@0: * Retrieves the read/write path on a Windows system. paulo@0: * SHGetFolderPath() is described in paulo@0: * http://msdn.microsoft.com/library/default.asp?url=/library/en-us/shellcc/platform/shell/reference/functions/shgetfolderpath.asp paulo@0: */ paulo@0: static void ljpathSetRWFolder(void) { paulo@0: static const char suffix1[] = "/Pin Eight"; paulo@0: static const char suffix2[] = "/lockjaw"; paulo@0: char path[MAX_PATH + sizeof(suffix1) + sizeof(suffix2)] = ""; paulo@0: paulo@0: int rVal = SHGetFolderPath( paulo@0: NULL, // owner window used with obscure dial-up functionality paulo@0: CSIDL_APPDATA, // type of folder to retrieve paulo@0: NULL, // user ID when impersonating another user paulo@0: 0, // get the current path, not the default path paulo@0: path paulo@0: ); paulo@0: if (rVal) { paulo@0: return; paulo@0: } paulo@0: paulo@0: strcat(path, suffix1); paulo@0: ljmkdir(path); paulo@0: strcat(path, suffix2); paulo@0: ljmkdir(path); paulo@0: if (strlen(path) >= PATH_MAX) { paulo@0: return; paulo@0: } paulo@0: paulo@0: strncpy(readWriteFolder, path, PATH_MAX - 1); paulo@0: readWriteFolder[PATH_MAX - 1] = 0; paulo@0: } paulo@0: #else paulo@0: /** paulo@0: * Retrieves the read/write path on a UNIX system using the HOME paulo@0: * environment variable. paulo@0: */ paulo@0: static void ljpathSetRWFolder(void) { paulo@0: static const char suffix[] = "/.lockjaw"; paulo@0: const char *value = getenv("HOME"); paulo@0: if (!value) { paulo@0: return; paulo@0: } paulo@0: paulo@0: unsigned int len = strlen(value) + sizeof(suffix); paulo@0: if (len > PATH_MAX) { paulo@0: return; paulo@0: } paulo@0: paulo@0: strcpy(readWriteFolder, value); paulo@0: strcat(readWriteFolder, suffix); paulo@0: ljmkdir(readWriteFolder); paulo@0: //allegro_message("readWriteFolder is now \"%s\"\n", skinFolder); paulo@0: } paulo@0: #endif paulo@0: paulo@0: void ljpathSetSkinFolder(const char *filename) { paulo@0: //allegro_message("ljpathSetFolder(skinFolder, \"%s\")\n", filename); paulo@0: ljpathSetFolder(skinFolder, filename); paulo@0: //allegro_message("skinFolder is now \"%s\"\n", skinFolder); paulo@0: } paulo@0: paulo@0: static int ljpathJoin(char *restrict dst, paulo@0: const char *restrict folder, paulo@0: const char *restrict filename) { paulo@0: size_t len = 0; paulo@0: paulo@0: //allegro_message("ljpathJoin(\"%s\", \"%s\")\n", folder, filename); paulo@0: paulo@0: // If the path is not absolute, copy the folder and the paulo@0: // path separator into the destination string. paulo@0: if (filename[0] != '\\' && filename[0] != '\\' paulo@0: && filename[1] != ':') { paulo@0: while (len < PATH_MAX - 1 && *folder != 0) { paulo@0: dst[len++] = *folder++; paulo@0: } paulo@0: /* The path separator '/' is standard under Linux and Mac OS X. paulo@0: It also works on MS-DOS and Windows API, notwithstanding paulo@0: certain Microsoft command-line utilities. */ paulo@0: dst[len++] = '/'; paulo@0: while (len < PATH_MAX - 1 && *filename != 0) { paulo@0: dst[len++] = *filename++; paulo@0: } paulo@0: } paulo@0: paulo@0: if (len < PATH_MAX - 1) { paulo@0: dst[len] = 0; paulo@0: //allegro_message("equals \"%s\"\n", dst); paulo@0: return 1; paulo@0: } paulo@0: //allegro_message("failed\n"); paulo@0: return 0; paulo@0: } paulo@0: paulo@0: /** paulo@0: * Determines whether a file exists and is readable. paulo@0: * @param path the path of a file paulo@0: * @return nonzero iff readable paulo@0: */ paulo@0: static int fexists(const char *path) { paulo@0: struct stat st; paulo@0: if (!hasFAT) { paulo@0: return 0; paulo@0: } paulo@0: int rval = stat(path, &st); paulo@0: //allegro_message("stat(\"%s\") returned %d\n", path, rval); paulo@0: return rval == 0; paulo@0: } paulo@0: paulo@0: int ljpathInit(const char *argv0) { paulo@0: #ifdef ARM9 paulo@0: hasFAT = fatInitDefault(); paulo@0: if (hasFAT) { paulo@0: if (makeFolder("/data") < 0 || makeFolder("/data/lockjaw") < 0) { paulo@0: hasFAT = 0; paulo@0: } paulo@0: } paulo@0: strcpy(readOnlyFolder, "/data/lockjaw"); paulo@0: strcpy(readWriteFolder, "/data/lockjaw"); paulo@0: strcpy(skinFolder, "/data/lockjaw"); paulo@0: return hasFAT; paulo@0: #else paulo@0: char path[PATH_MAX]; paulo@0: int installed; paulo@0: paulo@0: //allegro_message("argv[0] = \"%s\"", argv0); paulo@0: ljpathSetFolder(readOnlyFolder, argv0); paulo@0: strcpy(readWriteFolder, "."); paulo@0: strcpy(skinFolder, "."); paulo@0: paulo@0: // determine whether "installed.ini" (an empty text file) paulo@0: // exists paulo@0: installed = ljpathJoin(path, readOnlyFolder, "installed.ini") >= 0 paulo@0: && fexists(path); paulo@0: if (installed) { paulo@0: ljpathSetRWFolder(); paulo@0: } paulo@0: return installed; paulo@0: #endif paulo@0: } paulo@0: paulo@0: /** paulo@0: * Finds a file name for reading. paulo@0: * @param dst buffer for path, of size PATH_MAX paulo@0: * @param src name of file paulo@0: * @return 0 if not found, nonzero if found paulo@0: */ paulo@0: int ljpathFind_r(char *restrict dst, const char *restrict filename) { paulo@0: if (!hasFAT) { paulo@0: return 0; paulo@0: } paulo@0: if (ljpathJoin(dst, readWriteFolder, filename) >= 0 paulo@0: && fexists(dst)) { paulo@0: return 1; paulo@0: } paulo@0: if (ljpathJoin(dst, skinFolder, filename) >= 0 paulo@0: && fexists(dst)) { paulo@0: return 1; paulo@0: } paulo@0: if (ljpathJoin(dst, readOnlyFolder, filename) >= 0 paulo@0: && fexists(dst)) { paulo@0: return 1; paulo@0: } paulo@0: return 0; paulo@0: } paulo@0: paulo@0: int ljpathFind_w(char *restrict dst, const char *restrict filename) { paulo@0: return ljpathJoin(dst, readWriteFolder, filename); paulo@0: } paulo@0: paulo@0: FILE *ljfopen(const char *restrict filename, const char *restrict mode) { paulo@0: char path[PATH_MAX]; paulo@0: FILE *fp; paulo@0: paulo@0: if (!hasFAT) { paulo@0: return NULL; paulo@0: } paulo@0: paulo@0: //allegro_message("ljfopen(\"%s\", \"%s\")\n", filename, mode); paulo@0: if (mode[0] == 'r') { paulo@0: paulo@0: // For read-only files, search all three paths. paulo@0: if (ljpathFind_r(path, filename) >= 0 paulo@0: && (fp = fopen(path, mode)) != 0) { paulo@0: //allegro_message("fopen(\"%s\", \"%s\") for reading\n", path, mode); paulo@0: return fp; paulo@0: } paulo@0: } else { paulo@0: paulo@0: // For read/write files, check only the read/write path. paulo@0: if (ljpathFind_w(path, filename) >= 0 paulo@0: && (fp = fopen(path, mode)) != 0) { paulo@0: //allegro_message("fopen(\"%s\", \"%s\") for writing\n", path, mode); paulo@0: return fp; paulo@0: } paulo@0: } paulo@0: return NULL; paulo@0: } paulo@0: