graphics updates
[voxelands-alt.git] / src / core / path.c
blob1241b1cd1f1c5e0b50d2aa9e8b767d6000e894f6
1 /************************************************************************
2 * path.c
3 * voxelands - 3d voxel world sandbox game
4 * Copyright (C) Lisa 'darkrose' Milne 2016 <lisa@ltmnet.com>
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14 * See the GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>
18 ************************************************************************/
20 #include "common.h"
21 #include "file.h"
22 #include "path.h"
23 #include "list.h"
25 #ifdef WIN32
26 #include <windows.h>
27 #else
28 #include <sys/time.h>
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <libgen.h>
32 #include <dirent.h>
33 #include <errno.h>
34 #include <ftw.h>
35 #endif
36 #include <unistd.h>
37 #include <stdint.h>
38 #include <stdio.h>
39 #include <string.h>
40 #include <stdlib.h>
42 static struct {
43 char* cwd; /* current working directory */
44 char* data_custom; /* set by config data_path */
45 char* data_user; /* ~/.local/share/voxelands */
46 char* data_global; /* /usr/share/voxelands */
47 char* data; /* ./data if it exists */
48 char* game; /* data_user + /worlds/ + world name */
49 char* home; /* ~/. */
50 char* config; /* ~/.config/voxelands */
51 char* screenshot; /* set by config screenshot_path */
52 } path = {
53 NULL,
54 NULL,
55 NULL,
56 NULL,
57 NULL,
58 NULL,
59 NULL,
60 NULL,
61 NULL
64 static int path_check(char* base, char* rel)
66 int l;
67 char path[2048];
68 #ifndef WIN32
69 struct stat st;
70 #else
71 DWORD atts;
72 #endif
74 if (!rel) {
75 if (base) {
76 l = snprintf(path,2048,"%s",base);
77 }else{
78 return -1;
80 }else if (base) {
81 l = snprintf(path,2048,"%s/%s",base,rel);
82 }else{
83 l = snprintf(path,2048,"%s",rel);
85 if (l >= 2048)
86 return -1;
88 #ifndef WIN32
89 if (stat(path,&st) != 0)
90 return 0;
91 if ((st.st_mode&S_IFMT) == S_IFDIR)
92 return 2;
93 if ((st.st_mode&S_IFMT) == S_IFREG)
94 return 1;
95 #else
96 atts = GetFileAttributes(path);
97 if (atts == INVALID_FILE_ATTRIBUTES)
98 return 0;
99 if (atts == FILE_ATTRIBUTE_DIRECTORY)
100 return 2;
101 if (atts == FILE_ATTRIBUTE_NORMAL)
102 return 1;
103 #endif
105 return 0;
108 static char* path_set(char* base, char* rel, char* buff, int size)
110 int l;
111 char path[2048];
113 if (base) {
114 l = snprintf(path,2048,"%s/%s",base,rel);
115 }else{
116 l = snprintf(path,2048,"%s",rel);
118 if (l >= 2048)
119 return NULL;
121 if (!buff)
122 return strdup(path);
124 if (size <= l)
125 return NULL;
127 if (!strcpy(buff,path))
128 return NULL;
130 return buff;
133 static int dir_create(char* p);
134 static int dir_create(char* path)
136 #ifdef WIN32
137 if (CreateDirectory(path, NULL))
138 return 0;
139 if (GetLastError() == ERROR_ALREADY_EXISTS)
140 return 0;
141 #else
142 mode_t process_mask = umask(0);
143 mode_t mode = S_IRWXU | S_IRWXG | S_IRWXO;
144 char* q;
145 char* r = NULL;
146 char* p = NULL;
147 char* up = NULL;
148 int ret = 1;
149 if (!strcmp(path, ".") || !strcmp(path, "/")) {
150 umask(process_mask);
151 return 0;
154 if ((p = strdup(path)) == NULL) {
155 umask(process_mask);
156 return 1;
159 if ((q = strdup(path)) == NULL) {
160 umask(process_mask);
161 return 1;
164 if ((r = dirname(q)) == NULL)
165 goto out;
167 if ((up = strdup(r)) == NULL) {
168 umask(process_mask);
169 return 1;
172 if ((dir_create(up) == 1) && (errno != EEXIST))
173 goto out;
175 if ((mkdir(p, mode) == -1) && (errno != EEXIST)) {
176 ret = 1;
177 }else{
178 ret = 0;
181 out:
182 umask(process_mask);
183 if (up)
184 free(up);
185 free(q);
186 free(p);
187 return ret;
188 #endif
189 return 1;
192 #ifndef WIN32
193 int unlink_cb(const char* fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf)
195 return remove(fpath);
197 #endif
199 /* initialises the common paths */
200 int path_init()
202 char buff[2048];
204 path.data_global = strdup(GAMEDATA);
206 #ifndef WIN32
207 if (getcwd(buff,2048)) {
208 path.cwd = strdup(buff);
209 }else{
210 path.cwd = strdup(".");
213 path.home = getenv("HOME");
214 if (path.home) {
215 path.home = strdup(path.home);
216 }else{
217 path.home = strdup(path.cwd);
220 path.data_user = getenv("XDG_DATA_HOME");
221 if (path.data_user) {
222 path.data_user = path_set(path.data_user,"voxelands",NULL,0);
223 }else if (path.home) {
224 path.data_user = path_set(path.home,".local/share/voxelands",NULL,0);
225 }else{
226 path.data_user = path_set(path.cwd,"data",NULL,0);
229 path.config = getenv("XDG_CONFIG_HOME");
230 if (path.config) {
231 path.config = path_set(path.config,"voxelands",NULL,0);
232 }else if (path.home) {
233 path.config = path_set(path.home,".config/voxelands",NULL,0);
234 }else{
235 path.config = strdup(path.cwd);
237 #else
238 /* TODO: windows, and mac? */
239 #endif
241 if (snprintf(buff,2048,"%s/data",path.cwd) < 2048 && path_check(NULL,buff) == 2)
242 path.data = strdup(buff);
244 return 0;
247 /* frees all the paths */
248 void path_exit()
250 if (path.cwd)
251 free(path.cwd);
252 path.cwd = NULL;
254 if (path.data_custom)
255 free(path.data_custom);
256 path.data_custom = NULL;
258 if (path.data_user)
259 free(path.data_user);
260 path.data_user = NULL;
262 if (path.data_global)
263 free(path.data_global);
264 path.data_global = NULL;
266 if (path.data)
267 free(path.data);
268 path.data = NULL;
270 if (path.game)
271 free(path.game);
272 path.game = NULL;
274 if (path.home)
275 free(path.home);
276 path.home = NULL;
278 if (path.config)
279 free(path.config);
280 path.config = NULL;
282 if (path.screenshot)
283 free(path.screenshot);
284 path.screenshot = NULL;
287 /* sets path.data_custom */
288 int path_custom_setter(char* p)
290 if (path.data_custom)
291 free(path.data_custom);
292 path.data_custom = NULL;
294 if (p)
295 path.data_custom = strdup(p);
297 return 0;
300 /* sets path.screenshot */
301 int path_screenshot_setter(char* p)
303 if (path.screenshot)
304 free(path.screenshot);
305 path.screenshot = NULL;
307 if (p)
308 path.screenshot = strdup(p);
310 return 0;
313 /* sets the game/world path to user_data + /worlds/ + p, creates the path if necessary */
314 int path_game_setter(char* p)
316 int c;
317 char buff[2048];
318 char* base = path.data_user;
320 if (path.game)
321 free(path.game);
322 path.game = NULL;
324 if (!p)
325 return 1;
327 if (snprintf(buff,2048,"worlds/%s",p) >= 2048)
328 return 1;
330 if (!base)
331 base = path.data;
332 if (!base)
333 base = path.home;
334 if (!base)
335 base = path.cwd;
336 if (!base)
337 return 1;
339 path.game = path_set(base,buff,NULL,0);
341 c = path_check(path.game,NULL);
342 if (c == 2)
343 return 0;
344 if (c == 0)
345 return dir_create(path.game);
346 return 1;
350 * get the full path for a file/directory
351 * type is the usual "texture" "model" etc
352 * file is the file name
353 * must_exist is pretty self explanatory
354 * buff is a buffer to write the path into, if NULL allocate
355 * size is the size of buff
357 * returns the path or NULL if either:
358 * must_exist is non-zero and the path doesn't exist
359 * buff is not NULL and too small to hold the full path
361 char* path_get(char* type, char* file, int must_exist, char* buff, int size)
363 char rel_path[1024];
365 if (!file)
366 return NULL;
368 if (file[0] == '/') {
369 return path_set(NULL,file,buff,size);
370 }else if (!type) {
371 strcpy(rel_path,file);
372 }else if (!strcmp(type,"game")) {
373 if (path.game && (!must_exist || path_check(path.game,file)))
374 return path_set(path.game,file,buff,size);
375 return NULL;
376 }else if (!strcmp(type,"screenshot")) {
377 if (path.screenshot) {
378 return path_set(path.screenshot,file,buff,size);
379 }else if (path.home) {
380 return path_set(path.home,file,buff,size);
381 }else if (path.data_user) {
382 return path_set(path.data_user,file,buff,size);
383 }else if (path.data_custom) {
384 return path_set(path.data_custom,file,buff,size);
386 return NULL;
387 }else if (!strcmp(type,"config")) {
388 if (path.config && (!must_exist || path_check(path.config,file)))
389 return path_set(path.config,file,buff,size);
390 return NULL;
391 }else if (!strcmp(type,"model")) {
392 snprintf(rel_path,1024,"models/%s",file);
393 }else if (!strcmp(type,"texture")) {
394 snprintf(rel_path,1024,"textures/%s",file);
395 }else if (!strcmp(type,"shader")) {
396 snprintf(rel_path,1024,"shaders/%s",file);
397 }else if (!strcmp(type,"html")) {
398 snprintf(rel_path,1024,"html/%s",file);
399 }else if (!strcmp(type,"skin")) {
400 snprintf(rel_path,1024,"textures/skins/%s",file);
401 }else if (!strcmp(type,"sound")) {
402 snprintf(rel_path,1024,"sounds/%s",file);
403 }else if (!strcmp(type,"font")) {
404 snprintf(rel_path,1024,"fonts/%s",file);
405 }else if (!strncmp(type,"translation-",12)) {
406 char* lang = type+12;
407 type = "translation";
408 snprintf(rel_path,1024,"locale/%s/%s",lang,file);
409 }else{
410 strcpy(rel_path,file);
413 /* check from data_path */
414 if (path.data_custom) {
415 if (path_check(path.data_custom,rel_path))
416 return path_set(path.data_custom,rel_path,buff,size);
419 /* check from user data directory */
420 if (path.data_user) {
421 if (path_check(path.data_user,rel_path))
422 return path_set(path.data_user,rel_path,buff,size);
425 /* check from default data directory */
426 if (path.data) {
427 if (path_check(path.data,rel_path))
428 return path_set(path.data,rel_path,buff,size);
431 /* check from default data directory */
432 if (path.data_global) {
433 if (path_check(path.data_global,rel_path))
434 return path_set(path.data_global,rel_path,buff,size);
437 if (must_exist)
438 return NULL;
440 if (path.data)
441 return path_set(path.data,rel_path,buff,size);
442 if (path.data_user)
443 return path_set(path.data_user,rel_path,buff,size);
444 if (path.data_custom)
445 return path_set(path.data_custom,rel_path,buff,size);
447 return NULL;
451 * check if a path exists
452 * returns:
453 * -1 on error
454 * 0 if path does not exist
455 * 1 if path exists and is a file
456 * 2 if path exists and is a directory
458 int path_exists(char* path)
460 return path_check(NULL,path);
464 * create the full path for the type
465 * assumes that files have a dot somewhere in their name
466 * thus:
467 * if file is NULL, creates the base path for the type
468 * if file contains a dot, creates the base path for the type, and
469 * an empty file along with any subdirectories
470 * if file does not contain a dot, creates the base path for the
471 * <type>/file
472 * if type is NULL, file must be an absolute path
473 * returns 0 if successful
475 int path_create(char* type, char* file)
477 char path[2048];
478 char* fn = NULL;
479 FILE *f;
481 if (!path_get(type,file,0,path,2048))
482 return -1;
484 if (file) {
485 char* b = strrchr(file,'/');
486 if (!b) {
487 b = file;
488 }else{
489 b++;
491 fn = strchr(b,'.');
492 if (fn) {
493 fn = strrchr(path,'/');
494 if (!fn)
495 return -1;
496 *fn = 0;
497 fn++;
501 if (dir_create(path))
502 return -1;
504 if (!fn)
505 return 0;
507 *fn = '/';
509 f = fopen(path,"w");
510 if (!f)
511 return -1;
513 fclose(f);
515 return 0;
518 /* removes (recursively) the last node in the full path of <type>/file */
519 int path_remove(char* type, char* file)
521 char path[2048];
522 if (!path_get(type,file,1,path,2048))
523 return 0;
525 #ifdef WIN32
526 DWORD attributes = GetFileAttributes(path);
528 /* delete if it's a file, or call recursive delete if a directory */
529 if (attributes == INVALID_FILE_ATTRIBUTES)
530 return -1;
532 if (attributes == FILE_ATTRIBUTE_DIRECTORY) {
533 char fpath[2048];
534 dirlist_t *list;
535 dirlist_t *n;
536 int r = 0;
537 list = path_dirlist(NULL,path);
538 for (n=list; n != NULL && r == 0; n=n->next) {
539 if (snprintf(fpath,2048,"%s/%s",path,n->name) >= 2048)
540 r = -1;
541 if (path_remove(NULL,fpath))
542 r = -1;
544 while ((n = list_pop(&list))) {
545 free(n->name);
546 free(n);
548 if (r != 0)
549 return r;
550 if (RemoveDirectory(path))
551 return 0;
552 }else{
553 if (DeleteFile(path))
554 return 0;
556 #else
557 if (path[0] != '/' || path[1] == 0)
558 return -1;
559 /* file tree walk, calls the unlink_cb function on every file/directory */
560 return nftw(path, unlink_cb, 64, FTW_DEPTH | FTW_PHYS);
561 #endif
562 return -1;
565 dirlist_t *path_dirlist(char* type, char* file)
567 dirlist_t *n;
568 dirlist_t *list = NULL;
569 char path[2048];
570 #ifdef WIN32
571 dirlist_t nn;
572 WIN32_FIND_DATA FindFileData;
573 HANDLE hFind = INVALID_HANDLE_VALUE;
574 DWORD dwError;
575 LPTSTR DirSpec;
576 INT retval;
577 #else
578 DIR *dp;
579 struct dirent *dirp;
580 #endif
581 if (!path_get(type,file,1,path,2048))
582 return NULL;
583 #ifdef WIN32
584 DirSpec = malloc(MAX_PATH);
586 if (!DirSpec) {
587 retval = 1;
588 }else if (strlen(path) > (MAX_PATH-2)) {
589 retval = 3;
590 }else{
591 retval = 0;
592 sprintf(DirSpec, "%s\\*", path);
594 hFind = FindFirstFile(DirSpec, &FindFileData);
596 if (hFind == INVALID_HANDLE_VALUE) {
597 retval = -1;
598 }else{
599 nn.name = FindFileData.cFileName;
600 nn.dir = FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
601 if (n->name[0] != '.' || (n->name[1] && strcmp(n->name,".."))) {
602 n = malloc(sizeof(dirlist_t));
603 n->name = strdup(nn.name);
604 n->dir = nn.dir;
605 list = list_push(&list,n);
608 while (FindNextFile(hFind, &FindFileData) != 0) {
609 nn.name = FindFileData.cFileName;
610 nn.dir = FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
611 if (n->name[0] != '.' || (n->name[1] && strcmp(n->name,".."))) {
612 n = malloc(sizeof(dirlist_t));
613 n->name = strdup(nn.name);
614 n->dir = nn.dir;
615 list = list_push(&list,n);
619 dwError = GetLastError();
620 FindClose(hFind);
621 if (dwError != ERROR_NO_MORE_FILES)
622 retval = -1;
626 free(DirSpec);
628 if (retval != 0 && list) {
629 while ((n = list_pop(&list))) {
630 free(n->name);
631 free(n);
634 #else
635 dp = opendir(path);
636 if (!dp)
637 return NULL;
639 while ((dirp = readdir(dp)) != NULL) {
640 if (dirp->d_name[0] == '.' && (!dirp->d_name[1] || !strcmp(dirp->d_name,"..")))
641 continue;
642 n = malloc(sizeof(dirlist_t));
643 n->name = strdup(dirp->d_name);
644 if (dirp->d_type == DT_DIR) {
645 n->dir = 1;
646 }else{
647 n->dir = 0;
649 list = list_push(&list,n);
651 closedir(dp);
652 #endif
653 return list;