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