Show location for backsights out of tolerance
[survex.git] / src / filename.c
blobd854b7045f4eaba62e9401d146109efb42c51c8d
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 #ifdef HAVE_CONFIG_H
20 # include <config.h>
21 #endif
23 #include "filename.h"
24 #include "debug.h"
25 #include "whichos.h"
27 #include <ctype.h>
28 #include <string.h>
30 typedef struct filelist {
31 char *fnm;
32 FILE *fh;
33 struct filelist *next;
34 } filelist;
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. */
47 extern FILE *
48 safe_fopen(const char *fnm, const char *mode)
50 FILE *f;
51 SVX_ASSERT(mode[0] == 'w'); /* only expect to be used for writing */
52 if (fDirectory(fnm))
53 fatalerror(/*Filename “%s” refers to directory*/44, fnm);
55 f = fopen(fnm, mode);
56 if (!f) fatalerror(/*Failed to open output file “%s”*/47, fnm);
58 filename_register_output_with_fh(fnm, f);
59 return f;
62 /* Wrapper for fclose which throws a fatal error if there's been a write
63 * error.
65 extern void
66 safe_fclose(FILE *f)
68 SVX_ASSERT(f);
69 /* NB: use of | rather than || - we always want to call fclose() */
70 if (ferror(f) | (fclose(f) == EOF)) {
71 filelist *p;
72 for (p = flhead; p != NULL; p = p->next)
73 if (p->fh == f) break;
75 if (p && p->fnm) {
76 const char *fnm = p->fnm;
77 p->fnm = NULL;
78 p->fh = NULL;
79 (void)remove(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);
87 extern FILE *
88 safe_fopen_with_ext(const char *fnm, const char *ext, const char *mode)
90 FILE *f;
91 char *p;
92 p = add_ext(fnm, ext);
93 f = safe_fopen(p, mode);
94 osfree(p);
95 return f;
98 static FILE *
99 fopen_not_dir(const char *fnm, const char *mode)
101 if (fDirectory(fnm)) return NULL;
102 return fopen(fnm, mode);
105 extern char *
106 path_from_fnm(const char *fnm)
108 char *pth;
109 const char *lf;
110 int lenpth = 0;
112 lf = strrchr(fnm, FNM_SEP_LEV);
113 #ifdef FNM_SEP_LEV2
115 const char *lf2 = strrchr(lf ? lf + 1 : fnm, FNM_SEP_LEV2);
116 if (lf2) lf = lf2;
118 #endif
119 #ifdef FNM_SEP_DRV
120 if (!lf) lf = strrchr(fnm, FNM_SEP_DRV);
121 #endif
122 if (lf) lenpth = lf - fnm + 1;
124 pth = osmalloc(lenpth + 1);
125 memcpy(pth, fnm, lenpth);
126 pth[lenpth] = '\0';
128 return pth;
131 extern char *
132 base_from_fnm(const char *fnm)
134 char *p;
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)
139 #ifdef FNM_SEP_LEV2
140 && !strchr(p, FNM_SEP_LEV2)
141 #endif
143 size_t len = (const char *)p - fnm;
145 p = osmalloc(len + 1);
146 memcpy(p, fnm, len);
147 p[len] = '\0';
148 return p;
151 return osstrdup(fnm);
154 extern char *
155 baseleaf_from_fnm(const char *fnm)
157 const char *p;
158 char *q;
159 size_t len;
161 p = fnm;
162 q = strrchr(p, FNM_SEP_LEV);
163 if (q) p = q + 1;
164 #ifdef FNM_SEP_LEV2
165 q = strrchr(p, FNM_SEP_LEV2);
166 if (q) p = q + 1;
167 #endif
169 q = strrchr(p, FNM_SEP_EXT);
170 if (q) len = (const char *)q - p; else len = strlen(p);
172 q = osmalloc(len + 1);
173 memcpy(q, p, len);
174 q[len] = '\0';
175 return q;
178 extern char *
179 leaf_from_fnm(const char *fnm)
181 const char *lf;
182 lf = strrchr(fnm, FNM_SEP_LEV);
183 if (lf) fnm = lf + 1;
184 #ifdef FNM_SEP_LEV2
185 lf = strrchr(fnm, FNM_SEP_LEV2);
186 if (lf) fnm = lf + 1;
187 #endif
188 #ifdef FNM_SEP_DRV
189 lf = strrchr(fnm, FNM_SEP_DRV);
190 if (lf) fnm = lf + 1;
191 #endif
192 return osstrdup(fnm);
195 /* Make fnm from pth and lf, inserting an FNM_SEP_LEV if appropriate */
196 extern char *
197 use_path(const char *pth, const char *lf)
199 char *fnm;
200 int len, len_total;
201 bool fAddSep = false;
203 len = strlen(pth);
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) {
208 #ifdef FNM_SEP_LEV2
209 if (pth[len - 1] != FNM_SEP_LEV2) {
210 #endif
211 #ifdef FNM_SEP_DRV
212 if (pth[len - 1] != FNM_SEP_DRV) {
213 #endif
214 fAddSep = true;
215 len_total++;
216 #ifdef FNM_SEP_DRV
218 #endif
219 #ifdef FNM_SEP_LEV2
221 #endif
224 fnm = osmalloc(len_total);
225 strcpy(fnm, pth);
226 if (fAddSep) fnm[len++] = FNM_SEP_LEV;
227 strcpy(fnm + len, lf);
228 return fnm;
231 /* Add ext to fnm, inserting an FNM_SEP_EXT if appropriate */
232 extern char *
233 add_ext(const char *fnm, const char *ext)
235 char * fnmNew;
236 int len, len_total;
237 bool fAddSep = false;
239 len = strlen(fnm);
240 len_total = len + strlen(ext) + 1;
241 if (ext[0] != FNM_SEP_EXT) {
242 fAddSep = true;
243 len_total++;
246 fnmNew = osmalloc(len_total);
247 strcpy(fnmNew, fnm);
248 if (fAddSep) fnmNew[len++] = FNM_SEP_EXT;
249 strcpy(fnmNew + len, ext);
250 return fnmNew;
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
257 extern FILE *
258 fopenWithPthAndExt(const char *pth, const char *fnm, const char *ext,
259 const char *mode, char **fnmUsed)
261 char *fnmFull = NULL;
262 FILE *fh = NULL;
263 bool fAbs;
265 /* if no pth treat fnm as absolute */
266 fAbs = (pth == NULL || *pth == '\0' || fAbsoluteFnm(fnm));
268 /* if appropriate, try it without pth */
269 if (fAbs) {
270 fh = fopen_not_dir(fnm, mode);
271 if (fh) {
272 if (fnmUsed) fnmFull = osstrdup(fnm);
273 } else {
274 if (ext && *ext) {
275 /* we've been given an extension so try using it */
276 fnmFull = add_ext(fnm, ext);
277 fh = fopen_not_dir(fnmFull, mode);
280 } else {
281 /* try using path given - first of all without the extension */
282 fnmFull = use_path(pth, fnm);
283 fh = fopen_not_dir(fnmFull, mode);
284 if (!fh) {
285 if (ext && *ext) {
286 /* we've been given an extension so try using it */
287 char *fnmTmp;
288 fnmTmp = fnmFull;
289 fnmFull = add_ext(fnmFull, ext);
290 osfree(fnmTmp);
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);
301 return fh;
304 /* Like fopenWithPthAndExt except that "foreign" paths are translated to
305 * native ones (e.g. on Unix dir\file.ext -> dir/file.ext) */
306 FILE *
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);
311 if (fh == NULL) {
312 #if OS_UNIX
313 bool changed = false;
314 char *fnm_trans = osstrdup(fnm);
315 for (char *p = fnm_trans; *p; p++) {
316 switch (*p) {
317 case '\\': /* swap a backslash to a forward slash */
318 *p = '/';
319 changed = true;
320 break;
323 if (changed)
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.
329 if (fh == NULL) {
330 bool had_lower = false;
331 changed = false;
332 for (char *p = fnm_trans; *p ; p++) {
333 unsigned char ch = *p;
334 if (isupper(ch)) {
335 *p = tolower(ch);
336 changed = true;
337 } else if (islower(ch)) {
338 had_lower = true;
341 if (changed)
342 fh = fopenWithPthAndExt(pth, fnm_trans, ext, mode, fnmUsed);
344 /* If that fails, try upper casing the initial character of the leaf. */
345 if (fh == NULL) {
346 char *leaf = strrchr(fnm_trans, '/');
347 leaf = (leaf ? leaf + 1 : fnm_trans);
348 if (islower(*leaf)) {
349 *leaf = toupper(*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);
362 osfree(fnm_trans);
363 #endif
365 return fh;
368 void
369 filename_register_output(const char *fnm)
371 filelist *p = osnew(filelist);
372 SVX_ASSERT(fnm);
373 p->fnm = osstrdup(fnm);
374 p->fh = NULL;
375 p->next = flhead;
376 flhead = p;
379 static void
380 filename_register_output_with_fh(const char *fnm, FILE *fh)
382 filelist *p = osnew(filelist);
383 SVX_ASSERT(fnm);
384 p->fnm = osstrdup(fnm);
385 p->fh = fh;
386 p->next = flhead;
387 flhead = p;
390 void
391 filename_delete_output(void)
393 while (flhead) {
394 filelist *p = flhead;
395 flhead = flhead->next;
396 if (p->fnm) {
397 (void)remove(p->fnm);
398 osfree(p->fnm);
400 osfree(p);
404 #if OS_WIN32
406 /* NB "c:fred" isn't relative. Eg "c:\data\c:fred" won't work */
407 bool
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');
417 #elif OS_UNIX
419 bool
420 fAbsoluteFnm(const char *fnm)
422 return (fnm[0] == '/');
425 #endif
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
431 * empty string).
434 #if OS_UNIX || OS_WIN32
436 # include <sys/types.h>
437 # include <sys/stat.h>
438 # include <stdio.h>
440 bool
441 fDirectory(const char *fnm)
443 struct stat buf;
444 if (!fnm[0] || fnm[strlen(fnm) - 1] == FNM_SEP_LEV
445 #ifdef FNM_SEP_LEV2
446 || fnm[strlen(fnm) - 1] == FNM_SEP_LEV2
447 #endif
448 ) return 1;
449 if (stat(fnm, &buf) != 0) return 0;
450 #ifdef S_ISDIR
451 /* POSIX way */
452 return S_ISDIR(buf.st_mode);
453 #else
454 /* BSD way */
455 return ((buf.st_mode & S_IFMT) == S_IFDIR);
456 #endif
459 #else
460 # error Unknown OS
461 #endif