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