source: git/src/filename.c

Last change on this file 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
RevLine 
[846746e]1/* OS dependent filename manipulation routines
[5318008]2 * Copyright (c) Olly Betts 1998-2003,2004,2005,2010,2011,2014,2025
[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>
[07a6d16]27#include <sys/types.h>
28#include <sys/stat.h>
29#include <stdio.h>
[9af1d7a9]30
[908298d]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
[c606770]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
[a0b076f]67/* safe_fopen should be used when writing a file
68 * fopenWithPthAndExt should be used when reading a file
69 */
[ce5b43c]70
[a420b49]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;
[4c07c51]78   SVX_ASSERT(mode[0] == 'w'); /* only expect to be used for writing */
[a420b49]79   if (fDirectory(fnm))
[7962c9d]80      fatalerror(/*Filename “%s” refers to directory*/5, fnm);
[a420b49]81
82   f = fopen(fnm, mode);
[7962c9d]83   if (!f) fatalerror(/*Failed to open output file “%s”*/3, fnm);
[a0b076f]84
[908298d]85   filename_register_output_with_fh(fnm, f);
[a420b49]86   return f;
87}
88
[f1067a2]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{
[4c07c51]95   SVX_ASSERT(f);
[f1067a2]96   /* NB: use of | rather than || - we always want to call fclose() */
[c0f9e1e]97   if (FERROR(f) | (fclose(f) == EOF)) {
[908298d]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);
[7962c9d]107         fatalerror(/*Error writing to file “%s”*/7, fnm);
[908298d]108      }
[dc533fe]109      /* f wasn't opened with safe_fopen(), so we don't know the filename. */
[f1067a2]110      fatalerror(/*Error writing to file*/111);
[908298d]111   }
[f1067a2]112}
113
[ce5b43c]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);
[ae917b96]121   free(p);
[ce5b43c]122   return f;
123}
124
125static FILE *
[a420b49]126fopen_not_dir(const char *fnm, const char *mode)
127{
128   if (fDirectory(fnm)) return NULL;
129   return fopen(fnm, mode);
[9af1d7a9]130}
131
[9965b2b]132extern char *
[ce5b43c]133path_from_fnm(const char *fnm)
[a420b49]134{
[9af1d7a9]135   char *pth;
136   const char *lf;
137   int lenpth = 0;
138
[a420b49]139   lf = strrchr(fnm, FNM_SEP_LEV);
[9af1d7a9]140#ifdef FNM_SEP_LEV2
[029824d]141   {
[eee67ab]142      const char *lf2 = strrchr(lf ? lf + 1 : fnm, FNM_SEP_LEV2);
[029824d]143      if (lf2) lf = lf2;
144   }
[9af1d7a9]145#endif
146#ifdef FNM_SEP_DRV
[a420b49]147   if (!lf) lf = strrchr(fnm, FNM_SEP_DRV);
[9af1d7a9]148#endif
149   if (lf) lenpth = lf - fnm + 1;
150
[908298d]151   pth = osmalloc(lenpth + 1);
[a420b49]152   memcpy(pth, fnm, lenpth);
[9af1d7a9]153   pth[lenpth] = '\0';
154
155   return pth;
156}
157
[ce5b43c]158extern char *
159base_from_fnm(const char *fnm)
160{
161   char *p;
[cb3d1e2]162
[ce5b43c]163   p = strrchr(fnm, FNM_SEP_EXT);
164   /* Trim off any leaf extension, but dirs can have extensions too */
[029824d]165   if (p && !strchr(p, FNM_SEP_LEV)
[ce5b43c]166#ifdef FNM_SEP_LEV2
[029824d]167       && !strchr(p, FNM_SEP_LEV2)
[ce5b43c]168#endif
169       ) {
[e917a13]170      size_t len = (const char *)p - fnm;
[ce5b43c]171
[908298d]172      p = osmalloc(len + 1);
[ce5b43c]173      memcpy(p, fnm, len);
174      p[len] = '\0';
175      return p;
176   }
177
[908298d]178   return osstrdup(fnm);
[ce5b43c]179}
180
181extern char *
182baseleaf_from_fnm(const char *fnm)
183{
184   const char *p;
[5318008]185   const char *q;
[ce5b43c]186   size_t len;
[cb3d1e2]187
[ce5b43c]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
[cb3d1e2]195
[ce5b43c]196   q = strrchr(p, FNM_SEP_EXT);
[e917a13]197   if (q) len = (const char *)q - p; else len = strlen(p);
[ce5b43c]198
[5318008]199   char* res = osmalloc(len + 1);
200   memcpy(res, p, len);
201   res[len] = '\0';
202   return res;
[ce5b43c]203}
204
[9965b2b]205extern char *
[ce5b43c]206leaf_from_fnm(const char *fnm)
[a420b49]207{
[029824d]208   const char *lf;
[a420b49]209   lf = strrchr(fnm, FNM_SEP_LEV);
[029824d]210   if (lf) fnm = lf + 1;
[9af1d7a9]211#ifdef FNM_SEP_LEV2
[029824d]212   lf = strrchr(fnm, FNM_SEP_LEV2);
213   if (lf) fnm = lf + 1;
[9af1d7a9]214#endif
215#ifdef FNM_SEP_DRV
[029824d]216   lf = strrchr(fnm, FNM_SEP_DRV);
217   if (lf) fnm = lf + 1;
[9af1d7a9]218#endif
[908298d]219   return osstrdup(fnm);
[9af1d7a9]220}
221
222/* Make fnm from pth and lf, inserting an FNM_SEP_LEV if appropriate */
[9965b2b]223extern char *
[ce5b43c]224use_path(const char *pth, const char *lf)
[a420b49]225{
[9af1d7a9]226   char *fnm;
227   int len, len_total;
[63d4f07]228   bool fAddSep = false;
[9af1d7a9]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 */
[a420b49]234   if (len && pth[len - 1] != FNM_SEP_LEV) {
[9af1d7a9]235#ifdef FNM_SEP_LEV2
[a420b49]236      if (pth[len - 1] != FNM_SEP_LEV2) {
[9af1d7a9]237#endif
238#ifdef FNM_SEP_DRV
[421b7d2]239         if (pth[len - 1] != FNM_SEP_DRV) {
[9af1d7a9]240#endif
[63d4f07]241            fAddSep = true;
[421b7d2]242            len_total++;
[9af1d7a9]243#ifdef FNM_SEP_DRV
[421b7d2]244         }
[9af1d7a9]245#endif
246#ifdef FNM_SEP_LEV2
247      }
248#endif
249   }
250
[908298d]251   fnm = osmalloc(len_total);
[a420b49]252   strcpy(fnm, pth);
[9af1d7a9]253   if (fAddSep) fnm[len++] = FNM_SEP_LEV;
[a420b49]254   strcpy(fnm + len, lf);
[9af1d7a9]255   return fnm;
256}
257
258/* Add ext to fnm, inserting an FNM_SEP_EXT if appropriate */
[9965b2b]259extern char *
[ce5b43c]260add_ext(const char *fnm, const char *ext)
[a420b49]261{
[9af1d7a9]262   char * fnmNew;
263   int len, len_total;
[63d4f07]264   bool fAddSep = false;
[9af1d7a9]265
266   len = strlen(fnm);
267   len_total = len + strlen(ext) + 1;
268   if (ext[0] != FNM_SEP_EXT) {
[63d4f07]269      fAddSep = true;
[9af1d7a9]270      len_total++;
271   }
272
[908298d]273   fnmNew = osmalloc(len_total);
[a420b49]274   strcpy(fnmNew, fnm);
[9af1d7a9]275   if (fAddSep) fnmNew[len++] = FNM_SEP_EXT;
[a420b49]276   strcpy(fnmNew + len, ext);
[9af1d7a9]277   return fnmNew;
278}
279
[07a6d16]280#ifdef _WIN32
[c606770]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
[07a6d16]293#else
[c606770]294
295static bool
296fAbsoluteFnm(const char *fnm)
297{
298   return (fnm[0] == '/');
299}
300
301#endif
302
[9af1d7a9]303/* fopen file, found using pth and fnm
[25ab06b]304 * fnmUsed is used to return filename used to open file (ignored if NULL)
[9af1d7a9]305 * or NULL if file didn't open
306 */
[9965b2b]307extern FILE *
[25ab06b]308fopenWithPthAndExt(const char *pth, const char *fnm, const char *ext,
309                   const char *mode, char **fnmUsed)
[a420b49]310{
311   char *fnmFull = NULL;
312   FILE *fh = NULL;
313
[c606770]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)) {
[25ab06b]318      fh = fopen_not_dir(fnm, mode);
[a420b49]319      if (fh) {
[908298d]320         if (fnmUsed) fnmFull = osstrdup(fnm);
[a420b49]321      } else {
[25ab06b]322         if (ext && *ext) {
[a420b49]323            /* we've been given an extension so try using it */
[25ab06b]324            fnmFull = add_ext(fnm, ext);
325            fh = fopen_not_dir(fnmFull, mode);
[a420b49]326         }
[9af1d7a9]327      }
[a420b49]328   } else {
329      /* try using path given - first of all without the extension */
[ce5b43c]330      fnmFull = use_path(pth, fnm);
[25ab06b]331      fh = fopen_not_dir(fnmFull, mode);
[a420b49]332      if (!fh) {
[25ab06b]333         if (ext && *ext) {
[a420b49]334            /* we've been given an extension so try using it */
335            char *fnmTmp;
336            fnmTmp = fnmFull;
[25ab06b]337            fnmFull = add_ext(fnmFull, ext);
[ae917b96]338            free(fnmTmp);
[25ab06b]339            fh = fopen_not_dir(fnmFull, mode);
[a420b49]340         }
[9af1d7a9]341      }
[a420b49]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 */
[ae917b96]347   if (fh == NULL || fnmUsed == NULL) free(fnmFull);
[25ab06b]348   if (fnmUsed) *fnmUsed = (fh ? fnmFull : NULL);
[a420b49]349   return fh;
[9af1d7a9]350}
[47c7a94]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,
[25ab06b]356               const char *mode, char **fnmUsed)
[47c7a94]357{
[25ab06b]358   FILE *fh = fopenWithPthAndExt(pth, fnm, ext, mode, fnmUsed);
[47c7a94]359   if (fh == NULL) {
[07a6d16]360#ifndef _WIN32
[0c62a67]361      bool changed = false;
362      char *fnm_trans = osstrdup(fnm);
363      for (char *p = fnm_trans; *p; p++) {
[47c7a94]364         switch (*p) {
[421b7d2]365         case '\\': /* swap a backslash to a forward slash */
[47c7a94]366            *p = '/';
[0c62a67]367            changed = true;
[47c7a94]368            break;
369         }
370      }
[0c62a67]371      if (changed)
[25ab06b]372         fh = fopenWithPthAndExt(pth, fnm_trans, ext, mode, fnmUsed);
[47c7a94]373
[0c62a67]374      /* To help users process data that originated on a case-insensitive
375       * filing system, try lowercasing the filename if not found.
376       */
[47c7a94]377      if (fh == NULL) {
[0c62a67]378         bool had_lower = false;
379         changed = false;
380         for (char *p = fnm_trans; *p ; p++) {
[0580c6a]381            unsigned char ch = *p;
382            if (isupper(ch)) {
383               *p = tolower(ch);
[0c62a67]384               changed = true;
385            } else if (islower(ch)) {
386               had_lower = true;
[47c7a94]387            }
[0580c6a]388         }
[0c62a67]389         if (changed)
[25ab06b]390            fh = fopenWithPthAndExt(pth, fnm_trans, ext, mode, fnmUsed);
[0c62a67]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);
[8adbe49]396            if (islower((unsigned char)*leaf)) {
397               *leaf = toupper((unsigned char)*leaf);
[0c62a67]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         }
[47c7a94]409      }
[ae917b96]410      free(fnm_trans);
[47c7a94]411#endif
412   }
413   return fh;
414}
[25ab06b]415
416void
417filename_register_output(const char *fnm)
[421b7d2]418{
[908298d]419   filelist *p = osnew(filelist);
[4c07c51]420   SVX_ASSERT(fnm);
[908298d]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)
[25ab06b]429{
430   filelist *p = osnew(filelist);
[4c07c51]431   SVX_ASSERT(fnm);
[908298d]432   p->fnm = osstrdup(fnm);
433   p->fh = fh;
[25ab06b]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;
[908298d]444      if (p->fnm) {
445         (void)remove(p->fnm);
[ae917b96]446         free(p->fnm);
[908298d]447      }
[ae917b96]448      free(p);
[25ab06b]449   }
450}
Note: See TracBrowser for help on using the repository browser.