Fallback to lstat() in case the filesystem doesn't support d_type in struct dirent
[platform/upstream/fontconfig.git] / src / fcstat.c
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__
48 typedef long long INT64;
49 #define EPOCH_OFFSET 11644473600ll
50 #else
51 #define EPOCH_OFFSET 11644473600i64
52 typedef __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
74 int
75 FcStat (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
123 int
124 FcStat (const FcChar8 *file, struct stat *statb)
125 {
126   return stat ((char *) file, statb);
127 }
128
129 /* Adler-32 checksum implementation */
130 struct Adler32 {
131     int a;
132     int b;
133 };
134
135 static void
136 Adler32Init (struct Adler32 *ctx)
137 {
138     ctx->a = 1;
139     ctx->b = 0;
140 }
141
142 static void
143 Adler32Update (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
152 static int
153 Adler32Finish (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 */
160 static FcBool
161 FcDirChecksumScandirFilter(const struct dirent *entry)
162 {
163     return entry->d_type != DT_DIR;
164 }
165 #endif
166
167 #ifdef HAVE_SCANDIR
168 static int
169 FcDirChecksumScandirSorter(const struct dirent **lhs, const struct dirent **rhs)
170 {
171     return strcmp((*lhs)->d_name, (*rhs)->d_name);
172 }
173 #elif HAVE_SCANDIR_VOID_P
174 static int
175 FcDirChecksumScandirSorter(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
183 static int
184 FcDirChecksum (const FcChar8 *dir, time_t *checksum)
185 {
186     struct Adler32 ctx;
187     struct dirent **files;
188     int n;
189     int ret = 0;
190     size_t len = strlen ((const char *)dir);
191
192     Adler32Init (&ctx);
193
194     n = scandir ((const char *)dir, &files,
195 #ifdef HAVE_STRUCT_DIRENT_D_TYPE
196                  &FcDirChecksumScandirFilter,
197 #else
198                  NULL,
199 #endif
200                  &FcDirChecksumScandirSorter);
201     if (n == -1)
202         return -1;
203
204     while (n--)
205     {
206         size_t dlen = strlen (files[n]->d_name);
207         int dtype;
208
209 #ifdef HAVE_STRUCT_DIRENT_D_TYPE
210         dtype = files[n]->d_type;
211         if (dtype == DT_UNKNOWN)
212         {
213 #endif
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 #ifdef HAVE_STRUCT_DIRENT_D_TYPE
231         }
232 #endif
233         Adler32Update (&ctx, files[n]->d_name, dlen + 1);
234         Adler32Update (&ctx, (char *)&dtype, sizeof (int));
235
236       bail:
237         free (files[n]);
238     }
239     free (files);
240     if (ret == -1)
241         return -1;
242
243     *checksum = Adler32Finish (&ctx);
244
245     return 0;
246 }
247 #endif /* _WIN32 */
248
249 int
250 FcStatChecksum (const FcChar8 *file, struct stat *statb)
251 {
252     if (FcStat (file, statb) == -1)
253         return -1;
254
255 #ifndef _WIN32
256     /* We have a workaround of the broken stat() in FcStat() for Win32.
257      * No need to do something further more.
258      */
259     if (FcIsFsMtimeBroken (file))
260     {
261         if (FcDirChecksum (file, &statb->st_mtime) == -1)
262             return -1;
263     }
264 #endif
265
266     return 0;
267 }
268
269 static int
270 FcFStatFs (int fd, FcStatFS *statb)
271 {
272     const char *p = NULL;
273     int ret = -1;
274     FcBool flag = FcFalse;
275
276 #if defined(HAVE_FSTATVFS) && (defined(HAVE_STRUCT_STATVFS_F_BASETYPE) || defined(HAVE_STRUCT_STATVFS_F_FSTYPENAME))
277     struct statvfs buf;
278
279     memset (statb, 0, sizeof (FcStatFS));
280
281     if ((ret = fstatvfs (fd, &buf)) == 0)
282     {
283 #  if defined(HAVE_STRUCT_STATVFS_F_BASETYPE)
284         p = buf.f_basetype;
285 #  elif defined(HAVE_STRUCT_STATVFS_F_FSTYPENAME)
286         p = buf.f_fstypename;
287 #  endif
288     }
289 #elif defined(HAVE_FSTATFS) && (defined(HAVE_STRUCT_STATFS_F_FLAGS) || defined(HAVE_STRUCT_STATFS_F_FSTYPENAME) || defined(__linux__))
290     struct statfs buf;
291
292     memset (statb, 0, sizeof (FcStatFS));
293
294     if ((ret = fstatfs (fd, &buf)) == 0)
295     {
296 #  if defined(HAVE_STRUCT_STATFS_F_FLAGS) && defined(MNT_LOCAL)
297         statb->is_remote_fs = !(buf.f_flags & MNT_LOCAL);
298         flag = FcTrue;
299 #  endif
300 #  if defined(HAVE_STRUCT_STATFS_F_FSTYPENAME)
301         p = buf.f_fstypename;
302 #  elif defined(__linux__)
303         switch (buf.f_type)
304         {
305         case 0x6969: /* nfs */
306             statb->is_remote_fs = FcTrue;
307             break;
308         case 0x4d44: /* fat */
309             statb->is_mtime_broken = FcTrue;
310             break;
311         default:
312             break;
313         }
314
315         return ret;
316 #  else
317 #    error "BUG: No way to figure out with fstatfs()"
318 #  endif
319     }
320 #endif
321     if (p)
322     {
323         if (!flag && strcmp (p, "nfs") == 0)
324             statb->is_remote_fs = FcTrue;
325         if (strcmp (p, "msdosfs") == 0 ||
326             strcmp (p, "pcfs") == 0)
327             statb->is_mtime_broken = FcTrue;
328     }
329
330     return ret;
331 }
332
333 FcBool
334 FcIsFsMmapSafe (int fd)
335 {
336     FcStatFS statb;
337
338     if (FcFStatFs (fd, &statb) < 0)
339         return FcTrue;
340
341     return !statb.is_remote_fs;
342 }
343
344 FcBool
345 FcIsFsMtimeBroken (const FcChar8 *dir)
346 {
347     int fd = FcOpen ((const char *) dir, O_RDONLY);
348
349     if (fd != -1)
350     {
351         FcStatFS statb;
352         int ret = FcFStatFs (fd, &statb);
353
354         close (fd);
355         if (ret < 0)
356             return FcFalse;
357
358         return statb.is_mtime_broken;
359     }
360
361     return FcFalse;
362 }