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