345 lines
7.2 KiB
C
345 lines
7.2 KiB
C
/* $Id$ */
|
|
/* Copyright (c) 2007-2020 Pierre Pronchery <khorben@defora.org> */
|
|
/* This file is part of DeforaOS Unix utils */
|
|
/* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, version 3 of the License.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
|
/* TODO:
|
|
* - check commands validity before starting?
|
|
* - do all primaries apply to folders? (eg links) */
|
|
|
|
|
|
|
|
#include <sys/stat.h>
|
|
#include <dirent.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <fnmatch.h>
|
|
#include <pwd.h>
|
|
#include <grp.h>
|
|
#include <errno.h>
|
|
|
|
/* constants */
|
|
#ifndef PROGNAME
|
|
# define PROGNAME "find"
|
|
#endif
|
|
|
|
|
|
/* types */
|
|
typedef unsigned int Prefs;
|
|
#define FIND_PREFS_H 1
|
|
#define FIND_PREFS_L 2
|
|
|
|
|
|
typedef enum _FindCmd
|
|
{
|
|
FC_INVALID = -1,
|
|
FC_NAME = 0,
|
|
FC_NOGROUP,
|
|
FC_NOUSER,
|
|
FC_XDEV,
|
|
FC_PRUNE,
|
|
FC_PERM,
|
|
FC_TYPE,
|
|
FC_LINKS,
|
|
FC_USER,
|
|
FC_GROUP,
|
|
FC_SIZE,
|
|
FC_ATIME,
|
|
FC_CTIME,
|
|
FC_MTIME,
|
|
FC_EXEC,
|
|
FC_OK,
|
|
FC_PRINT,
|
|
FC_NEWER,
|
|
FC_DEPTH
|
|
} FindCmd;
|
|
#define FC_LAST FC_DEPTH
|
|
|
|
static int _find_error(char const * message, int ret);
|
|
static int _find_error_user(char const * message, char const * error, int ret);
|
|
static int _find_do(Prefs * prefs, char const * pathname, int cmdc,
|
|
char * cmdv[]);
|
|
|
|
static int _find(Prefs * prefs, int argc, char * argv[])
|
|
{
|
|
int ret = 0;
|
|
int i;
|
|
int filec;
|
|
|
|
for(i = 0; i < argc; i++)
|
|
if(argv[i][0] == '-' || argv[i][0] == '!' || argv[i][0] == '(')
|
|
break;
|
|
filec = i;
|
|
for(i = 0; i < filec; i++)
|
|
ret |= _find_do(prefs, argv[i], argc - filec, &argv[filec]);
|
|
return ret;
|
|
}
|
|
|
|
static int _find_error(char const * message, int ret)
|
|
{
|
|
fputs(PROGNAME ": ", stderr);
|
|
perror(message);
|
|
return ret;
|
|
}
|
|
|
|
static int _find_error_user(char const * message, char const * error, int ret)
|
|
{
|
|
fprintf(stderr, "%s%s: %s\n", PROGNAME ": ", message, error);
|
|
return ret;
|
|
}
|
|
|
|
/* find_do */
|
|
static int _do_cmd(char const * pathname, struct stat * st,
|
|
int cmdc, char * cmdv[]);
|
|
static int _do_dir(Prefs * prefs, char const * pathname, int cmdc,
|
|
char * cmdv[]);
|
|
|
|
static int _find_do(Prefs * prefs, char const * pathname, int cmdc,
|
|
char * cmdv[])
|
|
{
|
|
struct stat st;
|
|
|
|
if(stat(pathname, &st) != 0) /* XXX TOCTOU, danger of infinite loop */
|
|
{
|
|
if(errno != ENOENT || *prefs & FIND_PREFS_L
|
|
|| stat(pathname, &st) != 0)
|
|
return _find_error(pathname, 1);
|
|
}
|
|
if(_do_cmd(pathname, &st, cmdc, cmdv) != 0)
|
|
return 0;
|
|
if(S_ISDIR(st.st_mode))
|
|
return _do_dir(prefs, pathname, cmdc, cmdv);
|
|
return 0;
|
|
}
|
|
|
|
/* do_cmd */
|
|
static FindCmd _cmd_enum(char const * cmd);
|
|
static int _cmd_check_arg(int * i, int cmdc, char const * cmd);
|
|
|
|
static int _do_cmd(char const * pathname, struct stat * st,
|
|
int cmdc, char * cmdv[])
|
|
{
|
|
int ret = 0;
|
|
int i;
|
|
char const * filename;
|
|
struct group * grp;
|
|
struct passwd * pw;
|
|
long int l;
|
|
char * p;
|
|
|
|
if((filename = strrchr(pathname, '/')) == NULL)
|
|
filename = pathname;
|
|
else
|
|
filename++;
|
|
for(i = 0; i < cmdc; i++)
|
|
switch(_cmd_enum(cmdv[i]))
|
|
{
|
|
case FC_GROUP:
|
|
if(_cmd_check_arg(&i, cmdc, cmdv[i]) != 0)
|
|
return 1;
|
|
if((grp = getgrnam(cmdv[i])) == NULL)
|
|
/* FIXME handle numeric id */
|
|
return _find_error_user(cmdv[i],
|
|
"No such group", 1);
|
|
if(st->st_gid != grp->gr_gid)
|
|
ret = 1;
|
|
break;
|
|
case FC_NAME:
|
|
if(_cmd_check_arg(&i, cmdc, cmdv[i]) != 0)
|
|
return 1;
|
|
if(fnmatch(cmdv[i], filename, 0) != 0)
|
|
ret = 1;
|
|
break;
|
|
case FC_LINKS:
|
|
if(_cmd_check_arg(&i, cmdc, cmdv[i]) != 0)
|
|
return 1;
|
|
l = strtol(cmdv[i], &p, 0);
|
|
/* FIXME input validation */
|
|
if(st->st_nlink != l)
|
|
ret = 1;
|
|
break;
|
|
case FC_NOGROUP:
|
|
if(getgrgid(st->st_gid) != NULL)
|
|
ret = 1;
|
|
break;
|
|
case FC_NOUSER:
|
|
if(getpwuid(st->st_uid) != NULL)
|
|
ret = 1;
|
|
break;
|
|
case FC_PERM:
|
|
if(_cmd_check_arg(&i, cmdc, cmdv[i]) != 0)
|
|
return 1;
|
|
/* FIXME handle "minus" prefix? */
|
|
l = strtol(cmdv[i], &p, 0);
|
|
/* FIXME input validation, mode as expression */
|
|
if(((st->st_mode & 07777) & l) != l)
|
|
ret = 1;
|
|
break;
|
|
case FC_PRINT:
|
|
puts(pathname);
|
|
break;
|
|
case FC_PRUNE:
|
|
if(S_ISDIR(st->st_mode))
|
|
ret = 1;
|
|
break;
|
|
case FC_USER:
|
|
if(_cmd_check_arg(&i, cmdc, cmdv[i]) != 0)
|
|
return 1;
|
|
if((pw = getpwnam(cmdv[i])) == NULL)
|
|
/* FIXME handle numeric id */
|
|
return _find_error_user(cmdv[i],
|
|
"No such user", 1);
|
|
if(st->st_uid != pw->pw_uid)
|
|
return 1;
|
|
break;
|
|
case FC_ATIME:
|
|
case FC_CTIME:
|
|
case FC_DEPTH:
|
|
case FC_EXEC:
|
|
case FC_MTIME:
|
|
case FC_NEWER:
|
|
case FC_OK:
|
|
case FC_SIZE:
|
|
case FC_TYPE:
|
|
case FC_XDEV:
|
|
errno = ENOSYS; /* FIXME not implemented */
|
|
return _find_error(cmdv[i], 1);
|
|
case FC_INVALID:
|
|
errno = EINVAL;
|
|
return _find_error(cmdv[i], 1);
|
|
}
|
|
if(ret == 0 && i == cmdc)
|
|
puts(pathname);
|
|
if(S_ISDIR(st->st_mode))
|
|
return 0;
|
|
return ret;
|
|
}
|
|
|
|
static FindCmd _cmd_enum(char const * cmd)
|
|
{
|
|
const char * cmds[FC_LAST + 1] =
|
|
{
|
|
"-name",
|
|
"-nouser",
|
|
"-nogroup",
|
|
"-xdev",
|
|
"-prune",
|
|
"-perm",
|
|
"-type",
|
|
"-links",
|
|
"-user",
|
|
"-group",
|
|
"-size",
|
|
"-atime",
|
|
"-ctime",
|
|
"-mtime",
|
|
"-exec",
|
|
"-ok",
|
|
"-print",
|
|
"-newer",
|
|
"-depth"
|
|
};
|
|
int i;
|
|
|
|
for(i = 0; i < FC_LAST + 1; i++)
|
|
if(strcmp(cmd, cmds[i]) == 0)
|
|
return i;
|
|
return FC_INVALID;
|
|
}
|
|
|
|
static int _cmd_check_arg(int * i, int cmdc, char const * cmd)
|
|
{
|
|
if(++(*i) == cmdc)
|
|
{
|
|
errno = EINVAL; /* XXX */
|
|
return _find_error(cmd, 1);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int _do_dir(Prefs * prefs, char const * pathname, int cmdc,
|
|
char * cmdv[])
|
|
{
|
|
int ret = 0;
|
|
DIR * dir;
|
|
struct dirent * de;
|
|
size_t len;
|
|
char * path;
|
|
char * p;
|
|
|
|
if((dir = opendir(pathname)) == NULL)
|
|
return _find_error(pathname, 1);
|
|
len = strlen(pathname) + 2;
|
|
if((path = malloc(len)) == NULL)
|
|
{
|
|
closedir(dir);
|
|
return _find_error(pathname, 1);
|
|
}
|
|
sprintf(path, "%s/", pathname);
|
|
while((de = readdir(dir)) != NULL)
|
|
{
|
|
if(de->d_name[0] == '.' && (de->d_name[1] == '\0'
|
|
|| (de->d_name[1] == '.'
|
|
&& de->d_name[2] == '\0')))
|
|
continue;
|
|
if((p = realloc(path, len + strlen(de->d_name))) == NULL)
|
|
break;
|
|
path = p;
|
|
strcpy(&path[len - 1], de->d_name);
|
|
ret |= _find_do(prefs, path, cmdc, cmdv);
|
|
}
|
|
free(path);
|
|
if(de != NULL)
|
|
ret |= _find_error(path, 1);
|
|
if(closedir(dir) != 0)
|
|
ret |= _find_error(path, 1);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* usage */
|
|
static int _usage(void)
|
|
{
|
|
fputs("Usage: " PROGNAME " [-H|-L] path... [expression...]\n"
|
|
" -H De-reference links unless dangling or in the command line\n"
|
|
" -L De-reference links always\n", stderr);
|
|
return 1;
|
|
}
|
|
|
|
|
|
/* main */
|
|
int main(int argc, char * argv[])
|
|
{
|
|
Prefs prefs = 0;
|
|
int o;
|
|
|
|
while((o = getopt(argc, argv, "HL")) != -1)
|
|
switch(o)
|
|
{
|
|
case 'H':
|
|
prefs &= ~FIND_PREFS_L;
|
|
prefs |= FIND_PREFS_H;
|
|
break;
|
|
case 'L':
|
|
prefs &= ~FIND_PREFS_H;
|
|
prefs |= FIND_PREFS_L;
|
|
break;
|
|
default:
|
|
return _usage();
|
|
}
|
|
if(argc - optind == 0)
|
|
return _usage();
|
|
return (_find(&prefs, argc - optind, &argv[optind]) == 0) ? 0 : 2;
|
|
}
|