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