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