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