Fix up some comments.
[obnox/wireshark/wip.git] / epan / filesystem.c
1 /* filesystem.c
2  * Filesystem utility routines
3  *
4  * $Id$
5  *
6  * Ethereal - Network traffic analyzer
7  * By Gerald Combs <gerald@ethereal.com>
8  * Copyright 1998 Gerald Combs
9  *
10  * This program is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU General Public License
12  * as published by the Free Software Foundation; either version 2
13  * of the License, or (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
23  */
24
25 #ifdef HAVE_CONFIG_H
26 # include "config.h"
27 #endif
28
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <errno.h>
33
34 #include <glib.h>
35
36 #ifdef HAVE_UNISTD_H
37 #include <unistd.h>
38 #endif
39
40 #ifdef HAVE_SYS_STAT_H
41 #include <sys/stat.h>
42 #endif
43
44 #ifdef HAVE_WINDOWS_H
45 #include <windows.h>
46 #endif
47
48 #ifndef _WIN32
49 #include <pwd.h>
50 #endif
51
52 #include "filesystem.h"
53 #include <wiretap/file_util.h>
54
55 /*
56  * Given a pathname, return a pointer to the last pathname separator
57  * character in the pathname, or NULL if the pathname contains no
58  * separators.
59  */
60 static char *
61 find_last_pathname_separator(const char *path)
62 {
63         char *separator;
64
65 #ifdef _WIN32
66         char c;
67
68         /*
69          * We have to scan for '\' or '/'.
70          * Get to the end of the string.
71          */
72         separator = strchr(path, '\0');         /* points to ending '\0' */
73         while (separator > path) {
74                 c = *--separator;
75                 if (c == '\\' || c == '/')
76                         return separator;       /* found it */
77         }
78
79         /*
80          * OK, we didn't find any, so no directories - but there might
81          * be a drive letter....
82          */
83         return strchr(path, ':');
84 #else
85         separator = strrchr(path, '/');
86 #endif
87         return separator;
88 }
89
90 /*
91  * Given a pathname, return the last component.
92  */
93 const char *
94 get_basename(const char *path)
95 {
96         const char *filename;
97
98         g_assert(path != NULL);
99         filename = find_last_pathname_separator(path);
100         if (filename == NULL) {
101                 /*
102                  * There're no directories, drive letters, etc. in the
103                  * name; the pathname *is* the file name.
104                  */
105                 filename = path;
106         } else {
107                 /*
108                  * Skip past the pathname or drive letter separator.
109                  */
110                 filename++;
111         }
112         return filename;
113 }
114
115 /*
116  * Given a pathname, return a string containing everything but the
117  * last component.  NOTE: this overwrites the pathname handed into
118  * it....
119  */
120 char *
121 get_dirname(char *path)
122 {
123         char *separator;
124
125         g_assert(path != NULL);
126         separator = find_last_pathname_separator(path);
127         if (separator == NULL) {
128                 /*
129                  * There're no directories, drive letters, etc. in the
130                  * name; there is no directory path to return.
131                  */
132                 return NULL;
133         }
134
135         /*
136          * Get rid of the last pathname separator and the final file
137          * name following it.
138          */
139         *separator = '\0';
140
141         /*
142          * "path" now contains the pathname of the directory containing
143          * the file/directory to which it referred.
144          */
145         return path;
146 }
147
148 /*
149  * Given a pathname, return:
150  *
151  *      the errno, if an attempt to "stat()" the file fails;
152  *
153  *      EISDIR, if the attempt succeeded and the file turned out
154  *      to be a directory;
155  *
156  *      0, if the attempt succeeded and the file turned out not
157  *      to be a directory.
158  */
159
160 /*
161  * Visual C++ on Win32 systems doesn't define these.  (Old UNIX systems don't
162  * define them either.)
163  *
164  * Visual C++ on Win32 systems doesn't define S_IFIFO, it defines _S_IFIFO.
165  */
166 #ifndef S_ISREG
167 #define S_ISREG(mode)   (((mode) & S_IFMT) == S_IFREG)
168 #endif
169 #ifndef S_IFIFO
170 #define S_IFIFO _S_IFIFO
171 #endif
172 #ifndef S_ISFIFO
173 #define S_ISFIFO(mode)  (((mode) & S_IFMT) == S_IFIFO)
174 #endif
175 #ifndef S_ISDIR
176 #define S_ISDIR(mode)   (((mode) & S_IFMT) == S_IFDIR)
177 #endif
178
179 int
180 test_for_directory(const char *path)
181 {
182         struct stat statb;
183
184         if (eth_stat(path, &statb) < 0)
185                 return errno;
186
187         if (S_ISDIR(statb.st_mode))
188                 return EISDIR;
189         else
190                 return 0;
191 }
192
193 int
194 test_for_fifo(const char *path)
195 {
196         struct stat statb;
197
198         if (eth_stat(path, &statb) < 0)
199                 return errno;
200
201         if (S_ISFIFO(statb.st_mode))
202                 return ESPIPE;
203         else
204                 return 0;
205 }
206
207 /*
208  * Get the directory in which Ethereal's global configuration and data
209  * files are stored.
210  *
211  * XXX - if we ever make libethereal a real library, used by multiple
212  * applications (more than just Tethereal and versions of Ethereal with
213  * various UIs), should the configuration files belong to the library
214  * (and be shared by all those applications) or to the applications?
215  *
216  * If they belong to the library, that could be done on UNIX by the
217  * configure script, but it's trickier on Windows, as you can't just
218  * use the pathname of the executable.
219  *
220  * If they belong to the application, that could be done on Windows
221  * by using the pathname of the executable, but we'd have to have it
222  * passed in as an argument, in some call, on UNIX.
223  *
224  * Note that some of those configuration files might be used by code in
225  * libethereal, some of them might be used by dissectors (would they
226  * belong to libethereal, the application, or a separate library?),
227  * and some of them might be used by other code (the Ethereal preferences
228  * file includes resolver preferences that control the behavior of code
229  * in libethereal, dissector preferences, and UI preferences, for
230  * example).
231  */
232 const char *
233 get_datafile_dir(void)
234 {
235 #ifdef _WIN32
236         char prog_pathname[_MAX_PATH+2];
237         char *dir_end;
238         size_t datafile_dir_len;
239         static char *datafile_dir;
240
241         /*
242          * Have we already gotten the pathname?
243          * If so, just return it.
244          */
245         if (datafile_dir != NULL)
246                 return datafile_dir;
247
248         /*
249          * No, we haven't.
250          * Start out by assuming it's the default installation directory.
251          */
252         datafile_dir = "C:\\Program Files\\Ethereal\\";
253
254         /*
255          * Now we attempt to get the full pathname of the currently running
256          * program, under the assumption that we're running an installed
257          * version of the program.  If we fail, we don't change "datafile_dir",
258          * and thus end up using the default.
259          *
260          * XXX - does NSIS put the installation directory into
261          * "\HKEY_LOCAL_MACHINE\SOFTWARE\Ethereal\InstallDir"?
262          * If so, perhaps we should read that from the registry,
263          * instead.
264          */
265         if (GetModuleFileName(NULL, prog_pathname, sizeof prog_pathname) != 0) {
266                 /*
267                  * If the program is an installed version, the full pathname
268                  * includes the pathname of the directory in which it was
269                  * installed; get that directory's pathname, and construct
270                  * from it the pathname of the directory in which the
271                  * plugins were installed.
272                  *
273                  * First, find the last "\\" in the directory, as that
274                  * marks the end of the directory pathname.
275                  *
276                  * XXX - Can the pathname be something such as
277                  * "C:ethereal.exe"?  Or is it always a full pathname
278                  * beginning with "\\" after the drive letter?
279                  */
280                 dir_end = strrchr(prog_pathname, '\\');
281                 if (dir_end != NULL) {
282                         /*
283                          * Found it - now figure out how long the datafile
284                          * directory pathname will be.
285                          */
286                         datafile_dir_len = (dir_end - prog_pathname);
287
288                         /*
289                          * Allocate a buffer for the plugin directory
290                          * pathname, and construct it.
291                          */
292                         datafile_dir = g_malloc(datafile_dir_len + 1);
293                         strncpy(datafile_dir, prog_pathname, datafile_dir_len);
294                         datafile_dir[datafile_dir_len] = '\0';
295                 }
296         }
297         return datafile_dir;
298 #else
299         /*
300          * Just use DATAFILE_DIR, as that's what the configure script
301          * set it to be.
302          */
303         return DATAFILE_DIR;
304 #endif
305 }
306
307 /*
308  * Get the directory in which files that, at least on UNIX, are
309  * system files (such as "/etc/ethers") are stored; on Windows,
310  * there's no "/etc" directory, so we get them from the Ethereal
311  * global configuration and data file directory.
312  */
313 const char *
314 get_systemfile_dir(void)
315 {
316 #ifdef _WIN32
317         return get_datafile_dir();
318 #else
319         return "/etc";
320 #endif
321 }
322
323 /*
324  * Name of directory, under the user's home directory, in which
325  * personal configuration files are stored.
326  */
327 #ifdef _WIN32
328 #define PF_DIR "Ethereal"
329 #else
330 /*
331  * XXX - should this be ".libepan"? For backwards-compatibility, I'll keep
332  * it ".ethereal" for now.
333  */
334 #define PF_DIR ".ethereal"
335 #endif
336
337 #ifdef WIN32
338 /* utf8 version of getenv, needed to get win32 filename paths */
339 char *getenv_utf8(const char *varname)
340 {
341         char *envvar;
342         wchar_t *envvarw;
343         wchar_t *varnamew;
344
345         envvar = getenv(varname);
346
347         /* since GLib 2.6 we need an utf8 version of the filename */
348 #if GLIB_MAJOR_VERSION > 2 || (GLIB_MAJOR_VERSION == 2 && GLIB_MINOR_VERSION >= 6)
349         if (!G_WIN32_HAVE_WIDECHAR_API ()) {
350                 /* Windows OT (9x, ME), convert from current code page to utf8 */
351                 /* it's the best we can do here ... */
352         envvar = g_locale_to_utf8(envvar, -1, NULL, NULL, NULL);
353                 /* XXX - memleak */
354                 return envvar;
355         }
356
357         /* Windows NT, 2000, XP, ... */
358         /* using the wide char version of getenv should work under all circumstances */
359
360         /* convert given varname to utf16, needed by _wgetenv */
361         varnamew = g_utf8_to_utf16(varname, -1, NULL, NULL, NULL);
362         if (varnamew == NULL) {
363                 return envvar;
364         }
365
366         /* use wide char version of getenv */
367         envvarw = _wgetenv(varnamew);
368         g_free(varnamew);
369         if (envvarw == NULL) {
370                 return envvar;
371         }
372
373         /* convert value to utf8 */
374         envvar = g_utf16_to_utf8(envvarw, -1, NULL, NULL, NULL);
375         /* XXX - memleak */
376 #endif
377
378         return envvar;
379 }
380 #endif
381
382 /*
383  * Get the directory in which personal configuration files reside;
384  * in UNIX-compatible systems, it's ".ethereal", under the user's home
385  * directory, and on Windows systems, it's "Ethereal", under %APPDATA%
386  * or, if %APPDATA% isn't set, it's "%USERPROFILE%\Application Data"
387  * (which is what %APPDATA% normally is on Windows 2000).
388  */
389 static const char *
390 get_persconffile_dir(void)
391 {
392 #ifdef _WIN32
393         char *appdatadir;
394         char *userprofiledir;
395 #else
396         const char *homedir;
397         struct passwd *pwd;
398 #endif
399         static char *pf_dir = NULL;
400
401         /* Return the cached value, if available */
402         if (pf_dir != NULL)
403                 return pf_dir;
404
405 #ifdef _WIN32
406         /*
407          * Use %APPDATA% or %USERPROFILE%, so that configuration files are
408          * stored in the user profile, rather than in the home directory.
409          * The Windows convention is to store configuration information
410          * in the user profile, and doing so means you can use
411          * Ethereal even if the home directory is an inaccessible
412          * network drive.
413          */
414         appdatadir = getenv_utf8("APPDATA");
415         if (appdatadir != NULL) {
416                 /*
417                  * Concatenate %APPDATA% with "\Ethereal".
418                  */
419                 pf_dir = g_strdup_printf("%s" G_DIR_SEPARATOR_S "%s", 
420                         appdatadir, PF_DIR);
421         } else {
422                 /*
423                  * OK, %APPDATA% wasn't set, so use
424                  * %USERPROFILE%\Application Data.
425                  */
426                 userprofiledir = getenv_utf8("USERPROFILE");
427                 if (userprofiledir != NULL) {
428                         pf_dir = g_strdup_printf(
429                             "%s" G_DIR_SEPARATOR_S "Application Data" G_DIR_SEPARATOR_S "%s",
430                             userprofiledir, PF_DIR);
431                 } else {
432                         /*
433                          * Give up and use "C:".
434                          */
435                         pf_dir = g_strdup_printf("C:" G_DIR_SEPARATOR_S "%s", PF_DIR);
436                 }
437         }
438 #else
439         /*
440          * If $HOME is set, use that.
441          */
442         homedir = getenv("HOME");
443         if (homedir == NULL) {
444                 /*
445                  * Get their home directory from the password file.
446                  * If we can't even find a password file entry for them,
447                  * use "/tmp".
448                  */
449                 pwd = getpwuid(getuid());
450                 if (pwd != NULL) {
451                         /*
452                          * This is cached, so we don't need to worry
453                          * about allocating multiple ones of them.
454                          */
455                         homedir = g_strdup(pwd->pw_dir);
456                 } else
457                         homedir = "/tmp";
458         }
459         pf_dir = g_strdup_printf("%s" G_DIR_SEPARATOR_S "%s", homedir, PF_DIR);
460 #endif
461
462         return pf_dir;
463 }
464
465 /*
466  * Create the directory that holds personal configuration files, if
467  * necessary.  If we attempted to create it, and failed, return -1 and
468  * set "*pf_dir_path_return" to the pathname of the directory we failed
469  * to create (it's g_mallocated, so our caller should free it); otherwise,
470  * return 0.
471  */
472 int
473 create_persconffile_dir(char **pf_dir_path_return)
474 {
475         const char *pf_dir_path;
476 #ifdef _WIN32
477         char *pf_dir_path_copy, *pf_dir_parent_path;
478         size_t pf_dir_parent_path_len;
479 #endif
480         struct stat s_buf;
481         int ret;
482
483         pf_dir_path = get_persconffile_dir();
484         if (eth_stat(pf_dir_path, &s_buf) != 0 && errno == ENOENT) {
485 #ifdef _WIN32
486                 /*
487                  * Does the parent directory of that directory
488                  * exist?  %APPDATA% may not exist even though
489                  * %USERPROFILE% does.
490                  *
491                  * We check for the existence of the directory
492                  * by first checking whether the parent directory
493                  * is just a drive letter and, if it's not, by
494                  * doing a "stat()" on it.  If it's a drive letter,
495                  * or if the "stat()" succeeds, we assume it exists.
496                  */
497                 pf_dir_path_copy = g_strdup(pf_dir_path);
498                 pf_dir_parent_path = get_dirname(pf_dir_path_copy);
499                 pf_dir_parent_path_len = strlen(pf_dir_parent_path);
500                 if (pf_dir_parent_path_len > 0
501                     && pf_dir_parent_path[pf_dir_parent_path_len - 1] != ':'
502                     && eth_stat(pf_dir_parent_path, &s_buf) != 0) {
503                         /*
504                          * No, it doesn't exist - make it first.
505                          */
506                         ret = eth_mkdir(pf_dir_parent_path, 0755);
507                         if (ret == -1) {
508                                 *pf_dir_path_return = pf_dir_parent_path;
509                                 return -1;
510                         }
511                 }
512                 g_free(pf_dir_path_copy);
513                 ret = eth_mkdir(pf_dir_path, 0755);
514 #else
515                 ret = eth_mkdir(pf_dir_path, 0755);
516 #endif
517         } else {
518                 /*
519                  * Something with that pathname exists; if it's not
520                  * a directory, we'll get an error if we try to put
521                  * something in it, so we don't fail here, we wait
522                  * for that attempt fo fail.
523                  */
524                 ret = 0;
525         }
526         if (ret == -1)
527                 *pf_dir_path_return = g_strdup(pf_dir_path);
528         return ret;
529 }
530
531 #ifdef _WIN32
532 /*
533  * Returns the user's home directory on Win32.
534  */
535 static const char *
536 get_home_dir(void)
537 {
538         static const char *home = NULL;
539         char *homedrive, *homepath;
540         char *homestring;
541         char *lastsep;
542
543         /* Return the cached value, if available */
544         if (home)
545                 return home;
546
547         /*
548          * XXX - should we use USERPROFILE anywhere in this process?
549          * Is there a chance that it might be set but one or more of
550          * HOMEDRIVE or HOMEPATH isn't set?
551          */
552         homedrive = getenv_utf8("HOMEDRIVE");
553         if (homedrive != NULL) {
554                 homepath = getenv_utf8("HOMEPATH");
555                 if (homepath != NULL) {
556                         /*
557                          * This is cached, so we don't need to worry about
558                          * allocating multiple ones of them.
559                          */
560                         homestring =
561                             g_malloc(strlen(homedrive) + strlen(homepath) + 1);
562                         strcpy(homestring, homedrive);
563                         strcat(homestring, homepath);
564
565                         /*
566                          * Trim off any trailing slash or backslash.
567                          */
568                         lastsep = find_last_pathname_separator(homestring);
569                         if (lastsep != NULL && *(lastsep + 1) == '\0') {
570                                 /*
571                                  * Last separator is the last character
572                                  * in the string.  Nuke it.
573                                  */
574                                 *lastsep = '\0';
575                         }
576                         home = homestring;
577                 } else
578                         home = homedrive;
579         } else {
580                 /*
581                  * Give up and use C:.
582                  */
583                 home = "C:";
584         }
585
586         return home;
587 }
588 #endif
589
590 /*
591  * Construct the path name of a personal configuration file, given the
592  * file name.
593  *
594  * On Win32, if "for_writing" is FALSE, we check whether the file exists
595  * and, if not, construct a path name relative to the ".ethereal"
596  * subdirectory of the user's home directory, and check whether that
597  * exists; if it does, we return that, so that configuration files
598  * from earlier versions can be read.
599  */
600 char *
601 get_persconffile_path(const char *filename, gboolean for_writing
602 #ifndef _WIN32
603         _U_
604 #endif
605 )
606 {
607         char *path;
608 #ifdef _WIN32
609         struct stat s_buf;
610         char *old_path;
611 #endif
612
613         path = g_strdup_printf("%s" G_DIR_SEPARATOR_S "%s", get_persconffile_dir(),
614             filename);
615 #ifdef _WIN32
616         if (!for_writing) {
617                 if (eth_stat(path, &s_buf) != 0 && errno == ENOENT) {
618                         /*
619                          * OK, it's not in the personal configuration file
620                          * directory; is it in the ".ethereal" subdirectory
621                          * of their home directory?
622                          */
623                         old_path = g_strdup_printf(
624                             "%s" G_DIR_SEPARATOR_S ".ethereal" G_DIR_SEPARATOR_S "%s",
625                             get_home_dir(), filename);
626                         if (eth_stat(old_path, &s_buf) == 0) {
627                                 /*
628                                  * OK, it exists; return it instead.
629                                  */
630                                 g_free(path);
631                                 path = old_path;
632                         }
633                 }
634         }
635 #endif
636
637         return path;
638 }
639
640 /*
641  * Construct the path name of a global configuration file, given the
642  * file name.
643  */
644 char *
645 get_datafile_path(const char *filename)
646 {
647
648         return g_strdup_printf("%s" G_DIR_SEPARATOR_S "%s", get_datafile_dir(),
649             filename);
650 }
651
652 /* Delete a file */
653 gboolean
654 deletefile(const char *path)
655 {
656         return eth_unlink(path) == 0;
657 }
658
659 /*
660  * Construct and return the path name of a file in the
661  * appropriate temporary file directory.
662  */
663 char *get_tempfile_path(const char *filename)
664 {
665
666         return g_strdup_printf("%s" G_DIR_SEPARATOR_S "%s", g_get_tmp_dir(), filename);
667 }
668
669 /*
670  * Return an error message for UNIX-style errno indications on open or
671  * create operations.
672  */
673 const char *
674 file_open_error_message(int err, gboolean for_writing)
675 {
676         const char *errmsg;
677         static char errmsg_errno[1024+1];
678
679         switch (err) {
680
681         case ENOENT:
682                 if (for_writing)
683                         errmsg = "The path to the file \"%s\" doesn't exist.";
684                 else
685                         errmsg = "The file \"%s\" doesn't exist.";
686                 break;
687
688         case EACCES:
689                 if (for_writing)
690                         errmsg = "You don't have permission to create or write to the file \"%s\".";
691                 else
692                         errmsg = "You don't have permission to read the file \"%s\".";
693                 break;
694
695         case EISDIR:
696                 errmsg = "\"%s\" is a directory (folder), not a file.";
697                 break;
698
699         case ENOSPC:
700                 errmsg = "The file \"%s\" could not be created because there is no space left on the file system.";
701                 break;
702
703 #ifdef EDQUOT
704         case EDQUOT:
705                 errmsg = "The file \"%s\" could not be created because you are too close to, or over, your disk quota.";
706                 break;
707 #endif
708
709         default:
710                 g_snprintf(errmsg_errno, sizeof(errmsg_errno),
711                                 "The file \"%%s\" could not be %s: %s.",
712                                 for_writing ? "created" : "opened",
713                                 strerror(err));
714                 errmsg = errmsg_errno;
715                 break;
716         }
717         return errmsg;
718 }
719
720 /*
721  * Return an error message for UNIX-style errno indications on write
722  * operations.
723  */
724 const char *
725 file_write_error_message(int err)
726 {
727         const char *errmsg;
728         static char errmsg_errno[1024+1];
729
730         switch (err) {
731
732         case ENOSPC:
733                 errmsg = "The file \"%s\" could not be saved because there is no space left on the file system.";
734                 break;
735
736 #ifdef EDQUOT
737         case EDQUOT:
738                 errmsg = "The file \"%s\" could not be saved because you are too close to, or over, your disk quota.";
739                 break;
740 #endif
741
742         default:
743                 g_snprintf(errmsg_errno, sizeof(errmsg_errno),
744                     "An error occurred while writing to the file \"%%s\": %s.",
745                     strerror(err));
746                 errmsg = errmsg_errno;
747                 break;
748         }
749         return errmsg;
750 }
751
752
753 gboolean
754 file_exists(const char *fname)
755 {
756   struct stat   file_stat;
757
758
759 #ifdef _WIN32
760   /*
761    * This is a bit tricky on win32. The st_ino field is documented as:
762    * "The inode, and therefore st_ino, has no meaning in the FAT, ..."
763    * but it *is* set to zero if stat() returns without an error,
764    * so this is working, but maybe not quite the way expected. ULFL
765    */
766    file_stat.st_ino = 1;   /* this will make things work if an error occured */
767    eth_stat(fname, &file_stat);
768    if (file_stat.st_ino == 0) {
769        return TRUE;
770    } else {
771        return FALSE;
772    }
773 #else
774    if (eth_stat(fname, &file_stat) != 0 && errno == ENOENT) {
775        return FALSE;
776    } else {
777        return TRUE;
778    }
779 #endif
780    
781 }
782
783 /*
784  * Check that the from file is not the same as to file
785  * We do it here so we catch all cases ...
786  * Unfortunately, the file requester gives us an absolute file
787  * name and the read file name may be relative (if supplied on
788  * the command line), so we can't just compare paths. From Joerg Mayer.
789  */
790 gboolean
791 files_identical(const char *fname1, const char *fname2)
792 {
793     /* Two different implementations, because:
794      *
795      * - _fullpath is not available on UN*X, so we can't get full
796      *   paths and compare them (which wouldn't work with hard links
797      *   in any case);
798      *
799      * - st_ino isn't filled in with a meaningful value on Windows.
800      */
801 #ifdef _WIN32
802     char full1[MAX_PATH], full2[MAX_PATH];
803
804     /*
805      * Get the absolute full paths of the file and compare them.
806      * That won't work if you have hard links, but those aren't
807      * much used on Windows, even though NTFS supports them.
808      *
809      * XXX - will _fullpath work with UNC?
810      */
811     if( _fullpath( full1, fname1, MAX_PATH ) == NULL ) {
812         return FALSE;
813     }
814
815     if( _fullpath( full2, fname2, MAX_PATH ) == NULL ) {
816         return FALSE;
817     }
818     
819     if(strcmp(full1, full2) == 0) {
820         return TRUE;
821     } else {
822         return FALSE;
823     }
824 #else
825   struct stat   filestat1, filestat2;
826
827    /*
828     * Compare st_dev and st_ino.
829     */
830    if (eth_stat(fname1, &filestat1) == -1)
831        return FALSE;    /* can't get info about the first file */
832    if (eth_stat(fname2, &filestat2) == -1)
833        return FALSE;    /* can't get info about the second file */
834    return (filestat1.st_dev == filestat2.st_dev &&
835            filestat1.st_ino == filestat2.st_ino);
836 #endif
837 }
838