source: git/src/filename.c

walls-data-hanging-as-warning
Last change on this file was c606770, checked in by Olly Betts <olly@…>, 2 weeks ago

Try to eliminate osdepend.{c,h} again

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