/* find.c - Marcus Fritzsch - 20040414 - 0.2 */ #define _GNU_SOURCE #define _BSD_SOURCE #define _SVID_SOURCE /* XXX: don't know if this one is right!! */ #define _POSIX_C_SOURCE 2 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /*#ifdef FNM_CASEFOLD #undef FNM_CASEFOLD #endif*/ #ifndef NAME_MAX # define NAME_MAX 255 #endif #ifndef DIR_SEP # define DIR_SEP '/' #endif #ifndef FALSE # define FALSE 0 # define TRUE 1 #endif #define SDE struct dirent #define SS struct stat #define SNS struct namestat /* need to have stat and name in one place... */ struct namestat { /* XXX: do not change the field's order!!! look at do_work_on_entry () */ char * n; SS * s; }; enum F_TYPES { F_TBLOCK = 'b', F_TCHAR = 'c', F_TDIR = 'd', F_TNPIPE = 'p', F_TFILE = 'f', F_TLINK = 'l', F_TSOCK = 's', #ifdef SOLARIS F_TDOOR = 'D', #endif F_NOTYPE = '\0' }; enum F_PERMTYPE { noperm = 0, p_equal, /* permission has to be equal */ p_all, /* permission on file has to have all bits of perm set */ p_any /* permission of file has to have at least one bit set */ }; struct OPTS { enum F_PERMTYPE check_perm; char ** dir; mode_t perm; char type; int mtime; char ** exec; int exec_fname_idx; unsigned char ls; unsigned char posixly_correct; char * name; unsigned char check_iname; char * prog; }; typedef int (* filter_func)(SNS * s); inline int do_check_type (register char a); int get_exec_cmnd (const char ** a); int check_is_dir (const char * n); inline int check_is_opt (const char * o); void set_opts (const char ** a); void tie_up_my_mess (); void find_init (); void do_ls_output (SS * s, const char * p, const char * n); void do_work_on_entry (SS * s, const char * p, const char * n); int do_find (const char * d); void show_help (); void add_filtfunc_entry (filter_func fp); /* filter functions, they know whether a file fits the cmnd-args o not */ inline int filt_perm (register SNS * s); inline int filt_mtime (register SNS * s); inline int filt_type (register SNS * s); inline int filt_name (register SNS * s); inline int filt_iname (register SNS * s); #define F_OPTPERM "perm" #define F_OPTTYPE "type" #define F_OPTMTIM "mtime" #define F_OPTEXEC "exec" #define F_OPTLS "ls" #define F_OPTNAME "name" #define F_OPTINAME "iname" #define F_OPTHELP "help" #define F_OPTHELPS "h" /* if no directory is specified on cmd-line, set default to . */ #define DOT "." /* the time to calculate with when using mtime */ time_t start_time; /* pointers to check-routines */ /* checks has to be filled at startup, preferably in find_init with all * possible filter functions. Note, that the last one needs to be zero * for further info look at do_work_on_entry () */ filter_func * checks; /* options */ struct OPTS my_opts; /* need those... */ #define VARG va_list p; va_start (p, fmt), vfprintf (stderr, fmt, p), va_end (p) void warn (char * fmt, ...) { VARG; } void bail (char * fmt, ...) { VARG; exit (EXIT_FAILURE); } #define dbg warn #undef VARG #define out_of_mem (void) write (STDERR_FILENO, "OUT OF MEMORY\n", 14), \ exit (EXIT_FAILURE) void show_help () { warn ("%s [path ...] [option ...]\n", my_opts.prog); exit (EXIT_SUCCESS); } inline int do_check_type (register char a) { /* check whether type-option a is vlid or not */ if (a == (char) F_TBLOCK || a == (char) F_TCHAR || a == (char) F_TDIR || a == (char) F_TNPIPE || a == (char) F_TFILE || a == (char) F_TLINK || a == (char) F_TSOCK #ifdef SOLARIS || a == (char) F_TDOOR #endif ) { my_opts.type = a; return TRUE; } return FALSE; } int get_exec_cmnd (const char ** a) { int arg_cnt, i; /*just count arguments... */ for (arg_cnt = 0; a [arg_cnt] != NULL && a [arg_cnt][0] != ';'; arg_cnt++) ; free (my_opts.exec); /* just for correctness */ if ((my_opts.exec = (char **) malloc (sizeof (char *) * (arg_cnt+1))) == NULL) out_of_mem; memset (my_opts.exec /*@null@*/, 0, sizeof (my_opts.exec)); for (i = 0; i < arg_cnt; i++) { my_opts.exec [i] = (char *) a [i]; /* find filename index in cmnd-string and put it to opts */ if (a [i][0] == '{' && a [i][1] == '}' && a[i][2] == '\0') my_opts.exec_fname_idx = i; } return arg_cnt; } inline int check_is_opt (const char * o) { return strcmp (o, "-" F_OPTEXEC) == 0 || strcmp (o, "-" F_OPTMTIM) == 0 || strcmp (o, "-" F_OPTLS) == 0 || strcmp (o, "-" F_OPTPERM) == 0 || strcmp (o, "-" F_OPTNAME) == 0 || strcmp (o, "-" F_OPTINAME) == 0 || strcmp (o, "-" F_OPTHELP) == 0 || strcmp (o, "-" F_OPTHELPS) == 0 || strcmp (o, "-" F_OPTTYPE) == 0; } void add_dir (const char * a) { /* add a directory to my_opts.dir */ static int n = 0; my_opts.dir = realloc (my_opts.dir, (n + 2) * sizeof (char *)); if (my_opts.dir == NULL) out_of_mem; n++; my_opts.dir [n - 1] = malloc (strlen (a) + 1); if (my_opts.dir [n - 1] == NULL) out_of_mem; memset (my_opts.dir [n - 1], 0, strlen (a) + 1); strcpy (my_opts.dir [n - 1], a); my_opts.dir [n] = 0; } void set_opts (const char ** a) { /* parse argv and set options in my_opts */ int i = 0; char * p; /* - sizeof (char *): dirty hack to get program name :o) */ /*memset (&my_opts, 0, sizeof (my_opts) - sizeof (char *));*/ while (a [i] != NULL) { p = (char *) a [i]; if (! check_is_opt (p)) add_dir (p); else if (p [0] == '-') { /* OPTION -perm */ if (strcmp (p, "-" F_OPTPERM) == 0) { if (a [i + 1] == NULL) bail ("-perm needs argument (.e.g. -perm 755)\n"); if (my_opts.check_perm != noperm) { warn ("-perm should be used only once!\n"); i+=2; continue; } switch (a [i + 1][0]) { case '+': my_opts.check_perm = p_any; break; case '-': my_opts.check_perm = p_all; break; default: my_opts.check_perm = p_equal; break; } if (my_opts.check_perm != p_equal) my_opts.perm = strtoul (&a [i + 1][1], NULL, 8); else my_opts.perm = strtoul (a [i + 1], NULL, 8); i++; add_filtfunc_entry (filt_perm); } /* OPTION -type */ else if (strcmp (p, "-" F_OPTTYPE) == 0) { if (a [i + 1] == NULL) bail ("-type needs argument (e.g. -type f)\n"); if (my_opts.type) { warn ("-type should be used only once!\n"); i+=2; continue; } if (! do_check_type (a [i + 1][0])) bail ("wrong argument to -type (%c)\n", a [i + 1][0]); my_opts.type = a [i + 1][0]; i++; add_filtfunc_entry (filt_type); } /* OPTION -mtime */ else if (strcmp (p, "-" F_OPTMTIM) == 0) { if (a [i + 1] == NULL) bail ("-mtime needs argument (e.g. -mtime 7)\n"); if (my_opts.mtime) { warn ("-mtime should be used only once!\n"); i+=2; continue; } my_opts.mtime = atoi (a [i + 1]); if (my_opts.mtime < 0) my_opts.mtime = -my_opts.mtime; i++; add_filtfunc_entry (filt_mtime); } /* OPTION -exec */ else if (strcmp (p, "-" F_OPTEXEC) == 0) i += get_exec_cmnd (& a [i + 1]) + 1; /* OPTION -ls */ else if (strcmp (p, "-" F_OPTLS) == 0) my_opts.ls = 1; /* OPTION -name */ else if (strcmp (p, "-" F_OPTNAME) == 0) { if (a [i + 1] == NULL) bail ("-name needs argument (e.g. -name *.c)\n"); if (my_opts.name) { warn ("-name should be used only once!\n"); i+=2; continue; } my_opts.name = (char *) a [i + 1]; i++; add_filtfunc_entry (filt_name); } /* OPTION -iname */ else if (strcmp (p, "-" F_OPTINAME) == 0) { #ifndef FNM_CASEFOLD int ic, l; #endif if (a [i + 1] == NULL) bail ("-iname needs argument (e.g. -iname *.c)\n"); if (my_opts.name && my_opts.check_iname == FALSE) { warn ("-iname and -name cannot be used together!\n"); i+=2; continue; } else if (my_opts.name && my_opts.check_iname == TRUE) { warn ("-iname should be used only once!\n"); i+=2; continue; } my_opts.name = (char *) a [i + 1]; my_opts.check_iname = TRUE; #ifndef FNM_CASEFOLD l = strlen (my_opts.name); for (ic = 0; ic < l; ic++) my_opts.name [ic] = toupper (my_opts.name [ic]); #endif i++; add_filtfunc_entry (filt_iname); } else if (strcmp (p, "-" F_OPTHELP) == 0 || strcmp (p, "-" F_OPTHELPS) == 0) show_help (); else bail ("unknown option %s\n", p); } i++; } /* just in case no path was specified! */ if (my_opts.dir == NULL) add_dir (DOT); if (my_opts.exec != NULL && my_opts.ls == 1) bail ("options -exec and -ls cannot be specified both!\n"); } void tie_up_my_mess () { int i; /* free pointers that might be unfreed */ free (my_opts.exec); if (my_opts.dir) { for (i = 0; my_opts.dir [i] != NULL; i++) free (my_opts.dir [i]); free (my_opts.dir); } free (checks); } void add_filtfunc_entry (filter_func fp) { /* just to consistently add filter functions */ static int times = 0; if (times == 0) checks = NULL; checks = realloc (checks, sizeof (filter_func) * (times + 2)); if (checks == NULL) out_of_mem; times++; checks [times - 1] = fp; checks [times] = NULL; } void find_init (char ** argv) { /* need this for -ls output */ memset (&my_opts, 0, sizeof (my_opts)); my_opts.posixly_correct = getenv ("POSIXLY_CORRECT") == NULL ? 0 : 1; time (& start_time); atexit (tie_up_my_mess); /* fritschy20040802_0200: set_opts does this now! */ /* maybe doing this automatically... for every function implemented * for this purpose try to implement some function-class that contains * all needed to register here */ /*add_filtfunc_entry (filt_perm); add_filtfunc_entry (filt_type); add_filtfunc_entry (filt_mtime); add_filtfunc_entry (filt_name); add_filtfunc_entry (filt_iname);*/ my_opts.prog = *argv; } /* do output ls -lids fname like * NOTE: this code is somewhat messed up... do not complain about it! */ void do_ls_output (SS * s, const char * p, const char * n) { /* * [pts/5] m@tau:~/dokumente/coding/c$ ls -lids find* * 4956001 12 -rwxr-xr-x 1 m m 8776 2004-04-15 12:36 find * 4956020 12 -rw-r--r-- 1 m m 8544 2004-04-15 12:36 find.c * inode lnk mode ? uid gid blocks mtime name */ char mode [11]; char mtime [17]; mode_t m = s->st_mode; SS sl; char nl [NAME_MAX * 5]; /* do not ask me why I did this crap... */ char l [NAME_MAX]; int ll; strftime (mtime, 17, "%F %H:%M", localtime (&s->st_mtime)); #define R(x) (x ? 'r' : '-') #define W(x) (x ? 'w' : '-') #define X(x) (x ? 'x' : '-') mode [0]=S_ISDIR(m) ?'d':S_ISREG(m)? '-':S_ISCHR(m) ?'c':S_ISBLK(m)?'b': S_ISFIFO(m)?'f':S_ISLNK(m)?'l' :S_ISSOCK(m)?'s': #ifdef SOLARIS S_ISDOOR(m)?'D': #endif '?'; mode [1]=R(m&S_IRUSR); mode [2]=W(m&S_IWUSR); mode [3]=m&S_ISUID?'S':X(m&S_IXUSR); mode [4]=R(m&S_IRGRP); mode [5]=W(m&S_IWGRP); mode [6]=m&S_ISGID?'S':X(m&S_IXGRP); mode [7]=R(m&S_IROTH); mode [8]=W(m&S_IWOTH); mode [9]= X(m&S_IXOTH); mode[10]='\0'; #undef R #undef W #undef X /* return TRUE;here's the special case, if file is a symlink, do one more stat * on file and successivly read the link's target */ if (S_ISLNK(m)) { if (stat (n, &sl) != 0) { warn ("could not stat link '%s%s': %s\n", p, n, strerror (errno)); return; } nl[0] = '\0'; /* I _DO NOT_ like those string-operations... */ strncat (nl, n, strlen (n)); strncat (nl, " -> ", 4); if ((ll = readlink (n, l, NAME_MAX)) <= 0) { warn ("could not read link '%s%s': %s\n", p, n, strerror (errno)); goto end_of_if; } strncat (nl, l, ll); printf ("%11d %3d %s %6u %6u %7u %s %s%s\n", (int) sl.st_ino, s->st_nlink, mode, s->st_uid, s->st_gid, (unsigned) sl.st_blocks * (my_opts.posixly_correct == 0?1:2), mtime, p, nl); return; } else end_of_if: /* if I could not readlink the link... do std-output */ printf ("%11d %3d %s %6u %6u %7u %s %s%s\n", (int) s->st_ino, s->st_nlink, mode, s->st_uid, s->st_gid, (unsigned) s->st_blocks * (my_opts.posixly_correct == 0?1:2), mtime, p, n); } inline int filt_perm (register SNS * s) { /* check if -perm was given, and if file matches permission mode */ if (my_opts.check_perm != noperm) switch (my_opts.check_perm) { case p_equal: if ((s->s->st_mode & 07777) != my_opts.perm) return FALSE; break; case p_all: if ((s->s->st_mode & my_opts.perm) != my_opts.perm) return FALSE; break; case p_any: if ((s->s->st_mode & my_opts.perm) == 0) return FALSE; break; default: break; } return TRUE; } inline int filt_mtime (register SNS * s) { /* if -mtime was given, check if file matches following expression */ if (my_opts.mtime != 0) if (s->s->st_mtime + my_opts.mtime * 86400 < start_time) return FALSE; return TRUE; } inline int filt_type (register SNS * s) { if (my_opts.type == F_NOTYPE) return TRUE; /* some ugly code for file types, maybe reimplement this crap by using * some GREY matter */ switch (my_opts.type) { case F_TFILE: if (! S_ISREG (s->s->st_mode)) return FALSE; break; case F_TDIR: if (! S_ISDIR (s->s->st_mode)) return FALSE; break; case F_TCHAR: if (! S_ISCHR (s->s->st_mode)) return FALSE; break; case F_TBLOCK: if (! S_ISBLK (s->s->st_mode)) return FALSE; break; case F_TNPIPE: /* FIFO? */ if (! S_ISFIFO (s->s->st_mode)) return FALSE; break; case F_TLINK: if (! S_ISLNK (s->s->st_mode)) return FALSE; break; case F_TSOCK: if (! S_ISSOCK (s->s->st_mode)) return FALSE; break; #ifdef SOLARIS case F_TDOOR: if (! S_ISDOOR (s->s->st_mode)) return FALSE; break; #endif default: break; } return TRUE; } inline int filt_name (register SNS * s) { if (my_opts.name == NULL || (my_opts.check_iname == TRUE && my_opts.name != NULL)) /* no need for namechecking */ return TRUE; return fnmatch (my_opts.name, s->n, 0) == 0 ? TRUE : FALSE; } #ifdef FNM_CASEFOLD inline #endif int filt_iname (register SNS * s) { /* this one is a case insensitive file name match * I need macros because FNM_CASEFOLD is * a GNU extension to POSIX.2 */ #ifndef FNM_CASEFOLD char * n; int i; int r; #endif if (my_opts.check_iname != TRUE) return TRUE; #ifndef FNM_CASEFOLD n = malloc (strlen (s->n) + 1); if (n == NULL) out_of_mem; for (i = 0; s->n [i] != '\0'; i++) n [i] = toupper (s->n [i]); r = fnmatch (my_opts.name, n, 0); free (n); return r == 0 #else /* FNM_CASEFOLD */ return fnmatch (my_opts.name, s->n, FNM_CASEFOLD) == 0 #endif ? TRUE : FALSE; } /* this one checks whether we should output this file or not... * each check returns from thsi function if not successful, at the end, * if we arrive there, output the file */ void do_work_on_entry (SS * s, const char * p, const char * n) { register filter_func * x = checks; char path [PATH_MAX]; SNS ns = { (char *) n, s }; /* do all checks... *x == NULL on last element! see add_filtfunc_entry */ while (*x) if ((*x++)(& ns) == FALSE) return; /* OK; for each file the (L)user wants to exec something... * let's do it here and exit if cmnd isn't found */ if (my_opts.exec != NULL) { pid_t p; my_opts.exec [my_opts.exec_fname_idx] = (char *) n; /* HERE WE GO! */ p = fork (); if (p == 0) { execvp (* my_opts.exec, my_opts.exec); /* if we could not exec, exit! */ bail ("could not execute '%s': %s\n", * my_opts.exec, strerror (errno)); } else if (p > 0) wait (NULL); else bail ("critical error: could not fork: %s\n", strerror (errno)); } else if (my_opts.ls == 1) do_ls_output (s, p, n); /* exec, ls and normal output do not work well together */ else { path [0] = '\0'; /* might be enough for strcat! */ strcat (path, p); strcat (path, n); puts (path); } } /* do_find does the finding job, it read all directories and descends the whole * directory tree. * * if there are error like no permission on that dir or similar, stat will * tell me about it */ int do_find (const char * d) { SDE * de; DIR * dirp; register int dl; SS s; static char path [PATH_MAX]; /* any do_find frame on stack will use this * to make copy overhead as small as possible */ static int pathlen = 0; /* again, pathlength for any do_find frame */ register int more = 0; /* if this one is set, a '/' was inserted to path and * one more char will be subtracted at the end of this * routine */ static int exit_status = EXIT_SUCCESS; #if 0 char ** dents; int dentbuf, dentcnt; #endif if ((dirp = opendir (d)) == NULL) { warn ("opendir (%s%s): %s\n", path, d, strerror (errno)); return 1; } dl = strlen (d); strcat (path, d); /* do I need to insert some DIR_SEP for correct paths? */ if (path [pathlen + dl] != DIR_SEP && path [pathlen + dl - 1] != DIR_SEP) { path [pathlen + dl] = DIR_SEP; path [pathlen + dl + 1] = '\0'; pathlen += dl + 1; more = 1; } else pathlen += dl; if (chdir (d) != 0) { warn ("could not chdir to '%s': %s\n", d, strerror (errno)); return EXIT_FAILURE; } /* trying to get faster dir traversal... */ #if 0 dentbuf = dentcnt = 0; while ((de = readdir (dirp)) != NULL) { if (dentbuf == 0) { dentbuf = 512; dents = (char **) malloc (sizeof (*dents) * dentbuf); if (dents == NULL) out_of_mem; } else if (dentbuf == dentcnt) { char ** t; dentbuf += 512; t = (char **) realloc (dents, sizeof (*dents) * dentbuf); if (t == NULL) out_of_mem; dents = t; } } #else while ((de = readdir (dirp)) != NULL) { register char * n = de->d_name; if ((n [0] == '.' && n [1] == '\0') || (n [0] == '.' && n [1] == '.' && n [2] == '\0')) continue; /* using lstat here to see the links * links are treated later in do_work_on_entry with one more stat and * a readlink call on it */ if (lstat (n, &s) != 0) { warn ("could not stat '%s': %s\n", n, strerror (errno)); exit_status = EXIT_FAILURE; continue; } /* if the current entry is a direactory, recursively call us * and lets start over again */ if (S_ISDIR (s.st_mode)) exit_status = do_find (n) != EXIT_SUCCESS ? EXIT_FAILURE : EXIT_SUCCESS; /* my worker routine... does all this crappy sh** for me */ do_work_on_entry (&s, path, n); } #endif chdir (".."); pathlen -= dl + more; /* let path were pathlen ends! */ path [pathlen] = '\0'; closedir (dirp); return exit_status; } int main (int argc, char ** argv) { int ret = 0, i; find_init (argv); /* setopts needs argv from first cmnd-arg on */ set_opts ((const char **) (argv + 1)); /* search all paths given... */ for (i = 0; my_opts.dir [i] != NULL; i++) ret += do_find (my_opts.dir [i]); return ret; }