fcstat.c revision 18bd4a06
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 = (struct dirent **) 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 = (struct dirent *) malloc (dentlen); 221 memcpy (p, dent, dentlen); 222 if ((n + 1) >= lsize) 223 { 224 lsize += 128; 225 dlp = (struct dirent **) realloc (dlist, sizeof (struct dirent *) * lsize); 226 if (!dlp) 227 { 228 free_dirent (dlist); 229 closedir (d); 230 errno = ENOMEM; 231 232 return -1; 233 } 234 dlist = dlp; 235 } 236 dlist[n++] = p; 237 dlist[n] = NULL; 238 } 239 } 240 closedir (d); 241 242 qsort (dlist, n, sizeof (struct dirent *), (int (*) (const void *, const void *))compar); 243 244 *namelist = dlist; 245 246 return n; 247} 248 249static int 250FcDirChecksum (const FcChar8 *dir, time_t *checksum) 251{ 252 struct Adler32 ctx; 253 struct dirent **files; 254 int n; 255 int ret = 0; 256 size_t len = strlen ((const char *)dir); 257 258 Adler32Init (&ctx); 259 260 n = FcScandir ((const char *)dir, &files, 261#ifdef HAVE_STRUCT_DIRENT_D_TYPE 262 &FcDirChecksumScandirFilter, 263#else 264 NULL, 265#endif 266 &FcDirChecksumScandirSorter); 267 if (n == -1) 268 return -1; 269 270 while (n--) 271 { 272 size_t dlen = strlen (files[n]->d_name); 273 int dtype; 274 275#ifdef HAVE_STRUCT_DIRENT_D_TYPE 276 dtype = files[n]->d_type; 277 if (dtype == DT_UNKNOWN) 278 { 279#endif 280 struct stat statb; 281 char *f = malloc (len + 1 + dlen + 1); 282 283 if (!f) 284 { 285 ret = -1; 286 goto bail; 287 } 288 memcpy (f, dir, len); 289 f[len] = FC_DIR_SEPARATOR; 290 memcpy (&f[len + 1], files[n]->d_name, dlen); 291 f[len + 1 + dlen] = 0; 292 if (lstat (f, &statb) < 0) 293 { 294 ret = -1; 295 free (f); 296 goto bail; 297 } 298 if (S_ISDIR (statb.st_mode)) 299 { 300 free (f); 301 goto bail; 302 } 303 304 free (f); 305 dtype = statb.st_mode; 306#ifdef HAVE_STRUCT_DIRENT_D_TYPE 307 } 308#endif 309 Adler32Update (&ctx, files[n]->d_name, dlen + 1); 310 Adler32Update (&ctx, (char *)&dtype, sizeof (int)); 311 312 bail: 313 free (files[n]); 314 } 315 free (files); 316 if (ret == -1) 317 return -1; 318 319 *checksum = Adler32Finish (&ctx); 320 321 return 0; 322} 323#endif /* _WIN32 */ 324 325int 326FcStatChecksum (const FcChar8 *file, struct stat *statb) 327{ 328 if (FcStat (file, statb) == -1) 329 return -1; 330 331#ifndef _WIN32 332 /* We have a workaround of the broken stat() in FcStat() for Win32. 333 * No need to do something further more. 334 */ 335 if (FcIsFsMtimeBroken (file)) 336 { 337 if (FcDirChecksum (file, &statb->st_mtime) == -1) 338 return -1; 339 } 340#endif 341 342 return 0; 343} 344 345static int 346FcFStatFs (int fd, FcStatFS *statb) 347{ 348 const char *p = NULL; 349 int ret = -1; 350 FcBool flag = FcFalse; 351 352#if defined(HAVE_FSTATVFS) && (defined(HAVE_STRUCT_STATVFS_F_BASETYPE) || defined(HAVE_STRUCT_STATVFS_F_FSTYPENAME)) 353 struct statvfs buf; 354 355 memset (statb, 0, sizeof (FcStatFS)); 356 357 if ((ret = fstatvfs (fd, &buf)) == 0) 358 { 359# if defined(HAVE_STRUCT_STATVFS_F_BASETYPE) 360 p = buf.f_basetype; 361# elif defined(HAVE_STRUCT_STATVFS_F_FSTYPENAME) 362 p = buf.f_fstypename; 363# endif 364 } 365#elif defined(HAVE_FSTATFS) && (defined(HAVE_STRUCT_STATFS_F_FLAGS) || defined(HAVE_STRUCT_STATFS_F_FSTYPENAME) || defined(__linux__)) 366 struct statfs buf; 367 368 memset (statb, 0, sizeof (FcStatFS)); 369 370 if ((ret = fstatfs (fd, &buf)) == 0) 371 { 372# if defined(HAVE_STRUCT_STATFS_F_FLAGS) && defined(MNT_LOCAL) 373 statb->is_remote_fs = !(buf.f_flags & MNT_LOCAL); 374 flag = FcTrue; 375# endif 376# if defined(HAVE_STRUCT_STATFS_F_FSTYPENAME) 377 p = buf.f_fstypename; 378# elif defined(__linux__) 379 switch (buf.f_type) 380 { 381 case 0x6969: /* nfs */ 382 statb->is_remote_fs = FcTrue; 383 break; 384 case 0x4d44: /* fat */ 385 statb->is_mtime_broken = FcTrue; 386 break; 387 default: 388 break; 389 } 390 391 return ret; 392# else 393# error "BUG: No way to figure out with fstatfs()" 394# endif 395 } 396#endif 397 if (p) 398 { 399 if (!flag && strcmp (p, "nfs") == 0) 400 statb->is_remote_fs = FcTrue; 401 if (strcmp (p, "msdosfs") == 0 || 402 strcmp (p, "pcfs") == 0) 403 statb->is_mtime_broken = FcTrue; 404 } 405 406 return ret; 407} 408 409FcBool 410FcIsFsMmapSafe (int fd) 411{ 412 FcStatFS statb; 413 414 if (FcFStatFs (fd, &statb) < 0) 415 return FcTrue; 416 417 return !statb.is_remote_fs; 418} 419 420FcBool 421FcIsFsMtimeBroken (const FcChar8 *dir) 422{ 423 int fd = FcOpen ((const char *) dir, O_RDONLY); 424 425 if (fd != -1) 426 { 427 FcStatFS statb; 428 int ret = FcFStatFs (fd, &statb); 429 430 close (fd); 431 if (ret < 0) 432 return FcFalse; 433 434 return statb.is_mtime_broken; 435 } 436 437 return FcFalse; 438} 439 440#define __fcstat__ 441#include "fcaliastail.h" 442#undef __fcstat__ 443