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
29 typedef struct filelist
{
32 struct filelist
*next
;
35 static filelist
*flhead
= NULL
;
37 static void filename_register_output_with_fh(const char *fnm
, FILE *fh
);
39 /* fDirectory( fnm ) returns true if fnm is a directory; false if fnm is a
40 * file, doesn't exist, or another error occurs (eg disc not in drive, ...)
41 * NB If fnm has a trailing directory separator (e.g. “/” or “/home/olly/”
42 * then it's assumed to be a directory even if it doesn't exist (as is an
46 #if OS_UNIX || OS_WIN32
48 # include <sys/types.h>
49 # include <sys/stat.h>
53 fDirectory(const char *fnm
)
56 if (!fnm
[0] || fnm
[strlen(fnm
) - 1] == FNM_SEP_LEV
58 || fnm
[strlen(fnm
) - 1] == FNM_SEP_LEV2
61 if (stat(fnm
, &buf
) != 0) return 0;
64 return S_ISDIR(buf
.st_mode
);
67 return ((buf
.st_mode
& S_IFMT
) == S_IFDIR
);
75 /* safe_fopen should be used when writing a file
76 * fopenWithPthAndExt should be used when reading a file
79 /* Wrapper for fopen which throws a fatal error if it fails.
80 * Some versions of fopen() are quite happy to open a directory.
81 * We aren't, so catch this case. */
83 safe_fopen(const char *fnm
, const char *mode
)
86 SVX_ASSERT(mode
[0] == 'w'); /* only expect to be used for writing */
88 fatalerror(/*Filename “%s” refers to directory*/44, fnm
);
91 if (!f
) fatalerror(/*Failed to open output file “%s”*/47, fnm
);
93 filename_register_output_with_fh(fnm
, f
);
97 /* Wrapper for fclose which throws a fatal error if there's been a write
104 /* NB: use of | rather than || - we always want to call fclose() */
105 if (ferror(f
) | (fclose(f
) == EOF
)) {
107 for (p
= flhead
; p
!= NULL
; p
= p
->next
)
108 if (p
->fh
== f
) break;
111 const char *fnm
= p
->fnm
;
115 fatalerror(/*Error writing to file “%s”*/110, fnm
);
117 /* f wasn't opened with safe_fopen(), so we don't know the filename. */
118 fatalerror(/*Error writing to file*/111);
123 safe_fopen_with_ext(const char *fnm
, const char *ext
, const char *mode
)
127 p
= add_ext(fnm
, ext
);
128 f
= safe_fopen(p
, mode
);
134 fopen_not_dir(const char *fnm
, const char *mode
)
136 if (fDirectory(fnm
)) return NULL
;
137 return fopen(fnm
, mode
);
141 path_from_fnm(const char *fnm
)
147 lf
= strrchr(fnm
, FNM_SEP_LEV
);
150 const char *lf2
= strrchr(lf
? lf
+ 1 : fnm
, FNM_SEP_LEV2
);
155 if (!lf
) lf
= strrchr(fnm
, FNM_SEP_DRV
);
157 if (lf
) lenpth
= lf
- fnm
+ 1;
159 pth
= osmalloc(lenpth
+ 1);
160 memcpy(pth
, fnm
, lenpth
);
167 base_from_fnm(const char *fnm
)
171 p
= strrchr(fnm
, FNM_SEP_EXT
);
172 /* Trim off any leaf extension, but dirs can have extensions too */
173 if (p
&& !strchr(p
, FNM_SEP_LEV
)
175 && !strchr(p
, FNM_SEP_LEV2
)
178 size_t len
= (const char *)p
- fnm
;
180 p
= osmalloc(len
+ 1);
186 return osstrdup(fnm
);
190 baseleaf_from_fnm(const char *fnm
)
197 q
= strrchr(p
, FNM_SEP_LEV
);
200 q
= strrchr(p
, FNM_SEP_LEV2
);
204 q
= strrchr(p
, FNM_SEP_EXT
);
205 if (q
) len
= (const char *)q
- p
; else len
= strlen(p
);
207 q
= osmalloc(len
+ 1);
214 leaf_from_fnm(const char *fnm
)
217 lf
= strrchr(fnm
, FNM_SEP_LEV
);
218 if (lf
) fnm
= lf
+ 1;
220 lf
= strrchr(fnm
, FNM_SEP_LEV2
);
221 if (lf
) fnm
= lf
+ 1;
224 lf
= strrchr(fnm
, FNM_SEP_DRV
);
225 if (lf
) fnm
= lf
+ 1;
227 return osstrdup(fnm
);
230 /* Make fnm from pth and lf, inserting an FNM_SEP_LEV if appropriate */
232 use_path(const char *pth
, const char *lf
)
236 bool fAddSep
= false;
239 len_total
= len
+ strlen(lf
) + 1;
241 /* if there's a path and it doesn't end in a separator, insert one */
242 if (len
&& pth
[len
- 1] != FNM_SEP_LEV
) {
244 if (pth
[len
- 1] != FNM_SEP_LEV2
) {
247 if (pth
[len
- 1] != FNM_SEP_DRV
) {
259 fnm
= osmalloc(len_total
);
261 if (fAddSep
) fnm
[len
++] = FNM_SEP_LEV
;
262 strcpy(fnm
+ len
, lf
);
266 /* Add ext to fnm, inserting an FNM_SEP_EXT if appropriate */
268 add_ext(const char *fnm
, const char *ext
)
272 bool fAddSep
= false;
275 len_total
= len
+ strlen(ext
) + 1;
276 if (ext
[0] != FNM_SEP_EXT
) {
281 fnmNew
= osmalloc(len_total
);
283 if (fAddSep
) fnmNew
[len
++] = FNM_SEP_EXT
;
284 strcpy(fnmNew
+ len
, ext
);
290 /* NB "c:fred" isn't relative. Eg "c:\data\c:fred" won't work */
292 fAbsoluteFnm(const char *fnm
)
294 /* <drive letter>: or \<path> or /<path>
295 * or \\<host>\... or //<host>/... */
296 unsigned char ch
= (unsigned char)*fnm
;
297 return ch
== '/' || ch
== '\\' ||
298 (ch
&& fnm
[1] == ':' && (ch
| 32) >= 'a' && (ch
| 32) <= 'z');
304 fAbsoluteFnm(const char *fnm
)
306 return (fnm
[0] == '/');
311 /* fopen file, found using pth and fnm
312 * fnmUsed is used to return filename used to open file (ignored if NULL)
313 * or NULL if file didn't open
316 fopenWithPthAndExt(const char *pth
, const char *fnm
, const char *ext
,
317 const char *mode
, char **fnmUsed
)
319 char *fnmFull
= NULL
;
322 /* Don't try to use pth if it is unset or empty, or if the filename is
325 if (pth
== NULL
|| *pth
== '\0' || fAbsoluteFnm(fnm
)) {
326 fh
= fopen_not_dir(fnm
, mode
);
328 if (fnmUsed
) fnmFull
= osstrdup(fnm
);
331 /* we've been given an extension so try using it */
332 fnmFull
= add_ext(fnm
, ext
);
333 fh
= fopen_not_dir(fnmFull
, mode
);
337 /* try using path given - first of all without the extension */
338 fnmFull
= use_path(pth
, fnm
);
339 fh
= fopen_not_dir(fnmFull
, mode
);
342 /* we've been given an extension so try using it */
345 fnmFull
= add_ext(fnmFull
, ext
);
347 fh
= fopen_not_dir(fnmFull
, mode
);
352 /* either it opened or didn't. If not, fh == NULL from fopen_not_dir() */
354 /* free name if it didn't open or name isn't wanted */
355 if (fh
== NULL
|| fnmUsed
== NULL
) osfree(fnmFull
);
356 if (fnmUsed
) *fnmUsed
= (fh
? fnmFull
: NULL
);
360 /* Like fopenWithPthAndExt except that "foreign" paths are translated to
361 * native ones (e.g. on Unix dir\file.ext -> dir/file.ext) */
363 fopen_portable(const char *pth
, const char *fnm
, const char *ext
,
364 const char *mode
, char **fnmUsed
)
366 FILE *fh
= fopenWithPthAndExt(pth
, fnm
, ext
, mode
, fnmUsed
);
369 bool changed
= false;
370 char *fnm_trans
= osstrdup(fnm
);
371 for (char *p
= fnm_trans
; *p
; p
++) {
373 case '\\': /* swap a backslash to a forward slash */
380 fh
= fopenWithPthAndExt(pth
, fnm_trans
, ext
, mode
, fnmUsed
);
382 /* To help users process data that originated on a case-insensitive
383 * filing system, try lowercasing the filename if not found.
386 bool had_lower
= false;
388 for (char *p
= fnm_trans
; *p
; p
++) {
389 unsigned char ch
= *p
;
393 } else if (islower(ch
)) {
398 fh
= fopenWithPthAndExt(pth
, fnm_trans
, ext
, mode
, fnmUsed
);
400 /* If that fails, try upper casing the initial character of the leaf. */
402 char *leaf
= strrchr(fnm_trans
, '/');
403 leaf
= (leaf
? leaf
+ 1 : fnm_trans
);
404 if (islower((unsigned char)*leaf
)) {
405 *leaf
= toupper((unsigned char)*leaf
);
406 fh
= fopenWithPthAndExt(pth
, fnm_trans
, ext
, mode
, fnmUsed
);
408 if (fh
== NULL
&& had_lower
) {
409 /* Finally, try upper casing the filename if it wasn't all
410 * upper case to start with. */
411 for (char *p
= fnm_trans
; *p
; p
++) {
412 *p
= toupper((unsigned char)*p
);
414 fh
= fopenWithPthAndExt(pth
, fnm_trans
, ext
, mode
, fnmUsed
);
425 filename_register_output(const char *fnm
)
427 filelist
*p
= osnew(filelist
);
429 p
->fnm
= osstrdup(fnm
);
436 filename_register_output_with_fh(const char *fnm
, FILE *fh
)
438 filelist
*p
= osnew(filelist
);
440 p
->fnm
= osstrdup(fnm
);
447 filename_delete_output(void)
450 filelist
*p
= flhead
;
451 flhead
= flhead
->next
;
453 (void)remove(p
->fnm
);