source: git/src/filename.c

main
Last change on this file was 0b99107, checked in by Olly Betts <olly@…>, 7 weeks ago

Eliminate old FSF addresses

Update GPL/LGPL licence files and boilerplate to direct people who
didn't receive the licence text to the FSF website, as the current
versions of the FSF licence texts now do, rather than giving a postal
address.

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