source: git/src/filename.c @ 10bde2e

stereo-2025
Last change on this file since 10bde2e was ae917b96, checked in by Olly Betts <olly@…>, 4 months ago

Simplify allocation functions

We don't need the xosmalloc(), osfree(), etc as the memory allocation
on a modern OS isn't limited in the size it can allocate by default.

  • 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, write to the Free Software
16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
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.