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