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