fsmagic.c revision 328875
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.77 2017/05/24 19:17:50 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 MAJOR_IN_SYSMACROS
50# include <sys/sysmacros.h>
51# define HAVE_MAJOR
52#endif
53#ifdef major			/* Might be defined in sys/types.h.  */
54# define HAVE_MAJOR
55#endif
56#ifdef WIN32
57# define WIN32_LEAN_AND_MEAN
58# include <windows.h>
59#endif
60
61#ifndef HAVE_MAJOR
62# define major(dev)  (((dev) >> 8) & 0xff)
63# define minor(dev)  ((dev) & 0xff)
64#endif
65#undef HAVE_MAJOR
66#ifdef	S_IFLNK
67private int
68bad_link(struct magic_set *ms, int err, char *buf)
69{
70	int mime = ms->flags & MAGIC_MIME;
71	if ((mime & MAGIC_MIME_TYPE) &&
72	    file_printf(ms, "inode/symlink")
73	    == -1)
74		return -1;
75	else if (!mime) {
76		if (ms->flags & MAGIC_ERROR) {
77			file_error(ms, err,
78				   "broken symbolic link to %s", buf);
79			return -1;
80		}
81		if (file_printf(ms, "broken symbolic link to %s", buf) == -1)
82			return -1;
83	}
84	return 1;
85}
86#endif
87private int
88handle_mime(struct magic_set *ms, int mime, const char *str)
89{
90	if ((mime & MAGIC_MIME_TYPE)) {
91		if (file_printf(ms, "inode/%s", str) == -1)
92			return -1;
93		if ((mime & MAGIC_MIME_ENCODING) && file_printf(ms,
94		    "; charset=") == -1)
95			return -1;
96	}
97	if ((mime & MAGIC_MIME_ENCODING) && file_printf(ms, "binary") == -1)
98		return -1;
99	return 0;
100}
101
102protected int
103file_fsmagic(struct magic_set *ms, const char *fn, struct stat *sb)
104{
105	int ret, did = 0;
106	int mime = ms->flags & MAGIC_MIME;
107	int silent = ms->flags & (MAGIC_APPLE|MAGIC_EXTENSION);
108#ifdef	S_IFLNK
109	char buf[BUFSIZ+4];
110	ssize_t nch;
111	struct stat tstatbuf;
112#endif
113
114	if (fn == NULL)
115		return 0;
116
117#define COMMA	(did++ ? ", " : "")
118	/*
119	 * Fstat is cheaper but fails for files you don't have read perms on.
120	 * On 4.2BSD and similar systems, use lstat() to identify symlinks.
121	 */
122#ifdef	S_IFLNK
123	if ((ms->flags & MAGIC_SYMLINK) == 0)
124		ret = lstat(fn, sb);
125	else
126#endif
127	ret = stat(fn, sb);	/* don't merge into if; see "ret =" above */
128
129#ifdef WIN32
130	{
131		HANDLE hFile = CreateFile((LPCSTR)fn, 0, FILE_SHARE_DELETE |
132		    FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0,
133		    NULL);
134		if (hFile != INVALID_HANDLE_VALUE) {
135			/*
136			 * Stat failed, but we can still open it - assume it's
137			 * a block device, if nothing else.
138			 */
139			if (ret) {
140				sb->st_mode = S_IFBLK;
141				ret = 0;
142			}
143			switch (GetFileType(hFile)) {
144			case FILE_TYPE_CHAR:
145				sb->st_mode |= S_IFCHR;
146				sb->st_mode &= ~S_IFREG;
147				break;
148			case FILE_TYPE_PIPE:
149				sb->st_mode |= S_IFIFO;
150				sb->st_mode &= ~S_IFREG;
151				break;
152			}
153			CloseHandle(hFile);
154		}
155	}
156#endif
157
158	if (ret) {
159		if (ms->flags & MAGIC_ERROR) {
160			file_error(ms, errno, "cannot stat `%s'", fn);
161			return -1;
162		}
163		if (file_printf(ms, "cannot open `%s' (%s)",
164		    fn, strerror(errno)) == -1)
165			return -1;
166		return 0;
167	}
168
169	ret = 1;
170	if (!mime && !silent) {
171#ifdef S_ISUID
172		if (sb->st_mode & S_ISUID)
173			if (file_printf(ms, "%ssetuid", COMMA) == -1)
174				return -1;
175#endif
176#ifdef S_ISGID
177		if (sb->st_mode & S_ISGID)
178			if (file_printf(ms, "%ssetgid", COMMA) == -1)
179				return -1;
180#endif
181#ifdef S_ISVTX
182		if (sb->st_mode & S_ISVTX)
183			if (file_printf(ms, "%ssticky", COMMA) == -1)
184				return -1;
185#endif
186	}
187
188	switch (sb->st_mode & S_IFMT) {
189	case S_IFDIR:
190		if (mime) {
191			if (handle_mime(ms, mime, "directory") == -1)
192				return -1;
193		} else if (silent) {
194		} else if (file_printf(ms, "%sdirectory", COMMA) == -1)
195			return -1;
196		break;
197#ifdef S_IFCHR
198	case S_IFCHR:
199		/*
200		 * If -s has been specified, treat character special files
201		 * like ordinary files.  Otherwise, just report that they
202		 * are block special files and go on to the next file.
203		 */
204		if ((ms->flags & MAGIC_DEVICES) != 0) {
205			ret = 0;
206			break;
207		}
208		if (mime) {
209			if (handle_mime(ms, mime, "chardevice") == -1)
210				return -1;
211		} else if (silent) {
212		} else {
213#ifdef HAVE_STRUCT_STAT_ST_RDEV
214# ifdef dv_unit
215			if (file_printf(ms, "%scharacter special (%d/%d/%d)",
216			    COMMA, major(sb->st_rdev), dv_unit(sb->st_rdev),
217					dv_subunit(sb->st_rdev)) == -1)
218				return -1;
219# else
220			if (file_printf(ms, "%scharacter special (%ld/%ld)",
221			    COMMA, (long)major(sb->st_rdev),
222			    (long)minor(sb->st_rdev)) == -1)
223				return -1;
224# endif
225#else
226			if (file_printf(ms, "%scharacter special", COMMA) == -1)
227				return -1;
228#endif
229		}
230		break;
231#endif
232#ifdef S_IFBLK
233	case S_IFBLK:
234		/*
235		 * If -s has been specified, treat block special files
236		 * like ordinary files.  Otherwise, just report that they
237		 * are block special files and go on to the next file.
238		 */
239		if ((ms->flags & MAGIC_DEVICES) != 0) {
240			ret = 0;
241			break;
242		}
243		if (mime) {
244			if (handle_mime(ms, mime, "blockdevice") == -1)
245				return -1;
246		} else if (silent) {
247		} else {
248#ifdef HAVE_STRUCT_STAT_ST_RDEV
249# ifdef dv_unit
250			if (file_printf(ms, "%sblock special (%d/%d/%d)",
251			    COMMA, major(sb->st_rdev), dv_unit(sb->st_rdev),
252			    dv_subunit(sb->st_rdev)) == -1)
253				return -1;
254# else
255			if (file_printf(ms, "%sblock special (%ld/%ld)",
256			    COMMA, (long)major(sb->st_rdev),
257			    (long)minor(sb->st_rdev)) == -1)
258				return -1;
259# endif
260#else
261			if (file_printf(ms, "%sblock special", COMMA) == -1)
262				return -1;
263#endif
264		}
265		break;
266#endif
267	/* TODO add code to handle V7 MUX and Blit MUX files */
268#ifdef	S_IFIFO
269	case S_IFIFO:
270		if((ms->flags & MAGIC_DEVICES) != 0)
271			break;
272		if (mime) {
273			if (handle_mime(ms, mime, "fifo") == -1)
274				return -1;
275		} else if (silent) {
276		} else if (file_printf(ms, "%sfifo (named pipe)", COMMA) == -1)
277			return -1;
278		break;
279#endif
280#ifdef	S_IFDOOR
281	case S_IFDOOR:
282		if (mime) {
283			if (handle_mime(ms, mime, "door") == -1)
284				return -1;
285		} else if (silent) {
286		} else if (file_printf(ms, "%sdoor", COMMA) == -1)
287			return -1;
288		break;
289#endif
290#ifdef	S_IFLNK
291	case S_IFLNK:
292		if ((nch = readlink(fn, buf, BUFSIZ-1)) <= 0) {
293			if (ms->flags & MAGIC_ERROR) {
294			    file_error(ms, errno, "unreadable symlink `%s'",
295				fn);
296			    return -1;
297			}
298			if (mime) {
299				if (handle_mime(ms, mime, "symlink") == -1)
300					return -1;
301			} else if (silent) {
302			} else if (file_printf(ms,
303			    "%sunreadable symlink `%s' (%s)", COMMA, fn,
304			    strerror(errno)) == -1)
305				return -1;
306			break;
307		}
308		buf[nch] = '\0';	/* readlink(2) does not do this */
309
310		/* If broken symlink, say so and quit early. */
311		if (*buf == '/') {
312			if (stat(buf, &tstatbuf) < 0)
313				return bad_link(ms, errno, buf);
314		} else {
315			char *tmp;
316			char buf2[BUFSIZ+BUFSIZ+4];
317
318			if ((tmp = strrchr(fn,  '/')) == NULL) {
319				tmp = buf; /* in current directory anyway */
320			} else {
321				if (tmp - fn + 1 > BUFSIZ) {
322					if (ms->flags & MAGIC_ERROR) {
323						file_error(ms, 0,
324						    "path too long: `%s'", buf);
325						return -1;
326					}
327					if (mime) {
328						if (handle_mime(ms, mime,
329						    "x-path-too-long") == -1)
330							return -1;
331					} else if (silent) {
332					} else if (file_printf(ms,
333					    "%spath too long: `%s'", COMMA,
334					    fn) == -1)
335						return -1;
336					break;
337				}
338				/* take dir part */
339				(void)strlcpy(buf2, fn, sizeof buf2);
340				buf2[tmp - fn + 1] = '\0';
341				/* plus (rel) link */
342				(void)strlcat(buf2, buf, sizeof buf2);
343				tmp = buf2;
344			}
345			if (stat(tmp, &tstatbuf) < 0)
346				return bad_link(ms, errno, buf);
347		}
348
349		/* Otherwise, handle it. */
350		if ((ms->flags & MAGIC_SYMLINK) != 0) {
351			const char *p;
352			ms->flags &= MAGIC_SYMLINK;
353			p = magic_file(ms, buf);
354			ms->flags |= MAGIC_SYMLINK;
355			if (p == NULL)
356				return -1;
357		} else { /* just print what it points to */
358			if (mime) {
359				if (handle_mime(ms, mime, "symlink") == -1)
360					return -1;
361			} else if (silent) {
362			} else if (file_printf(ms, "%ssymbolic link to %s",
363			    COMMA, buf) == -1)
364				return -1;
365		}
366		break;
367#endif
368#ifdef	S_IFSOCK
369#ifndef __COHERENT__
370	case S_IFSOCK:
371		if (mime) {
372			if (handle_mime(ms, mime, "socket") == -1)
373				return -1;
374		} else if (silent) {
375		} else if (file_printf(ms, "%ssocket", COMMA) == -1)
376			return -1;
377		break;
378#endif
379#endif
380	case S_IFREG:
381		/*
382		 * regular file, check next possibility
383		 *
384		 * If stat() tells us the file has zero length, report here that
385		 * the file is empty, so we can skip all the work of opening and
386		 * reading the file.
387		 * But if the -s option has been given, we skip this
388		 * optimization, since on some systems, stat() reports zero
389		 * size for raw disk partitions. (If the block special device
390		 * really has zero length, the fact that it is empty will be
391		 * detected and reported correctly when we read the file.)
392		 */
393		if ((ms->flags & MAGIC_DEVICES) == 0 && sb->st_size == 0) {
394			if (mime) {
395				if (handle_mime(ms, mime, "x-empty") == -1)
396					return -1;
397			} else if (silent) {
398			} else if (file_printf(ms, "%sempty", COMMA) == -1)
399				return -1;
400			break;
401		}
402		ret = 0;
403		break;
404
405	default:
406		file_error(ms, 0, "invalid mode 0%o", sb->st_mode);
407		return -1;
408		/*NOTREACHED*/
409	}
410
411	if (!silent && !mime && did && ret == 0) {
412	    if (file_printf(ms, " ") == -1)
413		    return -1;
414	}
415	return ret;
416}
417