source: git/src/filename.c@ b3b0900

faster-cavernlog log-select main stereo-2025 walls-data walls-data-hanging-as-warning warn-only-for-hanging-survey
Last change on this file since b3b0900 was b3b0900, checked in by Olly Betts <olly@…>, 2 years ago

Clean up header includes

  • Property mode set to 100644
File size: 9.4 KB
RevLine 
[846746e]1/* OS dependent filename manipulation routines
[dc533fe]2 * Copyright (c) Olly Betts 1998-2003,2004,2005,2010,2011,2014
[846746e]3 *
[89231c4]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.
[846746e]8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
[89231c4]11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
[846746e]13 *
[89231c4]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
[ecbc6c18]16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
[846746e]17 */
[6ba8d69]18
[4c83f84]19#include <config.h>
[a420b49]20
[9af1d7a9]21#include "filename.h"
[13a1bd16]22#include "debug.h"
[b3b0900]23#include "osalloc.h"
[47c7a94]24
25#include <ctype.h>
[9af1d7a9]26#include <string.h>
27
[908298d]28typedef struct filelist {
29 char *fnm;
30 FILE *fh;
31 struct filelist *next;
32} filelist;
33
34static filelist *flhead = NULL;
35
36static void filename_register_output_with_fh(const char *fnm, FILE *fh);
37
[a0b076f]38/* safe_fopen should be used when writing a file
39 * fopenWithPthAndExt should be used when reading a file
40 */
[ce5b43c]41
[a420b49]42/* Wrapper for fopen which throws a fatal error if it fails.
43 * Some versions of fopen() are quite happy to open a directory.
44 * We aren't, so catch this case. */
45extern FILE *
46safe_fopen(const char *fnm, const char *mode)
47{
48 FILE *f;
[4c07c51]49 SVX_ASSERT(mode[0] == 'w'); /* only expect to be used for writing */
[a420b49]50 if (fDirectory(fnm))
[0804fbe]51 fatalerror(/*Filename “%s” refers to directory*/44, fnm);
[a420b49]52
53 f = fopen(fnm, mode);
[0804fbe]54 if (!f) fatalerror(/*Failed to open output file “%s”*/47, fnm);
[a0b076f]55
[908298d]56 filename_register_output_with_fh(fnm, f);
[a420b49]57 return f;
58}
59
[f1067a2]60/* Wrapper for fclose which throws a fatal error if there's been a write
61 * error.
62 */
63extern void
64safe_fclose(FILE *f)
65{
[4c07c51]66 SVX_ASSERT(f);
[f1067a2]67 /* NB: use of | rather than || - we always want to call fclose() */
[908298d]68 if (ferror(f) | (fclose(f) == EOF)) {
69 filelist *p;
70 for (p = flhead; p != NULL; p = p->next)
71 if (p->fh == f) break;
72
73 if (p && p->fnm) {
74 const char *fnm = p->fnm;
75 p->fnm = NULL;
76 p->fh = NULL;
77 (void)remove(fnm);
[0804fbe]78 fatalerror(/*Error writing to file “%s”*/110, fnm);
[908298d]79 }
[dc533fe]80 /* f wasn't opened with safe_fopen(), so we don't know the filename. */
[f1067a2]81 fatalerror(/*Error writing to file*/111);
[908298d]82 }
[f1067a2]83}
84
[ce5b43c]85extern FILE *
86safe_fopen_with_ext(const char *fnm, const char *ext, const char *mode)
87{
88 FILE *f;
89 char *p;
90 p = add_ext(fnm, ext);
91 f = safe_fopen(p, mode);
92 osfree(p);
93 return f;
94}
95
96static FILE *
[a420b49]97fopen_not_dir(const char *fnm, const char *mode)
98{
99 if (fDirectory(fnm)) return NULL;
100 return fopen(fnm, mode);
[9af1d7a9]101}
102
[9965b2b]103extern char *
[ce5b43c]104path_from_fnm(const char *fnm)
[a420b49]105{
[9af1d7a9]106 char *pth;
107 const char *lf;
108 int lenpth = 0;
109
[a420b49]110 lf = strrchr(fnm, FNM_SEP_LEV);
[9af1d7a9]111#ifdef FNM_SEP_LEV2
[029824d]112 {
[eee67ab]113 const char *lf2 = strrchr(lf ? lf + 1 : fnm, FNM_SEP_LEV2);
[029824d]114 if (lf2) lf = lf2;
115 }
[9af1d7a9]116#endif
117#ifdef FNM_SEP_DRV
[a420b49]118 if (!lf) lf = strrchr(fnm, FNM_SEP_DRV);
[9af1d7a9]119#endif
120 if (lf) lenpth = lf - fnm + 1;
121
[908298d]122 pth = osmalloc(lenpth + 1);
[a420b49]123 memcpy(pth, fnm, lenpth);
[9af1d7a9]124 pth[lenpth] = '\0';
125
126 return pth;
127}
128
[ce5b43c]129extern char *
130base_from_fnm(const char *fnm)
131{
132 char *p;
[cb3d1e2]133
[ce5b43c]134 p = strrchr(fnm, FNM_SEP_EXT);
135 /* Trim off any leaf extension, but dirs can have extensions too */
[029824d]136 if (p && !strchr(p, FNM_SEP_LEV)
[ce5b43c]137#ifdef FNM_SEP_LEV2
[029824d]138 && !strchr(p, FNM_SEP_LEV2)
[ce5b43c]139#endif
140 ) {
[e917a13]141 size_t len = (const char *)p - fnm;
[ce5b43c]142
[908298d]143 p = osmalloc(len + 1);
[ce5b43c]144 memcpy(p, fnm, len);
145 p[len] = '\0';
146 return p;
147 }
148
[908298d]149 return osstrdup(fnm);
[ce5b43c]150}
151
152extern char *
153baseleaf_from_fnm(const char *fnm)
154{
155 const char *p;
156 char *q;
157 size_t len;
[cb3d1e2]158
[ce5b43c]159 p = fnm;
160 q = strrchr(p, FNM_SEP_LEV);
161 if (q) p = q + 1;
162#ifdef FNM_SEP_LEV2
163 q = strrchr(p, FNM_SEP_LEV2);
164 if (q) p = q + 1;
165#endif
[cb3d1e2]166
[ce5b43c]167 q = strrchr(p, FNM_SEP_EXT);
[e917a13]168 if (q) len = (const char *)q - p; else len = strlen(p);
[ce5b43c]169
[908298d]170 q = osmalloc(len + 1);
[ce5b43c]171 memcpy(q, p, len);
172 q[len] = '\0';
173 return q;
174}
175
[9965b2b]176extern char *
[ce5b43c]177leaf_from_fnm(const char *fnm)
[a420b49]178{
[029824d]179 const char *lf;
[a420b49]180 lf = strrchr(fnm, FNM_SEP_LEV);
[029824d]181 if (lf) fnm = lf + 1;
[9af1d7a9]182#ifdef FNM_SEP_LEV2
[029824d]183 lf = strrchr(fnm, FNM_SEP_LEV2);
184 if (lf) fnm = lf + 1;
[9af1d7a9]185#endif
186#ifdef FNM_SEP_DRV
[029824d]187 lf = strrchr(fnm, FNM_SEP_DRV);
188 if (lf) fnm = lf + 1;
[9af1d7a9]189#endif
[908298d]190 return osstrdup(fnm);
[9af1d7a9]191}
192
193/* Make fnm from pth and lf, inserting an FNM_SEP_LEV if appropriate */
[9965b2b]194extern char *
[ce5b43c]195use_path(const char *pth, const char *lf)
[a420b49]196{
[9af1d7a9]197 char *fnm;
198 int len, len_total;
[63d4f07]199 bool fAddSep = false;
[9af1d7a9]200
201 len = strlen(pth);
202 len_total = len + strlen(lf) + 1;
203
204 /* if there's a path and it doesn't end in a separator, insert one */
[a420b49]205 if (len && pth[len - 1] != FNM_SEP_LEV) {
[9af1d7a9]206#ifdef FNM_SEP_LEV2
[a420b49]207 if (pth[len - 1] != FNM_SEP_LEV2) {
[9af1d7a9]208#endif
209#ifdef FNM_SEP_DRV
[421b7d2]210 if (pth[len - 1] != FNM_SEP_DRV) {
[9af1d7a9]211#endif
[63d4f07]212 fAddSep = true;
[421b7d2]213 len_total++;
[9af1d7a9]214#ifdef FNM_SEP_DRV
[421b7d2]215 }
[9af1d7a9]216#endif
217#ifdef FNM_SEP_LEV2
218 }
219#endif
220 }
221
[908298d]222 fnm = osmalloc(len_total);
[a420b49]223 strcpy(fnm, pth);
[9af1d7a9]224 if (fAddSep) fnm[len++] = FNM_SEP_LEV;
[a420b49]225 strcpy(fnm + len, lf);
[9af1d7a9]226 return fnm;
227}
228
229/* Add ext to fnm, inserting an FNM_SEP_EXT if appropriate */
[9965b2b]230extern char *
[ce5b43c]231add_ext(const char *fnm, const char *ext)
[a420b49]232{
[9af1d7a9]233 char * fnmNew;
234 int len, len_total;
[63d4f07]235 bool fAddSep = false;
[9af1d7a9]236
237 len = strlen(fnm);
238 len_total = len + strlen(ext) + 1;
239 if (ext[0] != FNM_SEP_EXT) {
[63d4f07]240 fAddSep = true;
[9af1d7a9]241 len_total++;
242 }
243
[908298d]244 fnmNew = osmalloc(len_total);
[a420b49]245 strcpy(fnmNew, fnm);
[9af1d7a9]246 if (fAddSep) fnmNew[len++] = FNM_SEP_EXT;
[a420b49]247 strcpy(fnmNew + len, ext);
[9af1d7a9]248 return fnmNew;
249}
250
251/* fopen file, found using pth and fnm
[25ab06b]252 * fnmUsed is used to return filename used to open file (ignored if NULL)
[9af1d7a9]253 * or NULL if file didn't open
254 */
[9965b2b]255extern FILE *
[25ab06b]256fopenWithPthAndExt(const char *pth, const char *fnm, const char *ext,
257 const char *mode, char **fnmUsed)
[a420b49]258{
259 char *fnmFull = NULL;
260 FILE *fh = NULL;
261 bool fAbs;
262
263 /* if no pth treat fnm as absolute */
264 fAbs = (pth == NULL || *pth == '\0' || fAbsoluteFnm(fnm));
265
266 /* if appropriate, try it without pth */
267 if (fAbs) {
[25ab06b]268 fh = fopen_not_dir(fnm, mode);
[a420b49]269 if (fh) {
[908298d]270 if (fnmUsed) fnmFull = osstrdup(fnm);
[a420b49]271 } else {
[25ab06b]272 if (ext && *ext) {
[a420b49]273 /* we've been given an extension so try using it */
[25ab06b]274 fnmFull = add_ext(fnm, ext);
275 fh = fopen_not_dir(fnmFull, mode);
[a420b49]276 }
[9af1d7a9]277 }
[a420b49]278 } else {
279 /* try using path given - first of all without the extension */
[ce5b43c]280 fnmFull = use_path(pth, fnm);
[25ab06b]281 fh = fopen_not_dir(fnmFull, mode);
[a420b49]282 if (!fh) {
[25ab06b]283 if (ext && *ext) {
[a420b49]284 /* we've been given an extension so try using it */
285 char *fnmTmp;
286 fnmTmp = fnmFull;
[25ab06b]287 fnmFull = add_ext(fnmFull, ext);
[a420b49]288 osfree(fnmTmp);
[25ab06b]289 fh = fopen_not_dir(fnmFull, mode);
[a420b49]290 }
[9af1d7a9]291 }
[a420b49]292 }
293
294 /* either it opened or didn't. If not, fh == NULL from fopen_not_dir() */
295
296 /* free name if it didn't open or name isn't wanted */
[25ab06b]297 if (fh == NULL || fnmUsed == NULL) osfree(fnmFull);
298 if (fnmUsed) *fnmUsed = (fh ? fnmFull : NULL);
[a420b49]299 return fh;
[9af1d7a9]300}
[47c7a94]301
302/* Like fopenWithPthAndExt except that "foreign" paths are translated to
303 * native ones (e.g. on Unix dir\file.ext -> dir/file.ext) */
304FILE *
305fopen_portable(const char *pth, const char *fnm, const char *ext,
[25ab06b]306 const char *mode, char **fnmUsed)
[47c7a94]307{
[25ab06b]308 FILE *fh = fopenWithPthAndExt(pth, fnm, ext, mode, fnmUsed);
[47c7a94]309 if (fh == NULL) {
[affaeee]310#if OS_UNIX
[0c62a67]311 bool changed = false;
312 char *fnm_trans = osstrdup(fnm);
313 for (char *p = fnm_trans; *p; p++) {
[47c7a94]314 switch (*p) {
[421b7d2]315 case '\\': /* swap a backslash to a forward slash */
[47c7a94]316 *p = '/';
[0c62a67]317 changed = true;
[47c7a94]318 break;
319 }
320 }
[0c62a67]321 if (changed)
[25ab06b]322 fh = fopenWithPthAndExt(pth, fnm_trans, ext, mode, fnmUsed);
[47c7a94]323
[0c62a67]324 /* To help users process data that originated on a case-insensitive
325 * filing system, try lowercasing the filename if not found.
326 */
[47c7a94]327 if (fh == NULL) {
[0c62a67]328 bool had_lower = false;
329 changed = false;
330 for (char *p = fnm_trans; *p ; p++) {
[0580c6a]331 unsigned char ch = *p;
332 if (isupper(ch)) {
333 *p = tolower(ch);
[0c62a67]334 changed = true;
335 } else if (islower(ch)) {
336 had_lower = true;
[47c7a94]337 }
[0580c6a]338 }
[0c62a67]339 if (changed)
[25ab06b]340 fh = fopenWithPthAndExt(pth, fnm_trans, ext, mode, fnmUsed);
[0c62a67]341
342 /* If that fails, try upper casing the initial character of the leaf. */
343 if (fh == NULL) {
344 char *leaf = strrchr(fnm_trans, '/');
345 leaf = (leaf ? leaf + 1 : fnm_trans);
[8adbe49]346 if (islower((unsigned char)*leaf)) {
347 *leaf = toupper((unsigned char)*leaf);
[0c62a67]348 fh = fopenWithPthAndExt(pth, fnm_trans, ext, mode, fnmUsed);
349 }
350 if (fh == NULL && had_lower) {
351 /* Finally, try upper casing the filename if it wasn't all
352 * upper case to start with. */
353 for (char *p = fnm_trans; *p ; p++) {
354 *p = toupper((unsigned char)*p);
355 }
356 fh = fopenWithPthAndExt(pth, fnm_trans, ext, mode, fnmUsed);
357 }
358 }
[47c7a94]359 }
360 osfree(fnm_trans);
361#endif
362 }
363 return fh;
364}
[25ab06b]365
366void
367filename_register_output(const char *fnm)
[421b7d2]368{
[908298d]369 filelist *p = osnew(filelist);
[4c07c51]370 SVX_ASSERT(fnm);
[908298d]371 p->fnm = osstrdup(fnm);
372 p->fh = NULL;
373 p->next = flhead;
374 flhead = p;
375}
376
377static void
378filename_register_output_with_fh(const char *fnm, FILE *fh)
[25ab06b]379{
380 filelist *p = osnew(filelist);
[4c07c51]381 SVX_ASSERT(fnm);
[908298d]382 p->fnm = osstrdup(fnm);
383 p->fh = fh;
[25ab06b]384 p->next = flhead;
385 flhead = p;
386}
387
388void
389filename_delete_output(void)
390{
391 while (flhead) {
392 filelist *p = flhead;
393 flhead = flhead->next;
[908298d]394 if (p->fnm) {
395 (void)remove(p->fnm);
396 osfree(p->fnm);
397 }
[25ab06b]398 osfree(p);
399 }
400}
Note: See TracBrowser for help on using the repository browser.