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