version 1.13, 2003/07/02 21:04:09 |
version 1.14, 2004/05/19 02:32:35 |
|
|
/* $OpenBSD$ */ |
/* $OpenBSD$ */ |
|
|
/* |
/* |
* file - find type of a file or files - main program. |
|
* |
|
* Copyright (c) Ian F. Darwin 1986-1995. |
* Copyright (c) Ian F. Darwin 1986-1995. |
* Software written by Ian F. Darwin and others; |
* Software written by Ian F. Darwin and others; |
* maintained 1995-present by Christos Zoulas and others. |
* maintained 1995-present by Christos Zoulas and others. |
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
* SUCH DAMAGE. |
* SUCH DAMAGE. |
*/ |
*/ |
|
/* |
|
* file - find type of a file or files - main program. |
|
*/ |
|
|
#ifndef lint |
#include "file.h" |
static char *moduleid = "$OpenBSD$"; |
#include "magic.h" |
#endif /* lint */ |
|
|
|
#include <stdio.h> |
#include <stdio.h> |
#include <stdlib.h> |
#include <stdlib.h> |
|
#include <unistd.h> |
#include <string.h> |
#include <string.h> |
#include <sys/types.h> |
#include <sys/types.h> |
#include <sys/param.h> /* for MAXPATHLEN */ |
#include <sys/param.h> /* for MAXPATHLEN */ |
#include <sys/stat.h> |
#include <sys/stat.h> |
#include <fcntl.h> /* for open() */ |
#include <fcntl.h> /* for open() */ |
#if (__COHERENT__ >= 0x420) |
#ifdef RESTORE_TIME |
# include <sys/utime.h> |
# if (__COHERENT__ >= 0x420) |
#else |
# include <sys/utime.h> |
# ifdef USE_UTIMES |
|
# include <sys/time.h> |
|
# else |
# else |
# include <utime.h> |
# ifdef USE_UTIMES |
|
# include <sys/time.h> |
|
# else |
|
# include <utime.h> |
|
# endif |
# endif |
# endif |
#endif |
#endif |
|
#ifdef HAVE_UNISTD_H |
#include <unistd.h> /* for read() */ |
#include <unistd.h> /* for read() */ |
#include <err.h> |
#endif |
|
#ifdef HAVE_LOCALE_H |
|
#include <locale.h> |
|
#endif |
|
#ifdef HAVE_WCHAR_H |
|
#include <wchar.h> |
|
#endif |
|
|
|
#ifdef HAVE_GETOPT_H |
|
#include <getopt.h> /* for long options (is this portable?)*/ |
|
#else |
|
#undef HAVE_GETOPT_LONG |
|
#endif |
|
|
#include <netinet/in.h> /* for byte swapping */ |
#include <netinet/in.h> /* for byte swapping */ |
|
|
#include "patchlevel.h" |
#include "patchlevel.h" |
#include "file.h" |
|
|
|
|
#ifndef lint |
|
FILE_RCSID("@(#)$Id$") |
|
#endif /* lint */ |
|
|
|
|
#ifdef S_IFLNK |
#ifdef S_IFLNK |
# define USAGE "Usage: %s [-vbczL] [-f namefile] [-m magicfiles] file...\n" |
#define SYMLINKFLAG "L" |
#else |
#else |
# define USAGE "Usage: %s [-vbcz] [-f namefile] [-m magicfiles] file...\n" |
#define SYMLINKFLAG "" |
#endif |
#endif |
|
|
#ifndef MAGIC |
# define USAGE "Usage: %s [-bcik" SYMLINKFLAG "nNsvz] [-f namefile] [-F separator] [-m magicfiles] file...\n %s -C -m magicfiles\n" |
# define MAGIC "/etc/magic" |
|
|
#ifndef MAXPATHLEN |
|
#define MAXPATHLEN 512 |
#endif |
#endif |
|
|
int /* Global command-line options */ |
private int /* Global command-line options */ |
debug = 0, /* debugging */ |
bflag = 0, /* brief output format */ |
bflag = 0, /* Don't print filename */ |
nopad = 0, /* Don't pad output */ |
lflag = 0, /* follow Symlinks (BSD only) */ |
nobuffer = 0; /* Do not buffer stdout */ |
zflag = 0; /* follow (uncompress) compressed files */ |
|
|
|
int /* Misc globals */ |
private const char *magicfile = 0; /* where the magic is */ |
nmagic = 0; /* number of valid magic[]s */ |
private const char *default_magicfile = MAGIC; |
|
private char *separator = ":"; /* Default field separator */ |
|
|
struct magic *magic; /* array of magic entries */ |
private char *progname; /* used throughout */ |
|
|
char *magicfile; /* where magic be found */ |
private struct magic_set *magic; |
|
|
int lineno; /* line number in the magic file */ |
private void unwrap(char *); |
|
private void usage(void); |
|
#ifdef HAVE_GETOPT_LONG |
static void unwrap(char *fn); |
private void help(void); |
|
#endif |
#if 0 |
#if 0 |
static int byteconv4(int, int, int); |
private int byteconv4(int, int, int); |
static short byteconv2(int, int, int); |
private short byteconv2(int, int, int); |
#endif |
#endif |
|
|
|
int main(int, char *[]); |
|
private void process(const char *, int); |
|
private void load(const char *, int); |
|
|
|
|
/* |
/* |
* main - parse arguments and handle options |
* main - parse arguments and handle options |
*/ |
*/ |
|
|
main(int argc, char *argv[]) |
main(int argc, char *argv[]) |
{ |
{ |
int c; |
int c; |
int check = 0, didsomefiles = 0, errflg = 0, ret = 0, app = 0; |
int action = 0, didsomefiles = 0, errflg = 0; |
extern char *__progname; |
int flags = 0; |
|
char *home, *usermagic; |
|
struct stat sb; |
|
#define OPTSTRING "bcCdf:F:ikLm:nNprsvz" |
|
#ifdef HAVE_GETOPT_LONG |
|
int longindex; |
|
private struct option long_options[] = |
|
{ |
|
{"version", 0, 0, 'v'}, |
|
{"help", 0, 0, 0}, |
|
{"brief", 0, 0, 'b'}, |
|
{"checking-printout", 0, 0, 'c'}, |
|
{"debug", 0, 0, 'd'}, |
|
{"files-from", 1, 0, 'f'}, |
|
{"separator", 1, 0, 'F'}, |
|
{"mime", 0, 0, 'i'}, |
|
{"keep-going", 0, 0, 'k'}, |
|
#ifdef S_IFLNK |
|
{"dereference", 0, 0, 'L'}, |
|
#endif |
|
{"magic-file", 1, 0, 'm'}, |
|
#if defined(HAVE_UTIME) || defined(HAVE_UTIMES) |
|
{"preserve-date", 0, 0, 'p'}, |
|
#endif |
|
{"uncompress", 0, 0, 'z'}, |
|
{"raw", 0, 0, 'r'}, |
|
{"no-buffer", 0, 0, 'n'}, |
|
{"no-pad", 0, 0, 'N'}, |
|
{"special-files", 0, 0, 's'}, |
|
{"compile", 0, 0, 'C'}, |
|
{0, 0, 0, 0}, |
|
}; |
|
#endif |
|
|
if (!(magicfile = getenv("MAGIC"))) |
#ifdef LC_CTYPE |
magicfile = MAGIC; |
setlocale(LC_CTYPE, ""); /* makes islower etc work for other langs */ |
|
#endif |
|
|
while ((c = getopt(argc, argv, "bvcdf:Lm:z")) != -1) |
#ifdef __EMX__ |
|
/* sh-like wildcard expansion! Shouldn't hurt at least ... */ |
|
_wildcard(&argc, &argv); |
|
#endif |
|
|
|
if ((progname = strrchr(argv[0], '/')) != NULL) |
|
progname++; |
|
else |
|
progname = argv[0]; |
|
|
|
magicfile = default_magicfile; |
|
if ((usermagic = getenv("MAGIC")) != NULL) |
|
magicfile = usermagic; |
|
else |
|
if ((home = getenv("HOME")) != NULL) { |
|
size_t len = strlen(home) + 8; |
|
if ((usermagic = malloc(len)) != NULL) { |
|
(void)strlcpy(usermagic, home, len); |
|
(void)strlcat(usermagic, "/.magic", len); |
|
if (stat(usermagic, &sb)<0) |
|
free(usermagic); |
|
else |
|
magicfile = usermagic; |
|
} |
|
} |
|
|
|
#ifndef HAVE_GETOPT_LONG |
|
while ((c = getopt(argc, argv, OPTSTRING)) != -1) |
|
#else |
|
while ((c = getopt_long(argc, argv, OPTSTRING, long_options, |
|
&longindex)) != -1) |
|
#endif |
switch (c) { |
switch (c) { |
case 'v': |
#ifdef HAVE_GETOPT_LONG |
(void) printf("%s-%d.%d\n", __progname, |
case 0 : |
FILE_VERSION_MAJOR, patchlevel); |
if (longindex == 1) |
return 1; |
help(); |
|
break; |
|
#endif |
case 'b': |
case 'b': |
++bflag; |
++bflag; |
break; |
break; |
case 'c': |
case 'c': |
++check; |
action = FILE_CHECK; |
break; |
break; |
|
case 'C': |
|
action = FILE_COMPILE; |
|
break; |
case 'd': |
case 'd': |
++debug; |
flags |= MAGIC_DEBUG|MAGIC_CHECK; |
break; |
break; |
case 'f': |
case 'f': |
if (!app) { |
if(action) |
ret = apprentice(magicfile, check); |
usage(); |
if (check) |
load(magicfile, flags); |
exit(ret); |
|
app = 1; |
|
} |
|
unwrap(optarg); |
unwrap(optarg); |
++didsomefiles; |
++didsomefiles; |
break; |
break; |
#ifdef S_IFLNK |
case 'F': |
case 'L': |
separator = optarg; |
++lflag; |
|
break; |
break; |
#endif |
case 'i': |
|
flags |= MAGIC_MIME; |
|
break; |
|
case 'k': |
|
flags |= MAGIC_CONTINUE; |
|
break; |
case 'm': |
case 'm': |
magicfile = optarg; |
magicfile = optarg; |
break; |
break; |
|
case 'n': |
|
++nobuffer; |
|
break; |
|
case 'N': |
|
++nopad; |
|
break; |
|
#if defined(HAVE_UTIME) || defined(HAVE_UTIMES) |
|
case 'p': |
|
flags |= MAGIC_PRESERVE_ATIME; |
|
break; |
|
#endif |
|
case 'r': |
|
flags |= MAGIC_RAW; |
|
break; |
|
case 's': |
|
flags |= MAGIC_DEVICES; |
|
break; |
|
case 'v': |
|
(void) fprintf(stdout, "%s-%d.%.2d\n", progname, |
|
FILE_VERSION_MAJOR, patchlevel); |
|
(void) fprintf(stdout, "magic file from %s\n", |
|
magicfile); |
|
return 1; |
case 'z': |
case 'z': |
zflag++; |
flags |= MAGIC_COMPRESS; |
break; |
break; |
|
#ifdef S_IFLNK |
|
case 'L': |
|
flags |= MAGIC_SYMLINK; |
|
break; |
|
#endif |
case '?': |
case '?': |
default: |
default: |
errflg++; |
errflg++; |
|
|
} |
} |
|
|
if (errflg) { |
if (errflg) { |
(void) fprintf(stderr, USAGE, __progname); |
usage(); |
exit(2); |
|
} |
} |
|
|
if (!app) { |
switch(action) { |
ret = apprentice(magicfile, check); |
case FILE_CHECK: |
if (check) |
case FILE_COMPILE: |
exit(ret); |
magic = magic_open(flags|MAGIC_CHECK); |
app = 1; |
if (magic == NULL) { |
|
(void)fprintf(stderr, "%s: %s\n", progname, |
|
strerror(errno)); |
|
return 1; |
|
} |
|
c = action == FILE_CHECK ? magic_check(magic, magicfile) : |
|
magic_compile(magic, magicfile); |
|
if (c == -1) { |
|
(void)fprintf(stderr, "%s: %s\n", progname, |
|
magic_error(magic)); |
|
return -1; |
|
} |
|
return 0; |
|
default: |
|
load(magicfile, flags); |
|
break; |
} |
} |
|
|
if (optind == argc) { |
if (optind == argc) { |
if (!didsomefiles) { |
if (!didsomefiles) { |
fprintf(stderr, USAGE, __progname); |
usage(); |
exit(2); |
|
} |
} |
} else { |
} |
|
else { |
int i, wid, nw; |
int i, wid, nw; |
for (wid = 0, i = optind; i < argc; i++) { |
for (wid = 0, i = optind; i < argc; i++) { |
nw = strlen(argv[i]); |
nw = file_mbswidth(argv[i]); |
if (nw > wid) |
if (nw > wid) |
wid = nw; |
wid = nw; |
} |
} |
|
|
} |
} |
|
|
|
|
|
private void |
|
load(const char *m, int flags) |
|
{ |
|
if (magic) |
|
return; |
|
magic = magic_open(flags); |
|
if (magic == NULL) { |
|
(void)fprintf(stderr, "%s: %s\n", progname, strerror(errno)); |
|
exit(1); |
|
} |
|
if (magic_load(magic, magicfile) == -1) { |
|
(void)fprintf(stderr, "%s: %s\n", |
|
progname, magic_error(magic)); |
|
exit(1); |
|
} |
|
} |
|
|
/* |
/* |
* unwrap -- read a file of filenames, do each one. |
* unwrap -- read a file of filenames, do each one. |
*/ |
*/ |
static void |
private void |
unwrap(fn) |
unwrap(char *fn) |
char *fn; |
|
{ |
{ |
char buf[MAXPATHLEN]; |
char buf[MAXPATHLEN]; |
FILE *f; |
FILE *f; |
|
|
wid = 1; |
wid = 1; |
} else { |
} else { |
if ((f = fopen(fn, "r")) == NULL) { |
if ((f = fopen(fn, "r")) == NULL) { |
err(1, "Cannot open `%s'", fn); |
(void)fprintf(stderr, "%s: Cannot open `%s' (%s).\n", |
/*NOTREACHED*/ |
progname, fn, strerror(errno)); |
|
exit(1); |
} |
} |
|
|
while (fgets(buf, sizeof(buf), f) != NULL) { |
while (fgets(buf, MAXPATHLEN, f) != NULL) { |
cwid = strlen(buf) - 1; |
cwid = file_mbswidth(buf) - 1; |
if (cwid > wid) |
if (cwid > wid) |
wid = cwid; |
wid = cwid; |
} |
} |
|
|
rewind(f); |
rewind(f); |
} |
} |
|
|
while (fgets(buf, sizeof(buf), f) != NULL) { |
while (fgets(buf, MAXPATHLEN, f) != NULL) { |
buf[strlen(buf)-1] = '\0'; |
buf[file_mbswidth(buf)-1] = '\0'; |
process(buf, wid); |
process(buf, wid); |
|
if(nobuffer) |
|
(void) fflush(stdout); |
} |
} |
|
|
(void) fclose(f); |
(void) fclose(f); |
} |
} |
|
|
|
private void |
|
process(const char *inname, int wid) |
|
{ |
|
const char *type; |
|
int std_in = strcmp(inname, "-") == 0; |
|
|
|
if (wid > 0 && !bflag) |
|
(void) printf("%s%s%*s ", std_in ? "/dev/stdin" : inname, |
|
separator, (int) (nopad ? 0 : (wid - file_mbswidth(inname))), ""); |
|
|
|
type = magic_file(magic, std_in ? NULL : inname); |
|
if (type == NULL) |
|
printf("ERROR: %s\n", magic_error(magic)); |
|
else |
|
printf("%s\n", type); |
|
} |
|
|
|
|
#if 0 |
#if 0 |
/* |
/* |
* byteconv4 |
* byteconv4 |
|
|
* same whether to perform byte swapping |
* same whether to perform byte swapping |
* big_endian whether we are a big endian host |
* big_endian whether we are a big endian host |
*/ |
*/ |
static int |
private int |
byteconv4(from, same, big_endian) |
byteconv4(int from, int same, int big_endian) |
int from; |
|
int same; |
|
int big_endian; |
|
{ |
{ |
if (same) |
if (same) |
return from; |
return from; |
else if (big_endian) /* lsb -> msb conversion on msb */ |
else if (big_endian) { /* lsb -> msb conversion on msb */ |
{ |
union { |
union { |
int i; |
int i; |
char c[4]; |
char c[4]; |
} retval, tmpval; |
} retval, tmpval; |
|
|
|
tmpval.i = from; |
tmpval.i = from; |
retval.c[0] = tmpval.c[3]; |
retval.c[0] = tmpval.c[3]; |
retval.c[1] = tmpval.c[2]; |
retval.c[1] = tmpval.c[2]; |
retval.c[2] = tmpval.c[1]; |
retval.c[2] = tmpval.c[1]; |
retval.c[3] = tmpval.c[0]; |
retval.c[3] = tmpval.c[0]; |
|
|
return retval.i; |
return retval.i; |
} |
} |
else |
else |
return ntohl(from); /* msb -> lsb conversion on lsb */ |
return ntohl(from); /* msb -> lsb conversion on lsb */ |
} |
} |
|
|
/* |
/* |
* byteconv2 |
* byteconv2 |
* Same as byteconv4, but for shorts |
* Same as byteconv4, but for shorts |
*/ |
*/ |
static short |
private short |
byteconv2(from, same, big_endian) |
byteconv2(int from, int same, int big_endian) |
int from; |
|
int same; |
|
int big_endian; |
|
{ |
{ |
if (same) |
if (same) |
return from; |
return from; |
else if (big_endian) /* lsb -> msb conversion on msb */ |
else if (big_endian) { /* lsb -> msb conversion on msb */ |
{ |
union { |
union { |
short s; |
short s; |
char c[2]; |
char c[2]; |
} retval, tmpval; |
} retval, tmpval; |
|
|
|
tmpval.s = (short) from; |
tmpval.s = (short) from; |
retval.c[0] = tmpval.c[1]; |
retval.c[0] = tmpval.c[1]; |
retval.c[1] = tmpval.c[0]; |
retval.c[1] = tmpval.c[0]; |
|
|
return retval.s; |
return retval.s; |
} |
} |
else |
else |
return ntohs(from); /* msb -> lsb conversion on lsb */ |
return ntohs(from); /* msb -> lsb conversion on lsb */ |
} |
} |
#endif |
#endif |
|
|
/* |
size_t |
* process - process input file |
file_mbswidth(const char *s) |
*/ |
|
void |
|
process(inname, wid) |
|
const char *inname; |
|
int wid; |
|
{ |
{ |
int fd = 0; |
#ifdef HAVE_WCHAR_H |
static const char stdname[] = "standard input"; |
size_t bytesconsumed, old_n, n, width = 0; |
unsigned char buf[HOWMANY+1]; /* one extra for terminating '\0' */ |
mbstate_t state; |
struct stat sb; |
wchar_t nextchar; |
int nbytes = 0; /* number of bytes read from a datafile */ |
(void)memset(&state, 0, sizeof(mbstate_t)); |
char match = '\0'; |
old_n = n = strlen(s); |
|
|
if (strcmp("-", inname) == 0) { |
while (n > 0) { |
if (fstat(0, &sb)<0) { |
bytesconsumed = mbrtowc(&nextchar, s, n, &state); |
err(1, "cannot fstat `%s'", stdname); |
if (bytesconsumed == (size_t)(-1) || |
/*NOTREACHED*/ |
bytesconsumed == (size_t)(-2)) { |
|
/* Something went wrong, return something reasonable */ |
|
return old_n; |
} |
} |
inname = stdname; |
if (s[0] == '\n') { |
} |
/* |
|
* do what strlen() would do, so that caller |
|
* is always right |
|
*/ |
|
width++; |
|
} else |
|
width += wcwidth(nextchar); |
|
|
if (wid > 0 && !bflag) |
s += bytesconsumed, n -= bytesconsumed; |
(void) printf("%s:%*s ", inname, |
|
(int) (wid - strlen(inname)), ""); |
|
|
|
if (inname != stdname) { |
|
/* |
|
* first try judging the file based on its filesystem status |
|
*/ |
|
if (fsmagic(inname, &sb) != 0) { |
|
putchar('\n'); |
|
return; |
|
} |
|
|
|
if ((fd = open(inname, O_RDONLY)) < 0) { |
|
/* We can't open it, but we were able to stat it. */ |
|
if (sb.st_mode & 0002) ckfputs("writable, ", stdout); |
|
if (sb.st_mode & 0111) ckfputs("executable, ", stdout); |
|
ckfprintf(stdout, "can't read `%s' (%s).\n", |
|
inname, strerror(errno)); |
|
return; |
|
} |
|
} |
} |
|
return width; |
|
#else |
/* |
return strlen(s); |
* try looking at the first HOWMANY bytes |
|
*/ |
|
if ((nbytes = read(fd, (char *)buf, HOWMANY)) == -1) { |
|
err(1, "read failed"); |
|
/*NOTREACHED*/ |
|
} |
|
|
|
if (nbytes == 0) |
|
ckfputs("empty", stdout); |
|
else { |
|
buf[nbytes++] = '\0'; /* null-terminate it */ |
|
match = tryit(buf, nbytes, zflag); |
|
} |
|
|
|
#ifdef BUILTIN_ELF |
|
if (match == 's' && nbytes > 5) |
|
tryelf(fd, buf, nbytes); |
|
#endif |
#endif |
|
} |
|
|
if (inname != stdname) { |
private void |
#ifdef RESTORE_TIME |
usage(void) |
/* |
{ |
* Try to restore access, modification times if read it. |
(void)fprintf(stderr, USAGE, progname, progname); |
*/ |
#ifdef HAVE_GETOPT_LONG |
# ifdef USE_UTIMES |
(void)fputs("Try `file --help' for more information.\n", stderr); |
struct timeval utsbuf[2]; |
|
utsbuf[0].tv_sec = sb.st_atime; |
|
utsbuf[1].tv_sec = sb.st_mtime; |
|
|
|
(void) utimes(inname, utsbuf); /* don't care if loses */ |
|
# else |
|
struct utimbuf utbuf; |
|
|
|
utbuf.actime = sb.st_atime; |
|
utbuf.modtime = sb.st_mtime; |
|
(void) utime(inname, &utbuf); /* don't care if loses */ |
|
# endif |
|
#endif |
#endif |
(void) close(fd); |
exit(1); |
} |
|
(void) putchar('\n'); |
|
} |
} |
|
|
|
#ifdef HAVE_GETOPT_LONG |
int |
private void |
tryit(buf, nb, zflag) |
help(void) |
unsigned char *buf; |
|
int nb, zflag; |
|
{ |
{ |
/* try compression stuff */ |
puts( |
if (zflag && zmagic(buf, nb)) |
"Usage: file [OPTION]... [FILE]...\n" |
return 'z'; |
"Determine file type of FILEs.\n" |
|
"\n" |
/* try tests in /etc/magic (or surrogate magic file) */ |
" -m, --magic-file LIST use LIST as a colon-separated list of magic\n" |
if (softmagic(buf, nb)) |
" number files\n" |
return 's'; |
" -z, --uncompress try to look inside compressed files\n" |
|
" -b, --brief do not prepend filenames to output lines\n" |
/* try known keywords, check whether it is ASCII */ |
" -c, --checking-printout print the parsed form of the magic file, use in\n" |
if (ascmagic(buf, nb)) |
" conjunction with -m to debug a new magic file\n" |
return 'a'; |
" before installing it\n" |
|
" -f, --files-from FILE read the filenames to be examined from FILE\n" |
/* see if it's international language text */ |
" -F, --separator string use string as separator instead of `:'\n" |
if (internatmagic(buf, nb)) |
" -i, --mime output mime type strings\n" |
return 'i'; |
" -k, --keep-going don't stop at the first match\n" |
|
" -L, --dereference causes symlinks to be followed\n" |
/* abandon hope, all ye who remain here */ |
" -n, --no-buffer do not buffer output\n" |
ckfputs("data", stdout); |
" -N, --no-pad do not pad output\n" |
return '\0'; |
" -p, --preserve-date preserve access times on files\n" |
|
" -r, --raw don't translate unprintable chars to \\ooo\n" |
|
" -s, --special-files treat special (block/char devices) files as\n" |
|
" ordinary ones\n" |
|
" --help display this help and exit\n" |
|
" --version output version information and exit\n" |
|
); |
|
exit(0); |
} |
} |
|
#endif |