1 /* Copyright (C) 2000-2004 by George Williams */
2 /*
3  * Redistribution and use in source and binary forms, with or without
4  * modification, are permitted provided that the following conditions are met:
5 
6  * Redistributions of source code must retain the above copyright notice, this
7  * list of conditions and the following disclaimer.
8 
9  * Redistributions in binary form must reproduce the above copyright notice,
10  * this list of conditions and the following disclaimer in the documentation
11  * and/or other materials provided with the distribution.
12 
13  * The name of the author may not be used to endorse or promote products
14  * derived from this software without specific prior written permission.
15 
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19  * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27 
28 #include <fontforge-config.h>
29 
30 #include "basics.h"
31 #include "ffglib.h"
32 #include "gfile.h"
33 #include "ustring.h"
34 
35 #include <errno.h>			/* for mkdir_p */
36 #include <fcntl.h>
37 #include <stdio.h>
38 #include <sys/param.h>
39 #include <sys/stat.h>		/* for mkdir */
40 #include <sys/types.h>
41 #include <unistd.h>
42 
43 #if !defined(__MINGW32__)
44  #include <pwd.h>
45 #else
46  #include <shlobj.h>
47  #include <windows.h>
48 #endif
49 
50 static char *program_dir = NULL;
51 static char dirname_[MAXPATHLEN+1];
52 
53 /**
54  * \brief Removes the extension from a file path, if it exists.
55  * This method assumes that the path is already normalized.
56  * \param path The path to be modified. Is modified in-place.
57  * \return A pointer to the input path.
58  */
GFileRemoveExtension(char * path)59 char *GFileRemoveExtension(char *path) {
60     char *ext = strrchr(path, '.');
61     if (ext) {
62         char *fp = strrchr(path, '/');
63         if (!fp || ext > fp) {
64             *ext = '\0';
65         }
66     }
67     return path;
68 }
69 
70 /**
71  * \brief Normalizes the file path as necessary.
72  * On Windows, this means changing backlashes to slashes.
73  *
74  * \param path The file path to be modified. Is modified in-place.
75  * \return A pointer to the input path
76  */
GFileNormalizePath(char * path)77 char *GFileNormalizePath(char *path) {
78 #if defined(__MINGW32__)
79     char *ptr;
80     for(ptr = path; *ptr; ptr++) {
81         if (*ptr == '\\') {
82             *ptr = '/';
83         }
84     }
85 #endif
86     return path;
87 }
88 
89 /**
90  * \brief Normalizes the file path as necessary.
91  * Unicode version of GFileNormalizePath.
92  *
93  * \param path The file path to be modified. Is modified in-place.
94  * \return A pointer to the input path
95  */
u_GFileNormalizePath(unichar_t * path)96 unichar_t *u_GFileNormalizePath(unichar_t *path) {
97 #if defined(__MINGW32__)
98     unichar_t *ptr;
99     for (ptr = path; *ptr; ptr++) {
100         if (*ptr == '\\') {
101             *ptr = '/';
102         }
103     }
104 #endif
105     return path;
106 }
107 
108 /* make directories.  make parent directories as needed,  with no error if
109  * the path already exists */
mkdir_p(const char * path,mode_t mode)110 int mkdir_p(const char *path, mode_t mode) {
111 	struct stat st;
112 	const char *e;
113 	char *p = NULL;
114 	char tmp[1024];
115 	size_t len;
116 	int r;
117 
118 	/* ensure the path is valid */
119 	if(!(e = strrchr(path, '/')))
120 return -EINVAL;
121 	/* ensure path is a directory */
122 	r = stat(path, &st);
123 	if (r == 0 && !S_ISDIR(st.st_mode))
124 return -ENOTDIR;
125 
126 	/* copy the pathname */
127 	snprintf(tmp, sizeof(tmp),"%s", path);
128 	len = strlen(tmp);
129 	if(tmp[len - 1] == '/')
130 	tmp[len - 1] = 0;
131 
132 	/* iterate mkdir over the path */
133 	for(p = tmp + 1; *p; p++)
134 	if(*p == '/') {
135 		*p = 0;
136 		r = GFileMkDir(tmp, mode);
137 		if (r < 0 && errno != EEXIST)
138 return -errno;
139 		*p = '/';
140 	}
141 
142 	/* try to make the whole path */
143 	r = GFileMkDir(tmp, mode);
144 	if(r < 0 && errno != EEXIST)
145 return -errno;
146 	/* creation successful or the file already exists */
147 return EXIT_SUCCESS;
148 }
149 
GFileGetHomeDir(void)150 char *GFileGetHomeDir(void) {
151 #if defined(__MINGW32__)
152     char* dir = getenv("HOME");
153     if(!dir)
154 	dir = getenv("USERPROFILE");
155     if(dir){
156 	char* buffer = copy(dir);
157 	GFileNormalizePath(buffer);
158 return buffer;
159     }
160 return NULL;
161 #else
162     static char *dir;
163     uid_t uid;
164     struct passwd *pw;
165 
166     dir = getenv("HOME");
167     if ( dir!=NULL )
168 	return( copy(dir) );
169 
170     uid = getuid();
171     while ( (pw=getpwent())!=NULL ) {
172 	if ( pw->pw_uid==uid ) {
173 	    dir = copy(pw->pw_dir);
174 	    endpwent();
175 return( dir );
176 	}
177     }
178     endpwent();
179 return( NULL );
180 #endif
181 }
182 
u_GFileGetHomeDir(void)183 unichar_t *u_GFileGetHomeDir(void) {
184     unichar_t* dir = NULL;
185     char* tmp = GFileGetHomeDir();
186     if( tmp ) {
187 	dir = uc_copy(tmp);
188 	free(tmp);
189     }
190 return dir;
191 }
192 
savestrcpy(char * dest,const char * src)193 static void savestrcpy(char *dest,const char *src) {
194     for (;;) {
195 	*dest = *src;
196 	if ( *dest=='\0' )
197     break;
198 	++dest; ++src;
199     }
200 }
201 
GFileGetAbsoluteName(const char * name,char * result,size_t rsiz)202 char *GFileGetAbsoluteName(const char *name, char *result, size_t rsiz) {
203     /* result may be the same as name */
204     char buffer[1000];
205 
206      if ( ! GFileIsAbsolute(name) ) {
207 	char *pt, *spt, *rpt, *bpt;
208 
209 	if ( dirname_[0]=='\0' ) {
210 	    getcwd(dirname_,sizeof(dirname_));
211 	}
212 	strcpy(buffer,dirname_);
213 	if ( buffer[strlen(buffer)-1]!='/' )
214 	    strcat(buffer,"/");
215 	strcat(buffer,name);
216 	#if defined(__MINGW32__)
217 	GFileNormalizePath(buffer);
218 	#endif
219 
220 	/* Normalize out any .. */
221 	spt = rpt = buffer;
222 	while ( *spt!='\0' ) {
223 	    if ( *spt=='/' )  {
224 		if ( *++spt=='\0' )
225 	break;
226 	    }
227 	    for ( pt = spt; *pt!='\0' && *pt!='/'; ++pt );
228 	    if ( pt==spt )	/* Found // in a path spec, reduce to / (we've*/
229 		savestrcpy(spt,spt+1); /*  skipped past the :// of the machine name) */
230 	    else if ( pt==spt+1 && spt[0]=='.' && *pt=='/' ) {	/* Noop */
231 		savestrcpy(spt,spt+2);
232 	    } else if (pt==spt+1 && spt[0]=='.' && *pt=='\0') { /* Remove trailing /. */
233 		pt = --spt;
234 		*spt = '\0';
235 	    } else if ( pt==spt+2 && spt[0]=='.' && spt[1]=='.' ) {
236 		for ( bpt=spt-2 ; bpt>rpt && *bpt!='/'; --bpt );
237 		if ( bpt>=rpt && *bpt=='/' ) {
238 		    savestrcpy(bpt,pt);
239 		    spt = bpt;
240 		} else {
241 		    rpt = pt;
242 		    spt = pt;
243 		}
244 	    } else
245 		spt = pt;
246 	}
247 	name = buffer;
248 	if ( rsiz>sizeof(buffer)) rsiz = sizeof(buffer);	/* Else valgrind gets unhappy */
249     }
250     if (result!=name) {
251 	strncpy(result,name,rsiz);
252 	result[rsiz-1]='\0';
253 	#if defined(__MINGW32__)
254 	GFileNormalizePath(result);
255 	#endif
256     }
257 return(result);
258 }
259 
GFileMakeAbsoluteName(char * name)260 char *GFileMakeAbsoluteName(char *name) {
261     char buffer[1025];
262 
263     GFileGetAbsoluteName(name,buffer,sizeof(buffer));
264 return( copy(buffer));
265 }
266 
GFileBuildName(char * dir,char * fname,char * buffer,size_t size)267 char *GFileBuildName(char *dir,char *fname,char *buffer,size_t size) {
268     int len;
269 
270     if ( dir==NULL || *dir=='\0' ) {
271 	if ( strlen( fname )<size-1 )		/* valgrind didn't like my strncpies but this complication makes it happy */
272 	    savestrcpy(buffer,fname);
273 	else {
274 	    strncpy(buffer,fname,size-1);
275 	    buffer[size-1]='\0';
276 	}
277     } else {
278 	if ( buffer!=dir ) {
279 	    if ( strlen( dir )<size-3 )
280 		strcpy(buffer,dir);
281 	    else {
282 		strncpy(buffer,dir,size-3);
283 		buffer[size-3]='\0';
284 	    }
285 	}
286 	len = strlen(buffer);
287 	if ( buffer[len-1]!='/' )
288 	    buffer[len++] = '/';
289 	if ( strlen( fname )<size-1 )
290 	    strcpy(buffer+len,fname);
291 	else {
292 	    strncpy(buffer+len,fname,size-len-1);
293 	    buffer[size-1]='\0';
294 	}
295     }
296 return( buffer );
297 }
298 
299 /* Given a filename in a directory, pick the directory out of it, and */
300 /*  create a new filename using that directory and the given nametail */
GFileReplaceName(char * oldname,char * fname,char * buffer,size_t size)301 char *GFileReplaceName(char *oldname,char *fname,char *buffer,size_t size) {
302     int len;
303     char *dirend;
304 
305     dirend = strrchr(oldname,'/');
306     if ( dirend == NULL ) {
307 	strncpy(buffer,fname,size-1);
308 	buffer[size-1]='\0';
309     } else {
310 	*dirend = '\0';
311 	if ( buffer!=oldname ) {
312 	    strncpy(buffer,oldname,size-3);
313 	    buffer[size-3]='\0';
314 	}
315 	len = strlen(buffer);
316 	*dirend = '/';
317 	buffer[len++] = '/';
318 	strncpy(buffer+len,fname,size-len-1);
319 	buffer[size-1]='\0';
320     }
321 return( buffer );
322 }
323 
GFileNameTail(const char * oldname)324 char *GFileNameTail(const char *oldname) {
325     char *pt = 0;
326 
327     pt = strrchr(oldname,'/');
328 
329     // a final slash was found, so we know that p+1 is a valid
330     // address in the string.
331     if ( pt )
332 	return( pt+1);
333 
334     return( (char *)oldname );
335 }
336 
GFileAppendFile(char * dir,char * name,int isdir)337 char *GFileAppendFile(char *dir,char *name,int isdir) {
338     char *ret, *pt;
339 
340     ret = (char *) malloc((strlen(dir)+strlen(name)+3));
341     strcpy(ret,dir);
342     pt = ret+strlen(ret);
343     if ( pt>ret && pt[-1]!='/' )
344 	*pt++ = '/';
345     strcpy(pt,name);
346     if ( isdir ) {
347 	pt += strlen(pt);
348 	if ( pt>ret && pt[-1]!='/' ) {
349 	    *pt++ = '/';
350 	    *pt = '\0';
351 	}
352     }
353 return(ret);
354 }
355 
GFileIsAbsolute(const char * file)356 int GFileIsAbsolute(const char *file) {
357 #if defined(__MINGW32__)
358     if( (file[1]==':') && (('a'<=file[0] && file[0]<='z') || ('A'<=file[0] && file[0]<='Z')) )
359 return ( true );
360 #else
361     if ( *file=='/' )
362 return( true );
363 #endif
364     if ( strstr(file,"://")!=NULL )
365 return( true );
366 
367 return( false );
368 }
369 
GFileIsDir(const char * file)370 int GFileIsDir(const char *file) {
371   struct stat info;
372   if ( stat(file, &info)==-1 )
373 return 0;
374   else
375 return( S_ISDIR(info.st_mode) );
376 }
377 
GFileExists(const char * file)378 int GFileExists(const char *file) {
379 return( access(file,0)==0 );
380 }
381 
GFileModifyable(const char * file)382 int GFileModifyable(const char *file) {
383 return( access(file,02)==0 );
384 }
385 
GFileModifyableDir(const char * file)386 int GFileModifyableDir(const char *file) {
387     char buffer[1025], *pt;
388 
389     buffer[1024]=0;
390     strncpy(buffer,file,1024);
391     pt = strrchr(buffer,'/');
392     if ( pt==NULL )
393 	strcpy(buffer,".");
394     else
395 	*pt='\0';
396     return( GFileModifyable(buffer) );
397 }
398 
GFileReadable(const char * file)399 int GFileReadable(const char *file) {
400 return( access(file,04)==0 );
401 }
402 
403 /**
404  *  Creates a temporary file, similar to tmpfile.
405  *  Used because the default tmpfile implementation on Windows is broken
406  */
GFileTmpfile()407 FILE *GFileTmpfile() {
408 #ifndef _WIN32
409     return tmpfile();
410 #else
411     wchar_t temp_path[MAX_PATH + 1];
412     DWORD ret = GetTempPathW(MAX_PATH + 1, temp_path);
413     if (!ret) {
414         return NULL;
415     }
416 
417     while(true) {
418         wchar_t *temp_name = _wtempnam(temp_path, L"FF_");
419         if (!temp_name) {
420             return NULL;
421         }
422 
423         HANDLE handle = CreateFileW(
424             temp_name,
425             GENERIC_READ | GENERIC_WRITE,
426             0,
427             NULL,
428             CREATE_NEW,
429             FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE,
430             NULL
431         );
432         free(temp_name);
433 
434         if (handle == INVALID_HANDLE_VALUE) {
435             if (GetLastError() != ERROR_FILE_EXISTS) {
436                 return NULL;
437             }
438         } else {
439             int fd = _open_osfhandle((intptr_t)handle, _O_RDWR|_O_CREAT|_O_TEMPORARY|_O_BINARY);
440             if (fd == -1) {
441                 CloseHandle(handle);
442                 return NULL;
443             }
444 
445             FILE *fp = _fdopen(fd, "w+");
446             if (!fp) {
447                 _close(fd);
448                 return NULL;
449             }
450 
451             return fp;
452         }
453     }
454     return NULL;
455 #endif
456 }
457 
458 /**
459  * Removes a file or folder.
460  *
461  * @param [in] path The path to be removed.
462  * @param [in] recursive Specify true to remove a folder and all of its
463  *                       sub-contents.
464  * @return true if the deletion was successful or the path does not exist. It
465  *         will fail if trying to remove a directory that is not empty and
466  *         where `recursive` is false.
467  */
GFileRemove(const char * path,int recursive)468 int GFileRemove(const char *path, int recursive) {
469     GDir *dir;
470     const gchar *entry;
471 
472     if (g_remove(path) != 0) {
473         if (recursive && (dir = g_dir_open(path, 0, NULL))) {
474             while ((entry = g_dir_read_name(dir))) {
475                 gchar *fpath = g_build_filename(path, entry, NULL);
476                 if (g_remove(fpath) != 0 && GFileIsDir(fpath)) {
477                     GFileRemove(fpath, recursive);
478                 }
479                 g_free(fpath);
480             }
481             g_dir_close(dir);
482         }
483         return (g_remove(path) == 0 || !GFileExists(path));
484     }
485 
486     return true;
487 }
488 
GFileMkDir(const char * name,int mode)489 int GFileMkDir(const char *name, int mode) {
490 #ifndef _WIN32
491 	return mkdir(name, mode);
492 #else
493 	return mkdir(name);
494 #endif
495 }
496 
GFileRmDir(const char * name)497 int GFileRmDir(const char *name) {
498 return(rmdir(name));
499 }
500 
GFileUnlink(const char * name)501 int GFileUnlink(const char *name) {
502 return(unlink(name));
503 }
504 
_GFile_find_program_dir(char * prog)505 char *_GFile_find_program_dir(char *prog) {
506     char *pt, *path, *program_dir=NULL;
507     char filename[2000];
508 
509     if (prog == NULL) {
510         return NULL;
511     }
512 
513 #if defined(__MINGW32__)
514     char* pt1 = strrchr(prog, '/');
515     char* pt2 = strrchr(prog, '\\');
516     if(pt1<pt2) pt1=pt2;
517     if(pt1)
518 	program_dir = copyn(prog, pt1-prog);
519     else if( (path = getenv("PATH")) != NULL ){
520 	char* tmppath = copy(path);
521 	path = tmppath;
522 	for(;;){
523 	    pt1 = strchr(path, ';');
524 	    if(pt1) *pt1 = '\0';
525 	    sprintf(filename,"%s/%s", path, prog);
526 	    if ( access(filename,1)!= -1 ) {
527 		program_dir = copy(path);
528 		break;
529 	    }
530 	    if(!pt1) break;
531 	    path = pt1+1;
532 	}
533 	free(tmppath);
534     }
535 #else
536     if ( (pt = strrchr(prog,'/'))!=NULL )
537 	program_dir = copyn(prog,pt-prog);
538     else if ( (path = getenv("PATH"))!=NULL ) {
539 	while ((pt = strchr(path,':'))!=NULL ) {
540 	  sprintf(filename,"%.*s/%s", (int)(pt-path), path, prog);
541 	    /* Under cygwin, applying access to "potrace" will find "potrace.exe" */
542 	    /*  no need for special check to add ".exe" */
543 	    if ( access(filename,1)!= -1 ) {
544 		program_dir = copyn(path,pt-path);
545 	break;
546 	    }
547 	    path = pt+1;
548 	}
549 	if ( program_dir==NULL ) {
550 	    sprintf(filename,"%s/%s", path, prog);
551 	    if ( access(filename,1)!= -1 )
552 		program_dir = copy(path);
553 	}
554     }
555 #endif
556 
557     if ( program_dir==NULL )
558 return( NULL );
559     GFileGetAbsoluteName(program_dir,filename,sizeof(filename));
560     free(program_dir);
561     program_dir = copy(filename);
562 return( program_dir );
563 }
564 
u_GFileGetAbsoluteName(unichar_t * name,unichar_t * result,int rsiz)565 unichar_t *u_GFileGetAbsoluteName(unichar_t *name, unichar_t *result, int rsiz) {
566     /* result may be the same as name */
567     unichar_t buffer[1000];
568 
569     if ( ! u_GFileIsAbsolute(name) ) {
570 	unichar_t *pt, *spt, *rpt, *bpt;
571 
572 	if ( dirname_[0]=='\0' ) {
573 	    getcwd(dirname_,sizeof(dirname_));
574 	}
575 	uc_strcpy(buffer,dirname_);
576 	if ( buffer[u_strlen(buffer)-1]!='/' )
577 	    uc_strcat(buffer,"/");
578 	u_strcat(buffer,name);
579 	u_GFileNormalizePath(buffer);
580 
581 	/* Normalize out any .. */
582 	spt = rpt = buffer;
583 	while ( *spt!='\0' ) {
584 	    if ( *spt=='/' ) ++spt;
585 	    for ( pt = spt; *pt!='\0' && *pt!='/'; ++pt );
586 	    if ( pt==spt )	/* Found // in a path spec, reduce to / (we've*/
587 		u_strcpy(spt,pt); /*  skipped past the :// of the machine name) */
588 	    else if ( pt==spt+1 && spt[0]=='.' && *pt=='/' )	/* Noop */
589 		u_strcpy(spt,spt+2);
590 	    else if ( pt==spt+2 && spt[0]=='.' && spt[1]=='.' ) {
591 		for ( bpt=spt-2 ; bpt>rpt && *bpt!='/'; --bpt );
592 		if ( bpt>=rpt && *bpt=='/' ) {
593 		    u_strcpy(bpt,pt);
594 		    spt = bpt;
595 		} else {
596 		    rpt = pt;
597 		    spt = pt;
598 		}
599 	    } else
600 		spt = pt;
601 	}
602 	name = buffer;
603     }
604     if (result!=name) {
605 	u_strncpy(result,name,rsiz);
606 	result[rsiz-1]='\0';
607 	u_GFileNormalizePath(result);
608     }
609 return(result);
610 }
611 
u_GFileBuildName(unichar_t * dir,unichar_t * fname,unichar_t * buffer,int size)612 unichar_t *u_GFileBuildName(unichar_t *dir,unichar_t *fname,unichar_t *buffer,int size) {
613     int len;
614 
615     if ( dir==NULL || *dir=='\0' ) {
616 	u_strncpy(buffer,fname,size-1);
617 	buffer[size-1]='\0';
618     } else {
619 	if ( buffer!=dir ) {
620 	    u_strncpy(buffer,dir,size-3);
621 	    buffer[size-3]='\0';
622 	}
623 	len = u_strlen(buffer);
624 	if ( buffer[len-1]!='/' )
625 	    buffer[len++] = '/';
626 	u_strncpy(buffer+len,fname,size-len-1);
627 	buffer[size-1]='\0';
628     }
629 return( buffer );
630 }
631 
632 /* Given a filename in a directory, pick the directory out of it, and */
633 /*  create a new filename using that directory and the given nametail */
u_GFileReplaceName(unichar_t * oldname,unichar_t * fname,unichar_t * buffer,int size)634 unichar_t *u_GFileReplaceName(unichar_t *oldname,unichar_t *fname,unichar_t *buffer,int size) {
635     int len;
636     unichar_t *dirend;
637 
638     dirend = u_strrchr(oldname,'/');
639     if ( dirend == NULL ) {
640 	u_strncpy(buffer,fname,size-1);
641 	buffer[size-1]='\0';
642     } else {
643 	*dirend = '\0';
644 	if ( buffer!=oldname ) {
645 	    u_strncpy(buffer,oldname,size-3);
646 	    buffer[size-3]='\0';
647 	}
648 	len = u_strlen(buffer);
649 	*dirend = '/';
650 	buffer[len++] = '/';
651 	u_strncpy(buffer+len,fname,size-len-1);
652 	buffer[size-1]='\0';
653     }
654 return( buffer );
655 }
656 
u_GFileNameTail(const unichar_t * oldname)657 unichar_t *u_GFileNameTail(const unichar_t *oldname) {
658     unichar_t *pt;
659 
660     pt = u_strrchr(oldname,'/');
661     if ( pt !=NULL )
662 return( pt+1);
663     else
664 return( (unichar_t *)oldname );
665 }
666 
667 /**
668  * Remove the 'root' part of the file path if it is absolute;
669  * On Unix this is '/' and on Windows this is for e.g. 'C:/'
670  */
u_GFileRemoveRoot(unichar_t * path)671 static unichar_t *u_GFileRemoveRoot(unichar_t *path) {
672     //May happen on Windows too e.g. CygWin
673     if (*path == '/') {
674         path++;
675     }
676 #ifdef _WIN32
677     //Check if it is a drive letter path
678     else if (((path[0] >= 'A' && path[0] <= 'Z') ||
679               (path[0] >= 'a' && path[0] <= 'z')) &&
680              path[1] == ':' && path[2] == '/') {
681 
682         path += 3;
683     }
684 #endif
685     return path;
686 }
687 
u_GFileNormalize(unichar_t * name)688 unichar_t *u_GFileNormalize(unichar_t *name) {
689     unichar_t *pt, *base, *ppt;
690 
691     if ( (pt = uc_strstr(name,"://"))!=NULL ) {
692 	base = u_strchr(pt+3,'/');
693 	if ( base==NULL )
694 return( name );
695 	++base;
696     }
697 
698     base = u_GFileRemoveRoot(name);
699     for ( pt=base; *pt!='\0'; ) {
700 	if ( *pt=='/' )
701 	    u_strcpy(pt,pt+1);
702 	else if ( uc_strncmp(pt,"./",2)==0 )
703 	    u_strcpy(pt,pt+2);
704 	else if ( uc_strncmp(pt,"../",2)==0 ) {
705 	    for ( ppt=pt-2; ppt>=base && *ppt!='/'; --ppt );
706 	    ++ppt;
707 	    if ( ppt>=base ) {
708 		u_strcpy(ppt,pt+3);
709 		pt = ppt;
710 	    } else
711 		pt += 3;
712 	} else {
713 	    while ( *pt!='/' && *pt!='\0' ) ++pt;
714 	    if ( *pt == '/' ) ++pt;
715 	}
716     }
717 return( name );
718 }
719 
u_GFileAppendFile(unichar_t * dir,unichar_t * name,int isdir)720 unichar_t *u_GFileAppendFile(unichar_t *dir,unichar_t *name,int isdir) {
721     unichar_t *ret, *pt;
722 
723     ret = (unichar_t *) malloc((u_strlen(dir)+u_strlen(name)+3)*sizeof(unichar_t));
724     u_strcpy(ret,dir);
725     pt = ret+u_strlen(ret);
726     if ( pt>ret && pt[-1]!='/' )
727 	*pt++ = '/';
728     u_strcpy(pt,name);
729     if ( isdir ) {
730 	pt += u_strlen(pt);
731 	if ( pt>ret && pt[-1]!='/' ) {
732 	    *pt++ = '/';
733 	    *pt = '\0';
734 	}
735     }
736 return(ret);
737 }
738 
u_GFileIsAbsolute(const unichar_t * file)739 int u_GFileIsAbsolute(const unichar_t *file) {
740 #if defined(__MINGW32__)
741     if( (file[1]==':') && (('a'<=file[0] && file[0]<='z') || ('A'<=file[0] && file[0]<='Z')) )
742 return ( true );
743 #else
744     if ( *file=='/' )
745 return( true );
746 #endif
747     if ( uc_strstr(file,"://")!=NULL )
748 return( true );
749 
750 return( false );
751 }
752 
u_GFileIsDir(const unichar_t * file)753 int u_GFileIsDir(const unichar_t *file) {
754     char buffer[1024];
755     u2def_strncpy(buffer,file,sizeof(buffer));
756     return GFileIsDir(buffer);
757 }
758 
u_GFileExists(const unichar_t * file)759 int u_GFileExists(const unichar_t *file) {
760     char buffer[1024];
761     u2def_strncpy(buffer,file,sizeof(buffer));
762 return( access(buffer,0)==0 );
763 }
764 
u_GFileModifyable(const unichar_t * file)765 int u_GFileModifyable(const unichar_t *file) {
766     char buffer[1024];
767     u2def_strncpy(buffer,file,sizeof(buffer));
768 return( access(buffer,02)==0 );
769 }
770 
u_GFileModifyableDir(const unichar_t * file)771 int u_GFileModifyableDir(const unichar_t *file) {
772     char buffer[1024], *pt;
773 
774     u2def_strncpy(buffer,file,sizeof(buffer));
775     pt = strrchr(buffer,'/');
776     if ( pt==NULL )
777 	strcpy(buffer,".");
778     else
779 	*pt='\0';
780 return( GFileModifyable(buffer));
781 }
782 
u_GFileReadable(unichar_t * file)783 int u_GFileReadable(unichar_t *file) {
784     char buffer[1024];
785     u2def_strncpy(buffer,file,sizeof(buffer));
786 return( access(buffer,04)==0 );
787 }
788 
u_GFileMkDir(unichar_t * name)789 int u_GFileMkDir(unichar_t *name) {
790     char buffer[1024];
791     u2def_strncpy(buffer,name,sizeof(buffer));
792 	return GFileMkDir(buffer, 0755);
793 }
794 
u_GFileRmDir(unichar_t * name)795 int u_GFileRmDir(unichar_t *name) {
796     char buffer[1024];
797     u2def_strncpy(buffer,name,sizeof(buffer));
798 return(rmdir(buffer));
799 }
800 
u_GFileUnlink(unichar_t * name)801 int u_GFileUnlink(unichar_t *name) {
802     char buffer[1024];
803     u2def_strncpy(buffer,name,sizeof(buffer));
804 return(unlink(buffer));
805 }
806 
FindProgDir(char * prog)807 void FindProgDir(char *prog) {
808     if (program_dir != NULL) {
809         return;
810     }
811 
812 #if defined(__MINGW32__)
813     char  path[MAX_PATH+4];
814     char* c = path;
815     char* tail = 0;
816     unsigned int  len = GetModuleFileNameA(NULL, path, MAX_PATH);
817     path[len] = '\0';
818     for(; *c; *c++){
819     	if(*c == '\\'){
820     	    tail=c;
821     	    *c = '/';
822     	}
823     }
824     if(tail) *tail='\0';
825     program_dir = copy(path);
826 #else
827     program_dir = _GFile_find_program_dir(prog);
828     if ( program_dir==NULL ) {
829         program_dir = smprintf("%s/%s", FONTFORGE_INSTALL_PREFIX, "bin");
830     }
831 #endif
832 }
833 
getShareDir(void)834 char *getShareDir(void) {
835     static char *sharedir=NULL;
836     static int set=false;
837     char *pt;
838     int len;
839 
840     if ( set )
841 	return( sharedir );
842 
843     set = true;
844 
845     //Assume share folder is one directory up
846     pt = strrchr(program_dir, '/');
847     if ( pt==NULL ) {
848 	pt = program_dir + strlen(program_dir);
849     }
850     len = (pt-program_dir)+strlen("/share/fontforge")+1;
851     sharedir = malloc(len);
852     strncpy(sharedir,program_dir,pt-program_dir);
853     strcpy(sharedir+(pt-program_dir),"/share/fontforge");
854     return( sharedir );
855 }
856 
857 
getLocaleDir(void)858 char *getLocaleDir(void) {
859     static char *sharedir=NULL;
860     static int set=false;
861 
862     if ( set )
863 	return( sharedir );
864 
865     char* prefix = getShareDir();
866     int len = strlen(prefix) + strlen("/../locale") + 2;
867     sharedir = malloc(len);
868     strcpy(sharedir,prefix);
869     strcat(sharedir,"/../locale");
870     set = true;
871     return sharedir;
872 }
873 
getPixmapDir(void)874 char *getPixmapDir(void) {
875     static char *sharedir=NULL;
876     static int set=false;
877 
878     if ( set )
879 	return( sharedir );
880 
881     char* prefix = getShareDir();
882     int len = strlen(prefix) + strlen("/pixmaps") + 2;
883     sharedir = malloc(len);
884     strcpy(sharedir,prefix);
885     strcat(sharedir,"/pixmaps");
886     set = true;
887     return sharedir;
888 }
889 
getHelpDir(void)890 char *getHelpDir(void) {
891     static char *sharedir=NULL;
892     static int set=false;
893 
894     if ( set )
895 	return( sharedir );
896 
897     char* prefix = getShareDir();
898     const char* postfix = "/../doc/fontforge/";
899     int len = strlen(prefix) + strlen(postfix) + 2;
900     sharedir = malloc(len);
901     strcpy(sharedir,prefix);
902     strcat(sharedir,postfix);
903     set = true;
904     return sharedir;
905 }
906 
907 /* reimplementation of GFileGetHomeDir, avoiding copy().  Returns NULL if home
908  * directory cannot be found */
getUserHomeDir(void)909 char *getUserHomeDir(void) {
910 #if defined(__MINGW32__)
911 	char* dir = getenv("APPDATA");
912 	if( dir==NULL )
913 	dir = getenv("USERPROFILE");
914 	if( dir!=NULL ) {
915 	GFileNormalizePath(dir);
916 return dir;
917 	}
918 return NULL;
919 #else
920 	uid_t uid;
921 	struct passwd *pw;
922 	char *home = getenv("HOME");
923 
924 	if( home!=NULL )
925 return home;
926 
927 	uid = getuid();
928 	while( (pw=getpwent())!=NULL ) {
929 	if ( pw->pw_uid==uid ) {
930 		home = pw->pw_dir;
931 		endpwent();
932 return home;
933 	}
934 	}
935 	endpwent();
936 return NULL;
937 #endif
938 }
939 
940 /* Find the directory in which FontForge places all of its configurations and
941  * save files.  On Unix-likes, the argument `dir` (see the below case switch,
942  * enum in inc/gfile.h) determines which directory is returned according to the
943  * XDG Base Directory Specification.  On Windows, the argument is ignored--the
944  * home directory as obtained by getUserHomeDir() appended with "/FontForge" is
945  * returned. On error, NULL is returned.
946  *
947  * http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
948  */
getFontForgeUserDir(int dir)949 char *getFontForgeUserDir(int dir) {
950 	const char *def;
951 	const char *home, *xdg;
952 	char *buf = NULL;
953 
954 	/* find home directory first, it is needed if any of the xdg env vars are
955 	 * not set */
956 	if (!(home = getUserHomeDir())) {
957 	/* if getUserHomeDir returns NULL, pass NULL to calling function */
958 	fprintf(stderr, "%s\n", "cannot find home directory");
959 return NULL;
960 	}
961 #ifdef _WIN32
962 	/* Allow for preferences to be saved locally in a 'portable' configuration. */
963 	if (getenv("FF_PORTABLE") != NULL) {
964 		buf = smprintf("%s/preferences/", getShareDir());
965 	} else {
966 		buf = smprintf("%s/FontForge/", home);
967 	}
968 	return buf;
969 #else
970 	/* Home directory exists, so check for environment variables.  For each of
971 	 * XDG_{CACHE,CONFIG,DATA}_HOME, assign `def` as the corresponding fallback
972 	 * for if the environment variable does not exist. */
973 	switch(dir) {
974 	  case Cache:
975 	xdg = getenv("XDG_CACHE_HOME");
976 	def = ".cache";
977 	  break;
978 	  case Config:
979 	xdg = getenv("XDG_CONFIG_HOME");
980 	def = ".config";
981 	  break;
982 	  case Data:
983 	xdg = getenv("XDG_DATA_HOME");
984 	def = ".local/share";
985 	  break;
986 	  default:
987 	/* for an invalid argument, return NULL */
988 	fprintf(stderr, "%s\n", "invalid input");
989 return NULL;
990 	}
991 	if(xdg != NULL)
992 	/* if, for example, XDG_CACHE_HOME exists, assign the value
993 	 * "$XDG_CACHE_HOME/fontforge" */
994 	buf = smprintf("%s/fontforge", xdg);
995 	else
996 	/* if, for example, XDG_CACHE_HOME does not exist, instead assign
997 	 * the value "$HOME/.cache/fontforge" */
998 	buf = smprintf("%s/%s/fontforge", home, def);
999 	if(buf != NULL) {
1000 	    /* try to create buf.  If creating the directory fails, return NULL
1001 	     * because nothing will get saved into an inaccessible directory.  */
1002             if ( mkdir_p(buf, 0755) != EXIT_SUCCESS ) {
1003                 free(buf);
1004                 return NULL;
1005             }
1006             return buf;
1007 	}
1008 return NULL;
1009 #endif
1010 }
1011 
GFileGetSize(char * name)1012 off_t GFileGetSize(char *name) {
1013 /* Get the binary file size for file 'name'. Return -1 if error. */
1014     struct stat buf;
1015     long rc;
1016 
1017     if ( (rc=stat(name,&buf)) )
1018 	return( -1 );
1019     return( buf.st_size );
1020 }
1021 
GFileReadAll(char * name)1022 char *GFileReadAll(char *name) {
1023 /* Read file 'name' all into one large string. Return 0 if error. */
1024     char *ret;
1025     long sz;
1026 
1027     if ( (sz=GFileGetSize(name))>=0 && \
1028 	 (ret=calloc(1,sz+1))!=NULL ) {
1029 	FILE *fp;
1030 	if ( (fp=fopen(name,"rb"))!=NULL ) {
1031 	    size_t bread=fread(ret,1,sz,fp);
1032 	    fclose(fp);
1033 
1034 	    if( bread==(size_t)sz )
1035 		return( ret );
1036 	}
1037 	free(ret);
1038     }
1039     return( 0 );
1040 }
1041 
1042 /*
1043  * Write char string 'data' into file 'name'. Return -1 if error.
1044  **/
GFileWriteAll(char * filepath,char * data)1045 int GFileWriteAll(char *filepath, char *data) {
1046 
1047     if( !data )
1048 	return -1;
1049 
1050     size_t bwrite = strlen(data);
1051     FILE* fp;
1052 
1053     if ( (fp = fopen( filepath, "wb" )) != NULL ) {
1054 	if ( (fwrite( data, 1, bwrite, fp ) == bwrite) && \
1055 	     (fflush(fp) == 0) )
1056 	    return( (fclose(fp) == 0 ? 0: -1) );
1057 	fclose(fp);
1058     }
1059     return -1;
1060 }
1061 
getTempDir(void)1062 const char *getTempDir(void)
1063 {
1064     return g_get_tmp_dir();
1065 }
1066 
GFileGetHomeDocumentsDir(void)1067 char *GFileGetHomeDocumentsDir(void)
1068 {
1069     static char* ret = 0;
1070     if( ret )
1071 	return ret;
1072 
1073 #if defined(__MINGW32__)
1074 
1075     CHAR my_documents[MAX_PATH+2];
1076     HRESULT result = SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT, my_documents );
1077     if (result != S_OK)
1078     {
1079     	fprintf(stderr,"Error: Can't get My Documents path!'\n");
1080         return ret;
1081     }
1082     int pos = strlen(my_documents);
1083     my_documents[ pos++ ] = '\\';
1084     my_documents[ pos++ ] = '\0';
1085     ret = copy( my_documents );
1086 	GFileNormalizePath(ret);
1087     return ret;
1088 #endif
1089 
1090     // On GNU/Linux and OSX it was decided that this should be just the
1091     // home directory itself.
1092     ret = GFileGetHomeDir();
1093     return ret;
1094 }
1095 
u_GFileGetHomeDocumentsDir(void)1096 unichar_t *u_GFileGetHomeDocumentsDir(void) {
1097     unichar_t* dir = NULL;
1098     char* tmp = GFileGetHomeDocumentsDir();
1099     if(tmp) {
1100         dir = uc_copy(tmp);
1101     }
1102     return dir;
1103 }
1104 
1105 
GFileDirNameEx(const char * path,int treat_as_file)1106 char *GFileDirNameEx(const char *path, int treat_as_file)
1107 {
1108     char *ret = NULL;
1109     if (path != NULL) {
1110         //Must allocate enough space to append a trailing slash.
1111         size_t len = strlen(path);
1112         ret = malloc(len + 2);
1113 
1114         if (ret != NULL) {
1115             char *pt;
1116 
1117             strcpy(ret, path);
1118             GFileNormalizePath(ret);
1119             if (treat_as_file || !GFileIsDir(ret)) {
1120                 pt = strrchr(ret, '/');
1121                 if (pt != NULL) {
1122                     *pt = '\0';
1123                 }
1124             }
1125 
1126             //Keep only one trailing slash
1127             len = strlen(ret);
1128             for (pt = ret + len - 1; pt >= ret && *pt == '/'; pt--) {
1129                 *pt = '\0';
1130             }
1131             *++pt = '/';
1132             *++pt = '\0';
1133         }
1134     }
1135     return ret;
1136 }
1137 
GFileDirName(const char * path)1138 char *GFileDirName(const char *path) {
1139     return GFileDirNameEx(path, 0);
1140 }
1141 
1142