Fix message references for inches and feet
[survex.git] / src / filename.c
blob8f3c0a5302bc7d293288cbe0b8d8a3c5afef5326
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
19 #include <config.h>
21 #include "filename.h"
22 #include "debug.h"
23 #include "osalloc.h"
24 #include "whichos.h"
26 #include <ctype.h>
27 #include <string.h>
29 typedef struct filelist {
30 char *fnm;
31 FILE *fh;
32 struct filelist *next;
33 } filelist;
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
43 * empty string).
46 #if OS_UNIX || OS_WIN32
48 # include <sys/types.h>
49 # include <sys/stat.h>
50 # include <stdio.h>
52 bool
53 fDirectory(const char *fnm)
55 struct stat buf;
56 if (!fnm[0] || fnm[strlen(fnm) - 1] == FNM_SEP_LEV
57 #ifdef FNM_SEP_LEV2
58 || fnm[strlen(fnm) - 1] == FNM_SEP_LEV2
59 #endif
60 ) return 1;
61 if (stat(fnm, &buf) != 0) return 0;
62 #ifdef S_ISDIR
63 /* POSIX way */
64 return S_ISDIR(buf.st_mode);
65 #else
66 /* BSD way */
67 return ((buf.st_mode & S_IFMT) == S_IFDIR);
68 #endif
71 #else
72 # error Unknown OS
73 #endif
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. */
82 extern FILE *
83 safe_fopen(const char *fnm, const char *mode)
85 FILE *f;
86 SVX_ASSERT(mode[0] == 'w'); /* only expect to be used for writing */
87 if (fDirectory(fnm))
88 fatalerror(/*Filename “%s” refers to directory*/44, fnm);
90 f = fopen(fnm, mode);
91 if (!f) fatalerror(/*Failed to open output file “%s”*/47, fnm);
93 filename_register_output_with_fh(fnm, f);
94 return f;
97 /* Wrapper for fclose which throws a fatal error if there's been a write
98 * error.
100 extern void
101 safe_fclose(FILE *f)
103 SVX_ASSERT(f);
104 /* NB: use of | rather than || - we always want to call fclose() */
105 if (ferror(f) | (fclose(f) == EOF)) {
106 filelist *p;
107 for (p = flhead; p != NULL; p = p->next)
108 if (p->fh == f) break;
110 if (p && p->fnm) {
111 const char *fnm = p->fnm;
112 p->fnm = NULL;
113 p->fh = NULL;
114 (void)remove(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);
122 extern FILE *
123 safe_fopen_with_ext(const char *fnm, const char *ext, const char *mode)
125 FILE *f;
126 char *p;
127 p = add_ext(fnm, ext);
128 f = safe_fopen(p, mode);
129 osfree(p);
130 return f;
133 static FILE *
134 fopen_not_dir(const char *fnm, const char *mode)
136 if (fDirectory(fnm)) return NULL;
137 return fopen(fnm, mode);
140 extern char *
141 path_from_fnm(const char *fnm)
143 char *pth;
144 const char *lf;
145 int lenpth = 0;
147 lf = strrchr(fnm, FNM_SEP_LEV);
148 #ifdef FNM_SEP_LEV2
150 const char *lf2 = strrchr(lf ? lf + 1 : fnm, FNM_SEP_LEV2);
151 if (lf2) lf = lf2;
153 #endif
154 #ifdef FNM_SEP_DRV
155 if (!lf) lf = strrchr(fnm, FNM_SEP_DRV);
156 #endif
157 if (lf) lenpth = lf - fnm + 1;
159 pth = osmalloc(lenpth + 1);
160 memcpy(pth, fnm, lenpth);
161 pth[lenpth] = '\0';
163 return pth;
166 extern char *
167 base_from_fnm(const char *fnm)
169 char *p;
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)
174 #ifdef FNM_SEP_LEV2
175 && !strchr(p, FNM_SEP_LEV2)
176 #endif
178 size_t len = (const char *)p - fnm;
180 p = osmalloc(len + 1);
181 memcpy(p, fnm, len);
182 p[len] = '\0';
183 return p;
186 return osstrdup(fnm);
189 extern char *
190 baseleaf_from_fnm(const char *fnm)
192 const char *p;
193 char *q;
194 size_t len;
196 p = fnm;
197 q = strrchr(p, FNM_SEP_LEV);
198 if (q) p = q + 1;
199 #ifdef FNM_SEP_LEV2
200 q = strrchr(p, FNM_SEP_LEV2);
201 if (q) p = q + 1;
202 #endif
204 q = strrchr(p, FNM_SEP_EXT);
205 if (q) len = (const char *)q - p; else len = strlen(p);
207 q = osmalloc(len + 1);
208 memcpy(q, p, len);
209 q[len] = '\0';
210 return q;
213 extern char *
214 leaf_from_fnm(const char *fnm)
216 const char *lf;
217 lf = strrchr(fnm, FNM_SEP_LEV);
218 if (lf) fnm = lf + 1;
219 #ifdef FNM_SEP_LEV2
220 lf = strrchr(fnm, FNM_SEP_LEV2);
221 if (lf) fnm = lf + 1;
222 #endif
223 #ifdef FNM_SEP_DRV
224 lf = strrchr(fnm, FNM_SEP_DRV);
225 if (lf) fnm = lf + 1;
226 #endif
227 return osstrdup(fnm);
230 /* Make fnm from pth and lf, inserting an FNM_SEP_LEV if appropriate */
231 extern char *
232 use_path(const char *pth, const char *lf)
234 char *fnm;
235 int len, len_total;
236 bool fAddSep = false;
238 len = strlen(pth);
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) {
243 #ifdef FNM_SEP_LEV2
244 if (pth[len - 1] != FNM_SEP_LEV2) {
245 #endif
246 #ifdef FNM_SEP_DRV
247 if (pth[len - 1] != FNM_SEP_DRV) {
248 #endif
249 fAddSep = true;
250 len_total++;
251 #ifdef FNM_SEP_DRV
253 #endif
254 #ifdef FNM_SEP_LEV2
256 #endif
259 fnm = osmalloc(len_total);
260 strcpy(fnm, pth);
261 if (fAddSep) fnm[len++] = FNM_SEP_LEV;
262 strcpy(fnm + len, lf);
263 return fnm;
266 /* Add ext to fnm, inserting an FNM_SEP_EXT if appropriate */
267 extern char *
268 add_ext(const char *fnm, const char *ext)
270 char * fnmNew;
271 int len, len_total;
272 bool fAddSep = false;
274 len = strlen(fnm);
275 len_total = len + strlen(ext) + 1;
276 if (ext[0] != FNM_SEP_EXT) {
277 fAddSep = true;
278 len_total++;
281 fnmNew = osmalloc(len_total);
282 strcpy(fnmNew, fnm);
283 if (fAddSep) fnmNew[len++] = FNM_SEP_EXT;
284 strcpy(fnmNew + len, ext);
285 return fnmNew;
288 #if OS_WIN32
290 /* NB "c:fred" isn't relative. Eg "c:\data\c:fred" won't work */
291 static bool
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');
301 #elif OS_UNIX
303 static bool
304 fAbsoluteFnm(const char *fnm)
306 return (fnm[0] == '/');
309 #endif
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
315 extern FILE *
316 fopenWithPthAndExt(const char *pth, const char *fnm, const char *ext,
317 const char *mode, char **fnmUsed)
319 char *fnmFull = NULL;
320 FILE *fh = NULL;
322 /* Don't try to use pth if it is unset or empty, or if the filename is
323 * already absolute.
325 if (pth == NULL || *pth == '\0' || fAbsoluteFnm(fnm)) {
326 fh = fopen_not_dir(fnm, mode);
327 if (fh) {
328 if (fnmUsed) fnmFull = osstrdup(fnm);
329 } else {
330 if (ext && *ext) {
331 /* we've been given an extension so try using it */
332 fnmFull = add_ext(fnm, ext);
333 fh = fopen_not_dir(fnmFull, mode);
336 } else {
337 /* try using path given - first of all without the extension */
338 fnmFull = use_path(pth, fnm);
339 fh = fopen_not_dir(fnmFull, mode);
340 if (!fh) {
341 if (ext && *ext) {
342 /* we've been given an extension so try using it */
343 char *fnmTmp;
344 fnmTmp = fnmFull;
345 fnmFull = add_ext(fnmFull, ext);
346 osfree(fnmTmp);
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);
357 return fh;
360 /* Like fopenWithPthAndExt except that "foreign" paths are translated to
361 * native ones (e.g. on Unix dir\file.ext -> dir/file.ext) */
362 FILE *
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);
367 if (fh == NULL) {
368 #if OS_UNIX
369 bool changed = false;
370 char *fnm_trans = osstrdup(fnm);
371 for (char *p = fnm_trans; *p; p++) {
372 switch (*p) {
373 case '\\': /* swap a backslash to a forward slash */
374 *p = '/';
375 changed = true;
376 break;
379 if (changed)
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.
385 if (fh == NULL) {
386 bool had_lower = false;
387 changed = false;
388 for (char *p = fnm_trans; *p ; p++) {
389 unsigned char ch = *p;
390 if (isupper(ch)) {
391 *p = tolower(ch);
392 changed = true;
393 } else if (islower(ch)) {
394 had_lower = true;
397 if (changed)
398 fh = fopenWithPthAndExt(pth, fnm_trans, ext, mode, fnmUsed);
400 /* If that fails, try upper casing the initial character of the leaf. */
401 if (fh == NULL) {
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);
418 osfree(fnm_trans);
419 #endif
421 return fh;
424 void
425 filename_register_output(const char *fnm)
427 filelist *p = osnew(filelist);
428 SVX_ASSERT(fnm);
429 p->fnm = osstrdup(fnm);
430 p->fh = NULL;
431 p->next = flhead;
432 flhead = p;
435 static void
436 filename_register_output_with_fh(const char *fnm, FILE *fh)
438 filelist *p = osnew(filelist);
439 SVX_ASSERT(fnm);
440 p->fnm = osstrdup(fnm);
441 p->fh = fh;
442 p->next = flhead;
443 flhead = p;
446 void
447 filename_delete_output(void)
449 while (flhead) {
450 filelist *p = flhead;
451 flhead = flhead->next;
452 if (p->fnm) {
453 (void)remove(p->fnm);
454 osfree(p->fnm);
456 osfree(p);