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__
51 typedef long long INT64;
52 #define EPOCH_OFFSET 11644473600ll
53 #else
54 #define EPOCH_OFFSET 11644473600i64
55 typedef __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
77 int
78 FcStat (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
126 int
127 FcStat (const FcChar8 *file, struct stat *statb)
128 {
129 return stat ((char *) file, statb);
130 }
131
132 /* Adler-32 checksum implementation */
133 struct Adler32 {
134 int a;
135 int b;
136 };
137
138 static void
139 Adler32Init (struct Adler32 *ctx)
140 {
141 ctx->a = 1;
142 ctx->b = 0;
143 }
144
145 static void
146 Adler32Update (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
155 static int
156 Adler32Finish (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 */
163 static FcBool
164 FcDirChecksumScandirFilter(const struct dirent *entry)
165 {
166 return entry->d_type != DT_DIR;
167 }
168 #endif
169
170 static int
171 FcDirChecksumScandirSorter(const struct dirent **lhs, const struct dirent **rhs)
172 {
173 return strcmp((*lhs)->d_name, (*rhs)->d_name);
174 }
175
176 static void
177 free_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
187 int
188 FcScandir (const char *dirp,
189 struct dirent ***namelist,
190 int (*filter) (const struct dirent *),
191 int (*compar) (const struct dirent **, const struct dirent **));
192
193 int
194 FcScandir (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 = (struct dirent **) 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
260 static int
261 FcDirChecksum (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
336 int
337 FcStatChecksum (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
356 static int
357 FcFStatFs (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
420 FcBool
421 FcIsFsMmapSafe (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
431 FcBool
432 FcIsFsMtimeBroken (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__