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