Fixed memory leaks resulting from fn_interp_vars et. al.
[pspp-builds.git] / src / data / filename.c
1 /* PSPP - computes sample statistics.
2    Copyright (C) 1997-9, 2000, 2006 Free Software Foundation, Inc.
3    Written by Ben Pfaff <blp@gnu.org>.
4
5    This program is free software; you can redistribute it and/or
6    modify it under the terms of the GNU General Public License as
7    published by the Free Software Foundation; either version 2 of the
8    License, or (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful, but
11    WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program; if not, write to the Free Software
17    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18    02110-1301, USA. */
19
20 #include <config.h>
21 #include <libpspp/message.h>
22 #include "filename.h"
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <ctype.h>
26 #include <errno.h>
27 #include <libpspp/alloc.h>
28 #include "intprops.h"
29 #include <libpspp/message.h>
30 #include <libpspp/str.h>
31 #include "settings.h"
32 #include <libpspp/version.h>
33 #include "xreadlink.h"
34
35 #include "gettext.h"
36 #define _(msgid) gettext (msgid)
37
38 #include <libpspp/debug-print.h>
39
40 /* PORTME: Everything in this file is system dependent. */
41
42 #ifdef unix
43 #include <pwd.h>
44 #include <unistd.h>
45 #include <sys/stat.h>
46 #include "stat-macros.h"
47 #endif
48
49 #ifdef __WIN32__
50 #define NOGDI
51 #define NOUSER
52 #define NONLS
53 #include <win32/windows.h>
54 #endif
55
56 #if __DJGPP__
57 #include <sys/stat.h>
58 #endif
59 \f
60 /* Initialization. */
61
62 const char *config_path;
63
64 void
65 fn_init (void)
66 {
67   config_path = fn_getenv_default ("STAT_CONFIG_PATH", default_config_path);
68 }
69 \f
70 /* Functions for performing operations on filenames. */
71
72
73 /* Substitutes $variables as defined by GETENV into TARGET.
74    TARGET must be a string containing the text for which substitution 
75    is required.
76    Supports $var and ${var} syntaxes;
77    $$ substitutes as $. 
78 */
79 void 
80 fn_interp_vars (struct string *target, 
81                    const char *(*getenv) (const char *))
82 {
83   assert (target);
84
85   char *input = xmalloc(ds_length(target) + 1);
86   char *s = input;
87
88   strcpy(input, ds_c_str(target));
89
90   if (NULL == strchr (ds_c_str(target), '$'))
91     goto done;
92
93   ds_clear(target);
94
95   for (;;)
96     {
97       switch (*s)
98         {
99         case '\0':
100           goto done ;
101         
102         case '$':
103           s++;
104
105           if (*s == '$')
106             {
107               ds_putc (target, '$');
108               s++;
109             }
110           else
111             {
112               int stop;
113               int start;
114               const char *value;
115
116               start = ds_length (target);
117
118               if (*s == '(')
119                 {
120                   stop = ')';
121                   s++;
122                 }
123               else if (*s == '{')
124                 {
125                   stop = '}';
126                   s++;
127                 }
128               else
129                 stop = 0;
130
131               while (*s && *s != stop
132                      && (stop || isalpha ((unsigned char) *s)))
133                 {
134                   ds_putc (target, *s++);
135                 }
136             
137               value = getenv (ds_c_str (target) + start);
138               ds_truncate (target, start);
139               ds_puts (target, value);
140
141               if (stop && *s == stop)
142                 s++;
143             }
144
145         default:
146           ds_putc (target, *s++);
147         }
148     }
149
150  done:
151   free(input);
152   return;
153 }
154
155 #ifdef unix
156 /* Expands csh tilde notation from the path INPUT into a malloc()'d
157    returned string. */
158 char *
159 fn_tilde_expand (const char *input)
160 {
161   const char *ip;
162   struct string output;
163
164   if (NULL == strchr (input, '~'))
165     return xstrdup (input);
166   ds_init (&output, strlen (input));
167
168   ip = input;
169
170   for (ip = input; *ip; )
171     if (*ip != '~' || (ip != input && ip[-1] != PATH_DELIMITER))
172       ds_putc (&output, *ip++);
173     else
174       {
175         static const char stop_set[3] = {DIR_SEPARATOR, PATH_DELIMITER, 0};
176         const char *cp;
177         
178         ip++;
179
180         cp = ip + strcspn (ip, stop_set);
181
182         if (cp > ip)
183           {
184             struct passwd *pwd;
185             char username[9];
186
187             strncpy (username, ip, cp - ip + 1);
188             username[8] = 0;
189             pwd = getpwnam (username);
190
191             if (!pwd || !pwd->pw_dir)
192               ds_putc (&output, *ip++);
193             else
194               ds_puts (&output, pwd->pw_dir);
195           }
196         else
197           {
198             const char *home = fn_getenv ("HOME");
199             if (!home)
200               ds_putc (&output, *ip++);
201             else
202               ds_puts (&output, home);
203           }
204
205         ip = cp;
206       }
207
208   return ds_c_str (&output);
209 }
210 #else /* !unix */
211 char *
212 fn_tilde_expand (const char *input)
213 {
214   return xstrdup (input);
215 }
216 #endif /* !unix */
217
218 /* Searches for a configuration file with name NAME in the path given
219    by PATH, which is tilde- and environment-interpolated.  Directories
220    in PATH are delimited by PATH_DELIMITER, defined in <pref.h>.
221    Returns the malloc'd full name of the first file found, or NULL if
222    none is found.
223
224    If PREPEND is non-NULL, then it is prepended to each filename;
225    i.e., it looks like PREPEND/PATH_COMPONENT/NAME.  This is not done
226    with absolute directories in the path. */
227 #if defined (unix) || defined (__MSDOS__) || defined (__WIN32__)
228 char *
229 fn_search_path (const char *basename, const char *path, const char *prepend)
230 {
231   char *subst_path;
232   struct string filename;
233   const char *bp;
234
235   if (fn_absolute_p (basename))
236     return fn_tilde_expand (basename);
237   
238   {
239     struct string temp;
240     ds_create(&temp, path);
241
242     fn_interp_vars(&temp, fn_getenv);
243
244     bp = subst_path = fn_tilde_expand (ds_c_str(&temp));
245
246     ds_destroy(&temp);
247   }
248
249   msg (VM (4), _("Searching for `%s'..."), basename);
250   ds_init (&filename, 64);
251
252   for (;;)
253     {
254       const char *ep;
255       if (0 == *bp)
256         {
257           msg (VM (4), _("Search unsuccessful!"));
258           ds_destroy (&filename);
259           free (subst_path);
260           return NULL;
261         }
262
263       for (ep = bp; *ep && *ep != PATH_DELIMITER; ep++)
264         ;
265
266       /* Paste together PREPEND/PATH/BASENAME. */
267       ds_clear (&filename);
268       if (prepend && !fn_absolute_p (bp))
269         {
270           ds_puts (&filename, prepend);
271           ds_putc (&filename, DIR_SEPARATOR);
272         }
273       ds_concat (&filename, bp, ep - bp);
274       if (ep - bp
275           && ds_c_str (&filename)[ds_length (&filename) - 1] != DIR_SEPARATOR)
276         ds_putc (&filename, DIR_SEPARATOR);
277       ds_puts (&filename, basename);
278       
279       msg (VM (5), " - %s", ds_c_str (&filename));
280       if (fn_exists_p (ds_c_str (&filename)))
281         {
282           msg (VM (4), _("Found `%s'."), ds_c_str (&filename));
283           free (subst_path);
284           return ds_c_str (&filename);
285         }
286
287       if (0 == *ep)
288         {
289           msg (VM (4), _("Search unsuccessful!"));
290           free (subst_path);
291           ds_destroy (&filename);
292           return NULL;
293         }
294       bp = ep + 1;
295     }
296 }
297 #else /* not unix, msdog, lose32 */
298 char *
299 fn_search_path (const char *basename, const char *path, const char *prepend)
300 {
301   size_t size = strlen (path) + 1 + strlen (basename) + 1;
302   char *string;
303   char *cp;
304   
305   if (prepend)
306     size += strlen (prepend) + 1;
307   string = xmalloc (size);
308   
309   cp = string;
310   if (prepend)
311     {
312       cp = stpcpy (cp, prepend);
313       *cp++ = DIR_SEPARATOR;
314     }
315   cp = stpcpy (cp, path);
316   *cp++ = DIR_SEPARATOR;
317   strcpy (cp, basename);
318
319   return string;
320 }
321 #endif /* not unix, msdog, lose32 */
322
323 /* Prepends directory DIR to filename FILE and returns a malloc()'d
324    copy of it. */
325 char *
326 fn_prepend_dir (const char *file, const char *dir)
327 {
328   char *temp;
329   char *cp;
330   
331   if (fn_absolute_p (file))
332     return xstrdup (file);
333
334   temp = xmalloc (strlen (file) + 1 + strlen (dir) + 1);
335   cp = stpcpy (temp, dir);
336   if (cp != temp && cp[-1] != DIR_SEPARATOR)
337     *cp++ = DIR_SEPARATOR;
338   cp = stpcpy (cp, file);
339
340   return temp;
341 }
342
343 /* fn_normalize(): This very OS-dependent routine canonicalizes
344    filename FN1.  The filename should not need to be the name of an
345    existing file.  Returns a malloc()'d copy of the canonical name.
346    This function must always succeed; if it needs to bail out then it
347    should return xstrdup(FN1).  */
348 #ifdef unix
349 char *
350 fn_normalize (const char *filename)
351 {
352   const char *src;
353   char *fn1, *fn2, *dest;
354   int maxlen;
355
356   if (fn_special_p (filename))
357     return xstrdup (filename);
358   
359   fn1 = fn_tilde_expand (filename);
360
361   /* Follow symbolic links. */
362   for (;;)
363     {
364       fn2 = fn1;
365       fn1 = fn_readlink (fn1);
366       if (!fn1)
367         {
368           fn1 = fn2;
369           break;
370         }
371       free (fn2);
372     }
373
374   maxlen = strlen (fn1) * 2;
375   if (maxlen < 31)
376     maxlen = 31;
377   dest = fn2 = xmalloc (maxlen + 1);
378   src = fn1;
379
380   if (*src == DIR_SEPARATOR)
381     *dest++ = *src++;
382   else
383     {
384       errno = 0;
385       while (getcwd (dest, maxlen - (dest - fn2)) == NULL && errno == ERANGE)
386         {
387           maxlen *= 2;
388           dest = fn2 = xrealloc (fn2, maxlen + 1);
389           errno = 0;
390         }
391       if (errno)
392         {
393           free (fn1);
394           free (fn2);
395           return NULL;
396         }
397       dest = strchr (fn2, '\0');
398       if (dest - fn2 >= maxlen)
399         {
400           int ofs = dest - fn2;
401           maxlen *= 2;
402           fn2 = xrealloc (fn2, maxlen + 1);
403           dest = fn2 + ofs;
404         }
405       if (dest[-1] != DIR_SEPARATOR)
406         *dest++ = DIR_SEPARATOR;
407     }
408
409   for (;;)
410     {
411       int c, f;
412
413       c = *src++;
414
415       f = 0;
416       if (c == DIR_SEPARATOR || c == 0)
417         {
418           /* remove `./', `../' from directory */
419           if (dest[-1] == '.' && dest[-2] == DIR_SEPARATOR)
420             dest--;
421           else if (dest[-1] == '.' && dest[-2] == '.' && dest[-3] == DIR_SEPARATOR)
422             {
423               dest -= 3;
424               if (dest == fn2)
425                 dest++;
426               while (dest[-1] != DIR_SEPARATOR)
427                 dest--;
428             }
429           else if (dest[-1] != DIR_SEPARATOR)   /* remove extra slashes */
430             f = 1;
431
432           if (c == 0)
433             {
434               if (dest[-1] == DIR_SEPARATOR && dest > fn2 + 1)
435                 dest--;
436               *dest = 0;
437               free (fn1);
438
439               return xrealloc (fn2, strlen (fn2) + 1);
440             }
441         }
442       else
443         f = 1;
444
445       if (f)
446         {
447           if (dest - fn2 >= maxlen)
448             {
449               int ofs = dest - fn2;
450               maxlen *= 2;
451               fn2 = xrealloc (fn2, maxlen + 1);
452               dest = fn2 + ofs;
453             }
454           *dest++ = c;
455         }
456     }
457 }
458 #elif defined (__WIN32__)
459 char *
460 fn_normalize (const char *fn1)
461 {
462   DWORD len;
463   DWORD success;
464   char *fn2;
465
466   /* Don't change special filenames. */
467   if (is_special_filename (filename))
468     return xstrdup (filename);
469
470   /* First find the required buffer length. */
471   len = GetFullPathName (fn1, 0, NULL, NULL);
472   if (!len)
473     {
474       fn2 = xstrdup (fn1);
475       return fn2;
476     }
477
478   /* Then make a buffer that big. */
479   fn2 = xmalloc (len);
480   success = GetFullPathName (fn1, len, fn2, NULL);
481   if (success >= len || success == 0)
482     {
483       free (fn2);
484       fn2 = xstrdup (fn1);
485       return fn2;
486     }
487   return fn2;
488 }
489 #elif __BORLANDC__
490 char *
491 fn_normalize (const char *fn1)
492 {
493   char *fn2 = _fullpath (NULL, fn1, 0);
494   if (fn2)
495     {
496       char *cp;
497       for (cp = fn2; *cp; cp++)
498         *cp = toupper ((unsigned char) (*cp));
499       return fn2;
500     }
501   return xstrdup (fn1);
502 }
503 #elif __DJGPP__
504 char *
505 fn_normalize (const char *fn1)
506 {
507   char *fn2 = xmalloc (1024);
508   _fixpath (fn1, fn2);
509   fn2 = xrealloc (fn2, strlen (fn2) + 1);
510   return fn2;
511 }
512 #else /* not Lose32, Unix, or DJGPP */
513 char *
514 fn_normalize (const char *fn)
515 {
516   return xstrdup (fn);
517 }
518 #endif /* not Lose32, Unix, or DJGPP */
519
520 /* Returns the directory part of FILENAME, as a malloc()'d
521    string. */
522 char *
523 fn_dirname (const char *filename)
524 {
525   const char *p;
526   char *s;
527   size_t len;
528
529   len = strlen (filename);
530   if (len == 1 && filename[0] == '/')
531     p = filename + 1;
532   else if (len && filename[len - 1] == DIR_SEPARATOR)
533     p = buf_find_reverse (filename, len - 1, filename + len - 1, 1);
534   else
535     p = strrchr (filename, DIR_SEPARATOR);
536   if (p == NULL)
537     p = filename;
538
539   s = xmalloc (p - filename + 1);
540   memcpy (s, filename, p - filename);
541   s[p - filename] = 0;
542
543   return s;
544 }
545
546 /* Returns the basename part of FILENAME as a malloc()'d string. */
547 #if 0
548 char *
549 fn_basename (const char *filename)
550 {
551   /* Not used, not implemented. */
552   abort ();
553 }
554 #endif
555
556 /* Returns the extension part of FILENAME as a malloc()'d string.
557    If FILENAME does not have an extension, returns an empty
558    string. */
559 char *
560 fn_extension (const char *filename) 
561 {
562   const char *extension = strrchr (filename, '.');
563   if (extension == NULL)
564     extension = "";
565   return xstrdup (extension);
566 }
567 \f
568 #if unix
569 /* Returns the current working directory, as a malloc()'d string.
570    From libc.info. */
571 char *
572 fn_get_cwd (void)
573 {
574   int size = 100;
575   char *buffer = xmalloc (size);
576      
577   for (;;)
578     {
579       char *value = getcwd (buffer, size);
580       if (value != 0)
581         return buffer;
582
583       size *= 2;
584       free (buffer);
585       buffer = xmalloc (size);
586     }
587 }
588 #else
589 char *
590 fn_get_cwd (void)
591 {
592   int size = 2;
593   char *buffer = xmalloc (size);
594   if ( buffer) 
595   {
596     buffer[0]='.';
597     buffer[1]='\0';
598   }
599
600   return buffer;
601      
602 }
603 #endif
604 \f
605 /* Find out information about files. */
606
607 /* Returns nonzero iff NAME specifies an absolute filename. */
608 int
609 fn_absolute_p (const char *name)
610 {
611 #ifdef unix
612   if (name[0] == '/'
613       || !strncmp (name, "./", 2)
614       || !strncmp (name, "../", 3)
615       || name[0] == '~')
616     return 1;
617 #elif defined (__MSDOS__)
618   if (name[0] == '\\'
619       || !strncmp (name, ".\\", 2)
620       || !strncmp (name, "..\\", 3)
621       || (name[0] && name[1] == ':'))
622     return 1;
623 #endif
624   
625   return 0;
626 }
627   
628 /* Returns 1 if the filename specified is a virtual file that doesn't
629    really exist on disk, 0 if it's a real filename. */
630 int
631 fn_special_p (const char *filename)
632 {
633   if (!strcmp (filename, "-") || !strcmp (filename, "stdin")
634       || !strcmp (filename, "stdout") || !strcmp (filename, "stderr")
635 #ifdef unix
636       || filename[0] == '|'
637       || (*filename && filename[strlen (filename) - 1] == '|')
638 #endif
639       )
640     return 1;
641
642   return 0;
643 }
644
645 /* Returns nonzero if file with name NAME exists. */
646 int
647 fn_exists_p (const char *name)
648 {
649 #ifdef unix
650   struct stat temp;
651
652   return stat (name, &temp) == 0;
653 #else
654   FILE *f = fopen (name, "r");
655   if (!f)
656     return 0;
657   fclose (f);
658   return 1;
659 #endif
660 }
661
662 /* Returns the symbolic link value for FILENAME as a dynamically
663    allocated buffer, or a null pointer on failure. */
664 char *
665 fn_readlink (const char *filename)
666 {
667   return xreadlink (filename, 32);
668 }
669 \f
670 /* Environment variables. */
671
672 /* Simulates $VER and $ARCH environment variables. */
673 const char *
674 fn_getenv (const char *s)
675 {
676   if (!strcmp (s, "VER"))
677     return fn_getenv_default ("STAT_VER", bare_version);
678   else if (!strcmp (s, "ARCH"))
679     return fn_getenv_default ("STAT_ARCH", host_system);
680   else
681     return getenv (s);
682 }
683
684 /* Returns getenv(KEY) if that's non-NULL; else returns DEF. */
685 const char *
686 fn_getenv_default (const char *key, const char *def)
687 {
688   const char *value = getenv (key);
689   return value ? value : def;
690 }
691 \f
692 /* Basic file handling. */
693
694 /* Used for giving an error message on a set_safer security
695    violation. */
696 static FILE *
697 safety_violation (const char *fn)
698 {
699   msg (SE, _("Not opening pipe file `%s' because SAFER option set."), fn);
700   errno = EPERM;
701   return NULL;
702 }
703
704 /* As a general comment on the following routines, a `sensible value'
705    for errno includes 0 if there is no associated system error.  The
706    routines will only set errno to 0 if there is an error in a
707    callback that sets errno to 0; they themselves won't. */
708
709 /* File open routine that understands `-' as stdin/stdout and `|cmd'
710    as a pipe to command `cmd'.  Returns resultant FILE on success,
711    NULL on failure.  If NULL is returned then errno is set to a
712    sensible value.  */
713 FILE *
714 fn_open (const char *fn, const char *mode)
715 {
716   assert (mode[0] == 'r' || mode[0] == 'w');
717
718   if (mode[0] == 'r' && (!strcmp (fn, "stdin") || !strcmp (fn, "-"))) 
719     return stdin;
720   else if (mode[0] == 'w' && (!strcmp (fn, "stdout") || !strcmp (fn, "-")))
721     return stdout;
722   else if (mode[0] == 'w' && !strcmp (fn, "stderr"))
723     return stderr;
724   
725 #ifdef unix
726   if (fn[0] == '|')
727     {
728       if (get_safer_mode ())
729         return safety_violation (fn);
730
731       return popen (&fn[1], mode);
732     }
733   else if (*fn && fn[strlen (fn) - 1] == '|')
734     {
735       char *s;
736       FILE *f;
737
738       if (get_safer_mode ())
739         return safety_violation (fn);
740       
741       s = local_alloc (strlen (fn));
742       memcpy (s, fn, strlen (fn) - 1);
743       s[strlen (fn) - 1] = 0;
744       
745       f = popen (s, mode);
746
747       local_free (s);
748
749       return f;
750     }
751   else
752 #endif
753     {
754       FILE *f = fopen (fn, mode);
755
756       if (f && mode[0] == 'w')
757         setvbuf (f, NULL, _IOLBF, 0);
758
759       return f;
760     }
761 }
762
763 /* Counterpart to fn_open that closes file F with name FN; returns 0
764    on success, EOF on failure.  If EOF is returned, errno is set to a
765    sensible value. */
766 int
767 fn_close (const char *fn, FILE *f)
768 {
769   if (!strcmp (fn, "-"))
770     return 0;
771 #ifdef unix
772   else if (fn[0] == '|' || (*fn && fn[strlen (fn) - 1] == '|'))
773     {
774       pclose (f);
775       return 0;
776     }
777 #endif
778   else
779     return fclose (f);
780 }
781 \f
782 /* More extensive file handling. */
783
784 /* File open routine that extends fn_open().  Opens or reopens a
785    file according to the contents of file_ext F.  Returns nonzero on
786    success.  If 0 is returned, errno is set to a sensible value. */
787 int
788 fn_open_ext (struct file_ext *f)
789 {
790   char *p;
791
792   p = strstr (f->filename, "%d");
793   if (p)
794     {
795       char *s = local_alloc (strlen (f->filename)
796                              + INT_STRLEN_BOUND (int) - 1);
797       char *cp;
798
799       memcpy (s, f->filename, p - f->filename);
800       cp = spprintf (&s[p - f->filename], "%d", *f->sequence_no);
801       strcpy (cp, &p[2]);
802
803       if (f->file)
804         {
805           int error = 0;
806
807           if (f->preclose)
808             if (f->preclose (f) == 0)
809               error = errno;
810
811           if (EOF == fn_close (f->filename, f->file) || error)
812             {
813               f->file = NULL;
814               local_free (s);
815
816               if (error)
817                 errno = error;
818
819               return 0;
820             }
821
822           f->file = NULL;
823         }
824
825       f->file = fn_open (s, f->mode);
826       local_free (s);
827
828       if (f->file && f->postopen)
829         if (f->postopen (f) == 0)
830           {
831             int error = errno;
832             fn_close (f->filename, f->file);
833             errno = error;
834
835             return 0;
836           }
837
838       return (f->file != NULL);
839     }
840   else if (f->file)
841     return 1;
842   else
843     {
844       f->file = fn_open (f->filename, f->mode);
845
846       if (f->file && f->postopen)
847         if (f->postopen (f) == 0)
848           {
849             int error = errno;
850             fn_close (f->filename, f->file);
851             errno = error;
852
853             return 0;
854           }
855
856       return (f->file != NULL);
857     }
858 }
859
860 /* Properly closes the file associated with file_ext F, if any.
861    Return nonzero on success.  If zero is returned, errno is set to a
862    sensible value. */
863 int
864 fn_close_ext (struct file_ext *f)
865 {
866   if (f->file)
867     {
868       int error = 0;
869
870       if (f->preclose)
871         if (f->preclose (f) == 0)
872           error = errno;
873
874       if (EOF == fn_close (f->filename, f->file) || error)
875         {
876           f->file = NULL;
877
878           if (error)
879             errno = error;
880
881           return 0;
882         }
883
884       f->file = NULL;
885     }
886   return 1;
887 }
888
889 #ifdef unix
890 /* A file's identity. */
891 struct file_identity 
892   {
893     dev_t device;               /* Device number. */
894     ino_t inode;                /* Inode number. */
895   };
896
897 /* Returns a pointer to a dynamically allocated structure whose
898    value can be used to tell whether two files are actually the
899    same file.  Returns a null pointer if no information about the
900    file is available, perhaps because it does not exist.  The
901    caller is responsible for freeing the structure with
902    fn_free_identity() when finished. */  
903 struct file_identity *
904 fn_get_identity (const char *filename) 
905 {
906   struct stat s;
907
908   if (stat (filename, &s) == 0) 
909     {
910       struct file_identity *identity = xmalloc (sizeof *identity);
911       identity->device = s.st_dev;
912       identity->inode = s.st_ino;
913       return identity;
914     }
915   else
916     return NULL;
917 }
918
919 /* Frees IDENTITY obtained from fn_get_identity(). */
920 void
921 fn_free_identity (struct file_identity *identity) 
922 {
923   free (identity);
924 }
925
926 /* Compares A and B, returning a strcmp()-type result. */
927 int
928 fn_compare_file_identities (const struct file_identity *a,
929                             const struct file_identity *b) 
930 {
931   assert (a != NULL);
932   assert (b != NULL);
933   if (a->device != b->device)
934     return a->device < b->device ? -1 : 1;
935   else
936     return a->inode < b->inode ? -1 : a->inode > b->inode;
937 }
938 #else /* not unix */
939 /* A file's identity. */
940 struct file_identity 
941   {
942     char *normalized_filename;  /* File's normalized name. */
943   };
944
945 /* Returns a pointer to a dynamically allocated structure whose
946    value can be used to tell whether two files are actually the
947    same file.  Returns a null pointer if no information about the
948    file is available, perhaps because it does not exist.  The
949    caller is responsible for freeing the structure with
950    fn_free_identity() when finished. */  
951 struct file_identity *
952 fn_get_identity (const char *filename) 
953 {
954   struct file_identity *identity = xmalloc (sizeof *identity);
955   identity->normalized_filename = fn_normalize (filename);
956   return identity;
957 }
958
959 /* Frees IDENTITY obtained from fn_get_identity(). */
960 void
961 fn_free_identity (struct file_identity *identity) 
962 {
963   if (identity != NULL) 
964     {
965       free (identity->normalized_filename);
966       free (identity);
967     }
968 }
969
970 /* Compares A and B, returning a strcmp()-type result. */
971 int
972 fn_compare_file_identities (const struct file_identity *a,
973                             const struct file_identity *b) 
974 {
975   return strcmp (a->normalized_filename, b->normalized_filename);
976 }
977 #endif /* not unix */