fcstat.c revision 1887081f
1/*
2 * Copyright © 2000 Keith Packard
3 * Copyright © 2005 Patrick Lam
4 *
5 * Permission to use, copy, modify, distribute, and sell this software and its
6 * documentation for any purpose is hereby granted without fee, provided that
7 * the above copyright notice appear in all copies and that both that
8 * copyright notice and this permission notice appear in supporting
9 * documentation, and that the name of the author(s) not be used in
10 * advertising or publicity pertaining to distribution of the software without
11 * specific, written prior permission.  The authors make no
12 * representations about the suitability of this software for any purpose.  It
13 * is provided "as is" without express or implied warranty.
14 *
15 * THE AUTHOR(S) DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
16 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
17 * EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY SPECIAL, INDIRECT OR
18 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
19 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
20 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
21 * PERFORMANCE OF THIS SOFTWARE.
22 */
23#include "fcint.h"
24#include "fcarch.h"
25#include <dirent.h>
26#include <limits.h>
27#include <sys/types.h>
28#include <sys/stat.h>
29#include <fcntl.h>
30#ifdef HAVE_SYS_VFS_H
31#include <sys/vfs.h>
32#endif
33#ifdef HAVE_SYS_STATVFS_H
34#include <sys/statvfs.h>
35#endif
36#ifdef HAVE_SYS_STATFS_H
37#include <sys/statfs.h>
38#endif
39#ifdef HAVE_SYS_PARAM_H
40#include <sys/param.h>
41#endif
42#ifdef HAVE_SYS_MOUNT_H
43#include <sys/mount.h>
44#endif
45#include <errno.h>
46
47#ifdef _WIN32
48#ifdef __GNUC__
49typedef long long INT64;
50#define EPOCH_OFFSET 11644473600ll
51#else
52#define EPOCH_OFFSET 11644473600i64
53typedef __int64 INT64;
54#endif
55
56/* Workaround for problems in the stat() in the Microsoft C library:
57 *
58 * 1) stat() uses FindFirstFile() to get the file
59 * attributes. Unfortunately this API doesn't return correct values
60 * for modification time of a directory until some time after a file
61 * or subdirectory has been added to the directory. (This causes
62 * run-test.sh to fail, for instance.) GetFileAttributesEx() is
63 * better, it returns the updated timestamp right away.
64 *
65 * 2) stat() does some strange things related to backward
66 * compatibility with the local time timestamps on FAT volumes and
67 * daylight saving time. This causes problems after the switches
68 * to/from daylight saving time. See
69 * http://bugzilla.gnome.org/show_bug.cgi?id=154968 , especially
70 * comment #30, and http://www.codeproject.com/datetime/dstbugs.asp .
71 * We don't need any of that, FAT and Win9x are as good as dead. So
72 * just use the UTC timestamps from NTFS, converted to the Unix epoch.
73 */
74
75int
76FcStat (const FcChar8 *file, struct stat *statb)
77{
78    WIN32_FILE_ATTRIBUTE_DATA wfad;
79    char full_path_name[MAX_PATH];
80    char *basename;
81    DWORD rc;
82
83    if (!GetFileAttributesEx ((LPCSTR) file, GetFileExInfoStandard, &wfad))
84	return -1;
85
86    statb->st_dev = 0;
87
88    /* Calculate a pseudo inode number as a hash of the full path name.
89     * Call GetLongPathName() to get the spelling of the path name as it
90     * is on disk.
91     */
92    rc = GetFullPathName ((LPCSTR) file, sizeof (full_path_name), full_path_name, &basename);
93    if (rc == 0 || rc > sizeof (full_path_name))
94	return -1;
95
96    rc = GetLongPathName (full_path_name, full_path_name, sizeof (full_path_name));
97    statb->st_ino = FcStringHash ((const FcChar8 *) full_path_name);
98
99    statb->st_mode = _S_IREAD | _S_IWRITE;
100    statb->st_mode |= (statb->st_mode >> 3) | (statb->st_mode >> 6);
101
102    if (wfad.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
103	statb->st_mode |= _S_IFDIR;
104    else
105	statb->st_mode |= _S_IFREG;
106
107    statb->st_nlink = 1;
108    statb->st_uid = statb->st_gid = 0;
109    statb->st_rdev = 0;
110
111    if (wfad.nFileSizeHigh > 0)
112	return -1;
113    statb->st_size = wfad.nFileSizeLow;
114
115    statb->st_atime = (*(INT64 *)&wfad.ftLastAccessTime)/10000000 - EPOCH_OFFSET;
116    statb->st_mtime = (*(INT64 *)&wfad.ftLastWriteTime)/10000000 - EPOCH_OFFSET;
117    statb->st_ctime = statb->st_mtime;
118
119    return 0;
120}
121
122#else
123
124int
125FcStat (const FcChar8 *file, struct stat *statb)
126{
127  return stat ((char *) file, statb);
128}
129
130/* Adler-32 checksum implementation */
131struct Adler32 {
132    int a;
133    int b;
134};
135
136static void
137Adler32Init (struct Adler32 *ctx)
138{
139    ctx->a = 1;
140    ctx->b = 0;
141}
142
143static void
144Adler32Update (struct Adler32 *ctx, const char *data, int data_len)
145{
146    while (data_len--)
147    {
148	ctx->a = (ctx->a + *data++) % 65521;
149	ctx->b = (ctx->b + ctx->a) % 65521;
150    }
151}
152
153static int
154Adler32Finish (struct Adler32 *ctx)
155{
156    return ctx->a + (ctx->b << 16);
157}
158
159#ifdef HAVE_STRUCT_DIRENT_D_TYPE
160/* dirent.d_type can be relied upon on FAT filesystem */
161static FcBool
162FcDirChecksumScandirFilter(const struct dirent *entry)
163{
164    return entry->d_type != DT_DIR;
165}
166#endif
167
168static int
169FcDirChecksumScandirSorter(const struct dirent **lhs, const struct dirent **rhs)
170{
171    return strcmp((*lhs)->d_name, (*rhs)->d_name);
172}
173
174static void
175free_dirent (struct dirent **p)
176{
177    struct dirent **x;
178
179    for (x = p; *x != NULL; x++)
180	free (*x);
181
182    free (p);
183}
184
185int
186FcScandir (const char		*dirp,
187	   struct dirent	***namelist,
188	   int (*filter) (const struct dirent *),
189	   int (*compar) (const struct dirent **, const struct dirent **));
190
191int
192FcScandir (const char		*dirp,
193	   struct dirent	***namelist,
194	   int (*filter) (const struct dirent *),
195	   int (*compar) (const struct dirent **, const struct dirent **))
196{
197    DIR *d;
198    struct dirent *dent, *p, **dlist, **dlp;
199    size_t lsize = 128, n = 0;
200
201    d = opendir (dirp);
202    if (!d)
203	return -1;
204
205    dlist = malloc (sizeof (struct dirent *) * lsize);
206    if (!dlist)
207    {
208	closedir (d);
209	errno = ENOMEM;
210
211	return -1;
212    }
213    *dlist = NULL;
214    while ((dent = readdir (d)))
215    {
216	if (!filter || (filter) (dent))
217	{
218	    size_t dentlen = FcPtrToOffset (dent, dent->d_name) + strlen (dent->d_name) + 1;
219	    dentlen = ((dentlen + ALIGNOF_VOID_P - 1) & ~(ALIGNOF_VOID_P - 1));
220	    p = (struct dirent *) malloc (dentlen);
221	    if (!p)
222	    {
223		free_dirent (dlist);
224		closedir (d);
225		errno = ENOMEM;
226
227		return -1;
228	    }
229	    memcpy (p, dent, dentlen);
230	    if ((n + 1) >= lsize)
231	    {
232		lsize += 128;
233		dlp = (struct dirent **) realloc (dlist, sizeof (struct dirent *) * lsize);
234		if (!dlp)
235		{
236		    free (p);
237		    free_dirent (dlist);
238		    closedir (d);
239		    errno = ENOMEM;
240
241		    return -1;
242		}
243		dlist = dlp;
244	    }
245	    dlist[n++] = p;
246	    dlist[n] = NULL;
247	}
248    }
249    closedir (d);
250
251    qsort (dlist, n, sizeof (struct dirent *), (int (*) (const void *, const void *))compar);
252
253    *namelist = dlist;
254
255    return n;
256}
257
258static int
259FcDirChecksum (const FcChar8 *dir, time_t *checksum)
260{
261    struct Adler32 ctx;
262    struct dirent **files;
263    int n;
264    int ret = 0;
265    size_t len = strlen ((const char *)dir);
266
267    Adler32Init (&ctx);
268
269    n = FcScandir ((const char *)dir, &files,
270#ifdef HAVE_STRUCT_DIRENT_D_TYPE
271		 &FcDirChecksumScandirFilter,
272#else
273		 NULL,
274#endif
275		 &FcDirChecksumScandirSorter);
276    if (n == -1)
277	return -1;
278
279    while (n--)
280    {
281	size_t dlen = strlen (files[n]->d_name);
282	int dtype;
283
284#ifdef HAVE_STRUCT_DIRENT_D_TYPE
285	dtype = files[n]->d_type;
286	if (dtype == DT_UNKNOWN)
287	{
288#endif
289	struct stat statb;
290	char *f = malloc (len + 1 + dlen + 1);
291
292	if (!f)
293	{
294	    ret = -1;
295	    goto bail;
296	}
297	memcpy (f, dir, len);
298	f[len] = FC_DIR_SEPARATOR;
299	memcpy (&f[len + 1], files[n]->d_name, dlen);
300	f[len + 1 + dlen] = 0;
301	if (lstat (f, &statb) < 0)
302	{
303	    ret = -1;
304	    free (f);
305	    goto bail;
306	}
307	if (S_ISDIR (statb.st_mode))
308	{
309	    free (f);
310	    goto bail;
311	}
312
313	free (f);
314	dtype = statb.st_mode;
315#ifdef HAVE_STRUCT_DIRENT_D_TYPE
316	}
317#endif
318	Adler32Update (&ctx, files[n]->d_name, dlen + 1);
319	Adler32Update (&ctx, (char *)&dtype, sizeof (int));
320
321      bail:
322	free (files[n]);
323    }
324    free (files);
325    if (ret == -1)
326	return -1;
327
328    *checksum = Adler32Finish (&ctx);
329
330    return 0;
331}
332#endif /* _WIN32 */
333
334int
335FcStatChecksum (const FcChar8 *file, struct stat *statb)
336{
337    if (FcStat (file, statb) == -1)
338        return -1;
339
340#ifndef _WIN32
341    /* We have a workaround of the broken stat() in FcStat() for Win32.
342     * No need to do something further more.
343     */
344    if (FcIsFsMtimeBroken (file))
345    {
346        if (FcDirChecksum (file, &statb->st_mtime) == -1)
347            return -1;
348    }
349#endif
350
351    return 0;
352}
353
354static int
355FcFStatFs (int fd, FcStatFS *statb)
356{
357    const char *p = NULL;
358    int ret = -1;
359    FcBool flag = FcFalse;
360
361#if defined(HAVE_FSTATVFS) && (defined(HAVE_STRUCT_STATVFS_F_BASETYPE) || defined(HAVE_STRUCT_STATVFS_F_FSTYPENAME))
362    struct statvfs buf;
363
364    memset (statb, 0, sizeof (FcStatFS));
365
366    if ((ret = fstatvfs (fd, &buf)) == 0)
367    {
368#  if defined(HAVE_STRUCT_STATVFS_F_BASETYPE)
369	p = buf.f_basetype;
370#  elif defined(HAVE_STRUCT_STATVFS_F_FSTYPENAME)
371	p = buf.f_fstypename;
372#  endif
373    }
374#elif defined(HAVE_FSTATFS) && (defined(HAVE_STRUCT_STATFS_F_FLAGS) || defined(HAVE_STRUCT_STATFS_F_FSTYPENAME) || defined(__linux__))
375    struct statfs buf;
376
377    memset (statb, 0, sizeof (FcStatFS));
378
379    if ((ret = fstatfs (fd, &buf)) == 0)
380    {
381#  if defined(HAVE_STRUCT_STATFS_F_FLAGS) && defined(MNT_LOCAL)
382	statb->is_remote_fs = !(buf.f_flags & MNT_LOCAL);
383	flag = FcTrue;
384#  endif
385#  if defined(HAVE_STRUCT_STATFS_F_FSTYPENAME)
386	p = buf.f_fstypename;
387#  elif defined(__linux__)
388	switch (buf.f_type)
389	{
390	case 0x6969: /* nfs */
391	    statb->is_remote_fs = FcTrue;
392	    break;
393	case 0x4d44: /* fat */
394	    statb->is_mtime_broken = FcTrue;
395	    break;
396	default:
397	    break;
398	}
399
400	return ret;
401#  else
402#    error "BUG: No way to figure out with fstatfs()"
403#  endif
404    }
405#endif
406    if (p)
407    {
408	if (!flag && strcmp (p, "nfs") == 0)
409	    statb->is_remote_fs = FcTrue;
410	if (strcmp (p, "msdosfs") == 0 ||
411	    strcmp (p, "pcfs") == 0)
412	    statb->is_mtime_broken = FcTrue;
413    }
414
415    return ret;
416}
417
418FcBool
419FcIsFsMmapSafe (int fd)
420{
421    FcStatFS statb;
422
423    if (FcFStatFs (fd, &statb) < 0)
424	return FcTrue;
425
426    return !statb.is_remote_fs;
427}
428
429FcBool
430FcIsFsMtimeBroken (const FcChar8 *dir)
431{
432    int fd = FcOpen ((const char *) dir, O_RDONLY);
433
434    if (fd != -1)
435    {
436	FcStatFS statb;
437	int ret = FcFStatFs (fd, &statb);
438
439	close (fd);
440	if (ret < 0)
441	    return FcFalse;
442
443	return statb.is_mtime_broken;
444    }
445
446    return FcFalse;
447}
448
449#define __fcstat__
450#include "fcaliastail.h"
451#undef __fcstat__
452