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