1 /* $NetBSD: fsmagic.c,v 1.18 2023/08/18 19:00:11 christos Exp $ */
2
3 /*
4 * Copyright (c) Ian F. Darwin 1986-1995.
5 * Software written by Ian F. Darwin and others;
6 * maintained 1995-present by Christos Zoulas and others.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 * notice immediately at the beginning of the file, without modification,
13 * this list of conditions, and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
22 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 * SUCH DAMAGE.
29 */
30 /*
31 * fsmagic - magic based on filesystem info - directory, special files, etc.
32 */
33
34 #include "file.h"
35
36 #ifndef lint
37 #if 0
38 FILE_RCSID("@(#)$File: fsmagic.c,v 1.85 2022/12/26 17:31:14 christos Exp $")
39 #else
40 __RCSID("$NetBSD: fsmagic.c,v 1.18 2023/08/18 19:00:11 christos Exp $");
41 #endif
42 #endif /* lint */
43
44 #include "magic.h"
45 #include <string.h>
46 #ifdef HAVE_UNISTD_H
47 #include <unistd.h>
48 #endif
49 #include <stdlib.h>
50 /* Since major is a function on SVR4, we cannot use `ifndef major'. */
51 #ifdef MAJOR_IN_MKDEV
52 # include <sys/mkdev.h>
53 # define HAVE_MAJOR
54 #endif
55 #ifdef HAVE_SYS_SYSMACROS_H
56 # include <sys/sysmacros.h>
57 #endif
58 #ifdef MAJOR_IN_SYSMACROS
59 # define HAVE_MAJOR
60 #endif
61 #if defined(major) && !defined(HAVE_MAJOR)
62 /* Might be defined in sys/types.h. */
63 # define HAVE_MAJOR
64 #endif
65 #ifdef WIN32
66 # define WIN32_LEAN_AND_MEAN
67 # include <windows.h>
68 #endif
69
70 #ifndef HAVE_MAJOR
71 # define major(dev) (((dev) >> 8) & 0xff)
72 # define minor(dev) ((dev) & 0xff)
73 #endif
74 #undef HAVE_MAJOR
75 #ifdef S_IFLNK
76 file_private int
bad_link(struct magic_set * ms,int err,char * buf)77 bad_link(struct magic_set *ms, int err, char *buf)
78 {
79 int mime = ms->flags & MAGIC_MIME;
80 if ((mime & MAGIC_MIME_TYPE) &&
81 file_printf(ms, "inode/symlink")
82 == -1)
83 return -1;
84 else if (!mime) {
85 if (ms->flags & MAGIC_ERROR) {
86 file_error(ms, err,
87 "broken symbolic link to %s", buf);
88 return -1;
89 }
90 if (file_printf(ms, "broken symbolic link to %s", buf) == -1)
91 return -1;
92 }
93 return 1;
94 }
95 #endif
96 file_private int
handle_mime(struct magic_set * ms,int mime,const char * str)97 handle_mime(struct magic_set *ms, int mime, const char *str)
98 {
99 if ((mime & MAGIC_MIME_TYPE)) {
100 if (file_printf(ms, "inode/%s", str) == -1)
101 return -1;
102 if ((mime & MAGIC_MIME_ENCODING) && file_printf(ms,
103 "; charset=") == -1)
104 return -1;
105 }
106 if ((mime & MAGIC_MIME_ENCODING) && file_printf(ms, "binary") == -1)
107 return -1;
108 return 0;
109 }
110
111 file_protected int
file_fsmagic(struct magic_set * ms,const char * fn,struct stat * sb)112 file_fsmagic(struct magic_set *ms, const char *fn, struct stat *sb)
113 {
114 int ret, did = 0;
115 int mime = ms->flags & MAGIC_MIME;
116 int silent = ms->flags & (MAGIC_APPLE|MAGIC_EXTENSION);
117 #ifdef S_IFLNK
118 char buf[BUFSIZ+4];
119 ssize_t nch;
120 struct stat tstatbuf;
121 #endif
122
123 if (fn == NULL)
124 return 0;
125
126 #define COMMA (did++ ? ", " : "")
127 /*
128 * Fstat is cheaper but fails for files you don't have read perms on.
129 * On 4.2BSD and similar systems, use lstat() to identify symlinks.
130 */
131 #ifdef S_IFLNK
132 if ((ms->flags & MAGIC_SYMLINK) == 0)
133 ret = lstat(fn, sb);
134 else
135 #endif
136 ret = stat(fn, sb); /* don't merge into if; see "ret =" above */
137
138 #ifdef WIN32
139 {
140 HANDLE hFile = CreateFile((LPCSTR)fn, 0, FILE_SHARE_DELETE |
141 FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0,
142 NULL);
143 if (hFile != INVALID_HANDLE_VALUE) {
144 /*
145 * Stat failed, but we can still open it - assume it's
146 * a block device, if nothing else.
147 */
148 if (ret) {
149 sb->st_mode = S_IFBLK;
150 ret = 0;
151 }
152 switch (GetFileType(hFile)) {
153 case FILE_TYPE_CHAR:
154 sb->st_mode |= S_IFCHR;
155 sb->st_mode &= ~S_IFREG;
156 break;
157 case FILE_TYPE_PIPE:
158 sb->st_mode |= S_IFIFO;
159 sb->st_mode &= ~S_IFREG;
160 break;
161 }
162 CloseHandle(hFile);
163 }
164 }
165 #endif
166
167 if (ret) {
168 if (ms->flags & MAGIC_ERROR) {
169 file_error(ms, errno, "cannot stat `%s'", fn);
170 return -1;
171 }
172 if (file_printf(ms, "cannot open `%s' (%s)",
173 fn, strerror(errno)) == -1)
174 return -1;
175 return 0;
176 }
177
178 ret = 1;
179 if (!mime && !silent) {
180 #ifdef S_ISUID
181 if (sb->st_mode & S_ISUID)
182 if (file_printf(ms, "%ssetuid", COMMA) == -1)
183 return -1;
184 #endif
185 #ifdef S_ISGID
186 if (sb->st_mode & S_ISGID)
187 if (file_printf(ms, "%ssetgid", COMMA) == -1)
188 return -1;
189 #endif
190 #ifdef S_ISVTX
191 if (sb->st_mode & S_ISVTX)
192 if (file_printf(ms, "%ssticky", COMMA) == -1)
193 return -1;
194 #endif
195 }
196
197 switch (sb->st_mode & S_IFMT) {
198 case S_IFDIR:
199 if (mime) {
200 if (handle_mime(ms, mime, "directory") == -1)
201 return -1;
202 } else if (silent) {
203 } else if (file_printf(ms, "%sdirectory", COMMA) == -1)
204 return -1;
205 break;
206 #ifdef S_IFCHR
207 case S_IFCHR:
208 /*
209 * If -s has been specified, treat character special files
210 * like ordinary files. Otherwise, just report that they
211 * are block special files and go on to the next file.
212 */
213 if ((ms->flags & MAGIC_DEVICES) != 0) {
214 ret = 0;
215 break;
216 }
217 if (mime) {
218 if (handle_mime(ms, mime, "chardevice") == -1)
219 return -1;
220 } else if (silent) {
221 } else {
222 #ifdef HAVE_STRUCT_STAT_ST_RDEV
223 # ifdef dv_unit
224 if (file_printf(ms, "%scharacter special (%d/%d/%d)",
225 COMMA, major(sb->st_rdev), dv_unit(sb->st_rdev),
226 dv_subunit(sb->st_rdev)) == -1)
227 return -1;
228 # else
229 if (file_printf(ms, "%scharacter special (%ld/%ld)",
230 COMMA, (long)major(sb->st_rdev),
231 (long)minor(sb->st_rdev)) == -1)
232 return -1;
233 # endif
234 #else
235 if (file_printf(ms, "%scharacter special", COMMA) == -1)
236 return -1;
237 #endif
238 }
239 break;
240 #endif
241 #ifdef S_IFBLK
242 case S_IFBLK:
243 /*
244 * If -s has been specified, treat block special files
245 * like ordinary files. Otherwise, just report that they
246 * are block special files and go on to the next file.
247 */
248 if ((ms->flags & MAGIC_DEVICES) != 0) {
249 ret = 0;
250 break;
251 }
252 if (mime) {
253 if (handle_mime(ms, mime, "blockdevice") == -1)
254 return -1;
255 } else if (silent) {
256 } else {
257 #ifdef HAVE_STRUCT_STAT_ST_RDEV
258 # ifdef dv_unit
259 if (file_printf(ms, "%sblock special (%d/%d/%d)",
260 COMMA, major(sb->st_rdev), dv_unit(sb->st_rdev),
261 dv_subunit(sb->st_rdev)) == -1)
262 return -1;
263 # else
264 if (file_printf(ms, "%sblock special (%ld/%ld)",
265 COMMA, (long)major(sb->st_rdev),
266 (long)minor(sb->st_rdev)) == -1)
267 return -1;
268 # endif
269 #else
270 if (file_printf(ms, "%sblock special", COMMA) == -1)
271 return -1;
272 #endif
273 }
274 break;
275 #endif
276 /* TODO add code to handle V7 MUX and Blit MUX files */
277 #ifdef S_IFIFO
278 case S_IFIFO:
279 if((ms->flags & MAGIC_DEVICES) != 0)
280 break;
281 if (mime) {
282 if (handle_mime(ms, mime, "fifo") == -1)
283 return -1;
284 } else if (silent) {
285 } else if (file_printf(ms, "%sfifo (named pipe)", COMMA) == -1)
286 return -1;
287 break;
288 #endif
289 #ifdef S_IFDOOR
290 case S_IFDOOR:
291 if (mime) {
292 if (handle_mime(ms, mime, "door") == -1)
293 return -1;
294 } else if (silent) {
295 } else if (file_printf(ms, "%sdoor", COMMA) == -1)
296 return -1;
297 break;
298 #endif
299 #ifdef S_IFLNK
300 case S_IFLNK:
301 if ((nch = readlink(fn, buf, BUFSIZ-1)) <= 0) {
302 if (ms->flags & MAGIC_ERROR) {
303 file_error(ms, errno, "unreadable symlink `%s'",
304 fn);
305 return -1;
306 }
307 if (mime) {
308 if (handle_mime(ms, mime, "symlink") == -1)
309 return -1;
310 } else if (silent) {
311 } else if (file_printf(ms,
312 "%sunreadable symlink `%s' (%s)", COMMA, fn,
313 strerror(errno)) == -1)
314 return -1;
315 break;
316 }
317 buf[nch] = '\0'; /* readlink(2) does not do this */
318
319 /* If broken symlink, say so and quit early. */
320 #ifdef __linux__
321 /*
322 * linux procfs/devfs makes symlinks like pipe:[3515864880]
323 * that we can't stat their readlink output, so stat the
324 * original filename instead.
325 */
326 if (stat(fn, &tstatbuf) < 0)
327 return bad_link(ms, errno, buf);
328 #else
329 if (*buf == '/') {
330 if (stat(buf, &tstatbuf) < 0)
331 return bad_link(ms, errno, buf);
332 } else {
333 char *tmp;
334 char buf2[BUFSIZ+BUFSIZ+4];
335
336 if ((tmp = CCAST(char *, strrchr(fn, '/'))) == NULL) {
337 tmp = buf; /* in current directory anyway */
338 } else {
339 if (tmp - fn + 1 > BUFSIZ) {
340 if (ms->flags & MAGIC_ERROR) {
341 file_error(ms, 0,
342 "path too long: `%s'", buf);
343 return -1;
344 }
345 if (mime) {
346 if (handle_mime(ms, mime,
347 "x-path-too-long") == -1)
348 return -1;
349 } else if (silent) {
350 } else if (file_printf(ms,
351 "%spath too long: `%s'", COMMA,
352 fn) == -1)
353 return -1;
354 break;
355 }
356 /* take dir part */
357 (void)strlcpy(buf2, fn, sizeof buf2);
358 buf2[tmp - fn + 1] = '\0';
359 /* plus (rel) link */
360 (void)strlcat(buf2, buf, sizeof buf2);
361 tmp = buf2;
362 }
363 if (stat(tmp, &tstatbuf) < 0)
364 return bad_link(ms, errno, buf);
365 }
366 #endif
367
368 /* Otherwise, handle it. */
369 if ((ms->flags & MAGIC_SYMLINK) != 0) {
370 const char *p;
371 ms->flags &= MAGIC_SYMLINK;
372 p = magic_file(ms, buf);
373 ms->flags |= MAGIC_SYMLINK;
374 if (p == NULL)
375 return -1;
376 } else { /* just print what it points to */
377 if (mime) {
378 if (handle_mime(ms, mime, "symlink") == -1)
379 return -1;
380 } else if (silent) {
381 } else if (file_printf(ms, "%ssymbolic link to %s",
382 COMMA, buf) == -1)
383 return -1;
384 }
385 break;
386 #endif
387 #ifdef S_IFSOCK
388 #ifndef __COHERENT__
389 case S_IFSOCK:
390 if (mime) {
391 if (handle_mime(ms, mime, "socket") == -1)
392 return -1;
393 } else if (silent) {
394 } else if (file_printf(ms, "%ssocket", COMMA) == -1)
395 return -1;
396 break;
397 #endif
398 #endif
399 case S_IFREG:
400 /*
401 * regular file, check next possibility
402 *
403 * If stat() tells us the file has zero length, report here that
404 * the file is empty, so we can skip all the work of opening and
405 * reading the file.
406 * But if the -s option has been given, we skip this
407 * optimization, since on some systems, stat() reports zero
408 * size for raw disk partitions. (If the block special device
409 * really has zero length, the fact that it is empty will be
410 * detected and reported correctly when we read the file.)
411 */
412 if ((ms->flags & MAGIC_DEVICES) == 0 && sb->st_size == 0) {
413 if (mime) {
414 if (handle_mime(ms, mime, "x-empty") == -1)
415 return -1;
416 } else if (silent) {
417 } else if (file_printf(ms, "%sempty", COMMA) == -1)
418 return -1;
419 break;
420 }
421 ret = 0;
422 break;
423
424 default:
425 file_error(ms, 0, "invalid mode 0%o", sb->st_mode);
426 return -1;
427 /*NOTREACHED*/
428 }
429
430 if (!silent && !mime && did && ret == 0) {
431 if (file_printf(ms, " ") == -1)
432 return -1;
433 }
434 /*
435 * If we were looking for extensions or apple (silent) it is not our
436 * job to print here, so don't count this as a match.
437 */
438 if (ret == 1 && silent)
439 return 0;
440 return ret;
441 }
442