1 /**
2  * Compute and store the search paths
3 
4  * Copyright (C) 2007, 2008, 2009  Sylvain Beucler
5 
6  * This file is part of GNU FreeDink
7 
8  * GNU FreeDink is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU General Public License as
10  * published by the Free Software Foundation; either version 3 of the
11  * License, or (at your option) any later version.
12 
13  * GNU FreeDink is distributed in the hope that it will be useful, but
14  * WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * General Public License for more details.
17 
18  * You should have received a copy of the GNU General Public License
19  * along with this program.  If not, see
20  * <http://www.gnu.org/licenses/>.
21  */
22 
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26 #include <stdlib.h>
27 
28 #include "io_util.h"
29 #include "paths.h"
30 #include "msgbox.h"
31 #include "log.h"
32 
33 
34 #if defined _WIN32 || defined __WIN32__ || defined __CYGWIN__
35 #define WIN32_LEAN_AND_MEAN
36 #define _WIN32_IE 0x0401
37 #include <windows.h>
38 #include <shlobj.h>
39 #endif
40 
41 #include "relocatable.h"
42 #include "progname.h"
43 #include "binreloc.h"
44 
45 #include <string.h> /* strdup */
46 #include "canonicalize.h" /* canonicalize_file_name */
47 
48 /* basename */
49 #include <libgen.h>
50 #include "dirname.h"
51 
52 /* mkdir */
53 #include <sys/stat.h>
54 #include <sys/types.h>
55 
56 #include "str_util.h" /* asprintf_append */
57 
58 
59 static char* defaultpkgdatadir = NULL;
60 static char* pkgdatadir = NULL;
61 static char* exedir = NULL;
62 static char* fallbackdir = NULL;
63 static char* dmoddir = NULL;
64 static char* dmodname = NULL;
65 static char* userappdir = NULL;
66 static char* exefile = NULL;
67 
68 
paths_init(char * argv0,char * refdir_opt,char * dmoddir_opt)69 void paths_init(char *argv0, char *refdir_opt, char *dmoddir_opt)
70 {
71   char *datadir = NULL;
72   char *refdir = NULL;
73 
74   /* relocatable_prog */
75   set_program_name(argv0);
76 
77   /** default pkgdatadir (e.g. "/usr/share/freedink") **/
78   {
79     defaultpkgdatadir = br_build_path(DEFAULT_DATA_DIR, PACKAGE);
80   }
81 
82   /** datadir (e.g. "/usr/share") **/
83   {
84     const char *datadir_relocatable;
85     char *datadir_binreloc, *default_data_dir;
86 
87     /* First, ask relocable-prog */
88     /* Put in a variable to avoid "comparison with string literal" */
89     default_data_dir = DEFAULT_DATA_DIR;
90     datadir_relocatable = relocate(default_data_dir);
91 
92     /* Then ask binreloc - it handles ../share even if CWD != "bin"
93        (e.g. "src"). However it's not as precise and portable ($PATH
94        lookup, etc.). */
95     BrInitError error;
96     if (br_init(&error) == 0 && error != BR_INIT_ERROR_DISABLED)
97       {
98 	log_warn("BinReloc failed to initialize (error code %d)", error);
99 	datadir_binreloc = strdup(datadir_relocatable);
100       }
101     else
102       {
103 	datadir_binreloc = br_find_data_dir(datadir_relocatable);
104       }
105 
106     /* Free relocable-prog's path, if necessary */
107     if (datadir_relocatable != default_data_dir)
108       free((char *)datadir_relocatable);
109 
110     /* BinReloc always return a newly allocated string, with either the
111        built directory, or a copy of its argument */
112     datadir = datadir_binreloc;
113   }
114 
115   /** pkgdatadir (e.g. ".local/share/freedink") **/
116   {
117     pkgdatadir = br_build_path(datadir, PACKAGE);
118   }
119 
120   /** exedir (e.g. "C:/Program Files/Dink Smallwood") **/
121   {
122     char* fullprogname = get_full_program_name();
123     if (fullprogname != NULL)
124       exefile = strdup(fullprogname);
125     else
126       exefile = strdup(argv0);
127     log_info("Hi, I'm '%s'", exefile);
128     /* gnulib's dir_name always returns a newly xalloc'd string */
129     exedir = dir_name(exefile);
130   }
131 
132   /** refdir  (e.g. "/usr/share/dink") **/
133   {
134     /** => refdir **/
135     char* match = NULL;
136     int nb_dirs = 8;
137     char** lookup = malloc(sizeof(char*) * nb_dirs);
138     int i = 0;
139     if (refdir_opt == NULL)
140       lookup[0] = NULL;
141     else
142       lookup[0] = refdir_opt;
143     lookup[1] = ".";
144     lookup[2] = exedir;
145 
146     char *default3 = NULL, *default4 = NULL, *default5 = NULL,
147       *default6 = NULL, *default7 = NULL;
148     /* FHS mentions optional 'share/games' which some Debian packagers
149        seem to be found of */
150     /* Packagers: don't alter these paths. FreeDink must run in a
151        _consistent_ way across platforms. If you need an alternate
152        path, consider using ./configure --prefix=..., or contact
153        bug-freedink@gnu.org to discuss it. */
154     default3 = br_build_path(datadir, "dink");
155     default4 = "/usr/local/share/games/dink";
156     default5 = "/usr/local/share/dink";
157     default6 = "/usr/share/games/dink";
158     default7 = "/usr/share/dink";
159     lookup[3] = default3;
160     lookup[4] = default4;
161     lookup[5] = default5;
162     lookup[6] = default6;
163     lookup[7] = default7;
164 
165     for (; i < nb_dirs; i++)
166       {
167 	char *dir_graphics_ci = NULL, *dir_tiles_ci = NULL;
168 	if (lookup[i] == NULL)
169 	  continue;
170 	dir_graphics_ci = br_build_path(lookup[i], "dink/graphics");
171 	dir_tiles_ci = br_build_path(lookup[i], "dink/tiles");
172 	ciconvert(dir_graphics_ci);
173 	ciconvert(dir_tiles_ci);
174 	if (is_directory(dir_graphics_ci) && is_directory(dir_tiles_ci))
175 	  {
176 	    match = lookup[i];
177 	  }
178 
179 	if (match == NULL && i == 0)
180 	  {
181 	    msgbox_init_error("Invalid --refdir option: %s and/or %s are not accessible.",
182 			      dir_graphics_ci, dir_tiles_ci);
183 	    exit(1);
184 	  }
185 
186 	free(dir_graphics_ci);
187 	free(dir_tiles_ci);
188 	if (match != NULL)
189 	    break;
190       }
191     refdir = match;
192     if (refdir != NULL)
193       {
194 	refdir = strdup(refdir);
195       }
196     else
197       {
198 	char *msg = NULL;
199 	asprintf_append(&msg, "Error: cannot find reference directory (--refdir). I looked in:\n");
200 	// lookup[0] already treated above
201 	asprintf_append(&msg, "- %s [current dir]\n", lookup[1]);
202 	asprintf_append(&msg, "- %s [exedir]\n", lookup[2]);
203 	asprintf_append(&msg, "- %s [detected prefix]\n", lookup[3]);
204 	asprintf_append(&msg, "- %s [/usr/local/share/games prefix]\n", lookup[4]);
205 	asprintf_append(&msg, "- %s [/usr/local/share prefix]\n", lookup[5]);
206 	asprintf_append(&msg, "- %s [/usr/share/games prefix]\n", lookup[6]);
207 	asprintf_append(&msg, "- %s [/usr/share prefix]\n", lookup[7]);
208 	asprintf_append(&msg, "The reference directory contains among others the "
209 		"'dink/graphics/' and 'dink/tiles/' directories (as well as "
210 		"D-Mods).");
211 	msgbox_init_error(msg);
212 	free(msg);
213 	exit(1);
214       }
215 
216     free(default3); // br_build_path()
217     free(lookup);
218   }
219 
220   /** fallbackdir (e.g. "/usr/share/dink/dink") **/
221   /* (directory used when a file cannot be found in a D-Mod) */
222   {
223     fallbackdir = br_strcat(refdir, "/dink");
224   }
225 
226   /** dmoddir (e.g. "/usr/share/dink/island") **/
227   {
228     if (dmoddir_opt != NULL && is_directory(dmoddir_opt))
229       {
230 	/* Use path given on the command line, either a full path or a
231 	   path relative to the current directory. */
232 	/* Note: don't search for "dink" in the default dir if no
233 	   '-game' option was given */
234 	dmoddir = strdup(dmoddir_opt);
235       }
236     else
237       {
238 	/* Use path given on the command line, relative to $refdir */
239 	char *subdir = dmoddir_opt;
240 	if (subdir == NULL)
241 	  subdir = "dink";
242 	dmoddir = malloc(strlen(refdir) + 1 + strlen(subdir) + 1);
243 	strcpy(dmoddir, refdir);
244 	strcat(dmoddir, "/");
245 	strcat(dmoddir, subdir);
246 	if (!is_directory(dmoddir))
247 	  {
248 	    char *msg = NULL;
249 
250 	    asprintf_append(&msg, "Error: D-Mod directory '%s' doesn't exist. I looked in:\n", subdir);
251 	    if (dmoddir_opt != NULL)
252 	      asprintf_append(&msg, "- ./%s\n", dmoddir_opt);
253 	    asprintf_append(&msg, "- %s (refdir is '%s')", dmoddir, refdir);
254 
255 	    msgbox_init_error(msg);
256 	    free(msg);
257 	    exit(1);
258 	  }
259       }
260     /* Strip slashes */
261     while (strlen(dmoddir) > 0 && dmoddir[strlen(dmoddir) - 1] == '/')
262       dmoddir[strlen(dmoddir) - 1] = '\0';
263   }
264 
265   /** dmodname (e.g. "island") **/
266   /* Used to save games in ~/.dink/<dmod>/... */
267   {
268     dmodname = base_name(dmoddir);
269     if (strcmp(dmodname, ".") == 0)
270       {
271 	free(dmodname);
272 	char *canonical_dmoddir = canonicalize_file_name(dmoddir);
273 	dmodname = base_name(canonical_dmoddir);
274 	free(canonical_dmoddir);
275       }
276   }
277 
278   /** userappdir (e.g. "~/.dink") **/
279   {
280 #if defined _WIN32 || defined __WIN32__ || defined __CYGWIN__
281     userappdir = malloc(MAX_PATH);
282     /* C:\Documents and Settings\name\Application Data */
283     SHGetSpecialFolderPath(NULL, userappdir, CSIDL_APPDATA, 1);
284 #else
285     char* envhome = getenv("HOME");
286     if (envhome != NULL)
287       userappdir = strdup(getenv("HOME"));
288 #endif
289     if (userappdir != NULL)
290       {
291 	userappdir = realloc(userappdir, strlen(userappdir) + 1 + 1 + strlen(PACKAGE) + 1);
292 	strcat(userappdir, "/");
293 #if defined _WIN32 || defined __WIN32__ || defined __CYGWIN__
294 #else
295 	strcat(userappdir, ".");
296 #endif
297 	strcat(userappdir, "dink");
298       }
299     else
300       {
301 	// No detected home directory - saving in the reference
302 	// directory
303 	userappdir = strdup(refdir);
304       }
305   }
306 
307   log_info("exedir = %s", exedir);
308   log_info("datadir = %s", datadir);
309   log_info("pkgdatadir = %s", pkgdatadir);
310   log_info("defaultpkgdatadir = %s", defaultpkgdatadir);
311   log_info("refdir = %s", refdir);
312   log_info("dmoddir = %s", dmoddir);
313   log_info("dmodname = %s", dmodname);
314   log_info("userappdir = %s", userappdir);
315 
316   free(datadir);
317   free(refdir);
318 }
319 
320 
paths_getdefaultpkgdatadir(void)321 const char *paths_getdefaultpkgdatadir(void)
322 {
323   return defaultpkgdatadir;
324 }
325 
326 
paths_getpkgdatadir(void)327 const char *paths_getpkgdatadir(void)
328 {
329   return pkgdatadir;
330 }
paths_getdmoddir(void)331 const char *paths_getdmoddir(void)
332 {
333   return dmoddir;
334 }
paths_getdmodname(void)335 const char *paths_getdmodname(void)
336 {
337   return dmodname;
338 }
339 
paths_getfallbackdir(void)340 const char *paths_getfallbackdir(void)
341 {
342   return fallbackdir;
343 }
344 
paths_getexedir(void)345 const char *paths_getexedir(void)
346 {
347   return exedir;
348 }
349 
paths_getexefile(void)350 const char *paths_getexefile(void)
351 {
352   return exefile;
353 }
354 
355 
paths_dmodfile(char * file)356 char* paths_dmodfile(char *file)
357 {
358   char *fullpath = br_build_path(dmoddir, file);
359   ciconvert(fullpath);
360   return fullpath;
361 }
362 
paths_dmodfile_fopen(char * file,char * mode)363 FILE* paths_dmodfile_fopen(char *file, char *mode)
364 {
365   char *fullpath = paths_dmodfile(file);
366   FILE *result = fopen(fullpath, mode);
367   free(fullpath);
368   return result;
369 }
370 
paths_fallbackfile(char * file)371 char* paths_fallbackfile(char *file)
372 {
373   char *fullpath = br_build_path(fallbackdir, file);
374   ciconvert(fullpath);
375   return fullpath;
376 }
377 
paths_fallbackfile_fopen(char * file,char * mode)378 FILE* paths_fallbackfile_fopen(char *file, char *mode)
379 {
380   char *fullpath = paths_fallbackfile(file);
381   FILE *result = fopen(fullpath, mode);
382   free(fullpath);
383   return result;
384 }
385 
paths_defaultpkgdatafile(char * file)386 char* paths_defaultpkgdatafile(char *file)
387 {
388   char *fullpath = br_build_path(defaultpkgdatadir, file);
389   ciconvert(fullpath);
390   return fullpath;
391 }
392 
paths_defaultpkgdatafile_fopen(char * file,char * mode)393 FILE* paths_defaultpkgdatafile_fopen(char *file, char *mode)
394 {
395   char *fullpath = paths_defaultpkgdatafile(file);
396   FILE *result = fopen(fullpath, mode);
397   free(fullpath);
398   return result;
399 }
400 
paths_pkgdatafile(char * file)401 char* paths_pkgdatafile(char *file)
402 {
403   char *fullpath = br_build_path(pkgdatadir, file);
404   ciconvert(fullpath);
405   return fullpath;
406 }
407 
paths_pkgdatafile_fopen(char * file,char * mode)408 FILE* paths_pkgdatafile_fopen(char *file, char *mode)
409 {
410   char *fullpath = paths_pkgdatafile(file);
411   FILE *result = fopen(fullpath, mode);
412   free(fullpath);
413   return result;
414 }
415 
paths_exedirfile(char * file)416 char* paths_exedirfile(char *file)
417 {
418   char *fullpath = br_build_path(exedir, file);
419   ciconvert(fullpath);
420   return fullpath;
421 }
422 
paths_exedirfile_fopen(char * file,char * mode)423 FILE* paths_exedirfile_fopen(char *file, char *mode)
424 {
425   char *fullpath = paths_exedirfile(file);
426   FILE *result = fopen(fullpath, mode);
427   free(fullpath);
428   return result;
429 }
430 
paths_savegame_fopen(int num,char * mode)431 FILE *paths_savegame_fopen(int num, char *mode)
432 {
433   char *fullpath_in_dmoddir = NULL;
434   char *fullpath_in_userappdir = NULL;
435   FILE *fp = NULL;
436 
437   /* 20 decimal digits max for 64bit integer - should be enough :) */
438   char file[4 + 20 + 4 + 1];
439   sprintf(file, "save%d.dat", num);
440 
441 
442   /** fullpath_in_userappdir **/
443   char *savedir = strdup(userappdir);
444   savedir = realloc(savedir, strlen(userappdir) + 1 + strlen(dmodname) + 1);
445   strcat(savedir, "/");
446   strcat(savedir, dmodname);
447   /* Create directories if needed */
448   if (strchr(mode, 'w') != NULL || strchr(mode, 'a') != NULL)
449       /* Note: 0777 & umask => 0755 in common case */
450       if ((!is_directory(userappdir) && (mkdir(userappdir, 0777) < 0))
451 	  || (!is_directory(savedir) && (mkdir(savedir, 0777) < 0)))
452 	{
453 	  free(savedir);
454 	  return NULL;
455 	}
456   fullpath_in_userappdir = br_build_path(savedir, file);
457   ciconvert(fullpath_in_userappdir);
458   free(savedir);
459 
460 
461   /** fullpath_in_dmoddir **/
462   fullpath_in_dmoddir = paths_dmodfile(file);
463   ciconvert(fullpath_in_dmoddir);
464 
465 
466   /* Try ~/.dink (if present) when reading - but don't try that
467      first when writing */
468   if (strchr(mode, 'r') != NULL)
469     fp = fopen(fullpath_in_userappdir, mode);
470 
471   /* Try in the D-Mod dir */
472   if (fp == NULL)
473     fp = fopen(fullpath_in_dmoddir, mode);
474 
475   /* Then try in ~/.dink */
476   if (fp == NULL)
477     fp = fopen(fullpath_in_userappdir, mode);
478 
479   free(fullpath_in_dmoddir);
480   free(fullpath_in_userappdir);
481 
482   return fp;
483 }
484 
paths_quit(void)485 void paths_quit(void)
486 {
487   free(defaultpkgdatadir);
488   free(pkgdatadir);
489   free(exedir);
490   free(fallbackdir);
491   free(dmoddir);
492   free(dmodname);
493   free(userappdir);
494   free(exefile);
495 
496   defaultpkgdatadir = NULL;
497   pkgdatadir        = NULL;
498   exedir            = NULL;
499   fallbackdir       = NULL;
500   dmoddir           = NULL;
501   dmodname          = NULL;
502   userappdir        = NULL;
503   exefile           = NULL;
504 }
505