fcstat.c revision b2a52337
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
46#ifdef _WIN32
47#ifdef __GNUC__
48typedef long long INT64;
49#define EPOCH_OFFSET 11644473600ll
50#else
51#define EPOCH_OFFSET 11644473600i64
52typedef __int64 INT64;
53#endif
54
55/* Workaround for problems in the stat() in the Microsoft C library:
56 *
57 * 1) stat() uses FindFirstFile() to get the file
58 * attributes. Unfortunately this API doesn't return correct values
59 * for modification time of a directory until some time after a file
60 * or subdirectory has been added to the directory. (This causes
61 * run-test.sh to fail, for instance.) GetFileAttributesEx() is
62 * better, it returns the updated timestamp right away.
63 *
64 * 2) stat() does some strange things related to backward
65 * compatibility with the local time timestamps on FAT volumes and
66 * daylight saving time. This causes problems after the switches
67 * to/from daylight saving time. See
68 * http://bugzilla.gnome.org/show_bug.cgi?id=154968 , especially
69 * comment #30, and http://www.codeproject.com/datetime/dstbugs.asp .
70 * We don't need any of that, FAT and Win9x are as good as dead. So
71 * just use the UTC timestamps from NTFS, converted to the Unix epoch.
72 */
73
74int
75FcStat (const FcChar8 *file, struct stat *statb)
76{
77    WIN32_FILE_ATTRIBUTE_DATA wfad;
78    char full_path_name[MAX_PATH];
79    char *basename;
80    DWORD rc;
81
82    if (!GetFileAttributesEx ((LPCSTR) file, GetFileExInfoStandard, &wfad))
83	return -1;
84
85    statb->st_dev = 0;
86
87    /* Calculate a pseudo inode number as a hash of the full path name.
88     * Call GetLongPathName() to get the spelling of the path name as it
89     * is on disk.
90     */
91    rc = GetFullPathName ((LPCSTR) file, sizeof (full_path_name), full_path_name, &basename);
92    if (rc == 0 || rc > sizeof (full_path_name))
93	return -1;
94
95    rc = GetLongPathName (full_path_name, full_path_name, sizeof (full_path_name));
96    statb->st_ino = FcStringHash ((const FcChar8 *) full_path_name);
97
98    statb->st_mode = _S_IREAD | _S_IWRITE;
99    statb->st_mode |= (statb->st_mode >> 3) | (statb->st_mode >> 6);
100
101    if (wfad.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
102	statb->st_mode |= _S_IFDIR;
103    else
104	statb->st_mode |= _S_IFREG;
105
106    statb->st_nlink = 1;
107    statb->st_uid = statb->st_gid = 0;
108    statb->st_rdev = 0;
109
110    if (wfad.nFileSizeHigh > 0)
111	return -1;
112    statb->st_size = wfad.nFileSizeLow;
113
114    statb->st_atime = (*(INT64 *)&wfad.ftLastAccessTime)/10000000 - EPOCH_OFFSET;
115    statb->st_mtime = (*(INT64 *)&wfad.ftLastWriteTime)/10000000 - EPOCH_OFFSET;
116    statb->st_ctime = statb->st_mtime;
117
118    return 0;
119}
120
121#else
122
123int
124FcStat (const FcChar8 *file, struct stat *statb)
125{
126  return stat ((char *) file, statb);
127}
128
129/* Adler-32 checksum implementation */
130struct Adler32 {
131    int a;
132    int b;
133};
134
135static void
136Adler32Init (struct Adler32 *ctx)
137{
138    ctx->a = 1;
139    ctx->b = 0;
140}
141
142static void
143Adler32Update (struct Adler32 *ctx, const char *data, int data_len)
144{
145    while (data_len--)
146    {
147	ctx->a = (ctx->a + *data++) % 65521;
148	ctx->b = (ctx->b + ctx->a) % 65521;
149    }
150}
151
152static int
153Adler32Finish (struct Adler32 *ctx)
154{
155    return ctx->a + (ctx->b << 16);
156}
157
158#ifdef HAVE_STRUCT_DIRENT_D_TYPE
159/* dirent.d_type can be relied upon on FAT filesystem */
160static FcBool
161FcDirChecksumScandirFilter(const struct dirent *entry)
162{
163    return entry->d_type != DT_DIR;
164}
165#endif
166
167#ifdef HAVE_SCANDIR
168static int
169FcDirChecksumScandirSorter(const void *arg1, const void *arg2)
170{
171    const struct dirent * const *lhs = arg1;
172    const struct dirent * const *rhs = arg2;
173
174    return strcmp((*lhs)->d_name, (*rhs)->d_name);
175}
176#elif HAVE_SCANDIR_VOID_P
177static int
178FcDirChecksumScandirSorter(const void *a, const void *b)
179{
180    const struct dirent *lhs = a, *rhs = b;
181
182    return strcmp(lhs->d_name, rhs->d_name);
183}
184#endif
185
186static int
187FcDirChecksum (const FcChar8 *dir, time_t *checksum)
188{
189    struct Adler32 ctx;
190    struct dirent **files;
191    int n;
192    int ret = 0;
193    size_t len = strlen ((const char *)dir);
194
195    Adler32Init (&ctx);
196
197    n = scandir ((const char *)dir, &files,
198#ifdef HAVE_STRUCT_DIRENT_D_TYPE
199		 &FcDirChecksumScandirFilter,
200#else
201		 NULL,
202#endif
203		 &FcDirChecksumScandirSorter);
204    if (n == -1)
205	return -1;
206
207    while (n--)
208    {
209	size_t dlen = strlen (files[n]->d_name);
210	int dtype;
211
212#ifdef HAVE_STRUCT_DIRENT_D_TYPE
213	dtype = files[n]->d_type;
214	if (dtype == DT_UNKNOWN)
215	{
216#endif
217	struct stat statb;
218	char f[PATH_MAX + 1];
219
220	memcpy (f, dir, len);
221	f[len] = FC_DIR_SEPARATOR;
222	memcpy (&f[len + 1], files[n]->d_name, dlen);
223	f[len + 1 + dlen] = 0;
224	if (lstat (f, &statb) < 0)
225	{
226	    ret = -1;
227	    goto bail;
228	}
229	if (S_ISDIR (statb.st_mode))
230	    goto bail;
231
232	dtype = statb.st_mode;
233#ifdef HAVE_STRUCT_DIRENT_D_TYPE
234	}
235#endif
236	Adler32Update (&ctx, files[n]->d_name, dlen + 1);
237	Adler32Update (&ctx, (char *)&dtype, sizeof (int));
238
239      bail:
240	free (files[n]);
241    }
242    free (files);
243    if (ret == -1)
244	return -1;
245
246    *checksum = Adler32Finish (&ctx);
247
248    return 0;
249}
250#endif /* _WIN32 */
251
252int
253FcStatChecksum (const FcChar8 *file, struct stat *statb)
254{
255    if (FcStat (file, statb) == -1)
256        return -1;
257
258#ifndef _WIN32
259    /* We have a workaround of the broken stat() in FcStat() for Win32.
260     * No need to do something further more.
261     */
262    if (FcIsFsMtimeBroken (file))
263    {
264        if (FcDirChecksum (file, &statb->st_mtime) == -1)
265            return -1;
266    }
267#endif
268
269    return 0;
270}
271
272static int
273FcFStatFs (int fd, FcStatFS *statb)
274{
275    const char *p = NULL;
276    int ret = -1;
277    FcBool flag = FcFalse;
278
279#if defined(HAVE_FSTATVFS) && (defined(HAVE_STRUCT_STATVFS_F_BASETYPE) || defined(HAVE_STRUCT_STATVFS_F_FSTYPENAME))
280    struct statvfs buf;
281
282    memset (statb, 0, sizeof (FcStatFS));
283
284    if ((ret = fstatvfs (fd, &buf)) == 0)
285    {
286#  if defined(HAVE_STRUCT_STATVFS_F_BASETYPE)
287	p = buf.f_basetype;
288#  elif defined(HAVE_STRUCT_STATVFS_F_FSTYPENAME)
289	p = buf.f_fstypename;
290#  endif
291    }
292#elif defined(HAVE_FSTATFS) && (defined(HAVE_STRUCT_STATFS_F_FLAGS) || defined(HAVE_STRUCT_STATFS_F_FSTYPENAME) || defined(__linux__))
293    struct statfs buf;
294
295    memset (statb, 0, sizeof (FcStatFS));
296
297    if ((ret = fstatfs (fd, &buf)) == 0)
298    {
299#  if defined(HAVE_STRUCT_STATFS_F_FLAGS) && defined(MNT_LOCAL)
300	statb->is_remote_fs = !(buf.f_flags & MNT_LOCAL);
301	flag = FcTrue;
302#  endif
303#  if defined(HAVE_STRUCT_STATFS_F_FSTYPENAME)
304	p = buf.f_fstypename;
305#  elif defined(__linux__)
306	switch (buf.f_type)
307	{
308	case 0x6969: /* nfs */
309	    statb->is_remote_fs = FcTrue;
310	    break;
311	case 0x4d44: /* fat */
312	    statb->is_mtime_broken = FcTrue;
313	    break;
314	default:
315	    break;
316	}
317
318	return ret;
319#  else
320#    error "BUG: No way to figure out with fstatfs()"
321#  endif
322    }
323#endif
324    if (p)
325    {
326	if (!flag && strcmp (p, "nfs") == 0)
327	    statb->is_remote_fs = FcTrue;
328	if (strcmp (p, "msdosfs") == 0 ||
329	    strcmp (p, "pcfs") == 0)
330	    statb->is_mtime_broken = FcTrue;
331    }
332
333    return ret;
334}
335
336FcBool
337FcIsFsMmapSafe (int fd)
338{
339    FcStatFS statb;
340
341    if (FcFStatFs (fd, &statb) < 0)
342	return FcTrue;
343
344    return !statb.is_remote_fs;
345}
346
347FcBool
348FcIsFsMtimeBroken (const FcChar8 *dir)
349{
350    int fd = FcOpen ((const char *) dir, O_RDONLY);
351
352    if (fd != -1)
353    {
354	FcStatFS statb;
355	int ret = FcFStatFs (fd, &statb);
356
357	close (fd);
358	if (ret < 0)
359	    return FcFalse;
360
361	return statb.is_mtime_broken;
362    }
363
364    return FcFalse;
365}
366