find.c revision 68a94df11c9810ff7f455e0638cc865a474bd586
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2007 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
/* All Rights Reserved */
/* Parts of this product may be derived from */
/* Mortice Kern Systems Inc. and Berkeley 4.3 BSD systems. */
/* licensed from Mortice Kern Systems Inc. and */
/* the University of California. */
/*
* Copyright 1985, 1990 by Mortice Kern Systems Inc. All rights reserved.
*/
#include <stdio.h>
#include <errno.h>
#include <pwd.h>
#include <grp.h>
#include <limits.h>
#include <unistd.h>
#include <stdlib.h>
#include <locale.h>
#include <string.h>
#include <strings.h>
#include <libgen.h>
#include <ctype.h>
#include <wait.h>
#include <fnmatch.h>
#include <langinfo.h>
#include <ftw.h>
#define A_MIN (long)(60)
#define BLKSIZ 512
#ifndef FTW_SLN
#define FTW_SLN 7
#endif
#define N_FSTYPES 20
/*
* This is the list of operations
* F_USER and F_GROUP are named to avoid conflict with USER and GROUP defined
*/
enum Command
{
};
enum Type
{
};
struct Args
{
char name[10];
};
/*
* Except for pathnames, these are the only legal arguments
*/
{
NULL, 0, 0
};
union Item
{
time_t t;
char *cp;
char **ap;
long l;
int i;
long long ll;
};
struct Node
{
};
/* if no -print, -exec or -ok replace "expression" with "(expression) -print" */
/*
* Prototype variable size arglist buffer
*/
struct Arglist
{
char *end;
char *nextstr;
char **firstvar;
char **nextvar;
char *arglist[1];
};
static int compile();
static int execute();
static int doexec(char *, char **, int *);
static int ok();
static void usage(void) __NORETURN;
static int list();
static char *getgroup();
static int cmdclose();
static char *getshell();
static void init_remote_fs();
static char *getname();
static int readmode();
static char *gettail();
static char *dummyarg = (char *)-1;
static int lastval;
static int varsize;
static char *cmdname;
static int fstype_index = 0;
static int action_expression = 0; /* -print, -exec, or -ok */
static int error = 0;
static int paren_cnt = 0; /* keeps track of parentheses */
static int hflag = 0;
static int lflag = 0;
/* set when doexec()-invoked utility returns non-zero */
static int exec_exitcode = 0;
extern char **environ;
int
{
char *cp;
int c;
int paths;
char *cwdpath;
#if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */
#endif
(void) textdomain(TEXT_DOMAIN);
exit(1);
}
switch (c) {
case 'H':
hflag = 1;
lflag = 0;
break;
case 'L':
hflag = 0;
lflag = 1;
break;
case '?':
usage();
break;
}
}
if (argc < 1) {
usage();
}
if (*cp == '-')
break;
break;
}
if (paths == 0) /* no path-list */
usage();
/* lflag is the same as -follow */
if (lflag)
/* allocate enough space for the compiler */
/* no expression, default to -print */
} else if (!action_expression) {
/*
* if no action expression, insert an LPAREN node above topnode,
* with a PRINT node as its next node
*/
" implicitly; try explicit -print option\n"),
cmdname);
exit(1);
}
}
while (paths--) {
char *curpath;
/*
* If -H is specified, it means we walk the first
* level (pathname on command line) logically, following
* symlinks, but lower levels are walked physically.
* We use our own secret interface to nftw() to change
* the from stat to lstat after the top level is walked.
*/
if (hflag) {
walkflags &= ~FTW_HOPTION;
else
walkflags |= FTW_HOPTION;
}
/*
* We need this check as nftw needs a CWD and we have no
* way of returning back from that code with a meaningful
* error related to this
*/
gettext("%s : cannot get the current working "
"directory\n"), cmdname);
exit(1);
} else
gettext("%s: cannot open %s: %s\n"),
error = 1;
}
if (paths > 1)
}
/* execute any remaining variable length lists */
while (lastlist) {
}
}
}
/*
* compile the arguments
*/
static int
char **argv;
int *actionp;
{
char *b;
char **av;
char **com;
int i;
gettext("%s: operand follows operand\n"),
cmdname);
exit(1);
}
goto err;
} else {
if (!(b = *++av)) {
gettext("%s: incomplete statement\n"),
cmdname);
exit(1);
}
(*b != '+')) {
if (*b == '+' || *b == '-') {
b++;
}
}
}
}
}
case AND:
break;
case NOT:
break;
case OR:
break;
case LPAREN: {
paren_cnt++;
av += i;
np += i + 1;
continue;
}
case RPAREN:
if (paren_cnt <= 0) {
gettext("%s: unmatched ')'\n"),
cmdname);
exit(1);
}
paren_cnt--;
if (oldnp == 0)
goto err;
gettext("%s: cannot immediately"
" follow an operand with ')'\n"),
cmdname);
exit(1);
}
case FOLLOW:
break;
case MOUNT:
break;
case DEPTH:
break;
case LOCAL:
/*
* Make it compatible to df -l for
* future enhancement. So, anything
* that is not remote, then it is
* local.
*/
break;
case SIZE:
/*FALLTHROUGH*/
case INUM:
break;
case CMIN:
case CTIME:
case MMIN:
case MTIME:
case AMIN:
case ATIME:
case LINKS:
break;
case F_USER:
case F_GROUP: {
i = -1;
} else {
}
if (i == -1) {
if (fnmatch("[0-9][0-9][0-9]*", b, 0) &&
fnmatch("[0-9][0-9]", b, 0) &&
fnmatch("[0-9]", b, 0)) {
"%s: cannot find %s name\n"),
exit(1);
}
i = atoi(b);
}
break;
}
case EXEC:
case OK:
(*actionp)++;
for (;;) {
if ((b = *av) == 0) {
gettext("%s: incomplete statement\n"),
cmdname);
exit(1);
}
if (strcmp(b, ";") == 0) {
*av = 0;
break;
} else if (strcmp(b, "{}") == 0)
else if (strcmp(b, "+") == 0 &&
av[-1] = 0;
break;
}
av++;
}
break;
case NAME:
break;
case PERM:
if (*b == '-')
++b;
"find: -perm: Bad permission string\n"));
usage();
}
break;
case TYPE:
i = *b;
i == 'd' ? S_IFDIR :
i == 'b' ? S_IFBLK :
i == 'c' ? S_IFCHR :
#ifdef S_IFIFO
i == 'p' ? S_IFIFO :
#endif
i == 'f' ? S_IFREG :
#ifdef S_IFLNK
i == 'l' ? S_IFLNK :
#endif
#ifdef S_IFSOCK
i == 's' ? S_IFSOCK :
#endif
#ifdef S_IFDOOR
i == 'D' ? S_IFDOOR :
#endif
0;
break;
case CPIO:
else
goto common;
case NCPIO: {
else
/* set up cpio */
gettext("%s: cannot create %s\n"),
cmdname, b);
exit(1);
}
}
/*FALLTHROUGH*/
case PRINT:
(*actionp)++;
break;
case NEWER: {
gettext("%s: cannot access %s\n"),
cmdname, b);
exit(1);
}
break;
}
case PRUNE:
case NOUSER:
case NOGRP:
break;
case FSTYPE:
break;
case LS:
(*actionp)++;
break;
case XATTR:
break;
case ACL:
break;
}
}
goto err;
if (paren_cnt != 0) {
cmdname);
exit(1);
}
/* just before returning, save next free node from the list */
err:
if (*av)
else
usage();
/*NOTREACHED*/
}
/*
* print out a usage message
*/
static void
usage(void)
{
exit(1);
}
/*
* This is the function that gets executed at each node
*/
static int
char *name;
int type;
{
int val;
time_t t;
long l;
long long ll;
int not = 1;
char *filename;
error = 1;
return (0);
error = 1;
return (0);
gettext("%s: cannot follow symbolic link %s: %s\n"),
error = 1;
return (0);
error = 1;
return (0);
}
while (np) {
case NOT:
continue;
case AND:
continue;
case OR:
/*
* handle naked OR (no term on left hand side)
*/
gettext("%s: invalid -o construction\n"),
cmdname);
exit(2);
}
/* FALLTHROUGH */
case LPAREN: {
if (val)
return (0);
val = 1;
}
break;
}
case LOCAL: {
int nremfs;
val = 1;
/*
* If file system type matches the remote
* file system type, then it is not local.
*/
val = 0;
break;
}
}
break;
}
case TYPE:
goto num;
case PERM:
else
break;
case INUM:
goto llnum;
case NEWER:
goto num;
case ATIME:
goto days;
case CTIME:
goto days;
case MTIME:
days:
goto num;
case MMIN:
goto mins;
case AMIN:
goto mins;
case CMIN:
goto mins;
mins:
goto num;
case CSIZE:
goto llnum;
case SIZE:
goto llnum;
case F_USER:
goto num;
case F_GROUP:
goto num;
case LINKS:
goto num;
else
break;
num:
else
break;
case OK:
break;
case EXEC:
break;
case VARARGS: {
char *cp;
/* there is room just copy the name */
val = 1;
} else {
/* no more room, exec command */
val = 1;
}
break;
}
case DEPTH:
case MOUNT:
case FOLLOW:
val = 1;
break;
case NAME: {
/*
* XPG4 find should not treat a leading '.' in a
* filename specially for pattern matching.
* '.' in a filename, unless '.' is explicitly
* specified.
*/
#ifdef XPG4
#else
#endif
break;
}
case PRUNE:
val = 1;
break;
case NOUSER:
break;
case NOGRP:
break;
case FSTYPE:
break;
case CPIO:
val = 1;
break;
case PRINT:
val = 1;
break;
case LS:
val = 1;
break;
case XATTR:
break;
case ACL:
/*
* Need to get the tail of the file name, since we have
* already chdir()ed into the directory (performed in
* nftw()) of the file
*/
break;
}
/*
* evaluate 'val' and 'not' (exclusive-or)
* if no inversion (not == 1), return only when val == 0
* (primary not true). Otherwise, invert the primary
* and return when the primary is true.
* 'Lastval' saves the last result (fail or pass) when
* returning back to the calling routine.
*/
lastval = 0;
return (0);
}
lastval = 1;
not = 1;
}
return (0);
}
/*
* code for the -ok option
*/
static int
char *name;
char *argv[];
{
int c, yes = 0;
else
yes = 1;
while (c != '\n')
if (c == EOF)
exit(2);
else
c = getchar();
}
/*
* execute argv with {} replaced by name
*
* Per XPG6, find must exit non-zero if an invocation through
* -exec, punctuated by a plus sign, exits non-zero, so set
* exitcode if we see a non-zero exit.
* exitcode should be NULL when -exec or -ok is not punctuated
* by a plus sign.
*/
static int
{
char *cp;
int dummyseen = 0;
int exit_status = 0;
if (name) {
dummyseen = 1;
}
}
}
return (r);
/* fork failed */
*exitcode = 1;
return (0);
}
if (pid != 0) {
/* parent */
do {
/* wait for child to exit */
*exitcode = 1;
return (0);
}
} else {
/* child */
exit(1);
/*
* We are in a situation where argv[0] points to a
* limited to SHELL_MAXARGS arguments. If you try to
* pass more than SHELL_MAXARGS arguments, execvp()
* fails with E2BIG.
*
* In this situation, process the argument list by
* packets of SHELL_MAXARGS arguments with respect of
* the following rules:
* 1. the invocations have to complete before find exits
* 2. only one invocation can be running at a time
*/
i = 1;
while (argv[i]) {
j = 1;
while (j <= SHELL_MAXARGS && argv[i]) {
}
/* fork failed */
exit(1);
}
if (pid1 == 0) {
/* child */
exit(1);
}
status = 0;
do {
/* wait for the child to exit */
gettext("wait failed %s"),
exit(1);
}
if (status)
exit_status = 1;
}
/* all the invocations have completed */
}
}
}
return (!r);
}
/*
* Table lookup routine
*/
static struct Args *
char *word;
{
int second;
return (0);
return (argp);
argp++;
}
return (0);
}
/*
* Get space for variable length argument list
*/
static struct Arglist *
char **com;
{
int n;
char **ep;
if (varsize == 0) {
n = 2*sizeof (char **);
}
return (ap);
}
/*
* filter command support
* fork and exec cmd(argv) according to mode:
*
* "r" with fp as stdin of cmd (default stdin), cmd stdout returned
* "w" with fp as stdout of cmd (default stdout), cmd stdin returned
*/
static struct /* info for each cmdopen() */
{
static FILE *
char *cmd;
char **argv;
char *mode;
{
int proc;
int cmdfd;
int usrfd;
int pio[2];
switch (*mode) {
case 'r':
cmdfd = 1;
usrfd = 0;
break;
case 'w':
cmdfd = 0;
usrfd = 1;
break;
default:
return (0);
}
break;
return (0);
return (0);
case -1:
return (0);
case 0:
}
char **p;
char **v;
/*
* assume cmd is a shell script
*/
p = argv;
while (*p++);
sizeof (char **))) {
p = v;
*p++ = cmd;
while (*p++ = *argv++);
}
}
/*NOTREACHED*/
default:
}
}
/*
* close a stream opened by cmdopen()
* -1 returned if cmdopen() had a problem
* otherwise exit() status of command is returned
*/
static int
{
int i;
int status;
for (i = 0; i < MAXCMDS; i++)
if (i >= MAXCMDS)
return (-1);
if (p == pid) {
status = -1;
}
else
status = -1;
return (status);
}
/*
* return pointer to the full path name of the shell
*
* SHELL is read from the environment and must start with /
*
* if set-uid or set-gid then the executable and its containing
* directory must not be writable by the real user
*
*/
char *
getshell()
{
char *s;
char *sh;
uid_t u;
int j;
if (u = getuid()) {
goto defshell;
*s = 0;
*s = '/';
if (!j) goto defshell;
}
return (sh);
}
}
/*
* the following functions implement the added "-ls" option
*/
#include <utmpx.h>
#define NUID 64
#define NGID 64
static struct ncache {
int id;
/*
* This function assumes that the password file is hashed
* (or some such) to allow fast access based on a name key.
*/
static char *
{
int cp;
#else
#endif
if (!pw)
return (0);
}
/*
* This function assumes that the group file is hashed
* (or some such) to allow fast access based on a name key.
*/
static char *
{
int cp;
#else
#endif
if (!gr)
return (0);
}
static int
char *file;
{
int trivial;
/*
* Each line below contains the relevant permission (column 1) and character
* shown when the corresponding execute bit is either clear (column 2)
* or set (column 3)
* These permissions are as shown by ls(1b)
*/
#ifdef S_IFLNK
#endif
int who;
char *cp;
char *tailname;
long long ksize;
return (-1);
if (sixmonthsago == -1)
#ifdef S_IFDIR
case S_IFDIR: /* directory */
pmode[0] = 'd';
break;
#endif
#ifdef S_IFCHR
case S_IFCHR: /* character special */
pmode[0] = 'c';
break;
#endif
#ifdef S_IFBLK
case S_IFBLK: /* block special */
pmode[0] = 'b';
break;
#endif
#ifdef S_IFIFO
case S_IFIFO: /* fifo special */
pmode[0] = 'p';
break;
#endif
#ifdef S_IFLNK
case S_IFLNK: /* symbolic link */
pmode[0] = 'l';
break;
#endif
#ifdef S_IFSOCK
case S_IFSOCK: /* socket */
pmode[0] = 's';
break;
#endif
#ifdef S_IFDOOR
case S_IFDOOR: /* door */
pmode[0] = 'D';
break;
#endif
#ifdef S_IFREG
case S_IFREG: /* regular */
pmode[0] = '-';
break;
#endif
default:
pmode[0] = '?';
break;
}
else
else
else if (is_exec)
else
}
/*
* Need to get the tail of the file name, since we have
* already chdir()ed into the directory of the file
*/
if (trivial == -1)
trivial = 0;
if (trivial == 1)
else
/*
* Prepare uname and gname. Always add a space afterwards
* to keep columns from running together.
*/
else
else
else {
#ifdef S_IFLNK
if (pmode[0] == 'l') {
if (who >= 0)
else
flink[0] = '\0';
}
#endif
}
else
#ifdef S_IFSOCK
#else
#endif
(void) printf("%s %2ld %s%s%s %s %s%s%s\n",
pmode, /* protection */
uname, /* owner */
gname, /* group */
fsize, /* # of bytes */
ftime, /* modify time */
file, /* name */
#ifdef S_IFLNK
#else
"",
""
#endif
);
return (0);
}
static char *
new_string(char *s)
{
char *p = strdup(s);
if (p)
return (p);
exit(1);
/*NOTREACHED*/
}
/*
* Read remote file system types from REMOTE_FS into the
* remote_fstypes array.
*/
static void
{
char line_buf[LINEBUF_SIZE];
gettext("%s: Warning: can't open %s, ignored\n"),
/* Use default string name for NFS */
return;
}
char buf[LINEBUF_SIZE];
/* LINTED - unbounded string specifier */
if (fstype_index == N_FSTYPES)
break;
}
}
/*
* The PERM struct is the machine that builds permissions. The p_special
* field contains what permissions need to be checked at run-time in
* getmode(). This is one of 'X', 'u', 'g', or 'o'. It contains '\0' to
* indicate normal processing.
*/
typedef struct PERMST {
} PERMST;
#ifndef S_ISVTX
#define S_ISVTX 0 /* Not .1 */
#endif
/* Mask values */
static int iswho(int c);
static int isop(int c);
/*
* compile an automaton that recognizes it. The return value
* is NULL if everything is OK, otherwise it is -1.
*/
static int
const char *ascmode;
{
int seen_X;
nowho = 0;
seen_X = 0;
int mode;
mode = 0;
if (*amode != '\0')
return (-1);
/*
* There is no requirement of the octal mode bits being
* the same as the S_ macros.
*/
{
0
};
int i, newmode = 0;
for (i = 0; mapping[i] != 0; i++)
if (mode & (1<<i))
}
#endif
} else for (;;) {
int t;
int who = 0;
++amode;
who |= t;
}
if (who == 0) {
/*
* If no who specified, must use contents of
* umask to determine which bits to flip. This
*/
++nowho;
} else
nowho = 0;
return (-1);
seen_X = 1;
/*
* Remember the 'who' for the previous
* transformation.
*/
/* Keep 'X' separate */
++pp;
}
} else if (seen_X) {
/* Remember the 'who' for the X */
/* Keep 'X' separate */
++pp;
}
++amode;
}
/*
* These returned 0, but were actually parsed, so
* don't look at them again.
*/
case 'u':
case 'g':
case 'o':
++amode;
break;
}
switch (*amode) {
case '\0':
break;
case ',':
++amode;
++pp;
continue;
default:
++pp;
goto samewho;
}
break;
}
return (NULL);
}
/*
* Given a character from the mode, return the associated
* value as who (user designation) mask or 0 if this isn't valid.
*/
static int
iswho(c)
int c;
{
switch (c) {
case 'a':
return (P_A);
case 'u':
return (P_U);
case 'g':
return (P_G);
case 'o':
return (P_O);
default:
return (0);
}
/* NOTREACHED */
}
/*
* Return non-zero if this is a valid op code
* in a symbolic mode.
*/
static int
isop(c)
int c;
{
switch (c) {
case '+':
case '-':
case '=':
return (1);
default:
return (0);
}
/* NOTREACHED */
}
/*
* Return the permission bits implied by this character or 0
* if it isn't valid. Also returns 0 when the pseudo-permissions 'u', 'g', or
* 'o' are used, and sets pp->p_special to the one used.
*/
static int
int c;
{
switch (c) {
case 'u':
case 'g':
case 'o':
return (0);
case 'r':
case 'w':
case 'x':
#if S_ISVTX != 0
case 't':
return (S_ISVTX);
#endif
case 'X':
#if S_ISVTX != 0
case 'a':
return (S_ISVTX);
#endif
case 'h':
return (S_ISUID);
/*
* This change makes:
* chmod +s file
* set the system bit on dos but means that
* chmod u+s file
* chmod g+s file
* chmod a+s file
* are all like UNIX.
*/
case 's':
default:
return (0);
}
/* NOTREACHED */
}
/*
* Execute the automaton that is created by readmode()
* to generate the final mode that will be used. This
* code is passed a starting mode that is usually the original
* mode of the file being changed (or 0). Note that this mode must contain
* the file-type bits as well, so that S_ISDIR will succeed on directories.
*/
static mode_t
{
/*
* For the special modes 'u', 'g' and 'o', the named portion
* of the mode refers to after the previous clause has been
* processed, while the 'X' mode refers to the contents of the
* mode before any clauses have been processed.
*
* lines 2568-2570, 2578-2583
*/
case 'u':
break;
case 'g':
break;
case 'o':
break;
case 'X':
break;
default:
break;
}
case '-':
break;
case '=':
/* FALLTHROUGH */
case '+':
break;
}
}
return (startmode);
}
/*
* Returns the last component of a path name, unless it is
* an absolute path, in which case it returns the whole path
*/
static char
{
if (*fname != '/') {
base++;
else
}
return (base);
}