1 /* OS dependent filename manipulation routines
2 * Copyright (c) Olly Betts 1998-2003,2004,2005,2010,2011,2014
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
30 typedef struct filelist
{
33 struct filelist
*next
;
36 static filelist
*flhead
= NULL
;
38 static void filename_register_output_with_fh(const char *fnm
, FILE *fh
);
40 /* safe_fopen should be used when writing a file
41 * fopenWithPthAndExt should be used when reading a file
44 /* Wrapper for fopen which throws a fatal error if it fails.
45 * Some versions of fopen() are quite happy to open a directory.
46 * We aren't, so catch this case. */
48 safe_fopen(const char *fnm
, const char *mode
)
51 SVX_ASSERT(mode
[0] == 'w'); /* only expect to be used for writing */
53 fatalerror(/*Filename “%s” refers to directory*/44, fnm
);
56 if (!f
) fatalerror(/*Failed to open output file “%s”*/47, fnm
);
58 filename_register_output_with_fh(fnm
, f
);
62 /* Wrapper for fclose which throws a fatal error if there's been a write
69 /* NB: use of | rather than || - we always want to call fclose() */
70 if (ferror(f
) | (fclose(f
) == EOF
)) {
72 for (p
= flhead
; p
!= NULL
; p
= p
->next
)
73 if (p
->fh
== f
) break;
76 const char *fnm
= p
->fnm
;
80 fatalerror(/*Error writing to file “%s”*/110, fnm
);
82 /* f wasn't opened with safe_fopen(), so we don't know the filename. */
83 fatalerror(/*Error writing to file*/111);
88 safe_fopen_with_ext(const char *fnm
, const char *ext
, const char *mode
)
92 p
= add_ext(fnm
, ext
);
93 f
= safe_fopen(p
, mode
);
99 fopen_not_dir(const char *fnm
, const char *mode
)
101 if (fDirectory(fnm
)) return NULL
;
102 return fopen(fnm
, mode
);
106 path_from_fnm(const char *fnm
)
112 lf
= strrchr(fnm
, FNM_SEP_LEV
);
115 const char *lf2
= strrchr(lf
? lf
+ 1 : fnm
, FNM_SEP_LEV2
);
120 if (!lf
) lf
= strrchr(fnm
, FNM_SEP_DRV
);
122 if (lf
) lenpth
= lf
- fnm
+ 1;
124 pth
= osmalloc(lenpth
+ 1);
125 memcpy(pth
, fnm
, lenpth
);
132 base_from_fnm(const char *fnm
)
136 p
= strrchr(fnm
, FNM_SEP_EXT
);
137 /* Trim off any leaf extension, but dirs can have extensions too */
138 if (p
&& !strchr(p
, FNM_SEP_LEV
)
140 && !strchr(p
, FNM_SEP_LEV2
)
143 size_t len
= (const char *)p
- fnm
;
145 p
= osmalloc(len
+ 1);
151 return osstrdup(fnm
);
155 baseleaf_from_fnm(const char *fnm
)
162 q
= strrchr(p
, FNM_SEP_LEV
);
165 q
= strrchr(p
, FNM_SEP_LEV2
);
169 q
= strrchr(p
, FNM_SEP_EXT
);
170 if (q
) len
= (const char *)q
- p
; else len
= strlen(p
);
172 q
= osmalloc(len
+ 1);
179 leaf_from_fnm(const char *fnm
)
182 lf
= strrchr(fnm
, FNM_SEP_LEV
);
183 if (lf
) fnm
= lf
+ 1;
185 lf
= strrchr(fnm
, FNM_SEP_LEV2
);
186 if (lf
) fnm
= lf
+ 1;
189 lf
= strrchr(fnm
, FNM_SEP_DRV
);
190 if (lf
) fnm
= lf
+ 1;
192 return osstrdup(fnm
);
195 /* Make fnm from pth and lf, inserting an FNM_SEP_LEV if appropriate */
197 use_path(const char *pth
, const char *lf
)
201 bool fAddSep
= false;
204 len_total
= len
+ strlen(lf
) + 1;
206 /* if there's a path and it doesn't end in a separator, insert one */
207 if (len
&& pth
[len
- 1] != FNM_SEP_LEV
) {
209 if (pth
[len
- 1] != FNM_SEP_LEV2
) {
212 if (pth
[len
- 1] != FNM_SEP_DRV
) {
224 fnm
= osmalloc(len_total
);
226 if (fAddSep
) fnm
[len
++] = FNM_SEP_LEV
;
227 strcpy(fnm
+ len
, lf
);
231 /* Add ext to fnm, inserting an FNM_SEP_EXT if appropriate */
233 add_ext(const char *fnm
, const char *ext
)
237 bool fAddSep
= false;
240 len_total
= len
+ strlen(ext
) + 1;
241 if (ext
[0] != FNM_SEP_EXT
) {
246 fnmNew
= osmalloc(len_total
);
248 if (fAddSep
) fnmNew
[len
++] = FNM_SEP_EXT
;
249 strcpy(fnmNew
+ len
, ext
);
253 /* fopen file, found using pth and fnm
254 * fnmUsed is used to return filename used to open file (ignored if NULL)
255 * or NULL if file didn't open
258 fopenWithPthAndExt(const char *pth
, const char *fnm
, const char *ext
,
259 const char *mode
, char **fnmUsed
)
261 char *fnmFull
= NULL
;
265 /* if no pth treat fnm as absolute */
266 fAbs
= (pth
== NULL
|| *pth
== '\0' || fAbsoluteFnm(fnm
));
268 /* if appropriate, try it without pth */
270 fh
= fopen_not_dir(fnm
, mode
);
272 if (fnmUsed
) fnmFull
= osstrdup(fnm
);
275 /* we've been given an extension so try using it */
276 fnmFull
= add_ext(fnm
, ext
);
277 fh
= fopen_not_dir(fnmFull
, mode
);
281 /* try using path given - first of all without the extension */
282 fnmFull
= use_path(pth
, fnm
);
283 fh
= fopen_not_dir(fnmFull
, mode
);
286 /* we've been given an extension so try using it */
289 fnmFull
= add_ext(fnmFull
, ext
);
291 fh
= fopen_not_dir(fnmFull
, mode
);
296 /* either it opened or didn't. If not, fh == NULL from fopen_not_dir() */
298 /* free name if it didn't open or name isn't wanted */
299 if (fh
== NULL
|| fnmUsed
== NULL
) osfree(fnmFull
);
300 if (fnmUsed
) *fnmUsed
= (fh
? fnmFull
: NULL
);
304 /* Like fopenWithPthAndExt except that "foreign" paths are translated to
305 * native ones (e.g. on Unix dir\file.ext -> dir/file.ext) */
307 fopen_portable(const char *pth
, const char *fnm
, const char *ext
,
308 const char *mode
, char **fnmUsed
)
310 FILE *fh
= fopenWithPthAndExt(pth
, fnm
, ext
, mode
, fnmUsed
);
313 bool changed
= false;
314 char *fnm_trans
= osstrdup(fnm
);
315 for (char *p
= fnm_trans
; *p
; p
++) {
317 case '\\': /* swap a backslash to a forward slash */
324 fh
= fopenWithPthAndExt(pth
, fnm_trans
, ext
, mode
, fnmUsed
);
326 /* To help users process data that originated on a case-insensitive
327 * filing system, try lowercasing the filename if not found.
330 bool had_lower
= false;
332 for (char *p
= fnm_trans
; *p
; p
++) {
333 unsigned char ch
= *p
;
337 } else if (islower(ch
)) {
342 fh
= fopenWithPthAndExt(pth
, fnm_trans
, ext
, mode
, fnmUsed
);
344 /* If that fails, try upper casing the initial character of the leaf. */
346 char *leaf
= strrchr(fnm_trans
, '/');
347 leaf
= (leaf
? leaf
+ 1 : fnm_trans
);
348 if (islower((unsigned char)*leaf
)) {
349 *leaf
= toupper((unsigned char)*leaf
);
350 fh
= fopenWithPthAndExt(pth
, fnm_trans
, ext
, mode
, fnmUsed
);
352 if (fh
== NULL
&& had_lower
) {
353 /* Finally, try upper casing the filename if it wasn't all
354 * upper case to start with. */
355 for (char *p
= fnm_trans
; *p
; p
++) {
356 *p
= toupper((unsigned char)*p
);
358 fh
= fopenWithPthAndExt(pth
, fnm_trans
, ext
, mode
, fnmUsed
);
369 filename_register_output(const char *fnm
)
371 filelist
*p
= osnew(filelist
);
373 p
->fnm
= osstrdup(fnm
);
380 filename_register_output_with_fh(const char *fnm
, FILE *fh
)
382 filelist
*p
= osnew(filelist
);
384 p
->fnm
= osstrdup(fnm
);
391 filename_delete_output(void)
394 filelist
*p
= flhead
;
395 flhead
= flhead
->next
;
397 (void)remove(p
->fnm
);
406 /* NB "c:fred" isn't relative. Eg "c:\data\c:fred" won't work */
408 fAbsoluteFnm(const char *fnm
)
410 /* <drive letter>: or \<path> or /<path>
411 * or \\<host>\... or //<host>/... */
412 unsigned char ch
= (unsigned char)*fnm
;
413 return ch
== '/' || ch
== '\\' ||
414 (ch
&& fnm
[1] == ':' && (ch
| 32) >= 'a' && (ch
| 32) <= 'z');
420 fAbsoluteFnm(const char *fnm
)
422 return (fnm
[0] == '/');
427 /* fDirectory( fnm ) returns true if fnm is a directory; false if fnm is a
428 * file, doesn't exist, or another error occurs (eg disc not in drive, ...)
429 * NB If fnm has a trailing directory separator (e.g. “/” or “/home/olly/”
430 * then it's assumed to be a directory even if it doesn't exist (as is an
434 #if OS_UNIX || OS_WIN32
436 # include <sys/types.h>
437 # include <sys/stat.h>
441 fDirectory(const char *fnm
)
444 if (!fnm
[0] || fnm
[strlen(fnm
) - 1] == FNM_SEP_LEV
446 || fnm
[strlen(fnm
) - 1] == FNM_SEP_LEV2
449 if (stat(fnm
, &buf
) != 0) return 0;
452 return S_ISDIR(buf
.st_mode
);
455 return ((buf
.st_mode
& S_IFMT
) == S_IFDIR
);