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#ifdef HAVE_DIRENT_H
26#include <dirent.h>
27#endif
28#include <limits.h>
29#include <sys/types.h>
30#include <sys/stat.h>
31#include <fcntl.h>
32#ifdef HAVE_SYS_VFS_H
33#include <sys/vfs.h>
34#endif
35#ifdef HAVE_SYS_STATVFS_H
36#include <sys/statvfs.h>
37#endif
38#ifdef HAVE_SYS_STATFS_H
39#include <sys/statfs.h>
40#endif
41#ifdef HAVE_SYS_PARAM_H
42#include <sys/param.h>
43#endif
44#ifdef HAVE_SYS_MOUNT_H
45#include <sys/mount.h>
46#endif
47#include <errno.h>
48
49#ifdef _WIN32
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 struct dirent **lhs, const struct dirent **rhs)
172{
173    return strcmp((*lhs)->d_name, (*rhs)->d_name);
174}
175
176static void
177free_dirent (struct dirent **p)
178{
179    struct dirent **x;
180
181    for (x = p; *x != NULL; x++)
182	free (*x);
183
184    free (p);
185}
186
187int
188FcScandir (const char		*dirp,
189	   struct dirent	***namelist,
190	   int (*filter) (const struct dirent *),
191	   int (*compar) (const struct dirent **, const struct dirent **));
192
193int
194FcScandir (const char		*dirp,
195	   struct dirent	***namelist,
196	   int (*filter) (const struct dirent *),
197	   int (*compar) (const struct dirent **, const struct dirent **))
198{
199    DIR *d;
200    struct dirent *dent, *p, **dlist, **dlp;
201    size_t lsize = 128, n = 0;
202
203    d = opendir (dirp);
204    if (!d)
205	return -1;
206
207    dlist = malloc (sizeof (struct dirent *) * lsize);
208    if (!dlist)
209    {
210	closedir (d);
211	errno = ENOMEM;
212
213	return -1;
214    }
215    *dlist = NULL;
216    while ((dent = readdir (d)))
217    {
218	if (!filter || (filter) (dent))
219	{
220	    size_t dentlen = FcPtrToOffset (dent, dent->d_name) + strlen (dent->d_name) + 1;
221	    dentlen = ((dentlen + ALIGNOF_VOID_P - 1) & ~(ALIGNOF_VOID_P - 1));
222	    p = (struct dirent *) malloc (dentlen);
223	    if (!p)
224	    {
225		free_dirent (dlist);
226		closedir (d);
227		errno = ENOMEM;
228
229		return -1;
230	    }
231	    memcpy (p, dent, dentlen);
232	    if ((n + 1) >= lsize)
233	    {
234		lsize += 128;
235		dlp = (struct dirent **) realloc (dlist, sizeof (struct dirent *) * lsize);
236		if (!dlp)
237		{
238		    free (p);
239		    free_dirent (dlist);
240		    closedir (d);
241		    errno = ENOMEM;
242
243		    return -1;
244		}
245		dlist = dlp;
246	    }
247	    dlist[n++] = p;
248	    dlist[n] = NULL;
249	}
250    }
251    closedir (d);
252
253    qsort (dlist, n, sizeof (struct dirent *), (int (*) (const void *, const void *))compar);
254
255    *namelist = dlist;
256
257    return n;
258}
259
260static int
261FcDirChecksum (const FcChar8 *dir, time_t *checksum)
262{
263    struct Adler32 ctx;
264    struct dirent **files;
265    int n;
266    int ret = 0;
267    size_t len = strlen ((const char *)dir);
268
269    Adler32Init (&ctx);
270
271    n = FcScandir ((const char *)dir, &files,
272#ifdef HAVE_STRUCT_DIRENT_D_TYPE
273		 &FcDirChecksumScandirFilter,
274#else
275		 NULL,
276#endif
277		 &FcDirChecksumScandirSorter);
278    if (n == -1)
279	return -1;
280
281    while (n--)
282    {
283	size_t dlen = strlen (files[n]->d_name);
284	int dtype;
285
286#ifdef HAVE_STRUCT_DIRENT_D_TYPE
287	dtype = files[n]->d_type;
288	if (dtype == DT_UNKNOWN)
289	{
290#endif
291	struct stat statb;
292	char *f = malloc (len + 1 + dlen + 1);
293
294	if (!f)
295	{
296	    ret = -1;
297	    goto bail;
298	}
299	memcpy (f, dir, len);
300	f[len] = FC_DIR_SEPARATOR;
301	memcpy (&f[len + 1], files[n]->d_name, dlen);
302	f[len + 1 + dlen] = 0;
303	if (lstat (f, &statb) < 0)
304	{
305	    ret = -1;
306	    free (f);
307	    goto bail;
308	}
309	if (S_ISDIR (statb.st_mode))
310	{
311	    free (f);
312	    goto bail;
313	}
314
315	free (f);
316	dtype = statb.st_mode;
317#ifdef HAVE_STRUCT_DIRENT_D_TYPE
318	}
319#endif
320	Adler32Update (&ctx, files[n]->d_name, dlen + 1);
321	Adler32Update (&ctx, (char *)&dtype, sizeof (int));
322
323      bail:
324	free (files[n]);
325    }
326    free (files);
327    if (ret == -1)
328	return -1;
329
330    *checksum = Adler32Finish (&ctx);
331
332    return 0;
333}
334#endif /* _WIN32 */
335
336int
337FcStatChecksum (const FcChar8 *file, struct stat *statb)
338{
339    if (FcStat (file, statb) == -1)
340        return -1;
341
342#ifndef _WIN32
343    /* We have a workaround of the broken stat() in FcStat() for Win32.
344     * No need to do something further more.
345     */
346    if (FcIsFsMtimeBroken (file))
347    {
348        if (FcDirChecksum (file, &statb->st_mtime) == -1)
349            return -1;
350    }
351#endif
352
353    return 0;
354}
355
356static int
357FcFStatFs (int fd, FcStatFS *statb)
358{
359    const char *p = NULL;
360    int ret = -1;
361    FcBool flag = FcFalse;
362
363#if defined(HAVE_FSTATVFS) && (defined(HAVE_STRUCT_STATVFS_F_BASETYPE) || defined(HAVE_STRUCT_STATVFS_F_FSTYPENAME))
364    struct statvfs buf;
365
366    memset (statb, 0, sizeof (FcStatFS));
367
368    if ((ret = fstatvfs (fd, &buf)) == 0)
369    {
370#  if defined(HAVE_STRUCT_STATVFS_F_BASETYPE)
371	p = buf.f_basetype;
372#  elif defined(HAVE_STRUCT_STATVFS_F_FSTYPENAME)
373	p = buf.f_fstypename;
374#  endif
375    }
376#elif defined(HAVE_FSTATFS) && (defined(HAVE_STRUCT_STATFS_F_FLAGS) || defined(HAVE_STRUCT_STATFS_F_FSTYPENAME) || defined(__linux__))
377    struct statfs buf;
378
379    memset (statb, 0, sizeof (FcStatFS));
380
381    if ((ret = fstatfs (fd, &buf)) == 0)
382    {
383#  if defined(HAVE_STRUCT_STATFS_F_FLAGS) && defined(MNT_LOCAL)
384	statb->is_remote_fs = !(buf.f_flags & MNT_LOCAL);
385	flag = FcTrue;
386#  endif
387#  if defined(HAVE_STRUCT_STATFS_F_FSTYPENAME)
388	p = buf.f_fstypename;
389#  elif defined(__linux__) || defined (__EMSCRIPTEN__)
390	switch (buf.f_type)
391	{
392	case 0x6969: /* nfs */
393	    statb->is_remote_fs = FcTrue;
394	    break;
395	case 0x4d44: /* fat */
396	    statb->is_mtime_broken = FcTrue;
397	    break;
398	default:
399	    break;
400	}
401
402	return ret;
403#  else
404#    error "BUG: No way to figure out with fstatfs()"
405#  endif
406    }
407#endif
408    if (p)
409    {
410	if (!flag && strcmp (p, "nfs") == 0)
411	    statb->is_remote_fs = FcTrue;
412	if (strcmp (p, "msdosfs") == 0 ||
413	    strcmp (p, "pcfs") == 0)
414	    statb->is_mtime_broken = FcTrue;
415    }
416
417    return ret;
418}
419
420FcBool
421FcIsFsMmapSafe (int fd)
422{
423    FcStatFS statb;
424
425    if (FcFStatFs (fd, &statb) < 0)
426	return FcTrue;
427
428    return !statb.is_remote_fs;
429}
430
431FcBool
432FcIsFsMtimeBroken (const FcChar8 *dir)
433{
434    int fd = FcOpen ((const char *) dir, O_RDONLY);
435
436    if (fd != -1)
437    {
438	FcStatFS statb;
439	int ret = FcFStatFs (fd, &statb);
440
441	close (fd);
442	if (ret < 0)
443	    return FcFalse;
444
445	return statb.is_mtime_broken;
446    }
447
448    return FcFalse;
449}
450
451#define __fcstat__
452#include "fcaliastail.h"
453#undef __fcstat__
454