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