System dependent standard directory retrieval functions update : return const strings...
[blender.git] / source / blender / blenlib / intern / BLI_bfile.c
1 /*
2  * $Id$
3  *
4  * ***** BEGIN GPL LICENSE BLOCK *****
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19  *
20  * The Original Code is Copyright (C) 2009 by Stichting Blender Foundation.
21  * All rights reserved.
22  *
23  * ***** END GPL LICENSE BLOCK *****
24  * BFILE* based abstraction for file access.
25  */
26
27 #include <string.h>
28 #include <stdlib.h>
29 #ifndef WIN32
30  #include <libgen.h>
31  #include <unistd.h>
32  #include <sys/param.h>
33 #else
34  #include <io.h>
35  #include "BLI_winstuff.h"
36 #endif
37 #include <sys/types.h>
38 #include <sys/stat.h>
39 #include <fcntl.h>
40
41 #include "MEM_guardedalloc.h"
42 #include "BKE_utildefines.h"
43 #include "BKE_blender.h"
44 #include "BLI_path_util.h"
45 #include "BLI_fileops.h"
46 #include "BLI_storage.h"
47 #include "BLI_bfile.h"
48
49 #include "GHOST_C-api.h"
50
51 /* Internal bfile classification flags */
52 #define BCF_OPEN     (0)
53 #define BCF_FOPEN    (1<<0)
54 #define BCF_READ     (1<<1)
55 #define BCF_WRITE    (1<<2)
56 #define BCF_AT_END   (1<<3)
57 #define BCF_DISCARD  (1<<4)
58
59 /* Standard files names */
60 #define LAST_SESSION_FILE "last-session"
61 #define ENVIRONMENT_FILE "environment"
62
63
64 /* Declaration of internal functions */
65 void chomp(char* line);
66 void expand_envvars(char* src, char* dst);
67 void fill_paths(BFILE *bfile, const char *path);
68 char* find_in_pathlist(char* filename, char* pathlist);
69 void init_vars_from_file(const char* path);
70 void setup_temp();
71
72 /*** Exported functions ***/
73
74 BFILE *BLI_bfile_fopen(const char *path, const char *mode, int bflags,
75                        BEnvVarFam envvars)
76 {
77         BFILE *bfile;
78
79         bfile = MEM_mallocN(sizeof(BFILE), "bfile-fopen");
80         bfile->classf = BCF_FOPEN;
81         bfile->uflags = bflags;
82
83         /* From fopen() doc, we can guess some logic:
84         r  BCF_READ
85         r+ BCF_READ | BCF_WRITE
86         w  BCF_DISCARD | BCF_WRITE
87         w+ BCF_DISCARD | BCF_WRITE | BCF_READ
88         a  BCF_AT_END | BCF_WRITE
89         a+ BCF_AT_END | BCF_WRITE | BCF_READ
90         */
91         if(strchr(mode, 'r'))
92                 bfile->classf |= BCF_READ;
93         if(strchr(mode, 'w'))
94                 bfile->classf |= (BCF_DISCARD | BCF_WRITE);
95         if(strchr(mode, 'a'))
96                 bfile->classf |= (BCF_AT_END | BCF_WRITE);
97         if(strchr(mode, '+'))
98                 bfile->classf |= (BCF_READ | BCF_WRITE);
99
100         fill_paths(bfile, path);
101
102         bfile->stream = fopen(bfile->tpath, mode);
103         // detect failed fopen
104         bfile->fd = fileno(bfile->stream);
105         return bfile;
106 }
107
108
109 BFILE *BLI_bfile_open(const char *pathname, int flags, int bflags,
110                       BEnvVarFam envvars)
111 {
112         BFILE *bfile;
113
114         bfile = MEM_mallocN(sizeof(BFILE), "bfile-open");
115         bfile->classf = BCF_OPEN;
116         bfile->uflags = bflags;
117
118         /* Easy mapping for open() */
119         if(flags & O_RDONLY)
120                 bfile->classf |= BCF_READ;
121         if(flags & O_WRONLY)
122                 bfile->classf |= BCF_WRITE;
123         if(flags & O_RDWR)
124                 bfile->classf |= (BCF_READ | BCF_WRITE);
125         if(flags & O_APPEND)
126                 bfile->classf |= BCF_AT_END;
127         if(flags & O_TRUNC)
128                 bfile->classf |= BCF_DISCARD;
129
130         fill_paths(bfile, pathname);
131
132         bfile->fd = open(bfile->tpath, flags);
133         // detect failed open
134 //      bfile->stream = fdopen(bfile->fd, XXX); /* MSWindows _fdopen? */
135         return bfile;
136 }
137
138
139 FILE *BLI_bfile_file_from_bfile(BFILE *bfile) {
140         return bfile->stream;
141 }
142
143
144 int BLI_bfile_fd_from_bfile(BFILE *bfile) {
145         return bfile->fd;
146 }
147
148
149 ssize_t BLI_bfile_write(BFILE *f, const void *buf, size_t count) {
150         ssize_t ret;
151
152         ret = write((f->fd), buf, count);
153         if (ret == -1) {
154                 f->error = 1;
155         }
156
157         return ret;
158 }
159
160
161 ssize_t BLI_bfile_read(BFILE *f, void *buf, size_t count) {
162         ssize_t ret;
163
164         ret = read((f->fd), buf, count);
165         if (ret == -1) {
166                 f->error = 1;
167         }
168
169         return ret;
170 }
171
172
173 size_t BLI_bfile_fwrite(const void *ptr, size_t size, size_t nmemb,
174                         BFILE *f)
175 {
176         size_t ret;
177
178         ret = fwrite(ptr, size, nmemb, f->stream);
179         if (ret < 0) {
180                 f->error = 1;
181         }
182
183         return ret;
184 }
185
186
187 size_t BLI_bfile_fread(void *ptr, size_t size, size_t nmemb, BFILE *f) {
188         size_t ret;
189
190         ret = fread(ptr, size, nmemb, f->stream);
191         if ((ret < 0) && ferror(f->stream)) {
192                 f->error = 1;
193         }
194
195         return ret;
196 }
197
198
199 void BLI_bfile_close(BFILE *bfile) {
200         if((bfile->classf | BCF_WRITE) &&
201            !(bfile->uflags | BFILE_RAW)) {
202                 /* Make sure data is on disk */
203                 /* Move to final name if no errors */
204         }
205
206         /* Normal close */
207
208         /* Cleanup */
209         if(bfile->fpath) {
210                 MEM_freeN(bfile->fpath);
211         }
212         if(bfile->tpath) {
213                 MEM_freeN(bfile->tpath);
214         }
215 }
216
217
218 void BLI_bfile_clear_error(BFILE *bfile) {
219         bfile->error = 0;
220 }
221
222
223 void BLI_bfile_set_error(BFILE *bfile, int error) {
224         /* No cheating, use clear_error() for 0 */
225         if (error) {
226                 bfile->error = error;
227         }
228 }
229
230
231 void BLI_bfile_init_vars() {
232         char file[MAXPATHLEN];
233         char temp[MAXPATHLEN];
234         extern char bprogname[];
235         FILE* fp;
236
237         /* This one is unconditional */
238         sprintf(temp, "%d", BLENDER_VERSION);
239         BLI_setenv("BLENDER_VERSION", temp);
240
241         /* Is this unpack&run? */
242         sprintf(temp, "%s/%d/environment", dirname(bprogname), BLENDER_VERSION);
243         if(BLI_exist(temp)) {
244                 BLI_setenv_if_new("BLENDER_SHARE", dirname(bprogname));
245         } else {
246                 BLI_setenv_if_new("BLENDER_SHARE", (const char*)GHOST_getSystemDir());
247         }
248
249         strcpy(file, (const char*)GHOST_getUserDir());
250         BLI_add_slash(file);
251         strcat(file, LAST_SESSION_FILE);
252         fp = fopen(file, "r");
253         /* 1st line, read previous version */
254         if (fp && (fscanf(fp, "%3c\n", temp) == 1)) {
255                 temp[3] = '\0';
256                 BLI_setenv("BLENDER_VERSION_PREV", temp);
257                 /* 2nd line, read previous session path if needed */
258                 if(!getenv("BLENDER_TEMP")) {
259                         if ((fgets(temp, MAXPATHLEN, fp) != NULL)) {
260                                 /* Clean any \n */
261                                 chomp(temp);
262                                 /* Check the dir is still there or generate new one */
263                                 if(!BLI_exist(temp)) {
264                                         setup_temp();
265                                 }
266                         } else {
267                                 /* We have to generate it for sure */
268                                 setup_temp();
269                         }
270                 }
271         } else {
272                 /* Probably new user, or only <=249 before */
273                 BLI_setenv("BLENDER_VERSION_PREV", "0");
274                 setup_temp();
275         }
276
277         if(fp) {
278                 fclose(fp);
279         }
280
281         /* Load vars from user and system files */
282         strcpy(file, (const char *)GHOST_getUserDir());
283         BLI_add_slash(file);
284         strcat(file, ENVIRONMENT_FILE);
285         init_vars_from_file(file);
286         sprintf(temp, "/%d/environment", BLENDER_VERSION);
287         BLI_make_file_string("/", file, getenv("BLENDER_SHARE"), temp);
288         init_vars_from_file(file);
289 }
290
291
292 /*** Internal functions ***/
293
294 /**
295  Eliminate trailing EOL by writing a \0 over it.
296  Name taken from Perl.
297  */
298 void chomp(char* line) {
299         int len = strlen(line);
300 #ifndef WIN32
301         if (line[len - 1] == '\n') {
302                 line[len - 1] = '\0';
303         }
304 #else
305         if ((line[len - 2] == '\r' ) && ((line[len - 1] == '\n'))) {
306                 line[len - 2] = '\0';
307         }
308 #endif /* WIN32 */
309 }
310
311
312 /**
313  Parse a file with lines like FOO=bar (comment lines have # as first
314  character) assigning to envvar FOO the value bar if FOO does not
315  exist yet.
316  Any white space before FOO, around the = or trailing will be used,
317  so beware.
318  */
319 #define MAX_LINE 4096
320 #define ENV_VAR 256
321 #define VAR_LEN 8192
322 void init_vars_from_file(const char* path) {
323         char line[MAX_LINE];
324         char name[ENV_VAR];
325         FILE *fp;
326         char* separator;
327         char expanded[VAR_LEN];
328
329         fp = fopen(path, "r");
330         if (!fp) return;
331
332         while (fgets(line, MAX_LINE, fp) != NULL) {
333                 /* Ignore comment lines */
334                 if (line[0] == '#')
335                         continue;
336
337                 /* Split into envvar name and contents */
338                 separator = strchr(line, '=');
339                 if(separator && ((separator - line) < ENV_VAR)) {
340                         /* First remove EOL */
341                         chomp(line);
342                         strncpy(name, line, separator - line);
343                         name[separator - line] = '\0';
344                         expand_envvars(separator + 1, expanded);
345                         BLI_setenv_if_new(name, expanded);
346                 }
347         }
348         fclose(fp);
349 }
350
351
352 /**
353  Look for ${} (or %%) env vars in src and expand if the var
354  exists (even if empty value). If not exist, the name is left as is.
355  The process is done all over src, and nested ${${}} is not supported.
356  src must be \0 terminated, and dst must be big enough.
357 */
358 #ifndef WIN32
359  #define ENVVAR_PREFFIX "${"
360  #define ENVVAR_P_SIZE 2
361  #define ENVVAR_SUFFIX "}"
362  #define ENVVAR_S_SIZE 1
363 #else
364  #define ENVVAR_PREFFIX "%"
365  #define ENVVAR_P_SIZE 1
366  #define ENVVAR_SUFFIX "%"
367  #define ENVVAR_S_SIZE 1
368 #endif /* WIN32 */
369 void expand_envvars(char* src, char* dst) {
370         char* hit1;
371         char* hit2;
372         char name[ENV_VAR];
373         char* value;
374         int prevlen;
375         int done = 0;
376         char* source = src;
377
378         dst[0] = '\0';
379         while (!done) {
380                 hit1 = strstr(source, ENVVAR_PREFFIX);
381                 if (hit1) {
382                         hit2 = strstr(hit1 + ENVVAR_P_SIZE, ENVVAR_SUFFIX);
383                         if (hit2) {
384                                 /* "Copy" the leading part, if any */
385                                 if (hit1 != source) {
386                                         prevlen = strlen(dst);
387                                         strncat(dst, source, hit1 - source);
388                                         dst[prevlen + (hit1 - source)] = '\0';
389                                 }
390                                 /* Figure the name of the env var we just found  */
391                                 strncpy(name, hit1 + ENVVAR_P_SIZE,
392                                         hit2 - (hit1 + ENVVAR_P_SIZE));
393                                 name[hit2 - (hit1 + ENVVAR_P_SIZE)] = '\0';
394                                 /* See if we can get something with that name */
395                                 value = getenv(name);
396                                 if (value) {
397                                         /* Push the var value */
398                                         strcat(dst, value);
399                                 } else {
400                                         /* Leave the var name, so it is clear that it failed */
401                                         strcat(dst, ENVVAR_PREFFIX);
402                                         strcat(dst, name);
403                                         strcat(dst, ENVVAR_SUFFIX);
404                                 }
405                                 /* Continue after closing mark, like a new string */
406                                 source = hit2 + ENVVAR_S_SIZE;
407                         } else {
408                                 /* Non terminated var so "copy as is" and finish */
409                                 strcat(dst, source);
410                                 done = 1;
411                         }
412                 } else {
413                         /* "Copy" whatever is left */
414                         strcat(dst, source);
415                         done = 1;
416                 }
417         }
418 }
419
420
421 /**
422  Return a full path if the filename exists when combined
423  with any item from pathlist. Or NULL otherwise.
424  */
425 #ifdef WIN32
426  #define SEPARATOR ';'
427 #else
428  #define SEPARATOR ':'
429 #endif
430 char* find_in_pathlist(char* filename, char* pathlist) {
431         char first[FILE_MAX + 10];
432         char* rest = NULL;
433
434         /* Separate first path from rest, use typical separator for current OS */
435         rest = strchr(pathlist, SEPARATOR);
436         if (rest) {
437                 strncpy(first, pathlist, rest - pathlist);
438                 first[rest - pathlist] = '\0';
439                 /* Skip the separator so it becomes a valid new pathlist */
440                 rest++;
441         } else {
442                 strcpy(first, pathlist);
443         }
444
445         /* Check if combination exists */
446         BLI_add_slash(first);
447         strcat(first, filename);
448         if(BLI_exist(first)) {
449                 return strdup(first);
450         }
451
452         /* First path failed, try with rest of paths if possible */
453         if(rest) {
454                 return find_in_pathlist(filename, rest);
455         } else {
456                 return NULL;
457         }
458 }
459
460
461 /**
462  Setup fpath and tpath based in the needs of the bfile.
463  */
464 void fill_paths(BFILE *bfile, const char *path) {
465         char* source_path = NULL;
466         char* temp_path = NULL;
467         int bflags = bfile->uflags;
468
469         if(bflags & BFILE_NORMAL || bflags & BFILE_RAW) {
470 //              bfile->fpath is path with // replaced
471         }
472         if(bflags & BFILE_TEMP) {
473                 temp_path = MEM_mallocN(MAXPATHLEN, "bfile-fpath-1");
474                 snprintf(temp_path, MAXPATHLEN, "%s/%s", getenv("BLENDER_TEMP"), path);
475                 bfile->fpath = temp_path;
476         }
477         if(bflags & BFILE_CONFIG) {
478 //              bfile->fpath is userdir+version+path
479 //              source_path is first hit in (if using fallback to older versions)
480 //                  userdir+curversion+path (... userdir+limitversion+path) sysdir+path
481 //              (limitversion is based in path, using some kind of regex or "tables")
482         }
483
484         if(bfile->classf & BCF_WRITE && !(bflags & BFILE_RAW)) {
485                 /* Generate temp path */
486                 temp_path = MEM_mallocN(MAXPATHLEN, "bfile-fpath-2");
487                 snprintf(temp_path, MAXPATHLEN, "%s.XXXXXX", path);
488                 bfile->tpath = mkdtemp(temp_path);
489                 if(!(bfile->classf & BCF_DISCARD)) {
490                         /* Copy data to tpath */
491                         if(source_path) {
492                                 // copy it from older version or sys version
493                         }
494                 }
495         } else {
496                 bfile->tpath = bfile->fpath;
497         }
498 }
499
500
501 /**
502  Create a temp directory in safe and multiuser way.
503  */
504 void setup_temp() {
505         char template[MAXPATHLEN];
506         char* tempdir;
507
508         if(getenv("TMPDIR")) {
509                 sprintf(template, "%s/blender-XXXXXX", getenv("TMPDIR"));
510         } else {
511                 sprintf(template, "/tmp/blender-XXXXXX");
512 // MacOSX NSTemporaryDirectory and WIN32 ???
513         }
514         tempdir = mkdtemp(template);
515         BLI_setenv("BLENDER_TEMP", tempdir);
516 }
517