From: John L. Hammond Date: Tue, 8 Feb 2022 14:20:11 +0000 (-0600) Subject: EX-4539 lipe: add lipe_find3 X-Git-Url: https://git.whamcloud.com/?a=commitdiff_plain;h=7defb83c760b983ccfe16626a5a4e022f91d5222;p=fs%2Flustre-release.git EX-4539 lipe: add lipe_find3 Add a lipe_find3 wrapper around the lipe_scan3 scanner and test script sanity-lipe-find3.sh. Test-Parameters: trivial testlist=sanity-lipe-find3 Signed-off-by: John L. Hammond Change-Id: I2259170e8b71a94394009aeaf9878a17c2a3fa6d Reviewed-on: https://review.whamcloud.com/46417 Tested-by: jenkins --- diff --git a/lipe/Makefile.am b/lipe/Makefile.am index b0f1a2b..86f8de0 100644 --- a/lipe/Makefile.am +++ b/lipe/Makefile.am @@ -1,4 +1,4 @@ -SUBDIRS = src src/lipe_scan3 pybuild pylipe . +SUBDIRS = src src/lipe_find3 src/lipe_scan3 pybuild pylipe . build_dir = `pwd`/build rpmbuild_opt = diff --git a/lipe/configure.ac b/lipe/configure.ac index c6f441d..239d2ba 100644 --- a/lipe/configure.ac +++ b/lipe/configure.ac @@ -313,6 +313,7 @@ AC_SUBST(ac_configure_args) AC_CONFIG_FILES([Makefile lipe.spec src/Makefile + src/lipe_find3/Makefile src/lipe_scan3/Makefile pybuild/Makefile pylipe/Makefile]) diff --git a/lipe/lipe.spec.in b/lipe/lipe.spec.in index de3a2bc..cf122e6 100644 --- a/lipe/lipe.spec.in +++ b/lipe/lipe.spec.in @@ -188,11 +188,13 @@ cp \ src/ext4_inode2path \ src/ldumpstripe \ src/lfill \ + src/lipe_find3/lipe_find3 \ src/lipe_scan \ src/lipe_scan2 \ src/lipe_scan3/lipe_scan3 \ $RPM_BUILD_ROOT%{_bindir} +cp -a src/lipe_find3/lipe $RPM_BUILD_ROOT%{guile_site_dir} cp -a src/lipe_scan3/lipe $RPM_BUILD_ROOT%{guile_site_dir} %if %{with laudit} @@ -327,6 +329,7 @@ rm -rf $RPM_BUILD_ROOT %{_bindir}/lipe_delete %{_bindir}/lipe_find %{_bindir}/lipe_find2 +%{_bindir}/lipe_find3 %{_bindir}/lipe_launch %{_bindir}/lipe_purge %{_bindir}/lipe_scan diff --git a/lipe/src/lipe_find3/.gitignore b/lipe/src/lipe_find3/.gitignore new file mode 100644 index 0000000..28e88e4 --- /dev/null +++ b/lipe/src/lipe_find3/.gitignore @@ -0,0 +1,13 @@ +*~ +*.gcda +*.gcno +*.gcov +*.o +*.tab.c +*.tab.h +lf3_convert_expr +lf3_lexer.c +lf3_printf +lipe_find3 +bison-3.8.2/ +opt/ diff --git a/lipe/src/lipe_find3/Makefile.am b/lipe/src/lipe_find3/Makefile.am new file mode 100644 index 0000000..29336b8 --- /dev/null +++ b/lipe/src/lipe_find3/Makefile.am @@ -0,0 +1,48 @@ +AUTOMAKE_OPTIONS = -Wall foreign +ACLOCAL_AMFLAGS = ${ALOCAL_FLAGS} +LEX = flex +OPT := $(shell pwd)/opt +YACC = $(OPT)/bin/bison + +if BUILD_SERVER + +# BUILT_SOURCES = lf3_lexer.c lf3_parse.tab.c lf3_parse.tab.h + +EXTRA_DIST = \ + bison-3.8.2.tar.gz \ + lf3_lexer.l \ + lf3_parse.y \ + lipe/find.scm + +bin_PROGRAMS = lipe_find3 + +lipe_find3_CPPFLAGS = -D_GNU_SOURCE -I .. -I ../.. -include config.h +lipe_find3_CFLAGS = -Wall -Werror -g -coverage +lipe_find3_SOURCES = \ + ../lipe_version.c \ + ../lipe_version.h \ + ../list.h \ + lf3_debug.h \ + lf3_lexer.h \ + lf3_parse_format.c \ + lf3_parse_format.h \ + lf3_printf.c \ + lf3_printf.h \ + xmalloc.h + +nodist_lipe_find3_SOURCES = \ + lf3_lexer.c \ + lf3_parse.tab.c \ + lf3_parse.tab.h + +# Need to callout the lf3_parse.tab.h dependency. +lf3_lexer.c: lf3_lexer.l lf3_parse.tab.h + $(LEX) $(LFLAGS) --outfile=$@ $^ + +lf3_parse.tab.c lf3_parse.tab.h: lf3_parse.y $(YACC) + $(YACC) $(YFLAGS) --defines lf3_parse.y + +$(YACC): bison-3.8.2.tar.gz + (tar -xzf bison-3.8.2.tar.gz && cd bison-3.8.2 && ./configure --prefix=$(OPT) && make install) + +endif # BUILD_SERVER diff --git a/lipe/src/lipe_find3/NOTES b/lipe/src/lipe_find3/NOTES new file mode 100644 index 0000000..abc5356 --- /dev/null +++ b/lipe/src/lipe_find3/NOTES @@ -0,0 +1,148 @@ +TODO "{fid}" in exec? +TODO dev, rdev +TODO -empty +TODO Check that format is MT safe. +TODO Check that system* is MT safe. +TODO dynamic-wind and MT. +TODO scm exceptions generate errors with no progname prefix. +TODO scanning and encryption +TODO xargs batch limits needed? +TODO crtime? +TODO symlinks need readlink in lipe_scan3 +TODO print-json attrs --list-json-attrs + +TODO fprintf + \a \b \f \n \r \t \v \0 \\ + \OOO => \xXX + + Field widths and precisions can be specified as with the + `printf' C function. Please note that many of the fields are + printed as %s rather than %d, and this may mean that flags + don't work as you might expect. This also means that the `-' + flag does work (it forces fields to be left-aligned). + + Flags #, 0, -, ' ', + + + '#' flag + + \c Stop printing from this format immediately and flush the output. + + NI A `\' character followed by any other character is treated as an ordinary character, so they both are printed. + A '\' followed by and other char is invalid. + + + Date and time, separated by `+', for example `2004-04-28+22:22:05.0'. + This is a GNU extension. The time is given in the current timezone + (which may be affected by setting the TZ environment variable). The + seconds field includes a fractional part. + + + %% a literal percent sign + %a atime in ctime() format + %Ak (k is @ or a strftime directive) + + %b block count + %c ctime in ctime() format + %Ck ... +NI %d File's depth in the directory tree; 0 means the file is a starting-point. +NI %D %D The device number on which the file exists (st_dev in decimal) + %f File's name with any leading directories removed (only the last element) +NI %F Type of the filesystem the file is on; this value can be used for -fstype. + %g File's group name, or numeric group ID if group has no name. + %G numeric gid + + %h Leading directories of file's name (all but the last + element). If the file name contains no slashes (since it is + in the current directory) the %h specifier expands to `.'. + + (call-with-relative-path dirname) + +?? %H Starting-point under which file was found. + + (client-mount-point) + + %i ino decimal + + %k disk space used in 1KB blocks. (rounded up) +DEFER %l symlink target or empty string if not symlink + + %m File's permission bits (in octal). This option uses the + `traditional' numbers which most Unix implementations use, + but if your particular implementation uses an unusual + ordering of octal permissions bits, you will see a dif‐ + ference between the actual value of the file's mode and the + output of %m. Normally you will want to have a leading + zero on this number, and to do this, you should use the # + flag (as in, for example, `%#m'). + + k:~# find /mnt/lustre -name f0 -printf '%m\n' + 644 + k:~# find /mnt/lustre -name f0 -printf '%#m\n' + 0644 + k:~# find /mnt/lustre -name f0 -printf '%03m\n' + 644 + k:~# find /mnt/lustre -name f0 -printf '%06m\n' + 000644 + k:~# find /mnt/lustre -name f0 -printf '%02m\n' + 644 + + %M File's permissions (in symbolic form, as for ls) + %n Number of hard links to file. + %p File's name. + + # find /mnt/lustre -name f9 -printf '%p\n' + /mnt/lustre/sanity/f9 + # cd /mnt/lustre && find . -name f9 -printf '%p\n' + ./sanity/f9 + + %P File's name with the name of the starting-point under which it was found removed + + k:~# find /mnt/lustre -name f9 -printf '%P\n' + sanity/f9 + k:~# cd /mnt/lustre && find . -name f9 -printf '%P\n' + sanity/f9 + + (relative-path) + + %S File's sparseness. This is calculated as (BLOCKSIZE*st_blocks / st_size). + The exact value you will get for an ordinary file of a certain length is + system-dependent. However, normally sparse files will have values less than + 1.0, and files which use indirect blocks may have a value which is greater + than 1.0. In general the number of blocks used by a file is file system de‐ + pendent. The value used for BLOCKSIZE is system-dependent, but is usually + 512 bytes. If the file size is zero, the value printed is undefined. On + systems which lack support for st_blocks, a file's sparseness is assumed to + be 1.0. + + size == 0 => sparseness = 1 + + +Extensions + %{fid} FID as string w/o braces + %{device-name} "fs0a42-MDT0007" + %{device-path} "/dev/mapper/mds1_flakey" + %{client-mount-path} + +TODO Regex: + Try compling the regex first for error messages. + +By default, Guile supports POSIX extended regular expressions. That +means that the characters ‘(’, ‘)’, ‘+’ and ‘?’ are special, and must +be escaped if you wish to match the literal characters and there is no +support for “non-greedy” variants of ‘*’, ‘+’ or ‘?’. + +regexp/icase +regexp/basic +regexp/extended + +(define %LEC:regex-42 (make-regexp pattern regexp/basic regexp/extended)) + +pattern flags + +(define %LEC:match-fname-107 + (let* ((%LEC:match-fname-108 + (lambda (str) + (fnmatch? ,pattern str ,flags)))) + (lambda () (any %LEC:match-fname-108 (paths))))) + +regexp/icase +(use-modules (ice-9 regex)) diff --git a/lipe/src/lipe_find3/bison-3.8.2.tar.gz b/lipe/src/lipe_find3/bison-3.8.2.tar.gz new file mode 100644 index 0000000..127db12 Binary files /dev/null and b/lipe/src/lipe_find3/bison-3.8.2.tar.gz differ diff --git a/lipe/src/lipe_find3/find-actions.txt b/lipe/src/lipe_find3/find-actions.txt new file mode 100644 index 0000000..98df9b5 --- /dev/null +++ b/lipe/src/lipe_find3/find-actions.txt @@ -0,0 +1,360 @@ +DONE + -delete + Delete files; true if removal succeeded. If the removal failed, an error message + is issued. If -delete fails, find's exit status will be nonzero (when it eventu‐ + ally exits). Use of -delete automatically turns on the `-depth' option. + + Warnings: Don't forget that the find command line is evaluated as an expression, so + putting -delete first will make find try to delete everything below the starting + points you specified. When testing a find command line that you later intend to + + use with -delete, you should explicitly specify -depth in order to avoid later sur‐ + prises. Because -delete implies -depth, you cannot usefully use -prune and -delete + together. + + Together with the -ignore_readdir_race option, find will ignore errors of the + -delete action in the case the file has disappeared since the parent directory was + read: it will not output an error diagnostic, and the return code of the -delete + action will be true. + + -exec command ; + Execute command; true if 0 status is returned. All following arguments to find are + taken to be arguments to the command until an argument consisting of `;' is encoun‐ + tered. The string `{}' is replaced by the current file name being processed every‐ + where it occurs in the arguments to the command, not just in arguments where it is + alone, as in some versions of find. Both of these constructions might need to be + escaped (with a `\') or quoted to protect them from expansion by the shell. See + the EXAMPLES section for examples of the use of the -exec option. The specified + command is run once for each matched file. The command is executed in the starting + directory. There are unavoidable security problems surrounding use of the -exec + action; you should use the -execdir option instead. + + -exec command {} + + This variant of the -exec action runs the specified command on the selected files, + but the command line is built by appending each selected file name at the end; the + total number of invocations of the command will be much less than the number of + matched files. The command line is built in much the same way that xargs builds + its command lines. Only one instance of `{}' is allowed within the command, and + (when find is being invoked from a shell) it should be quoted (for example, '{}') + to protect it from interpretation by shells. The command is executed in the start‐ + ing directory. If any invocation with the `+' form returns a non-zero value as + exit status, then find returns a non-zero exit status. If find encounters an er‐ + ror, this can sometimes cause an immediate exit, so some pending commands may not + be run at all. This variant of -exec always returns true. + + -fprint file + True; print the full file name into file file. If file does not exist when find is + run, it is created; if it does exist, it is truncated. The file names `/dev/std‐ + out' and `/dev/stderr' are handled specially; they refer to the standard output and + standard error output, respectively. The output file is always created, even if + the predicate is never matched. See the UNUSUAL FILENAMES section for information + about how unusual characters in filenames are handled. + + -fprint0 file + True; like -print0 but write to file like -fprint. The output file is always cre‐ + ated, even if the predicate is never matched. See the UNUSUAL FILENAMES section + for information about how unusual characters in filenames are handled. + + -fprintf file format + True; like -printf but write to file like -fprint. The output file is always cre‐ + ated, even if the predicate is never matched. See the UNUSUAL FILENAMES section + for information about how unusual characters in filenames are handled. + + -print True; print the full file name on the standard output, followed by a newline. If + you are piping the output of find into another program and there is the faintest + possibility that the files which you are searching for might contain a newline, + then you should seriously consider using the -print0 option instead of -print. See + the UNUSUAL FILENAMES section for information about how unusual characters in file‐ + names are handled. + + -print0 + True; print the full file name on the standard output, followed by a null character + (instead of the newline character that -print uses). This allows file names that + contain newlines or other types of white space to be correctly interpreted by pro‐ + grams that process the find output. This option corresponds to the -0 option of + xargs. + +PARTIALLY DONE + -printf + Not all format directives are implemented. Search for + LF3_EMIT_U(). Format width and precision is only + partially implemented. + + -printf format + True; print format on the standard output, interpreting `\' escapes and `%' direc‐ + tives. Field widths and precisions can be specified as with the `printf' C func‐ + tion. Please note that many of the fields are printed as %s rather than %d, and + this may mean that flags don't work as you might expect. This also means that the + `-' flag does work (it forces fields to be left-aligned). Unlike -print, -printf + does not add a newline at the end of the string. The escapes and directives are: + + \a Alarm bell. + + \b Backspace. + + \c Stop printing from this format immediately and flush the output. + + \f Form feed. + + \n Newline. + + \r Carriage return. + + \t Horizontal tab. + + \v Vertical tab. + + \0 ASCII NUL. + + \\ A literal backslash (`\'). + + \NNN The character whose ASCII code is NNN (octal). + + A `\' character followed by any other character is treated as an ordinary charac‐ + ter, so they both are printed. + + %% A literal percent sign. + + %a File's last access time in the format returned by the C `ctime' function. + + %Ak File's last access time in the format specified by k, which is either `@' or + a directive for the C `strftime' function. The possible values for k are + listed below; some of them might not be available on all systems, due to + differences in `strftime' between systems. + + @ seconds since Jan. 1, 1970, 00:00 GMT, with fractional part. + + Time fields: + + H hour (00..23) + + I hour (01..12) + + k hour ( 0..23) + + l hour ( 1..12) + + M minute (00..59) + + p locale's AM or PM + + r time, 12-hour (hh:mm:ss [AP]M) + + S Second (00.00 .. 61.00). There is a fractional part. + + T time, 24-hour (hh:mm:ss.xxxxxxxxxx) + + + Date and time, separated by `+', for example `2004-04-28+22:22:05.0'. + This is a GNU extension. The time is given in the current timezone + (which may be affected by setting the TZ environment variable). The + seconds field includes a fractional part. + + X locale's time representation (H:M:S). The seconds field includes a + fractional part. + + Z time zone (e.g., EDT), or nothing if no time zone is determinable + + Date fields: + + a locale's abbreviated weekday name (Sun..Sat) + + A locale's full weekday name, variable length (Sunday..Saturday) + + b locale's abbreviated month name (Jan..Dec) + + B locale's full month name, variable length (January..December) + + c locale's date and time (Sat Nov 04 12:02:33 EST 1989). The format is + the same as for ctime(3) and so to preserve compatibility with that + format, there is no fractional part in the seconds field. + + d day of month (01..31) + + D date (mm/dd/yy) + + h same as b + + j day of year (001..366) + + m month (01..12) + + U week number of year with Sunday as first day of week (00..53) + + w day of week (0..6) + + W week number of year with Monday as first day of week (00..53) + + x locale's date representation (mm/dd/yy) + + y last two digits of year (00..99) + + Y year (1970...) + + %b The amount of disk space used for this file in 512-byte blocks. Since disk + space is allocated in multiples of the filesystem block size this is usually + greater than %s/512, but it can also be smaller if the file is a sparse + file. + + %c File's last status change time in the format returned by the C `ctime' func‐ + tion. + + %Ck File's last status change time in the format specified by k, which is the + same as for %A. + + %d File's depth in the directory tree; 0 means the file is a starting-point. + + %D The device number on which the file exists (the st_dev field of struct + stat), in decimal. + + %f File's name with any leading directories removed (only the last element). + + %F Type of the filesystem the file is on; this value can be used for -fstype. + + %g File's group name, or numeric group ID if the group has no name. + + %G File's numeric group ID. + + %h Leading directories of file's name (all but the last element). If the file + name contains no slashes (since it is in the current directory) the %h spec‐ + ifier expands to `.'. + + %H Starting-point under which file was found. + + %i File's inode number (in decimal). + + %k The amount of disk space used for this file in 1 KB blocks. Since disk + space is allocated in multiples of the filesystem block size this is usually + greater than %s/1024, but it can also be smaller if the file is a sparse + file. + + %l Object of symbolic link (empty string if file is not a symbolic link). + + %m File's permission bits (in octal). This option uses the `traditional' num‐ + bers which most Unix implementations use, but if your particular implementa‐ + tion uses an unusual ordering of octal permissions bits, you will see a dif‐ + ference between the actual value of the file's mode and the output of %m. + Normally you will want to have a leading zero on this number, and to do + this, you should use the # flag (as in, for example, `%#m'). + + %M File's permissions (in symbolic form, as for ls). This directive is sup‐ + ported in findutils 4.2.5 and later. + + %n Number of hard links to file. + + %p File's name. + + %P File's name with the name of the starting-point under which it was found re‐ + moved. + + %s File's size in bytes. + + %S File's sparseness. This is calculated as (BLOCKSIZE*st_blocks / st_size). + The exact value you will get for an ordinary file of a certain length is + system-dependent. However, normally sparse files will have values less than + 1.0, and files which use indirect blocks may have a value which is greater + than 1.0. In general the number of blocks used by a file is file system de‐ + pendent. The value used for BLOCKSIZE is system-dependent, but is usually + 512 bytes. If the file size is zero, the value printed is undefined. On + systems which lack support for st_blocks, a file's sparseness is assumed to + be 1.0. + + %t File's last modification time in the format returned by the C `ctime' func‐ + tion. + + %Tk File's last modification time in the format specified by k, which is the + same as for %A. + + %u File's user name, or numeric user ID if the user has no name. + + %U File's numeric user ID. + + %y File's type (like in ls -l), U=unknown type (shouldn't happen) + + %Y File's type (like %y), plus follow symlinks: `L'=loop, `N'=nonexistent, `?' + for any other error when determining the type of the symlink target. + + %Z (SELinux only) file's security context. + + %{ %[ %( + Reserved for future use. + + A `%' character followed by any other character is discarded, but the other charac‐ + ter is printed (don't rely on this, as further format characters may be intro‐ + duced). A `%' at the end of the format argument causes undefined behaviour since + there is no following character. In some locales, it may hide your door keys, + while in others it may remove the final page from the novel you are reading. + + The %m and %d directives support the # , 0 and + flags, but the other directives do + not, even if they print numbers. Numeric directives that do not support these + flags include G, U, b, D, k and n. The `-' format flag is supported and changes + the alignment of a field from right-justified (which is the default) to left-justi‐ + fied. + + See the UNUSUAL FILENAMES section for information about how unusual characters in + filenames are handled. + + -quit Exit immediately. No child processes will be left running, but no more paths spec‐ + ified on the command line will be processed. For example, find /tmp/foo /tmp/bar + -print -quit will print only /tmp/foo. Any command lines which have been built up + with -execdir ... {} + will be invoked before find exits. The exit status may or + may not be zero, depending on whether an error has already occurred. + +DEFERRED + -execdir command ; + + -execdir command {} + + Like -exec, but the specified command is run from the subdirectory containing the + matched file, which is not normally the directory in which you started find. As + with -exec, the {} should be quoted if find is being invoked from a shell. This a + much more secure method for invoking commands, as it avoids race conditions during + resolution of the paths to the matched files. As with the -exec action, the `+' + form of -execdir will build a command line to process more than one matched file, + but any given invocation of command will only list files that exist in the same + subdirectory. If you use this option, you must ensure that your $PATH environment + variable does not reference `.'; otherwise, an attacker can run any commands they + like by leaving an appropriately-named file in a directory in which you will run + -execdir. The same applies to having entries in $PATH which are empty or which are + not absolute directory names. If any invocation with the `+' form returns a non- + zero value as exit status, then find returns a non-zero exit status. If find en‐ + counters an error, this can sometimes cause an immediate exit, so some pending com‐ + mands may not be run at all. The result of the action depends on whether the + or + the ; variant is being used; -execdir command {} + always returns true, while -ex‐ + ecdir command {} ; returns true only if command returns 0. + + -fls file + True; like -ls but write to file like -fprint. The output file is always created, + even if the predicate is never matched. See the UNUSUAL FILENAMES section for in‐ + formation about how unusual characters in filenames are handled. + + -ls True; list current file in ls -dils format on standard output. The block counts + are of 1 KB blocks, unless the environment variable POSIXLY_CORRECT is set, in + which case 512-byte blocks are used. See the UNUSUAL FILENAMES section for infor‐ + mation about how unusual characters in filenames are handled. + + -ok command ; + Like -exec but ask the user first. If the user agrees, run the command. Otherwise + just return false. If the command is run, its standard input is redirected from + /dev/null. + + The response to the prompt is matched against a pair of regular expressions to de‐ + termine if it is an affirmative or negative response. This regular expression is + obtained from the system if the `POSIXLY_CORRECT' environment variable is set, or + otherwise from find's message translations. If the system has no suitable defini‐ + tion, find's own definition will be used. In either case, the interpretation of + the regular expression itself will be affected by the environment variables + 'LC_CTYPE' (character classes) and 'LC_COLLATE' (character ranges and equivalence + classes). + + -okdir command ; + Like -execdir but ask the user first in the same way as for -ok. If the user does + not agree, just return false. If the command is run, its standard input is redi‐ + rected from /dev/null. + +WON'T DO + -prune True; if the file is a directory, do not descend into it. If -depth is given, then + -prune has no effect. Because -delete implies -depth, you cannot usefully use + -prune and -delete together. + For example, to skip the directory `src/emacs' and all files and directories un‐ + der it, and print the names of the other files found, do something like this: + find . -path ./src/emacs -prune -o -print + diff --git a/lipe/src/lipe_find3/find-tests.txt b/lipe/src/lipe_find3/find-tests.txt new file mode 100644 index 0000000..dbc2fa4 --- /dev/null +++ b/lipe/src/lipe_find3/find-tests.txt @@ -0,0 +1,184 @@ +DONE + -amin + -atime + -cmin + -ctime + -false + -gid + -inum + -links + -mmin + -mtime + -size Based on '(size)' which is based on ?. Needs clarificaiton. + -true + -type + -uid + + -user string or UID but no [+-] prefix on either. + -group ... + + # ls -ld /mnt/lustre/sanity + drwxr-xr-x 2 sanity sanity 4096 Oct 20 11:01 /mnt/lustre/sanity + # find /mnt/lustre -user sanity + /mnt/lustre/sanity + # find /mnt/lustre -user +sanity + find: ‘+sanity’ is not the name of a known user + # find /mnt/lustre -user -sanity + find: ‘-sanity’ is not the name of a known user + # id sanity + uid=500(sanity) gid=500(sanity) groups=500(sanity) + # find /mnt/lustre -user +500 + find: ‘+500’ is not the name of a known user + # find /mnt/lustre -user -500 + find: ‘-500’ is not the name of a known user + # find /mnt/lustre -user 500 + /mnt/lustre/sanity + + -iname pattern + Like -name, but the match is case insensitive. For example, the patterns `fo*' and + `F??' match the file names `Foo', `FOO', `foo', `fOo', etc. The pattern `*foo*` + will also match a file called '.foobar'. + + -ipath pattern + Like -path. but the match is case insensitive. + + -name pattern + Base of file name (the path with the leading directories removed) matches shell + pattern pattern. Because the leading directories are removed, the file names + considered for a match with -name will never include a slash, so `-name a/b' will + never match anything (you probably need to use -path instead). A warning is issued + if you try to do this, unless the environment variable POSIXLY_CORRECT is set. The + metacharacters (`*', `?', and `[]') match a `.' at the start of the base name (this + is a change in findutils-4.2.2; see section STANDARDS CONFORMANCE below). To ig‐ + nore a directory and the files under it, use -prune rather than checking every file + in the tree; see an example in the description of that action. Braces are not + recognised as being special, despite the fact that some shells including Bash imbue + braces with a special meaning in shell patterns. The filename matching is per‐ + formed with the use of the fnmatch(3) library function. Don't forget to enclose + the pattern in quotes in order to protect it from expansion by the shell. + + -path pattern + File name matches shell pattern pattern. The metacharacters do not treat `/' or + `.' specially; so, for example, + find . -path "./sr*sc" + will print an entry for a directory called `./src/misc' (if one exists). To ignore + a whole directory tree, use -prune rather than checking every file in the tree. + Note that the pattern match test applies to the whole file name, starting from one + of the start points named on the command line. It would only make sense to use an + absolute path name here if the relevant start point is also an absolute path. This + means that this command will never match anything: + find bar -path /foo/bar/myfile -print + Find compares the -path argument with the concatenation of a directory name and the + base name of the file it's examining. Since the concatenation will never end with + a slash, -path arguments ending in a slash will match nothing (except perhaps a + start point specified on the command line). The predicate -path is also supported + by HP-UX find and is part of the POSIX 2008 standard. + +TODO + -perm mode + File's permission bits are exactly mode (octal or symbolic). Since an exact match + is required, if you want to use this form for symbolic modes, you may have to spec‐ + ify a rather complex mode string. For example `-perm g=w' will only match files + which have mode 0020 (that is, ones for which group write permission is the only + permission set). It is more likely that you will want to use the `/' or `-' forms, + for example `-perm -g=w', which matches any file with group write permission. See + the EXAMPLES section for some illustrative examples. + + -perm -mode + All of the permission bits mode are set for the file. Symbolic modes are accepted + in this form, and this is usually the way in which you would want to use them. You + must specify `u', `g' or `o' if you use a symbolic mode. See the EXAMPLES section + for some illustrative examples. + + -perm /mode + Any of the permission bits mode are set for the file. Symbolic modes are accepted + in this form. You must specify `u', `g' or `o' if you use a symbolic mode. See + the EXAMPLES section for some illustrative examples. If no permission bits in mode + are set, this test matches any file (the idea here is to be consistent with the be‐ + haviour of -perm -000). + + -fstype + + -used n + File was last accessed n days after its status was last changed. + + -iregex pattern + Like -regex, but the match is case insensitive. + + -regex pattern + File name matches regular expression pattern. This is a match on the whole path, + not a search. For example, to match a file named `./fubar3', you can use the regu‐ + lar expression `.*bar.' or `.*b.*3', but not `f.*r3'. The regular expressions un‐ + derstood by find are by default Emacs Regular Expressions (except that `.' matches + newline), but this can be changed with the -regextype option. + +DEFERRED -empty + File is empty and is either a regular file or a directory. + + How to handle this for striped directories (parents and substripes)? + + +DEFERRED: Things that depend on a reference user or (access()): + -executable + Matches files which are executable and directories + which are searchable (in a file name resolution sense) + by the current user. This takes into account access + control lists and other permissions artefacts which + the -perm test ignores. This test makes use of the + access(2) system call, and so can be fooled by NFS + servers which do UID mapping (or root-squashing), + since many systems implement access(2) in the client's + kernel and so cannot make use of the UID mapping + information held on the server. Because this test is + based only on the result of the access(2) system call, + there is no guarantee that a file for which this test + succeeds can actually be executed. + -readable + ... + -writable + ... + +DEFERRED + -nogroup + No group corresponds to file's numeric group ID. + + -nouser + No user corresponds to file's numeric user ID. + + Read passwd file, get uids build an alist + + (define %LEC:nouser? + (let* ((%LEC:uid-list '(uid1 uid2 ...)) + (%LEC:nouser-uid-alist (map (lambda (uid) (cons uid #f)) %LEC:uid-list)) + (%LEC:nouser-uid-table (alist->hashv-table %LEC:nouser-uid-alist))) + (lambda () + (hashv-ref %LEC:nouser-uid-table (uid) #t)))) + + +DEFERRED (Need a way to read non-fast symlinks in scanner) + -lname pattern + File is a symbolic link whose contents match shell pattern pattern. The metachar‐ + acters do not treat `/' or `.' specially. If the -L option or the -follow option + is in effect, this test returns false unless the symbolic link is broken. + + -ilname pattern + Like -lname, but the match is case insensitive. If the -L option or the -follow + option is in effect, this test returns false unless the symbolic link is broken. + + -xtype c + The same as -type unless the file is a symbolic link. For symbolic links: if the + -H or -P option was specified, true if the file is a link to a file of type c; if + the -L option has been given, true if c is `l'. In other words, for symbolic + links, -xtype checks the type of the file that -type does not check. + +DEFERRED: + -context pattern + (SELinux only) Security context of the file matches glob pattern. + +WON'T DO: + Things that depend on a reference file. + -anewer + -cnewer + -mnewer + -newer + -samefile diff --git a/lipe/src/lipe_find3/lf3_debug.h b/lipe/src/lipe_find3/lf3_debug.h new file mode 100644 index 0000000..1c5ff2c --- /dev/null +++ b/lipe/src/lipe_find3/lf3_debug.h @@ -0,0 +1,41 @@ +#ifndef _LF3_DEBUG_H_ +#define _LF3_DEBUG_H_ +#include +#include +#include +#include + +extern int lf3_debug; +extern const char *lf3_progname_; + +static inline const char *lf3_progname(void) +{ + return lf3_progname_ != NULL ? lf3_progname_ : program_invocation_short_name; +} + +#define LF3_DEBUG(fmt, args...) \ + do { \ + if (lf3_debug) \ + fprintf(stderr, "%s: DEBUG %s:%d: "fmt, \ + lf3_progname(), __func__, __LINE__, ##args); \ + } while (0) + +#define LF3_DEBUG_B(x) LF3_DEBUG("%s = %s\n", #x, (x) ? "true" : "false") +#define LF3_DEBUG_D(x) LF3_DEBUG("%s = %"PRIdMAX"\n", #x, (intmax_t)(x)) +#define LF3_DEBUG_P(x) LF3_DEBUG("%s = %p\n", #x, x) +#define LF3_DEBUG_S(x) LF3_DEBUG("%s = '%s'\n", #x, x) +#define LF3_DEBUG_U(x) LF3_DEBUG("%s = %"PRIuMAX"\n", #x, (uintmax_t)(x)) +#define LF3_DEBUG_X(x) LF3_DEBUG("%s = %"PRIxMAX"\n", #x, (uintmax_t)(x)) + +#define LF3_FATAL(fmt, args...) \ + do { \ + fprintf(stderr, "%s: FATAL: "fmt, lf3_progname(), ##args); \ + exit(EXIT_FAILURE); \ + } while (0) + +/* TODO Make a nice error message with a caret. */ + +#define LF3_FATAL_AT(index, fmt, args...) \ + LF3_FATAL("at argument %d: "fmt, index, ##args) + +#endif diff --git a/lipe/src/lipe_find3/lf3_lexer.h b/lipe/src/lipe_find3/lf3_lexer.h new file mode 100644 index 0000000..7154953 --- /dev/null +++ b/lipe/src/lipe_find3/lf3_lexer.h @@ -0,0 +1,11 @@ +#ifndef _L3F_LEXER_H_ +#define _L3F_LEXER_H_ + +extern const char **lf3_arg; +extern int lf3_arg_count; +extern int lf3_arg_index; +int lf3_lexer_init(const char **arg, int arg_index, int arg_count); + +int yylex(void); + +#endif /* _LF3_LEXER_H_ */ diff --git a/lipe/src/lipe_find3/lf3_lexer.l b/lipe/src/lipe_find3/lf3_lexer.l new file mode 100644 index 0000000..8cf3003 --- /dev/null +++ b/lipe/src/lipe_find3/lf3_lexer.l @@ -0,0 +1,193 @@ +%option noinput +%option nounput +%{ +#include +#include +#include +#include +#include +#include "lf3_debug.h" +#include "lf3_lexer.h" +#include "lf3_parse.tab.h" + +const char **lf3_arg; +int lf3_arg_count; +int lf3_arg_index; + +static int expr_arg_begin; +static int expr_arg_end; +static int expr_arg_type; + +#define YY_USER_ACTION yylloc.first_line = lf3_arg_index; + +#define YY_INPUT(buf,result,max_size) EOF + +/* FIXME: Need an EOF rule to check for incomplete expressions. + * + * Notes: + * + * All tokens must end with a NUL (\0) char to properly handle empty + * arguments. + * + * INITIAL is the default start state to gather a new expression. + * + * ARG is the exclusive start state to gather arguments for normal expressions + * (-name foo, -print, -delete) with fixed argument counts. + * + * EXEC_ARG is the exclusive start state to gather arguments for the -exec action + * and is terminated by ';' or '+'. + */ + +%} + +%x ARG +%x EXEC_ARG + +%% /* rules */ + +-(delete|print|print0|print-file-fid|print-self-fid|print-absolute-path|print-relative-path|print-json|quit)\0 { + LF3_DEBUG("nullary '%s'\n", yytext); + yylval.u_range[0] = lf3_arg_index; + yylval.u_range[1] = lf3_arg_index + 1; + return TOKEN_ACTION; +} + +-(empty|executable|false|readable|true|writable)\0 { + LF3_DEBUG("nullary '%s'\n", yytext); + yylval.u_range[0] = lf3_arg_index; + yylval.u_range[1] = lf3_arg_index + 1; + return TOKEN_TEST; +} + +-(amin|atime|cmin|ctime|gid|group|iname|inum|links|mmin|mtime|name|path|perm|projid|size|type|uid|user)\0 { + LF3_DEBUG("unary '%s'\n", yytext); + expr_arg_begin = lf3_arg_index; + expr_arg_end = lf3_arg_index + 2; + expr_arg_type = TOKEN_TEST; + BEGIN ARG; +} + +-(fprint|fprint0|printf)\0 { + LF3_DEBUG("unary '%s'\n", yytext); + expr_arg_begin = lf3_arg_index; + expr_arg_end = lf3_arg_index + 2; + expr_arg_type = TOKEN_ACTION; + BEGIN ARG; +} + +-(fprintf)\0 { + LF3_DEBUG("binary '%s'\n", yytext); + expr_arg_begin = lf3_arg_index; + expr_arg_end = lf3_arg_index + 3; + expr_arg_type = TOKEN_ACTION; + BEGIN ARG; +} + +[^\0]*\0 { + LF3_DEBUG("arg '%s'\n", yytext); + if (lf3_arg_index + 1 == expr_arg_end) { + yylval.u_range[0] = expr_arg_begin; + yylval.u_range[1] = expr_arg_end; + BEGIN INITIAL; + return expr_arg_type; + } else { + /* Continue in ARG state. */ + } +} + +<> { + LF3_FATAL_AT(expr_arg_begin, "missing argument to '%s'\n", lf3_arg[expr_arg_begin]); +} + +-exec\0 { + LF3_DEBUG("begin-exec '%s'\n", yytext); + expr_arg_begin = lf3_arg_index; + BEGIN EXEC_ARG; +} + +(\+|\;)\0 { + LF3_DEBUG("end-exec '%s'\n", yytext); + yylval.u_range[0] = expr_arg_begin; + yylval.u_range[1] = lf3_arg_index + 1; + BEGIN INITIAL; + return TOKEN_ACTION; +} + +[^\0]*\0 { + LF3_DEBUG("exec-arg '%s'\n", yytext); + /* Continue in EXEC_ARG state/ */ +} + +<> { + LF3_FATAL_AT(expr_arg_begin, "missing argument to '%s'\n", lf3_arg[expr_arg_begin]); +} + +\(\0 return TOKEN_LEFT_PAREN; +\)\0 return TOKEN_RIGHT_PAREN; +(\!|-not)\0 return TOKEN_NOT; +-(a|and)\0 return TOKEN_AND; +-(o|or)\0 return TOKEN_OR; +,\0 return TOKEN_COMMA; + +(.|\n)*\0 { + LF3_FATAL_AT(lf3_arg_index, "unknown test '%s'\n", lf3_arg[lf3_arg_index]); +} + +%% + +static YY_BUFFER_STATE lf3_buffer_state; + +/* Set lf3_buffer_state to the next argument in lf3_arg. */ +int yywrap(void) +{ + const char *arg; + + LF3_DEBUG_D(lf3_arg_count); + LF3_DEBUG_D(lf3_arg_index); + + lf3_arg_index++; + if (lf3_arg_index >= lf3_arg_count) + return 1; /* Reached end of arguments. */ + + yy_flush_buffer(lf3_buffer_state); + yy_delete_buffer(lf3_buffer_state); + arg = lf3_arg[lf3_arg_index]; + LF3_DEBUG_S(arg); + lf3_buffer_state = yy_scan_bytes(arg, strlen(arg) + 1); + yy_switch_to_buffer(lf3_buffer_state); + + return 0; +} + +int lf3_lexer_init(const char **arg, int arg_index, int arg_count) +{ + /* arg: a non-empty array of arguments (usually argv of main()) + * arg_index: the index of the start of the the + * expression. For example: + * { "-true" } + * or + * { "-inum", "0", "-print-fid" }. + * arg_count: the total number of arguments (usually argc of main()) + * + * We do it this way instead of a 0-based array containing + * just the expression arguments so that error messages can provide + * argument indexes that agree with the user provided command line. */ + + LF3_DEBUG_S(arg[arg_index]); + LF3_DEBUG_D(arg_count); + LF3_DEBUG_D(arg_index); + + assert(0 <= arg_index); + assert(arg_index < arg_count); + + lf3_arg = arg; + lf3_arg_count = arg_count; + lf3_arg_index = arg_index - 1; + + /* The first call to yywrap() increments lf3_arg_index and + * initializes the lexer buffer lf3_arg[lf3_arg_index]. This + * is why we use arg_index -1 above. */ + yywrap(); + + return 0; +} diff --git a/lipe/src/lipe_find3/lf3_parse.y b/lipe/src/lipe_find3/lf3_parse.y new file mode 100644 index 0000000..ab3c77f --- /dev/null +++ b/lipe/src/lipe_find3/lf3_parse.y @@ -0,0 +1,1322 @@ +%{ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "lipe_version.h" +#include "list.h" +#include "lf3_debug.h" +#include "lf3_lexer.h" +#include "lf3_parse_format.h" +#include "lf3_printf.h" +#include "list.h" +#include "xmalloc.h" + +static void yyerror(const char *str); + +/* Like /usr/bin/getopt we pretend to be another program when we print + * error messages. */ + +int lf3_debug; +const char *lf3_progname_; +static unsigned long long lf3_now = -1; + +#ifndef LF3_SCAN_PROGRAM +# define LF3_SCAN_PROGRAM "lipe_scan3" +#endif + +static const char LF3_SCAN_COMMAND[] = LF3_SCAN_PROGRAM " --script=/dev/stdin"; +static const char LF3_SCM_SCAN_PROC[] = "lipe-scan"; +static char *lf3_policy_body; +static bool lf3_policy_body_has_action; + +typedef unsigned long long __ull; + +static const char lf3_gensym_prefix[] = "%lf3"; +static unsigned int lf3_gensym_count; + +struct lf3_file { + struct lipe_list_head lf_link; + const char *lf_path; + const char *lf_port; + const char *lf_mutex; +}; + +static LIPE_LIST_HEAD(lf3_file_list); /* lf3_file */ +static LIPE_LIST_HEAD(lf3_genvar_list); /* name -> initializer expr */ +static LIPE_LIST_HEAD(lf3_init_list); /* expr, NULL, to be evaluated in before thunk of dynamic-wind */ +static LIPE_LIST_HEAD(lf3_fini_list); /* expr, NULL, ... after ... */ + +static void lf3_list_add_tail(struct lipe_list_head *list, const char *car, const char *cdr) +{ + struct lf3_pair *lfp; + + lfp = xcalloc(1, sizeof(*lfp)); + if (car != NULL) + lfp->lfp_car = xstrdup(car); + + if (cdr != NULL) + lfp->lfp_cdr = xstrdup(cdr); + + lipe_list_add_tail(&lfp->lfp_link, list); +} + +/* Generate a fresh unique symbol with the given prefix and name. Name + * is only for readability of intermediate code. */ +static const char *lf3_gensym(const char *prefix, + const char *func, + const char *name) +{ +#if 1 + return xsprintf("%s:%s:%u", prefix, name, lf3_gensym_count++); +#else /* Include function name in symbol (too verbose). */ + return xsprintf("%s:%s:%s:%u", prefix, func, name, lf3_gensym_count++); +#endif +} + +static const char *lf3_genvar(const char *prefix, + const char *func, + const char *name, + const char *expr) +{ + const char *sym = lf3_gensym(prefix, func, name); + + lf3_list_add_tail(&lf3_genvar_list, sym, expr); + + return sym; +} + +#define LF3_GENSYM(name) \ + lf3_gensym(lf3_gensym_prefix, __FUNCTION__, name) + +#define LF3_GENVAR(name, expr) \ + lf3_genvar(lf3_gensym_prefix, __FUNCTION__, name, expr) + +#define LF3_GENVARF(name, fmt, args...) \ + lf3_genvar(lf3_gensym_prefix, __FUNCTION__, name, xsprintf(fmt, ##args)) + +static void lf3_init_add(const char *expr) +{ + lf3_list_add_tail(&lf3_init_list, expr, NULL); +} + +static void lf3_fini_add(const char *expr) +{ + lf3_list_add_tail(&lf3_fini_list, expr, NULL); +} + +#define LF3_FINI_ADDF(fmt, args...) \ + lf3_fini_add(xsprintf(fmt, ##args)) + +struct lf3_unit { + const char *apu_name; + __ull apu_value; +}; + +static int lf3_parse_unit(const struct lf3_unit *apu, const char *name, __ull *value) +{ + int i; + + for (i = 0; apu[i].apu_name != NULL; i++) { + if (strcmp(apu[i].apu_name, name) == 0) { + *value = apu[i].apu_value; + return 0; + } + } + + return -EINVAL; +} + +/* Parse a [+-]VAL[UNIT] (for example a "+10M" argument or '-size') + * string into components. */ +static int lf3_parse_numeric(const struct lf3_unit *apu, const char *str, char *cmp, __ull *val, __ull *unit) +{ + int saved_errno; + char *unit_name = NULL; + int rc; + + switch (str[0]) { + case '+': + *cmp = '>'; + break; + case '-': + *cmp = '<'; + break; + default: + *cmp = '='; + break; + } + + if (*cmp != '=') + str++; + + if (!isdigit(str[0])) + return -EINVAL; + + saved_errno = errno; + errno = 0; + *val = strtoul(str, &unit_name, 0); + if (errno != 0) + return -errno; + + errno = saved_errno; + + rc = lf3_parse_unit(apu, unit_name, unit); + if (rc < 0) + return rc; + + return 0; +} + +static const struct lf3_unit lf3_size_units[] = { + { "", 512 }, /* XXX This is the default used by find. */ + { "b", 512 }, + { "c", 1 }, + { "w", 2 }, + { "k", 1024 }, + { "M", 1048576 }, + { "G", 1073741824 }, + { NULL, 0 }, +}; + +static int lf3_parse_user(const char *name, id_t *id) +{ + struct passwd pw; + struct passwd *rpw = NULL; + char *buf = NULL; + size_t buf_size; + char *end; + int rc; + + end = NULL; + errno = 0; + *id = strtoul(name, &end, 10); + if (errno == 0 && *name != '\0' && *end == '\0') { + rc = 0; + goto out; + } + + buf_size = sysconf(_SC_GETPW_R_SIZE_MAX); + if (buf_size == -1) + buf_size = 16384; + + buf = xcalloc(1, buf_size); + + rc = getpwnam_r(name, &pw, buf, buf_size, &rpw); + if (rpw == NULL) { + if (rc != 0) + rc = -rc; + else + rc = INT_MIN; + + goto out; + } + + *id = pw.pw_uid; + rc = 0; +out: + free(buf); + + return rc; +} + +static int lf3_parse_group(const char *name, id_t *id) +{ + struct group gr; + struct group *rgr = NULL; + char *buf = NULL; + size_t buf_size; + char *end; + int rc; + + end = NULL; + errno = 0; + *id = strtoul(name, &end, 10); + if (errno == 0 && *name != '\0' && *end == '\0') { + rc = 0; + goto out; + } + + buf_size = sysconf(_SC_GETGR_R_SIZE_MAX); + if (buf_size == -1) + buf_size = 16384; + + buf = xcalloc(1, buf_size); + + rc = getgrnam_r(name, &gr, buf, buf_size, &rgr); + if (rgr == NULL) { + if (rc != 0) + rc = -rc; + else + rc = INT_MIN; + + goto out; + } + + *id = gr.gr_gid; + rc = 0; +out: + free(buf); + + return rc; +} + +static char *lf3_user_expr(int begin, int end) +{ + const char *name = lf3_arg[begin + 1]; + id_t id; + int rc; + + assert(begin + 2 == end); + + rc = lf3_parse_user(name, &id); + if (rc == INT_MIN) + LF3_FATAL_AT(begin + 1, "'%s' is not the name of a known user\n", name); + else if (rc < 0) + LF3_FATAL_AT(begin + 1, "cannot get UID for user '%s': %s\n", name, strerror(-rc)); + + return xsprintf("(= (uid) %llu)", (__ull)id); +} + +static char *lf3_group_expr(int begin, int end) +{ + const char *name = lf3_arg[begin + 1]; + id_t id; + int rc; + + assert(begin + 2 == end); + + rc = lf3_parse_group(name, &id); + if (rc == INT_MIN) + LF3_FATAL_AT(begin + 1, "'%s' is not the name of a known group\n", name); + else if (rc < 0) + LF3_FATAL_AT(begin + 1, "cannot get GID for group '%s': %s\n", name, strerror(-rc)); + + return xsprintf("(= (gid) %llu)", (__ull)id); +} + +static inline bool is_power_of_2(__ull x) +{ + return x != 0 && (x & (x - 1)) == 0; +} + +static char *lf3_size_expr(int begin, int end) +{ + const char *size_str = lf3_arg[begin + 1]; + char cmp; + __ull val; + __ull unit; + int rc; + + /* -size n[cwbkMG] + * File uses n units of space, rounding up. The following suffixes can be used: */ + + assert(begin + 2 == end); + + rc = lf3_parse_numeric(lf3_size_units, size_str, &cmp, &val, &unit); + if (rc < 0) + LF3_FATAL_AT(begin + 1, "invalid size '%s'\n", size_str); + + /* From find(1): The + and - prefixes signify greater than and + * less than, as usual; i.e., an exact size of n units does + * not match. Bear in mind that the size is rounded up to the + * next unit. Therefore -size -1M is not equivalent to -size + * -1048576c. The former only matches empty files, the latter + * matches files from 0 to 1,048,575 bytes. */ + + if ((cmp == '=' && val == 0) || unit == 1) + return xsprintf("(%c (size) %llu)", cmp, val); + else if (is_power_of_2(unit)) + return xsprintf("(%c (round-up-power-of-2 (size) %llu) %llu)", + cmp, + unit, + unit * val); + else + return xsprintf("(%c (round-up (size) %llu) %llu)", + cmp, + unit, + unit * val); +} + +static const struct lf3_unit lf3_time_units[] = { + { "", 0 }, /* See below */ + { "s", 1 }, + { "m", 60 }, + { "h", 3600 }, + { "d", 86400 }, + { NULL, 0 }, +}; + +static char *lf3_time_expr(char which, __ull unit_sec_def, int begin, int end) +{ + const char *time_str = lf3_arg[begin + 1]; + char cmp; + __ull val; + __ull unit_sec; + int rc; + + assert(begin + 2 == end); + assert(which == 'a' || which == 'm' || which == 'c'); + + rc = lf3_parse_numeric(lf3_time_units, time_str, &cmp, &val, &unit_sec); + if (rc < 0) + LF3_FATAL_AT(begin + 1, "invalid time '%s'\n", time_str); + + /* -atime n + * + * File was last accessed n*24 hours ago. When find figures + * out how many 24-hour periods ago the file was last + * accessed, any fractional part is ignored, so to match + * -atime +1, a file has to have been accessed at least two + * days ago. */ + + if (unit_sec == 0) + unit_sec = unit_sec_def; + + /* (cmp (quotient (- now (atime)) unit_sec) val) */ + + return xsprintf("(%c" /* cmp */ + " (quotient (- %llu" /* now */ + " (%ctime))" /* which */ + " %llu)" /* unit_sec */ + " %llu)" /* val */, + cmp, lf3_now, which, unit_sec, val); +} + +static const struct lf3_unit lf3_other_units[] = { + { "", 1 }, + { NULL, 0 }, +}; + +/* Numeric tests for uid, gid, inum, links... */ +static char *lf3_numeric_expr(const char *thunk, int begin, int end) +{ + const char *first = lf3_arg[begin] + 1; /* Drop leading '-'. */ + const char *arg_str = lf3_arg[begin + 1]; + char cmp; + __ull val; + __ull unit; + int rc; + + assert(begin + 2 == end); + + rc = lf3_parse_numeric(lf3_other_units, arg_str, &cmp, &val, &unit); + if (rc < 0) + LF3_FATAL_AT(begin + 1, "invalid argument to %s '%s'\n", first, arg_str); + + assert(unit == 1); + + return xsprintf("(%c (%s) %llu)", cmp, thunk, val); +} + +static char *lf3_type_expr(int begin, int end) +{ + const char *type_str = lf3_arg[begin + 1]; + const char *s; + // bool found_one_type = false; + char *expr = NULL; + size_t expr_size = 0; + FILE *expr_stream = NULL; + + assert(begin + 2 == end); + + expr_stream = open_memstream(&expr, &expr_size); + fprintf(expr_stream, "(or"); + + for (s = type_str; *s != '\0'; s++) { + mode_t fmt; + + switch (*s) { + case ',': + continue; + case 'b': + fmt = S_IFBLK; + break; + case 'c': + fmt = S_IFCHR; + break; + case 'd': + fmt = S_IFDIR; + break; + case 'p': + fmt = S_IFIFO; + break; + case 'f': + fmt = S_IFREG; + break; + case 'l': + fmt = S_IFLNK; + break; + case 's': + fmt = S_IFSOCK; + break; + default: + LF3_FATAL_AT(begin + 1, "invalid type '%s'\n", type_str); + } + + fprintf(expr_stream, " (= (logand (mode) %llu) %llu)", (__ull)S_IFMT, (__ull)fmt); + } + + fprintf(expr_stream, ")"); + fclose(expr_stream); + + return expr; +} + +static const char LF3_FNMATCH[] = "fnmatch?"; +static const char LF3_FNMATCH_CI[] = "fnmatch-ci?"; +static const char LF3_STRING_EQ[] = "string=?"; +static const char LF3_STRING_CI_EQ[] = "string-ci=?"; + +static char *lf3_fnmatch_expr(const char *which /* LF3_FNMATCH, LF3_FNMATCH_CI */, + const char *pattern, + const char *caller /* call-with-name, ...-path */) +{ + const char *p_str; + const char *v_match; + + /* From fnmatch(3): the fnmatch() function checks whether the + * string argument matches the pattern argument, which is a + * shell wildcard pattern (see glob(7)). + * + * From glob(7): A string is a wildcard pattern if it contains + * one of the characters '?', '*' or '['. Globbing is the + * operation that expands a wildcard pattern into the list of + * pathnames matching the pattern. + */ + if (strpbrk(pattern, "?*[") == NULL) { + /* pattern is a plain string. Do strength reduction. */ + if (strcmp(which, LF3_FNMATCH) == 0) + which = LF3_STRING_EQ; + else if (strcmp(which, LF3_FNMATCH_CI) == 0) + which = LF3_STRING_CI_EQ; + } + + /* We want something equivalent to the following but without + * creating a closure for each inode: + * (let ((match? + * (lambda (str) + * (which pattern str)))) + * (any match? (thunk))) + */ + + p_str = LF3_GENSYM("str"); + v_match = LF3_GENVARF("match", + "(lambda (%s) (%s %Q %s))", + p_str, which, pattern, p_str); + + return xsprintf("(%s %s)", caller, v_match); +} + +static char *lf3_name_expr(int begin, int end) +{ + /* FIXME find(1) warns if the pattern passed to -name includes + * a slash. We should do that here too. */ + + assert(begin + 2 == end); + + return lf3_fnmatch_expr(LF3_FNMATCH, lf3_arg[begin + 1], "call-with-name"); +} + +static char *lf3_iname_expr(int begin, int end) +{ + assert(begin + 2 == end); + + return lf3_fnmatch_expr(LF3_FNMATCH_CI, lf3_arg[begin + 1], "call-with-name"); +} + +static char *lf3_path_expr(int begin, int end) +{ + assert(begin + 2 == end); + + return lf3_fnmatch_expr(LF3_FNMATCH, lf3_arg[begin + 1], "call-with-relative-path"); +} + +static char *lf3_ipath_expr(int begin, int end) +{ + assert(begin + 2 == end); + + return lf3_fnmatch_expr(LF3_FNMATCH_CI, lf3_arg[begin + 1], "call-with-relative-path"); +} + +static char *lf3_exec_plus_expr(int begin, int end) +{ + char *expr = NULL; + size_t expr_size = 0; + FILE *stream = NULL; + const char *v_pipe; + const char *v_mutex; + const char *v_printer; + const char *cmd; + int i; + + assert(strcmp(lf3_arg[end - 1], "+") == 0); /* Thanks to scanner. */ + + /* + * $ find . -type f -exec echo {} bar +; + * find: missing argument to `-exec' + * + * $ find . -type f -exec echo foo{} +; + * find: In ‘-exec ... {} +’ the ‘{}’ must appear by itself, but you specified ‘foo{}’ + * + * $ find . -type f -exec echo foo {} {} +; + * find: Only one instance of {} is supported with -exec ... + + */ + if (strcmp(lf3_arg[end - 2], "{}") != 0) + LF3_FATAL_AT(begin, "missing argument to exec\n"); + + cmd = lf3_arg[begin + 1]; + LF3_DEBUG_S(cmd); + + for (i = begin + 2; i < end - 2; i++) + if (strstr(lf3_arg[i], "{}") != NULL) + LF3_FATAL_AT(i, "only one instance of {} is supported with -exec ... +\n"); + + /* (define v_pipe (make-exec-plus-pipe cmd arg[2] ... arg[end - 3]) */ + stream = open_memstream(&expr, &expr_size); + fprintf(stream, "(make-exec-plus-pipe"); + + /* lf3_arg[begin + 0] == "-exec" + * lf3_arg[begin + 1] == cmd + * ... + * lf3_arg[end - 2] == "{}" + * lf3_arg[end - 1] == "+" */ + for (i = begin + 1; i < end - 2; i++) + lf3_fprintf(stream, " %Q", lf3_arg[i]); + + fprintf(stream, ")"); + fclose(stream); + + v_pipe = LF3_GENVAR("pipe", expr); + v_mutex = LF3_GENVAR("mutex", "(make-mutex)"); + v_printer = LF3_GENVARF("print", "(make-printer %s %s #\\x00)", + v_pipe, v_mutex); + + LF3_FINI_ADDF("(close-exec-plus-pipe %s)", v_pipe); + + return xsprintf("(call-with-absolute-path %s)", v_printer); +} + +/* Make an exec arg expression from a single find -exec ... \; argument. + * + * When evaluated, the file name will be in the variable @sym. From + * find(1): The string `{}' is replaced by the current file name being + * processed everywhere it occurs in the arguments to the command, not + * just in arguments where it is alone, as in some versions of find. + * + * "{}" => sym + * "chacha" => "chacha" + * "{}cha" => (string-append sym "cha") + * "cha {} cha" => (string-append "cha " sym " cha") + */ +static int lf3_exec_arg(FILE *stream, int index, const char *arg, const char *sym) +{ + if (strchr(arg, '{') == NULL) + return lf3_fprintf(stream, " %Q", arg); + + if (strcmp(arg, "{}") == 0) + return fprintf(stream, " %s", sym); + + fprintf(stream, " (string-append"); + + while (*arg != '\0') { + const char *lbr = strchr(arg, '{'); + + if (lbr == NULL) { + lf3_fprintf(stream, " %Q", arg); + break; + } + + if (lbr[1] != '}') + LF3_FATAL_AT(index, "missing '}' in argument to exec\n"); + + if (lbr == arg) + fprintf(stream, " %s", sym); + else + lf3_fprintf(stream, " %.*Q %s", (int)(lbr - arg), arg, sym); + + arg = lbr + 2; + } + + fprintf(stream, ")"); + + return 0; +} + +static char *lf3_exec_expr(int begin, int end) +{ + char *expr = NULL; + size_t expr_size = 0; + FILE *stream = NULL; + const char *p_path; + const char *v_exec; + const char *cmd; + const char *last; + int i; + + /* "-exec \;", end - begin == 2, no command */ + if (end - begin < 3) + LF3_FATAL_AT(begin, "missing argument to exec\n"); + + cmd = lf3_arg[begin + 1]; + last = lf3_arg[end - 1]; + LF3_DEBUG_S(cmd); + LF3_DEBUG_S(last); + LF3_DEBUG_D(begin); + LF3_DEBUG_D(end); + LF3_DEBUG_D(end - begin); + + if (strcmp(last, "+") == 0) + return lf3_exec_plus_expr(begin, end); + + assert(strcmp(last, ";") == 0); /* Thanks to the scanner. */ + + p_path = LF3_GENSYM("path"); + + stream = open_memstream(&expr, &expr_size); + lf3_fprintf(stream, "(lambda (%s) (= 0 (system* %Q", p_path, cmd); + + for (i = begin + 2; i < end - 1; i++) { + const char *arg = lf3_arg[i]; + + LF3_DEBUG_S(arg); + lf3_exec_arg(stream, i, arg, p_path); + } + + fprintf(stream, ")))"); + fclose(stream); + + v_exec = LF3_GENVAR("exec", expr); + + return xsprintf("(call-with-absolute-path %s)", v_exec); +} + +/* This is a fallback handler used for -print, ... */ +static char *lf3_call_expr(int begin, int end) +{ + const char *first = lf3_arg[begin] + 1; /* Drop leading '-'. */ + char *expr = NULL; + size_t expr_size = 0; + FILE *expr_stream = NULL; + int i; + + /* first is a scheme procedure name, the rest are strings */ + + expr_stream = open_memstream(&expr, &expr_size); + fprintf(expr_stream, "(%s", first); + + for (i = begin + 1; i < end; i++) + lf3_fprintf(expr_stream, " %Q", lf3_arg[i]); + + fprintf(expr_stream, ")"); + fclose(expr_stream); + + return expr; +} + +static struct lf3_file *lf3_make_file(const char *path) +{ + struct lf3_file *lf; + + /* -fprint file + * + * True; print the full file name into file file. If file + * does not exist when find is run, it is created; if it does + * exist, it is truncated. The file names `/dev/stdout' and + * `/dev/stderr' are handled specially; they refer to the + * standard output and standard error output, + * respectively. The output file is always created, even if + * the predicate is never matched. See the UNUSUAL FILENAMES + * section for information about how unusual characters in + * filenames are handled. + * + * Passing "/dev/stdout" to open below would cause + * truncation. Which is not what is intended or what find + * does. + * + * To preserve the find semantics (and do what the user + * expects) each file (-fprint or -fprintf target) should be + * opened only once and then shared among the relevant action + * handlers. */ + + lipe_list_for_each_entry(lf, &lf3_file_list, lf_link) + if (strcmp(path, lf->lf_path) == 0) + return lf; + + lf = xcalloc(1, sizeof(*lf)); + + lf->lf_path = xstrdup(path); + + if (strcmp(path, "/dev/stdout") == 0) { + lf->lf_port = LF3_GENVARF("port", "(current-output-port)"); + } else if (strcmp(path, "/dev/stderr") == 0) { + lf->lf_port = LF3_GENVARF("port", "(current-error-port)"); + } else { + lf->lf_port = LF3_GENVARF("port", "(open-file %Q %Q)", path, "w"); + LF3_FINI_ADDF("(close-port %s)", lf->lf_port); + } + + lf->lf_mutex = LF3_GENVARF("mutex", "(make-mutex)"); + + return lf; +} + +static const char *lf3_make_printer(const char *path, int delim) +{ + struct lf3_file *lf; + const char *v_printer; + + lf = lf3_make_file(path); + + if (delim < 0) + v_printer = LF3_GENVARF("print", "(make-printer %s %s #f)", + lf->lf_port, lf->lf_mutex); + else + v_printer = LF3_GENVARF("print", "(make-printer %s %s #\\x%02hhx)", + lf->lf_port, lf->lf_mutex, (unsigned char)delim); + + return v_printer; +} + +static char *lf3_print_expr2(const char *file, int delim) +{ + const char *v_printer = lf3_make_printer(file, delim); + + /* FIXME How to ensure that client mount path was supplied? */ + + return xsprintf("(call-with-relative-path %s)", v_printer); +} + +/* -print */ +static char *lf3_print_expr(int begin, int end) +{ + return lf3_print_expr2("/dev/stdout", '\n'); +} + +/* -print0 */ +static char *lf3_print0_expr(int begin, int end) +{ + return lf3_print_expr2("/dev/stdout", '\0'); +} + +/* -fprint file */ +static char *lf3_fprint_expr(int begin, int end) +{ + const char *file = lf3_arg[begin + 1]; + + assert(begin + 2 == end); + + return lf3_print_expr2(file, '\n'); +} + +/* -fprint0 file */ +static char *lf3_fprint0_expr(int begin, int end) +{ + const char *file = lf3_arg[begin + 1]; + + assert(begin + 2 == end); + + return lf3_print_expr2(file, '\0'); +} + +static char *lf3_fprintf_expr2(const char *file, int fmt_index, const char *fmt) +{ + const char *v_printer = lf3_make_printer(file, -1); + char *fmt_expr = NULL; + size_t fmt_expr_size = 0; + char *arg_expr = NULL; + size_t arg_expr_size = 0; + FILE *fmt_stream; + FILE *arg_stream; + + fmt_stream = open_memstream(&fmt_expr, &fmt_expr_size); + arg_stream = open_memstream(&arg_expr, &arg_expr_size); + lf3_parse_format(fmt_index, fmt, fmt_stream, arg_stream); + fclose(fmt_stream); + fclose(arg_stream); + + return xsprintf("(%s (format #f %s %s))", v_printer, fmt_expr, arg_expr); +} + +/* -printf fmt */ +static char *lf3_printf_expr(int begin, int end) +{ + const char *fmt = lf3_arg[begin + 1]; + + assert(begin + 2 == end); + + return lf3_fprintf_expr2("/dev/stdout", begin + 1, fmt); +} + +/* -fprintf file fmt */ +static char *lf3_fprintf_expr(int begin, int end) +{ + const char *file = lf3_arg[begin + 1]; + const char *fmt = lf3_arg[begin + 2]; + + assert(begin + 3 == end); + + return lf3_fprintf_expr2(file, begin + 2, fmt); +} + +static char *lf3_delete_expr(int begin, int end) +{ + const char *v_deleter; + + v_deleter = LF3_GENVARF("delete", "(make-deleter)"); + + LF3_FINI_ADDF("(close-deleter %s)", v_deleter); + + return xsprintf("(%s (file-fid))", v_deleter); +} + +/* Build a simple expression, begin and end are indexes into lf3_arg. + * lf3_arg[begin] is the test or action name ('-size', '-print', + * '-delete'). Expression arguments start at lf3_arg[begin + 1] and end + * at lf3_arg[end] (non inclusive). */ +static char *lf3_simple_expr(int begin, int end) +{ + const char *first = lf3_arg[begin] + 1; /* Test or action name with leading '-' removed. */ + + /* The scanner has already checked the argument count for us. */ + +#define X2(name, expr) \ + do { \ + if (strcmp(first, name) == 0) \ + return (expr); \ + } while (0) + +#define X1(name) X2(#name, lf3_ ## name ## _expr(begin, end)) + + X1(user); + X1(group); + X1(name); + X1(iname); + X1(path); + X1(ipath); + X1(size); + X1(type); + X1(exec); + // X1(perm); TODO + // X1(used); TODO + X1(print); + X1(print0); + X1(fprint); + X1(fprint0); + X1(printf); + X1(fprintf); + X1(delete); + + /* lipe_scan attribute names are consistent with struct stat member names. */ + X2("inum", lf3_numeric_expr("ino", begin, end)); // find uses "-inum", lipe_scan uses "ino". + X2("uid", lf3_numeric_expr("uid", begin, end)); // ... + X2("gid", lf3_numeric_expr("gid", begin, end)); + X2("projid", lf3_numeric_expr("projid", begin, end)); + X2("links", lf3_numeric_expr("nlink", begin, end)); + + X2("amin", lf3_time_expr('a', 60, begin, end)); + X2("atime", lf3_time_expr('a', 86400, begin, end)); + X2("cmin", lf3_time_expr('c', 60, begin, end)); + X2("ctime", lf3_time_expr('c', 86400, begin, end)); + X2("mmin", lf3_time_expr('m', 60, begin, end)); + X2("mtime", lf3_time_expr('m', 86400, begin, end)); + + X2("true", xstrdup("#t")); + X2("false", xstrdup("#f")); + X2("wholename", lf3_path_expr(begin, end)); + X2("iwholename", lf3_ipath_expr(begin, end)); + + X2("quit", "(lipe-scan-break 0)"); + +#undef X1 +#undef X2 + + return lf3_call_expr(begin, end); +} + +static char *lf3_unary_expr(const char *op, const char *e1) +{ + return xsprintf("(%s %s)", op, e1); +} + +static char *lf3_binary_expr(const char *op, const char *e1, const char *e2) +{ + return xsprintf("(%s %s %s)", op, e1, e2); +} + +%} + +/* Bison declarations. */ + +%define parse.error custom +%define parse.lac full +%locations + +%union +{ + int u_range[2]; + char *u_expr; +} + +%left TOKEN_COMMA /* Lowest precedence */ +%left TOKEN_OR +%left TOKEN_AND +%token TOKEN_NOT /* Highest precedence */ +%token TOKEN_LEFT_PAREN +%token TOKEN_RIGHT_PAREN +%token TOKEN_ACTION +%token TOKEN_TEST + +/* We use three levels of expr to handle shift reduce conflicts around + * 'not' and implicit 'and'. */ +%type expr +%type expr1 +%type expr2 + +%% /* rules */ + +prog : expr { + lf3_policy_body = $1; +} + +expr : expr TOKEN_AND expr { $$ = lf3_binary_expr("and", $1, $3); } + | expr TOKEN_OR expr { $$ = lf3_binary_expr("or", $1, $3); } + | expr TOKEN_COMMA expr { $$ = lf3_binary_expr("begin", $1, $3); } + | expr1 { $$ = $1; } + ; + +expr1 : expr1 expr2 { $$ = lf3_binary_expr("and", $1, $2); } /* Implicit 'and'. */ + | expr2 { $$ = $1; } + ; + +expr2 : TOKEN_LEFT_PAREN expr TOKEN_RIGHT_PAREN { $$ = $2; } + | TOKEN_NOT expr2 { $$ = lf3_unary_expr("not", $2); } + | TOKEN_ACTION { $$ = lf3_simple_expr($1[0], $1[1]); lf3_policy_body_has_action = true; } + | TOKEN_TEST { $$ = lf3_simple_expr($1[0], $1[1]); } + ; + +%% /* end rules, start user code */ + +static void yyerror(const char *str) +{ + LF3_FATAL_AT(lf3_arg_index, "internal error: %s\n", str); +} + +static const char *lf3_symbol_name(enum yysymbol_kind_t kind) +{ + switch (kind) { + case YYSYMBOL_YYEOF: return "end of arguments"; + case YYSYMBOL_TOKEN_COMMA: return "','"; + case YYSYMBOL_TOKEN_OR: return "'-or'"; + case YYSYMBOL_TOKEN_AND: return "'-and'"; + case YYSYMBOL_TOKEN_NOT: return "'-not'"; + case YYSYMBOL_TOKEN_LEFT_PAREN: return "'('"; + case YYSYMBOL_TOKEN_RIGHT_PAREN: return "')'"; + case YYSYMBOL_TOKEN_ACTION: return "action"; + case YYSYMBOL_TOKEN_TEST: return "test"; + default: return yysymbol_name(kind); + } +} + +static int yyreport_syntax_error(const yypcontext_t *ctx) +{ + enum { TOKENMAX = 16 }; + yysymbol_kind_t expected[TOKENMAX]; + yysymbol_kind_t lookahead; + int i, n; + char *msg = NULL; + size_t msg_size = 0; + FILE *msg_stream = NULL; + int rc = 0; + + n = yypcontext_expected_tokens(ctx, expected, TOKENMAX); + if (n < 0) { + /* Forward errors to yyparse. */ + rc = n; + return n; + } + + assert(n != 0); + + msg_stream = open_memstream(&msg, &msg_size); + + /* Report the tokens expected at this point. */ + if (n == 1) + fprintf(msg_stream, "expected %s", lf3_symbol_name(expected[0])); + else + fprintf(msg_stream, "expected one of %s", lf3_symbol_name(expected[0])); + + for (i = 1; i < n - 1; ++i) + fprintf(msg_stream, ", %s", lf3_symbol_name(expected[i])); + + /* Report the unexpected token. */ + lookahead = yypcontext_token(ctx); + if (lookahead != YYSYMBOL_YYEMPTY) + fprintf(msg_stream, " before %s", lf3_symbol_name(lookahead)); + + fclose(msg_stream); + + /* We store the current argument index in first_line. See YY_USER_ACTION. */ + LF3_FATAL_AT(yypcontext_location(ctx)->first_line, "%s\n", msg); + + return rc; +} + +enum { + LF3_OPT_DEBUG, + LF3_OPT_DEBUG_SCAN, + LF3_OPT_NOW, + LF3_OPT_SCAN_COMMAND, + LF3_OPT_THREAD_COUNT, + LF3_OPT_HELP = 'h', + LF3_OPT_VERSION = 'v', +}; + +static void help(void) +{ + printf( +"Usage: %s [OPTION]... DEVICE [EXPRESSION]\n" +"Find files on a Lustre target device\n" +"\n" +"Mandatory arguments to long options are mandatory for short options too.\n" +" --debug enable debugging\n" +" --debug-scan enable scan debugging\n" +/* --now=EPOCH use EPOCH instead of current time (for testing) */ +/* --scan-command=COMMAND use 'cat' to print the code we would send to lipe_scan3 */ +" --thread-count=COUNT use COUNT scaninng threads\n" +" -h, --help display this help text and exit\n" +" -v, --version output version information and exit\n", + program_invocation_short_name); +} + +static const struct option options[] = { + { "debug", no_argument, 0, LF3_OPT_DEBUG }, + { "debug-scan", no_argument, 0, LF3_OPT_DEBUG_SCAN }, + { "now", required_argument, 0, LF3_OPT_NOW }, + { "scan-command", required_argument, 0, LF3_OPT_SCAN_COMMAND }, + { "thread-count", required_argument, 0, LF3_OPT_THREAD_COUNT }, + { "help", no_argument, 0, LF3_OPT_HELP }, + { "version", no_argument, 0, LF3_OPT_VERSION }, + { NULL }, +}; + +int main(int argc, char *argv[]) +{ + const char *scan_command = LF3_SCAN_COMMAND; + const char *thread_count = NULL; + bool debug_scan = false; + int rc; + int c; + + lipe_version_init(); + + rc = register_printf_specifier('K', &lf3_printf_extension_K, &lf3_printf_extension_K_arginfo); + assert(rc == 0); + rc = register_printf_specifier('P', &lf3_printf_extension_P, &lf3_printf_extension_P_arginfo); + assert(rc == 0); + rc = register_printf_specifier('Q', &lf3_printf_extension_Q, &lf3_printf_extension_Q_arginfo); + assert(rc == 0); + + while ((c = getopt_long(argc, argv, "+hv", options, NULL)) != -1) { + switch (c) { + case LF3_OPT_DEBUG: + lf3_debug = 1; + break; + case LF3_OPT_DEBUG_SCAN: + debug_scan = true; + break; + case LF3_OPT_NOW: + lf3_now = strtoull(optarg, NULL, 0); + break; + case LF3_OPT_SCAN_COMMAND: + scan_command = optarg; + break; + case LF3_OPT_THREAD_COUNT: + thread_count = optarg; + break; + case LF3_OPT_HELP: + help(); + exit(EXIT_SUCCESS); + case LF3_OPT_VERSION: + lipe_version(); + exit(EXIT_SUCCESS); + case '?': + fprintf(stderr, "Try '%s --help for more information.\n", + program_invocation_short_name); + exit(EXIT_FAILURE + 1); + } + } + + LF3_DEBUG_D(argc); + LF3_DEBUG_D(optind); + LF3_DEBUG_D(debug_scan); + LF3_DEBUG_S(scan_command); + LF3_DEBUG_S(PACKAGE_VERSION); + LF3_DEBUG_S(LIPE_REVISION); + + if (debug_scan) + lf3_init_add("(lipe-debug-enable #t)"); + + if (lf3_now == -1) + lf3_now = time(NULL); + + LF3_DEBUG_U(lf3_now); + + if (thread_count != NULL) { + char *end = NULL; + long val; + + errno = 0; + val = strtol(thread_count, &end, 0); + if (errno != 0 || *end != '\0' || val < 0) + LF3_FATAL("invalid thread count '%s'\n", thread_count); + } else { + thread_count = "(lipe-getopt-thread-count)"; + } + + LF3_DEBUG_S(thread_count); + + if (optind == argc) { + fprintf(stderr, + "Usage: %s [OPTION]... DEVICE [EXPRESSION]\n" + "Try '%s --help for more information.\n", + program_invocation_short_name, program_invocation_short_name); + exit(EXIT_FAILURE + 1); + } + + /* TODO Handle multiple devices. */ + + const char *device_path = argv[optind]; + LF3_DEBUG_S(device_path); + + if (optind + 1 == argc) { + /* If no expression is supplied then use '-true'. */ + static const char *lf3_true = "-true"; + lf3_lexer_init(&lf3_true, 0, 1); + } else { + lf3_lexer_init((const char **)argv, optind + 1, argc); + } + + rc = yyparse(); + + LF3_DEBUG_D(rc); + LF3_DEBUG_S(lf3_policy_body); + LF3_DEBUG_B(lf3_policy_body_has_action); + + if (rc != 0 || lf3_policy_body == NULL) + LF3_FATAL("internal argument parsing error\n"); + + /* At the end of expression parsing we have built the following: + * + * 1. lf3_genvar_list: a list of pairs of genvars and their initial values. + * + * 2. lf3_init_list: a list of initial actions (for example opening output files or creating -exec pipe-lines). + * + * 3. lf3_policy_body: the expression we evaluate for each + * inode, for example '(or (= (uid) 500) (= (gid) 507))'. + * This may or may not contain an action (it does iff lf3_policy_body_has_action). + * + * 4. lf3_fini_list: a list of final actions (closing pipes or ports). + * + * From this we build: + * + * 1. policy_thunk: a thunk containing the policy_body + * with an implicit action added if lf3_policy_body does + * not contain one. For example + * '(lambda () (and (or (= (uid) 500) (= (gid) 507)) (print-file-fid)))'. + * + * 2. scm_code: + * + * (let* (lf3_genvar_list) + * (dynamic-wind + * (lambda () lf3_init_list) + * (lambda () (lipe-scan3 device_path ... policy_thunk ...)) + * (lambda () lf3_fini_list))) + * + * Scheme does not like empty lambda bodies, so to make this + * work we need to fixup lf3_init_list and lf3_fini_list. + */ + if (lipe_list_empty(&lf3_init_list)) + lf3_init_add("#t"); + + if (lipe_list_empty(&lf3_fini_list)) + lf3_fini_add("#t"); + + /* find(1) adds an implicit -print action if no action was + * specified in the expression. We use -print-file-fid as the + * implicit action. */ + char *policy_thunk = lf3_policy_body_has_action ? + xsprintf("(lambda () %s)", lf3_policy_body) : + xsprintf("(lambda () (and %s (print-file-fid)))", lf3_policy_body); + LF3_DEBUG_S(policy_thunk); + + char *scm_code = xsprintf( + "(use-modules (lipe) (lipe find))\n" + "\n" + "(let* (%P)\n" + " (dynamic-wind\n" + " (lambda () %K)\n" + " (lambda () (%s\n" + " %Q\n" + " %s\n" + " %s\n" + " %s\n" + " %s))\n" + " (lambda () %K)))\n", + &lf3_genvar_list, + &lf3_init_list, + LF3_SCM_SCAN_PROC, /* lipe-scan */ + device_path, + "(lipe-getopt-client-mount-path)", + policy_thunk, + "(lipe-getopt-required-attrs)", + thread_count, + &lf3_fini_list); + LF3_DEBUG_S(scm_code); + + errno = 0; + FILE *scan_pipe = popen(scan_command, "w"); + if (scan_pipe == NULL) { + if (errno == 0) + errno = ENOMEM; /* See popen(3). */ + LF3_FATAL("cannot execute scan command '%s' for '%s': %s\n", + scan_command, device_path, strerror(errno)); + } + + rc = fputs(scm_code, scan_pipe); + if (rc < 0) + LF3_FATAL("cannot write scan command to '%s' for '%s': %s\n", + scan_command, device_path, strerror(errno)); + rc = pclose(scan_pipe); + LF3_DEBUG_D(rc); + if (rc < 0) + LF3_FATAL("error on close of pipe to '%s' for '%s': %s\n", + scan_command, device_path, strerror(errno)); + + if (WIFEXITED(rc)) { + exit(WEXITSTATUS(rc)); + } else if (WIFSIGNALED(rc)) { + int sig = WTERMSIG(rc); + + raise(sig); + + LF3_FATAL("scan of '%s' terminated by signal %d (%s)\n", + device_path, sig, strsignal(sig)); + } else { + LF3_FATAL("scan of '%s' terminated with unexpected status %d\n", device_path, rc); + } +} diff --git a/lipe/src/lipe_find3/lf3_parse_format.c b/lipe/src/lipe_find3/lf3_parse_format.c new file mode 100644 index 0000000..f74016e --- /dev/null +++ b/lipe/src/lipe_find3/lf3_parse_format.c @@ -0,0 +1,456 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "lf3_debug.h" +#include "lf3_parse_format.h" +#include "lf3_printf.h" +#include "xmalloc.h" + +static bool strstartswith(const char *str, const char *prefix) +{ + return strncmp(str, prefix, strlen(prefix)) == 0; +} + +static int isodigit(int c) +{ + return isdigit(c) && c != '9'; +} + +static int isnzdigit(int c) +{ + return isdigit(c) && c != '0'; +} + +static int lf3_parse_printf_width(const char *fmt, int *width) +{ + char *end = NULL; + long value = 0; + + if (!isnzdigit(*fmt)) { + *width = 0; + return 0; + } + + errno = 0; + value = strtol(fmt, &end, 0); + if (errno != 0) + return -errno; + + if (value != (int)value) + return -EINVAL; + + *width = value; + + return end - fmt; +} + +static int lf3_parse_printf_precision(const char *fmt, int *precision) +{ + char *end = NULL; + long value = 0; + + /* From printf(3): An optional precision, in the form of a + * period ('.') followed by an optional decimal digit + * string. ... If the precision is given as just '.', the + * precision is taken to be zero. A negative precision is + * taken as if the precision were omitted. */ + + if (*fmt != '.') { + *precision = 0; + return 0; + } + + errno = 0; + value = strtol(fmt + 1, &end, 0); + if (errno != 0) + return -errno; + + if (value != (int)value) + return -EINVAL; + + if (value < 0) + *precision = 0; + else + *precision = value; + + return end - fmt; +} + +/* LF3_FORMAT_AT() but with a char offset. */ +#define LF3_FATAL_AT_OFFSET(index, offset, fmt, args...) \ + LF3_FATAL_AT(index, "offset %d: "fmt, offset, ##args) + +/* Parse a single format directive ("%%", "%a", "%A@", ...) from the + * start of fmt. Return the length of the directive. */ +static int lf3_parse_format_directive(int index, const char *fmt, int n, FILE *s_fmt, FILE *s_arg) +{ + bool alt_form = false; + bool zero_pad = false; + bool left_justify = false; + const char *rbr; + char *ext = NULL; + int width = 0; + int precision = 0; + int n0 = n; + int rc; + int c; + int k; + + LF3_DEBUG_D(n); + LF3_DEBUG_S(&fmt[n]); + + assert(fmt[n] == '%'); + n++; + + if (fmt[n] == '%') { + fputc('%', s_fmt); + n++; + return n; + } + + /* Flags. */ + for (; fmt[n] != '\0'; n++) { + switch (fmt[n]) { + case '#': + alt_form = true; + break; + case '0': + zero_pad = true; + break; + case '-': + left_justify = true; + break; + default: + goto width; + } + } + +width: + rc = lf3_parse_printf_width(&fmt[n], &width); + if (rc < 0) + LF3_FATAL_AT_OFFSET(index, n, "invalid width in format directive '%s'\n", fmt); + + n += rc; + + rc = lf3_parse_printf_precision(&fmt[n], &precision); + if (rc < 0) + LF3_FATAL_AT_OFFSET(index, n, "invalid precision in format directive '%s'\n", fmt); + + n += rc; + + // TODO Support flags, width, precision. + // (format #t "~5,'*d" 12) -| ***12 + // (format #t "~5,'0d" 12) -| 00012 + // (format #t "~3d" 1234) -| 1234 + + if (alt_form || zero_pad || left_justify) + LF3_FATAL_AT_OFFSET(index, n0, "invalid format string '%s', flags are not supported\n", fmt); + + if (width != 0) + LF3_FATAL_AT_OFFSET(index, n0, "invalid format string '%s', width is not supported\n", fmt); + + if (precision != 0) + LF3_FATAL_AT_OFFSET(index, n0, "invalid format string '%s', precision is not supported\n", fmt); + +#define LF3_EMIT_S(c, expr, args...) \ + do { \ + fprintf(s_fmt, "~%c", c); \ + lf3_fprintf(s_arg, " "expr, ##args); \ + } while (0) + +#define LF3_EMIT_N(c, expr, args...) \ + do { \ + fprintf(s_fmt, "~%c", c); \ + lf3_fprintf(s_arg, " "expr, ##args); \ + } while (0) + +#define LF3_EMIT_U(c) \ + LF3_FATAL_AT_OFFSET(index, n, "unsupported format specifier '%c'\n", c) + + /* Specifier. */ + c = fmt[n++]; + switch (c) { + case '\0': + LF3_FATAL_AT_OFFSET(index, n0, "incomplete format directive '%s'\n", fmt); + case 'a': /* fall through */ + case 'c': /* fall through */ + case 't': /* t == mtime */ + /* [acm]time in ctime() format */ + if (c == 't') + c = 'm'; + LF3_EMIT_S('a', "(strftime %Q (localtime (%ctime)))", "%a %b %e %T %Y", c); + break; + case 'A': /* fall through */ + case 'C': /* fall through */ + case 'T': /* T == mtime */ + /* %[ACM] time in k format (k is @ or a strftime directive) */ + c = tolower(c); + if (c == 't') + c = 'm'; + k = fmt[n++]; + if (k == '\0') + LF3_FATAL_AT_OFFSET(index, n0, "incomplete format directive '%s'\n", fmt); + else if (k == '@') + LF3_EMIT_N('d', "(%ctime)", c); + else if (isalpha(k)) + LF3_EMIT_S('a', "(strftime \"%%%c\" (localtime (%ctime)))", k, c); + else + LF3_FATAL_AT_OFFSET(index, n, "unrecognized strftime format specifier '%c' in '%s'\n", k, fmt); + + break; + case 'b': + LF3_EMIT_N('d', "(blocks)"); + break; + case 'd': /* File's depth in the directory tree; 0 means the file is a starting-point. */ + LF3_EMIT_U(c); + break; + case 'D': /* The device number on which the file exists (st_dev in decimal). */ + LF3_EMIT_U(c); + break; + case 'f': /* File's name with any leading directories removed (only the last element) */ + LF3_EMIT_S('a', "(name)"); + break; + case 'F': /* Type of the filesystem the file is on; this value can be used for -fstype. */ + LF3_EMIT_U(c); + break; + case 'g': /* File's group name, or numeric group ID if group has no name. */ + LF3_EMIT_S('a', "(group)"); + break; + case 'G': /* File's numeric group ID. */ + LF3_EMIT_N('d', "(gid)"); + break; + case 'h': + /* Leading directories of file's name (all but the last + * element). If the file name contains no slashes (since it is + * in the current directory) the %h specifier expands to '.'. */ + LF3_EMIT_S('a', "(call-with-relative-path dirname)"); + break; + case 'H': /* Starting point under which file was found. */ + LF3_EMIT_S('a', "(lipe-scan-client-mount-path)"); + break; + case 'i': /* File's inode number (in decimal). */ + LF3_EMIT_N('d', "(ino)"); + break; + case 'k': /* disk space used in 1KB blocks (rounded up). */ + LF3_EMIT_N('d', "(quotient (+ (blocks) 1) 2)"); + break; + case 'l': /* symlink target or empty string if not symlink */ + LF3_EMIT_U(c); + break; + case 'm': /* File's permission bits (in octal). */ + LF3_EMIT_N('o', "(logand (mode) #o07777)"); /* FIXME octal. */ + break; + case 'M': /* Files permissions in symbolic form. */ + LF3_EMIT_U(c); + break; + case 'n': /* Number of hard links to file. */ + LF3_EMIT_N('d', "(nlink)"); + break; + case 'p': /* File's name with 'starting-point'. */ + LF3_EMIT_S('a', "(absolute-path)"); + break; + case 'P': /* File's name without 'starting-point' */ + LF3_EMIT_S('a', "(relative-path)"); + break; + case 's': /* File's size in bytes. */ + LF3_EMIT_N('d', "(size)"); + break; + case 'S': /* File's sparseness. */ + LF3_EMIT_N('f', "(/ (* 512 (blocks)) (size))"); + break; + case 'u': /* File's user name, or numeric user ID if the user has no name. */ + LF3_EMIT_S('a', "(user)"); + break; + case 'U': /* File's numeric user ID. */ + LF3_EMIT_N('d', "(uid)"); + break; + case 'y': /* single char type */ + LF3_EMIT_S('a', "(type->char (type))"); + break; + case 'Y': /* File's type (like %y), plus follow symlinks: 'L'=loop, 'N'=nonexistent, '?' ... */ + LF3_EMIT_U(c); + break; + case 'Z': /* (SELinux only) file's security context. */ + LF3_EMIT_U(c); + break; + case '{': + rbr = strchr(&fmt[n], '}'); + if (rbr == NULL) + LF3_FATAL_AT_OFFSET(index, n0, "missing '}' in format directive '%s'\n", fmt); + + ext = xstrndup(&fmt[n], rbr - &fmt[n]); + LF3_DEBUG_S(ext); + + // TODO + // client-mount-path + // device-name + // device-path + // fsname + // parent-fid + // parent-fids + // pools + if (strcmp(ext, "fid") == 0) + LF3_EMIT_S('a', "(fid)"); + else if (strcmp(ext, "projid") == 0 || + strcmp(ext, "stripe-count") == 0 || + strcmp(ext, "mirror-count") == 0) + LF3_EMIT_N('d', "(%s)", ext); + else if (strstartswith(ext, "xattr:")) + LF3_EMIT_S('a', "(xattr-ref-string %Q)", ext + strlen("xattr:")); + else + LF3_FATAL_AT_OFFSET(index, n, "unrecognized format directive '%s'\n", ext); + + n += rbr - &fmt[n] + 1; /* Step past '}'. */ + break; + default: + LF3_FATAL_AT_OFFSET(index, n - 1, "unrecognized format specifier '%c' in '%s'\n", c, fmt); + break; + } + + free(ext); + + return n; +} + +/* Write single char c properly escaped for inclusion in a scheme + * (format ...) format string. format uses '~' in the way that printf + * uses '%' so we need to tilde-escape '~'. */ +static int lf3_fprintc_scm_format(FILE *stream, int c) +{ + if (c == '~') + return fprintf(stream, "~~"); + else + return lf3_fprintc_scm_string(stream, c); +} + +/* Parse a "single char" from a find format string @fmt satrting at + * offset n which is not a part of a '%' directive. */ +static int lf3_parse_format_char(int index, const char *fmt, int n, int *p) +{ + int c; + int x; + + assert(fmt[n] != '\0' && fmt[n] != '%'); + + *p = fmt[n++]; + if (*p != '\\') + return n; + + switch ((c = fmt[n++])) { + default: + /* This is what find does. */ + *p = c; + return n; + case '\0': + LF3_FATAL_AT_OFFSET(index, n, "stray '\\' at end of format directive\n"); + case 'a': + *p = '\a'; + return n; + case 'b': + *p = '\b'; + return n; + case 't': + *p = '\t'; + return n; + case 'n': + *p = '\n'; + return n; + case 'v': + *p = '\v'; + return n; + case 'f': + *p = '\f'; + return n; + case 'r': + *p = '\r'; + return n; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + x = c - '0'; + + if (isodigit(fmt[n])) + x = (x << 3) | (fmt[n++] - '0'); + + if (isodigit(fmt[n])) + x = (x << 3) | (fmt[n++] - '0'); + + *p = (unsigned char)x; + + return n; + } +} + +/* Parse the find format string to a scheme format call. + * @index is the index of fmt in the original argv and is only for error messages. +*/ +int lf3_parse_format(int index, const char *fmt, FILE *s_fmt, FILE *s_arg) +{ + int n = 0; + + fprintf(s_fmt, "\""); + + while (fmt[n] != '\0') { + const char *mod = strchrnul(&fmt[n], '%'); + + /* Copy chars upto next '%' from fmt to s_fmt. */ + while (&fmt[n] < mod) { + int c; + + n = lf3_parse_format_char(index, fmt, n, &c); + assert(&fmt[n] <= mod); + lf3_fprintc_scm_format(s_fmt, c); + } + + if (fmt[n] == '\0') + break; + + assert(fmt[n] == '%'); + n = lf3_parse_format_directive(index, fmt, n, s_fmt, s_arg); + } + + fprintf(s_fmt, "\""); + + return n; +} + +#if 0 +int lf3_debug = 1; +const char *lf3_progname_; + +int main(int agrc, char *argv[]) +{ + const char *fmt = argv[1]; + char *fmt_expr = NULL; + size_t fmt_expr_size = 0; + char *arg_expr = NULL; + size_t arg_expr_size = 0; + FILE *s_fmt; + FILE *s_arg; + + register_printf_specifier('Q', &lf3_printf_extension_q, &lf3_printf_extension_q_arginfo); + + s_fmt = open_memstream(&fmt_expr, &fmt_expr_size); + s_arg = open_memstream(&arg_expr, &arg_expr_size); + lf3_parse_format(0, fmt, s_fmt, s_arg); + fclose(s_fmt); + fclose(s_arg); + + LF3_DEBUG_S(fmt_expr); + LF3_DEBUG_S(arg_expr); + + printf("(format #t %s%s)\n", fmt_expr, arg_expr); + + return 0; +} +#endif diff --git a/lipe/src/lipe_find3/lf3_parse_format.h b/lipe/src/lipe_find3/lf3_parse_format.h new file mode 100644 index 0000000..1685815 --- /dev/null +++ b/lipe/src/lipe_find3/lf3_parse_format.h @@ -0,0 +1,7 @@ +#ifndef _LF3_PARSE_FORMAT_H_ +#define _LF3_PARSE_FORMAT_H_ +#include + +int lf3_parse_format(int index, const char *fmt, FILE *s_fmt, FILE *s_arg); + +#endif diff --git a/lipe/src/lipe_find3/lf3_printf.c b/lipe/src/lipe_find3/lf3_printf.c new file mode 100644 index 0000000..5b95e97 --- /dev/null +++ b/lipe/src/lipe_find3/lf3_printf.c @@ -0,0 +1,129 @@ +#include +#include +#include +#include /* INT_MAX */ +#include "lf3_printf.h" + +/* Write single char c properly escaped for inclusion in a double + * quoted scheme string. */ +int lf3_fprintc_scm_string(FILE *stream, int c) +{ + switch (c) { + case '\a': + return fprintf(stream, "\\a"); + case '\b': + return fprintf(stream, "\\b"); + case '\t': + return fprintf(stream, "\\t"); + case '\n': + return fprintf(stream, "\\n"); + case '\v': + return fprintf(stream, "\\v"); + case '\f': + return fprintf(stream, "\\f"); + case '\r': + return fprintf(stream, "\\r"); + case '"': + return fprintf(stream, "\\\""); + case '\\': + return fprintf(stream, "\\\\"); + default: + if (isprint(c) && ' ' <= c && c < 0177) /* 0177 == DEL */ + return fprintf(stream, "%c", c); + else + return fprintf(stream, "\\x%02x", c); + } +} + +/* printf extension (%K) to print a list of forms. */ +int lf3_printf_extension_K(FILE *stream, + const struct printf_info *info, + const void *const *args) +{ + struct lipe_list_head *lis; + struct lf3_pair *lfp; + int i = 0, n = 0; + + lis = *((struct lipe_list_head **)(args[0])); + lipe_list_for_each_entry(lfp, lis, lfp_link) { + n += fprintf(stream, "%s%s", i == 0 ? "" : " ", lfp->lfp_car); + i++; + } + + return n; +} + +int lf3_printf_extension_K_arginfo(const struct printf_info *info, size_t n, + int *argtypes, int *sizes) +{ + if (n > 0) { + argtypes[0] = PA_POINTER; + sizes[0] = sizeof(void *); + } + + return 1; +} + + +/* printf extension (%P) to print a list of pairs */ +int lf3_printf_extension_P(FILE *stream, + const struct printf_info *info, + const void *const *args) +{ + struct lipe_list_head *lis; + struct lf3_pair *lfp; + int i = 0, n = 0; + + lis = *((struct lipe_list_head **)(args[0])); + lipe_list_for_each_entry(lfp, lis, lfp_link) { + n += fprintf(stream, "%s(%s %s)", i == 0 ? "" : " ", + lfp->lfp_car, lfp->lfp_cdr); + i++; + } + + return n; +} + +int lf3_printf_extension_P_arginfo(const struct printf_info *info, size_t n, + int *argtypes, int *sizes) +{ + if (n > 0) { + argtypes[0] = PA_POINTER; + sizes[0] = sizeof(void *); + } + + return 1; +} + +/* printf extension (%Q) to print a double quoted slash-escaped string */ +int lf3_printf_extension_Q(FILE *stream, + const struct printf_info *info, + const void *const *args) +{ + const char *s; + int prec = info->prec < 0 ? INT_MAX : info->prec; + int n = 0; + int i; + + s = *((const char **)(args[0])); + + n += fprintf(stream, "\""); + + for (i = 0; i < prec && s[i] != '\0'; i++) + n += lf3_fprintc_scm_string(stream, s[i]); + + n += fprintf(stream, "\""); + + return n; +} + +int lf3_printf_extension_Q_arginfo(const struct printf_info *info, size_t n, + int *argtypes, int *sizes) +{ + if (n > 0) { + argtypes[0] = PA_POINTER; + sizes[0] = sizeof(const char *); + } + + return 1; +} diff --git a/lipe/src/lipe_find3/lf3_printf.h b/lipe/src/lipe_find3/lf3_printf.h new file mode 100644 index 0000000..fda15bd --- /dev/null +++ b/lipe/src/lipe_find3/lf3_printf.h @@ -0,0 +1,71 @@ +#ifndef _LF3_PRINTF_H_ +#define _LF3_PRINTF_H_ +#include "lf3_debug.h" +#include +#include +#include +#include "list.h" + +struct lf3_pair { + struct lipe_list_head lfp_link; + const char *lfp_car; + const char *lfp_cdr; +}; + +/* We don't want to use -Wno-format, but the -Wformat checker does not + * recognize our fundamental human right to define printf + * extensions. So use lf3_fprintf() which has no + * __attribute__((__format__((__printf__((__tueday__, __thursday__)))))) annotiation + * to hide out from gcc. */ +static inline int lf3_fprintf(FILE *file, const char *fmt, ...) +{ + va_list ap; + int size; + + va_start(ap, fmt); + size = vfprintf(file, fmt, ap); + va_end(ap); + + return size; +} + +static inline char *xsprintf(const char *fmt, ...) +{ + char *str; + va_list args; + int rc; + + va_start(args, fmt); + rc = vasprintf(&str, fmt, args); + va_end(args); + + if (rc < 0) + LF3_FATAL("out of memory\n"); + + return str; +} + +int lf3_fprintc_scm_string(FILE *stream, int c); + +int lf3_printf_extension_K(FILE *stream, + const struct printf_info *info, + const void *const *args); + +int lf3_printf_extension_K_arginfo(const struct printf_info *info, size_t n, + int *argtypes, int *sizes); + +int lf3_printf_extension_P(FILE *stream, + const struct printf_info *info, + const void *const *args); + +int lf3_printf_extension_P_arginfo(const struct printf_info *info, size_t n, + int *argtypes, int *sizes); + +int lf3_printf_extension_Q(FILE *stream, + const struct printf_info *info, + const void *const *args); + +int lf3_printf_extension_Q_arginfo(const struct printf_info *info, size_t n, + int *argtypes, int *sizes); + +#endif diff --git a/lipe/src/lipe_find3/lipe/find.scm b/lipe/src/lipe_find3/lipe/find.scm new file mode 100644 index 0000000..9fb29f7 --- /dev/null +++ b/lipe/src/lipe_find3/lipe/find.scm @@ -0,0 +1,192 @@ +(define-module (lipe find) + #:use-module (lipe) + #:use-module (ice-9 format) + #:use-module (ice-9 iconv) ; bytevector->string + #:use-module (ice-9 popen) ;; open-pipe* + #:use-module (ice-9 threads) ;; make-mutex, with-mutex + #:use-module (rnrs base) ;; assert + #:use-module (rnrs bytevectors) + #:export ( + absolute-path + relative-path + call-with-absolute-path + call-with-relative-path + call-with-name + name + names + fnmatch-ci? + round-up + round-up-power-of-2 + make-exec-plus-pipe + close-exec-plus-pipe + make-printer + make-deleter + close-deleter + type + type->char + char->type + user + group + xattr-ref-string + )) + +(define-inlinable (any1 proc lis) + ;; Single list version of (any ...) from (srfi srfi-1) + (let loop ((lis lis)) + (if (pair? lis) + (or (proc (car lis)) + (loop (cdr lis))) + #f))) + +(define-inlinable (any-string proc lis) + (or (any1 proc lis) "")) + +(define (absolute-path) + ;; For use in -printf. Returns "" if file has no paths. + (any-string identity (absolute-paths))) + +(define (call-with-absolute-path proc) + (any1 proc (absolute-paths))) + +(define (relative-path) + ;; For use in -printf. Returns "" if file has no paths. + (any-string identity (relative-paths))) + +(define (call-with-relative-path proc) + (any1 proc (relative-paths))) + +(define (name) + ;; For use in -printf. Returns "" if file has no name. + (any-string cdr (links))) + +(define (names) + (map cdr (links))) + +(define (call-with-name proc) + (any1 proc (names))) + +(define-inlinable (fnmatch-ci? pattern string) + (fnmatch? pattern string FNM_CASEFOLD)) + +(define-inlinable (round-up-power-of-2 x m) + (1+ (logior (- x 1) (- m 1)))) + +(define-inlinable (round-up x m) + (* (quotient (+ x m -1) m) m)) + +(define (make-exec-plus-pipe . args) + (apply open-pipe* OPEN_WRITE "xargs" "--no-run-if-empty" "--null" "--" args)) + +(define (close-exec-plus-pipe pipe) + (close-pipe pipe)) + +(define (make-printer port mutex delim) + (assert (port? port)) + (assert (mutex? mutex)) + (assert (or (char? delim) (eq? delim #f))) + (if (char? delim) + (lambda (str) + (with-mutex mutex + (display str port) + (display delim port))) + (lambda (str) + (with-mutex mutex + (display str port))))) + +(define (make-deleter-pipe) + (let ((client-mount-path (lipe-scan-client-mount-path))) + (assert (string? client-mount-path)) ;; FIXME stat + (assert (not (string=? client-mount-path ""))) ;; FIXME stat + (open-pipe* OPEN_WRITE + "xargs" + "--max-args=1024" + "--no-run-if-empty" + "--null" + "--" + "lfs" + "rmfid" + client-mount-path))) + +(define (make-deleter) + (let ((mutex (make-mutex)) + (pipe #f)) + (lambda (fid) + ;; (assert (or (fid? fid) (eq? fid #f))) + (with-mutex mutex + (cond (fid + ;; Defer opening pipe until we need it. We don't know + ;; the client-mount-path here and we are too lazy to + ;; find it. + (if (not pipe) + (set! pipe (make-deleter-pipe))) + (display fid pipe) + (display #\x00 pipe)) + (else + (if pipe + (close-pipe pipe)) + (set! pipe #f))))))) + +(define (close-deleter deleter) + (deleter #f)) + +(define S_IFMT #o0170000) +(define S_IFDIR #o0040000) +(define S_IFCHR #o0020000) +(define S_IFBLK #o0060000) +(define S_IFREG #o0100000) +(define S_IFIFO #o0010000) +(define S_IFLNK #o0120000) +(define S_IFSOCK #o0140000) + +(define (type) + (logand (mode) S_IFMT)) + +(define (perm) + (logand (mode) #o07777)) + +(define (char->type c) + (cond + ((eq? c #\d) S_IFDIR) + ((eq? c #\c) S_IFCHR) + ((eq? c #\b) S_IFBLK) + ((eq? c #\f) S_IFREG) + ((eq? c #\p) S_IFIFO) + ((eq? c #\l) S_IFLNK) + ((eq? c #\s) S_IFSOCK))) + +(define (type->char t) + (cond + ((= t S_IFDIR) #\d) + ((= t S_IFCHR) #\c) + ((= t S_IFBLK) #\b) + ((= t S_IFREG) #\f) + ((= t S_IFIFO) #\p) + ((= t S_IFLNK) #\l) + ((= t S_IFSOCK) #\s))) + +(define %lipe-getpw-mutex (make-mutex)) + +(define (%lipe-uid->user id) + (catch 'misc-error + (lambda () + (passwd:name (with-mutex %lipe-getpw-mutex (getpw id)))) + (lambda (key . args) + (number->string id)))) + +(define %lipe-getgr-mutex (make-mutex)) + +(define (%lipe-gid->group id) + (catch 'misc-error + (lambda () + (group:name (with-mutex %lipe-getgr-mutex (getgr id)))) + (lambda (key . args) + (number->string id)))) + +(define (user) + (%lipe-uid->user (uid))) + +(define (group) + (%lipe-gid->group (gid))) + +(define (xattr-ref-string name) + (utf8->string (or (xattr-ref name) #vu8()))) diff --git a/lipe/src/lipe_find3/lipe_scan3_mock.scm b/lipe/src/lipe_find3/lipe_scan3_mock.scm new file mode 100755 index 0000000..fdab34b --- /dev/null +++ b/lipe/src/lipe_find3/lipe_scan3_mock.scm @@ -0,0 +1,227 @@ +#!/usr/bin/guile -s +!# +;; XXX XXX XXX This is out of sync with lipe_find3 and lipe_scan3. +;; +;; This is a mock testing utility for find-to-scheme expression +;; conversion. +;; +;; HOWTO +;; +;; Use env (/usr/bin/env) to run this script with the attribute values +;; you want in the environment. To avoid possible conflicts naming +;; conflicts attributes are prefixed with a '%' +;; (MOCK-SYMBOL-ENV-PREFIX) character. For example +;; +;; $ env %fid='"0x200000404:0x1:0x0"' %ino=7 ./lipe_find3 --scan-command=./lipe_scan3_mock.scm -- /dev/mapper/mds1_flakey -inum 7 -print-fid +;; +;; In the mock environment, we use strings to represent FIDs so the +;; value should be in two layers of quotes (single then double). For example +;; +;; $ env %fid='"0x200000404:0x1:0x0"' ... +;; +;; If this script is used as intended then it should not fail (exit +;; with non zero status). It should either print or not print the file +;; according to what lipe_find* would do for a file with specified +;; attributes. +;; +;; EXAMPLES +;; +;; $ env %uid=500 %paths='("f0" "d0/f1")' ... -uid 500 -print-path +;; f0 +;; $ env %fid='"0x200000404:0x1:0x0"' %uid=500 ... -uid 500 -print-fid +;; 0x200000404:0x1:0x0 +;; $ env %fid='"0x200000404:0x1:0x0"' %uid=500 %gid=501 ... -uid 500 -and -gid 501 -print-fid +;; 0x200000404:0x1:0x0 +;; $ env %fid='"0x200000404:0x1:0x0"' %uid=500 %gid=501 ... -uid 500 -and -gid 502 -print-fid +;; $ +;; $ env %fid='"0x200000404:0x1:0x0"' %names='("f0" "f1")' %uid=500 %gid=501 ... -name f0 -a -name f1 +;; 0x200000404:0x1:0x0 +;; +;; $ env %gid=60 %paths='("f0")' ... -group games -exec echo {} \; +;; EXEC: "echo" "f0" +;; +;; NOTES +;; +;; Add %DEBUG=1 in the environment to enable debugging in this script. + +;; (use-modules (system base compile)) +(use-modules (ice-9 format)) +(use-modules (ice-9 popen)) ;; open-pipe* +(use-modules (srfi srfi-1)) ;; any + +(define (mock-getenv str def) + (or (getenv str) def)) + +(define MOCK-SYMBOL-ENV-PREFIX "%") +(define MOCK-SYMBOLS '(fid dev ino mode nlink uid gid rdev size atime mtime ctime blocks links names paths projid)) + +(define *mock-client-mount-path* (mock-getenv "%MOUNT" "/lustre/fs0042/client")) +(define *mock-debug-enabled* (mock-getenv "%DEBUG" #f)) + +(define (mock-assert-value symbol value) + (or value (error symbol "no value"))) + +(define (mock-assert-exec-allowed args) + ;; Avoid exec-ing arbitrary commands. + (or (list? args) + (error args "not an argument list")) + (or (string=? (car args) "echo") + (error (car args) "exec not allowed"))) + +(define (mock-attr-name symbol) + (string-append MOCK-SYMBOL-ENV-PREFIX (symbol->string symbol))) + +;; TODO Allow copying attrs from a reference file. +;; regex +;; quit + +(define-syntax mock-debug1 + (syntax-rules () + ((_ x) (if *mock-debug-enabled* + (format (current-error-port) "DEBUG: ~a => ~s\n" (quote x) x))))) + +(define (mock-attr-ref symbol) + ;; Return a thunk that converts symbol to prefixed environment + ;; variable name, gets environment variable value, passes it to read + ;; to get a scheme value (datum) and returns that value. (Note datum + ;; is not evaluated.) + ;; + ;; Examples: + ;; If '%uid=42' is set in the environment then (uid) will return 42. + ;; + ;; If %paths='("foo.txt" "link-to-foo.txt" ...)' is set ... then + ;; (paths) will return the list ("foo.txt" "link-to-foo.txt" ...). + (lambda () + (let* ((value-as-string-or-false (getenv (mock-attr-name symbol))) + (value-as-string (mock-assert-value symbol value-as-string-or-false)) + (value (call-with-input-string value-as-string read))) + value))) + +;; These three defines are to prevent the warning: +;; 'lipe_scan_guile_mock.scm:44:14: warning: possibly unbound variable `fid'' +;; when this script is compiled (before it gets executed). +(define (fid) (mock-attr-ref 'fid)) +(define (uid) (mock-attr-ref 'uid)) +(define (gid) (mock-attr-ref 'gid)) +(define (mode) (mock-attr-ref 'mode)) +(define (names) (mock-attr-ref 'names)) +(define (paths) (mock-attr-ref 'paths)) + +(for-each (lambda (symbol) + (eval `(define ,symbol (mock-attr-ref ',symbol)) + (interaction-environment))) + MOCK-SYMBOLS) + +(define (lipe-scan-client-mount-path) + *mock-client-mount-path*) + +(define (make-absolute-path path) + (string-append (lipe-scan-client-mount-path) "/" path)) + +(define (call-with-absolute-path proc) + (any (lambda (path) + (proc (make-absolute-path path))) + (paths))) + +(define (call-with-relative-path proc) + (any (lambda (path) + (proc path)) + (paths))) + +(define (absolute-path) + (call-with-absolute-path identity)) + +(define (relative-path) + (call-with-relative-path identity)) + +(define (name) + ;; Returns #f if file has no name. + (any identity (names))) + +(define (user) + ;; Return uid as string if uid does not name a user. + (catch #t + (lambda () + (passwd:name (getpwuid (uid)))) + (lambda args + (number->string (uid))))) + +(define (group) + ;; Return gid as string if gid does not name a group. + (catch #t + (lambda () + (group:name (getgrgid (gid)))) + (lambda args + (number->string (gid))))) + +;; Temporary shim definition. +(define (fnmatch? pattern str) + (string=? pattern str)) + +;; Temporary shim definition. +(define (fnmatch-ci? pattern str) + (string-ci=? pattern str)) + +;; Shims +(define-inlinable (round-up-power-of-2 x m) + (1+ (logior (- x 1) (- m 1)))) + +(define-inlinable (round-up x m) + (* (/ (+ x m -1) m) m)) + +(define S_IFMT #o0170000) +(define S_IFDIR #o0040000) +(define S_IFCHR #o0020000) +(define S_IFBLK #o0060000) +(define S_IFREG #o0100000) +(define S_IFIFO #o0010000) +(define S_IFLNK #o0120000) +(define S_IFSOCK #o0140000) + +(define (type->char t) + (cond + ((= t S_IFDIR) #\d) + ((= t S_IFCHR) #\c) + ((= t S_IFBLK) #\b) + ((= t S_IFREG) #\f) + ((= t S_IFIFO) #\p) + ((= t S_IFLNK) #\l) + ((= t S_IFSOCK) #\s))) + +(define-inlinable (type) + (logand (mode) S_IFMT)) + +(define (print-fid) + (format (current-output-port) "~a\n" (fid))) + +(define (make-exec-plus-pipe . args) + (mock-assert-exec-allowed args) + (apply open-pipe* OPEN_WRITE "xargs" "--no-run-if-empty" "--null" "--" args)) + +(define (exec-plus-pipe-write pipe) + (call-with-absolute-path + (lambda (path) + (format pipe "~a~a" path #\x00)))) + +(define (exec . args) + (mock-assert-exec-allowed args) + (apply system* args)) + +(define (lipe-getopt-client-mount-path) + *mock-client-mount-path*) + +(define (lipe-getopt-required-attrs) + 0) + +(define (lipe-getopt-thread-count) + 1) + +(define (lipe-scan device client-mount-path policy required-attrs thread-count) + (policy)) + +(let ((port (current-input-port))) + (let loop ((expr (read port))) + (if (not (eof-object? expr)) + (begin (mock-debug1 expr) + (eval expr (interaction-environment)) + (loop (read port)))))) diff --git a/lipe/src/lipe_find3/xmalloc.h b/lipe/src/lipe_find3/xmalloc.h new file mode 100644 index 0000000..7358d88 --- /dev/null +++ b/lipe/src/lipe_find3/xmalloc.h @@ -0,0 +1,19 @@ +#ifndef _XMALLOC_H_ +#define _XMALLOC_H_ +#include +#include + +#define xcalloc calloc /* FIXME */ +#define xstrdup strdup /* FIXME */ + +static inline char *xstrndup(const char *str, size_t n) +{ + char *s = xstrdup(str); + + if (n < strlen(s)) + s[n] = '\0'; + + return s; +} + +#endif diff --git a/lustre/tests/Makefile.am b/lustre/tests/Makefile.am index 1e6dd4d..251f54b 100644 --- a/lustre/tests/Makefile.am +++ b/lustre/tests/Makefile.am @@ -36,6 +36,7 @@ noinst_SCRIPTS += parallel-scale-nfsv3.sh parallel-scale-nfsv4.sh noinst_SCRIPTS += setup-cifs.sh parallel-scale-cifs.sh noinst_SCRIPTS += posix.sh sanity-scrub.sh scrub-performance.sh ha.sh pjdfstest.sh noinst_SCRIPTS += sanity-lfsck.sh lfsck-performance.sh sanity-lipe.sh +noinst_SCRIPTS += sanity-lipe-find3.sh noinst_SCRIPTS += sanity-lipe-scan3.sh noinst_SCRIPTS += resolveip noinst_SCRIPTS += sanity-hsm.sh sanity-lsnapshot.sh sanity-pfl.sh sanity-flr.sh diff --git a/lustre/tests/sanity-lipe-find3.sh b/lustre/tests/sanity-lipe-find3.sh new file mode 100644 index 0000000..f1206b7 --- /dev/null +++ b/lustre/tests/sanity-lipe-find3.sh @@ -0,0 +1,928 @@ +#!/bin/bash + +ONLY=${ONLY:-"$*"} + +LUSTRE=${LUSTRE:-$(dirname $0)/..} +. $LUSTRE/tests/test-framework.sh +init_test_env $@ +. ${CONFIG:=$LUSTRE/tests/cfg/$NAME.sh} +init_logging +FAIL_ON_ERROR=false + +declare -r ROOT_FID="[0x200000007:0x1:0x0]" + +# bug number for skipped test: +ALWAYS_EXCEPT="$SANITY_LIPE_FIND3_EXCEPT" +# UPDATE THE COMMENT ABOVE WITH BUG NUMBERS WHEN CHANGING ALWAYS_EXCEPT! + +(( OSTCOUNT >= 2 )) || skip_env "need at least 2 OSTs" + +[[ $(facet_fstype mds1) = ldiskfs ]] || skip_env "need ldiskfs on MDS" + +! remote_mds_nodsh || skip_env "remote MDS with nodsh" +! remote_ost_nodsh || skip_env "remote OSS with nodsh" + +# check if lipe_find3 is installed on MDS(s) +for t in lipe_find3; do + do_nodes $(comma_list $(all_mdts_nodes)) "which $t" || + skip_env "$t is not installed on MDS" +done + +which jq || skip_env "jq is not installed" + +build_test_filter +check_and_setup_lustre + +# Try to support running from the build directory. +if [[ "$mds_HOST" == "$HOSTNAME" ]]; then + # Fixup GUILE_LOAD_PATH to support running from the build directory. + LIPE_SCAN3=$(which lipe_scan3) + LIPE_SCAN3_DIR=$(dirname "$LIPE_SCAN3") + if [[ "$LIPE_SCAN3_DIR" != /usr/bin ]]; then + export GUILE_LOAD_PATH="$GUILE_LOAD_PATH:$LIPE_SCAN3_DIR" + fi + + LIPE_FIND3=$(which lipe_find3) + LIPE_FIND3_DIR=$(dirname "$LIPE_FIND3") + if [[ "$LIPE_FIND3_DIR" != /usr/bin ]]; then + export GUILE_LOAD_PATH="$GUILE_LOAD_PATH:$LIPE_FIND3_DIR" + fi +fi + +mount_client_on_facet() { + local facet="$1" + local nodes=$(facets_nodes "$facet") + local node + + for node in $nodes; do + if local_node $node; then + continue + fi + + zconf_mount_clients $node $MOUNT || + error "cannot mount client on node '$node' for facet '$facet'" + + stack_trap "zconf_umount_clients $node $MOUNT" + done +} + +function init_lipe_find3_env() { + # Check "$MOUNT" is a Lustre client mount point. + local fid=$($LFS path2fid "$MOUNT") + [[ "$fid" == "$ROOT_FID" ]] || error "'$MOUNT' is not a lustre client mount" + + find "$MOUNT" -mindepth 1 -delete || error "cannot clean '$MOUNT'" + find "$MOUNT" -mindepth 1 | grep . && error "find -delete did not delete all files from '$MOUNT'" + + mount_client_on_facet mds1 + + for file in "$@"; do + touch $file || error "cannot create $file" + index=$($LFS getstripe --mdt-index $file) + ((index == 0)) || error "file '$file' is not on MDT0000" + done + + sync + sync +} + +function lipe_find3_on() { + local facet="$1" + shift 1 + + sync + do_facet_vp "$facet" sync + do_facet_vp "$facet" lipe_find3 "$@" +} + +function lipe_find3_facet() { + local facet="$1" + local device="$(facet_device "$facet")" + shift 1 + + lipe_find3_on "$facet" "$device" "$@" +} + +function expect_stdout() { + "$@" | grep --quiet . || error "command '$*' should write to stdout" +} + +function expect_no_stdout() { + "$@" | grep . && error "command '$*' should not write to stdout" + true +} + +function expect_stderr() { + "$@" 2>&1 > /dev/null | grep --quiet . || error "command '$*' should write to stderr" +} + +function expect_no_stderr() { + "$@" 2>&1 > /dev/null | grep . && error "command '$*' should not write to stderr" + true +} + +function expect_success() { + "$@" > /dev/null || error "command '$*' failed" +} + +function expect_failure() { + "$@" 2> /dev/null && error "command '$*' should fail" + true +} + +function expect_print() { + expect_success "$@" + expect_stdout "$@" + expect_no_stderr "$@" +} + +function expect_empty() { + expect_success "$@" + expect_no_stdout "$@" + expect_no_stderr "$@" +} + +function expect_error() { + expect_failure "$@" + expect_no_stdout "$@" + expect_stderr "$@" +} + +function expect1() { + # Will only DTRT with single line output due to how $(...) and + # == work. + local str="$1" + local out + shift + out=$("$@") + [[ "$str" == "$out" ]] || error "$*: expected '$str', got '$out'" +} + +test_0() { + expect_success true + expect_failure false + expect_no_stdout true + expect_no_stderr true + expect_stdout echo something + expect_stderr grep --barf + expect_print echo output + expect_empty true + expect_error grep --barf +} +run_test 0 "expect functions meet our expectations" + +test_10() { + expect_print lipe_find3_on mds1 -h + expect_print lipe_find3_on mds1 --help + + expect_print lipe_find3_on mds1 -v + expect_print lipe_find3_on mds1 --version + + expect_error lipe_find3_on mds1 + expect_error lipe_find3_on mds1 --barf +} +run_test 10 "lipe_find3 option handling" + +test_11() { + expect_error lipe_find3_on mds1 /dev/null + expect_error lipe_find3_on mds1 /dev/zero + expect_error lipe_find3_on mds1 /dev/zapper/mds1_flakey + expect_error lipe_find3_on mds1 /dev/ + expect_error lipe_find3_on mds1 '' + expect_error lipe_find3_on mds1 +} +run_test 11 "lipe_find3 bad device handling" + +test_12() { + expect_error lipe_find3_facet mds1 -quux ZZZ + expect_error lipe_find3_facet mds1 -quux + expect_error lipe_find3_facet mds1 -size + expect_error lipe_find3_facet mds1 -quux -quit + expect_error lipe_find3_facet mds1 -not -quux -quit + expect_error lipe_find3_facet mds1 -quit -quux + + expect_error lipe_find3_facet mds1 -name zalf zalf + expect_error lipe_find3_facet mds1 true + expect_error lipe_find3_facet mds1 name zalf + expect_error lipe_find3_facet mds1 -true name zalf + expect_error lipe_find3_facet mds1 name zalf -true + expect_error lipe_find3_facet mds1 true + expect_error lipe_find3_facet mds1 quux +} +run_test 12 "lipe_find3 bad tests" + +test_90() { + init_lipe_find3_env + mount_client_on_facet ost1 + + # Create some files to be deleted. + touch $MOUNT/f0 + mkdir $MOUNT/d0 + mkfifo $MOUNT/p0 + # lfs mkdir -c ... $MOUNT/d1 .... + ln -s $MOUNT/f0 $MOUNT/l0 + mknod $MOUNT/c0 c 1 3 + # ... + + # XXX Run once to get rid of auto-compilation message. + lipe_find3_facet mds1 + lipe_find3_facet ost1 + + # Now delete those files. + init_lipe_find3_env + wait_delete_completed + expect_empty lipe_find3_facet mds1 + expect_empty lipe_find3_facet ost1 +} +run_test 90 "lipe_find3 on an empty FS" + +test_100() { + local facet=mds1 + local device="$(facet_device $facet)" + local file=$MOUNT/$tfile + local fid + + init_lipe_find3_env + $LFS setstripe -i 0 "$file" + echo XXX > "$file" + fid=$($LFS path2fid "$file") + + expect1 "$fid" lipe_find3_facet mds1 + expect1 "$fid" lipe_find3_facet ost1 +} +run_test 100 "lipe_find3 finds a file by MDT and OST" + +declare -r S_IFREG=0100000 +declare -a MODES=(0 1 0111 0222 0444 0555 0666 0777) + +test_101() { + # TODO + true +} +run_test 101 "lipe_find3 perm does the right thing" + +declare -a IDS=( + 0 + 42 + 500 + 65534 + 65535 + 65536 + 2147483646 # (INT_MAX - 1) + 2147483647 # (INT_MAX) + 2147483648 # (INT_MAX + 1) + 2177452800 + 4294967293 # (UINT_MAX - 2) +) + +test_102() { + local file=$MOUNT/$tfile + local fid + local id + local user + + init_lipe_find3_env + $LFS setstripe -i 0 -c 1 "$file" + fid=$($LFS path2fid "$file") + echo XXX > "$file" + sync + + id=$(stat --format=%u $file) # uid + expect1 "$fid" lipe_find3_facet mds1 -user "$id" + expect1 "$fid" lipe_find3_facet ost1 -user "$id" + + for id in "${IDS[@]}"; do + chown $id $file || error "cannot set UID to '$id'" + wait_delete_completed + + expect1 "$fid" lipe_find3_facet mds1 -user "$id" + expect1 "$fid" lipe_find3_facet ost1 -user "$id" + + expect1 "$fid" lipe_find3_facet mds1 -uid "$id" + expect1 "$fid" lipe_find3_facet ost1 -uid "$id" + done + + user=sanityusr + chown $user $file || error "cannot set user to '$user'" + wait_delete_completed + + expect1 "$fid" lipe_find3_facet mds1 -user "$user" + expect1 "$fid" lipe_find3_facet ost1 -user "$user" + + id=$(id -u "$user") + expect1 "$fid" lipe_find3_facet mds1 -uid "$id" + expect1 "$fid" lipe_find3_facet ost1 -uid "$id" + + expect_error lipe_find3_facet mds1 -user + expect_error lipe_find3_facet mds1 -user '' + expect_error lipe_find3_facet mds1 -user -QQQ + expect_error lipe_find3_facet mds1 -user QQQ + expect_error lipe_find3_facet mds1 -user 42QQQ +} +run_test 102 "lipe_find3 -user does the right thing" + +test_103() { + local file=$MOUNT/$tfile + local fid + local id + local group + + init_lipe_find3_env + $LFS setstripe -i 0 -c 1 "$file" + fid=$($LFS path2fid "$file") + echo XXX > "$file" + sync + + id=$(stat --format=%g $file) # gid + expect1 "$fid" lipe_find3_facet mds1 -group "$id" + expect1 "$fid" lipe_find3_facet ost1 -group "$id" + + for id in "${IDS[@]}"; do + chown :$id $file || error "cannot set GID to '$id'" + wait_delete_completed + + expect1 "$fid" lipe_find3_facet mds1 -group "$id" + expect1 "$fid" lipe_find3_facet ost1 -group "$id" + + expect1 "$fid" lipe_find3_facet mds1 -gid "$id" + expect1 "$fid" lipe_find3_facet ost1 -gid "$id" + done + + group=sanityusr + chown :$group $file || error "cannot set group to '$group'" + wait_delete_completed + + expect1 "$fid" lipe_find3_facet mds1 -group "$group" + expect1 "$fid" lipe_find3_facet ost1 -group "$group" + + id=$(id -u "$group") + expect1 "$fid" lipe_find3_facet mds1 -gid "$id" + expect1 "$fid" lipe_find3_facet ost1 -gid "$id" + + expect_error lipe_find3_facet mds1 -group + expect_error lipe_find3_facet mds1 -group '' + expect_error lipe_find3_facet mds1 -group -QQQ + expect_error lipe_find3_facet mds1 -group QQQ + expect_error lipe_find3_facet mds1 -group 42QQQ +} +run_test 103 "lipe_find3 -group does the right thing" + +declare -a SIZES=( + 0 + 42 + 2147483646 # (INT_MAX - 1) + 2147483647 # (INT_MAX) + 2147483648 # (INT_MAX + 1) + 2177452800 + 4294967294 # (UINT_MAX - 1) + 4294967295 # (UINT_MAX) + 4294967296 # (UINT_MAX + 1) + 4815162342 + 8589934591 # 0x1ffffffff + 48151623420 + 481516234200 +) + +test_104() { + local facet=mds1 + local device="$(facet_device $facet)" + local file=$MOUNT/$tfile + local fid + local size + + init_lipe_find3_env "$file" + fid=$($LFS path2fid "$file") + + # XXX find uses 512 byte default unity for size + + expect1 "$fid" lipe_find3_facet mds1 -size 0 + expect1 "$fid" lipe_find3_facet mds1 -size 0b + expect1 "$fid" lipe_find3_facet mds1 -size 0c + expect1 "$fid" lipe_find3_facet mds1 -size 0k + expect1 "$fid" lipe_find3_facet mds1 -size 0M + expect1 "$fid" lipe_find3_facet mds1 -size 0G + + expect_empty lipe_find3_facet mds1 -size +0 + expect_empty lipe_find3_facet mds1 -size +0b + expect_empty lipe_find3_facet mds1 -size +0c + expect_empty lipe_find3_facet mds1 -size +0k + expect_empty lipe_find3_facet mds1 -size +0M + expect_empty lipe_find3_facet mds1 -size +0G + + expect_empty lipe_find3_facet mds1 -size -0 + expect_empty lipe_find3_facet mds1 -size -0b + expect_empty lipe_find3_facet mds1 -size -0c + expect_empty lipe_find3_facet mds1 -size -0k + expect_empty lipe_find3_facet mds1 -size -0M + expect_empty lipe_find3_facet mds1 -size -0G + + expect1 "$fid" lipe_find3_facet mds1 -size -1b + expect1 "$fid" lipe_find3_facet mds1 -size -1k + expect1 "$fid" lipe_find3_facet mds1 -size -1M + expect1 "$fid" lipe_find3_facet mds1 -size -1G + + truncate "$file" 512 + expect1 "$fid" lipe_find3_facet mds1 -size 1b + + # Match finds quirky +/- rounding blah blah. + truncate "$file" 1023 + expect_empty lipe_find3_facet mds1 -size -1k + expect1 "$fid" lipe_find3_facet mds1 -size 1k + expect_empty lipe_find3_facet mds1 -size +1k + + truncate "$file" 1024 + expect_empty lipe_find3_facet mds1 -size -1k + expect1 "$fid" lipe_find3_facet mds1 -size 1k + expect_empty lipe_find3_facet mds1 -size +1k + + truncate "$file" 1025 + expect_empty lipe_find3_facet mds1 -size 1k + expect1 "$fid" lipe_find3_facet mds1 -size +1k + expect1 "$fid" lipe_find3_facet mds1 -size 2k + expect_empty lipe_find3_facet mds1 -size -2k + expect_empty lipe_find3_facet mds1 -size +2k + + truncate "$file" 1048576 + expect1 "$fid" lipe_find3_facet mds1 -size 1M + + truncate "$file" 1073741824 + expect1 "$fid" lipe_find3_facet mds1 -size 1G + + for size in "${SIZES[@]}"; do + $TRUNCATE $file $size + expect1 "$fid" lipe_find3_facet mds1 -size ${size}c + done + + expect_error lipe_find3_facet mds1 -size + expect_error lipe_find3_facet mds1 -size QQQ + expect_error lipe_find3_facet mds1 -size '' + expect_error lipe_find3_facet mds1 -size 1Q + expect_error lipe_find3_facet mds1 -size -1Q + expect_error lipe_find3_facet mds1 -size +1Q + + # expect_attr "$device" blocks 0 +} +run_test 104 "lipe_find3 -size does the right thing" + +test_105() { + local facet=mds1 + local device="$(facet_device $facet)" + local file=$MOUNT/$tfile + local fid + + init_lipe_find3_env "$file" + fid=$($LFS path2fid "$file") + + expect_empty lipe_find3_facet mds1 -links 0 + expect1 "$fid" lipe_find3_facet mds1 -links +0 + expect1 "$fid" lipe_find3_facet mds1 -links 1 + expect1 "$fid" lipe_find3_facet mds1 -links -2 + + ln $file $file-2 + expect_empty lipe_find3_facet mds1 -links 1 + expect1 "$fid" lipe_find3_facet mds1 -links 2 + + ln $file $file-3 + expect_empty lipe_find3_facet mds1 -links 2 + expect1 "$fid" lipe_find3_facet mds1 -links 3 + + expect_error lipe_find3_facet mds1 -links + expect_error lipe_find3_facet mds1 -links '' + expect_error lipe_find3_facet mds1 -links QQQ + expect_error lipe_find3_facet mds1 -links 42QQQ + + # If we hold $file open and remove all three links then + # lipe_find3 will still return a link count of 1 because of + # the PENDING/$fid link. +} +run_test 105 "lipe_find3 -links does the right thing" + +test_106() { + local file=$MOUNT/$tfile + local fid + + init_lipe_find3_env "$file" + fid=$($LFS path2fid "$file") + + id=$($LFS project "$file" | awk '{ print $1 }') + expect1 "$fid" lipe_find3_facet mds1 -projid "$id" + + for id in "${IDS[@]}"; do + [[ $id =~ ^[0-9]+$ ]] || continue # exclude sanityusr + + $LFS project -p "$id" "$file" || error "cannot set projid to '$id'" + expect1 "$fid" lipe_find3_facet mds1 -projid "$id" + done + + expect_error lipe_find3_facet mds1 -projid + expect_error lipe_find3_facet mds1 -projid '' + expect_error lipe_find3_facet mds1 -projid QQQ +} +run_test 106 "lipe_find3 -projid does the right thing" + +test_107() { + local file=$MOUNT/$tfile + local fid + + init_lipe_find3_env "$file" + fid=$($LFS path2fid "$file") + + expect1 "$fid" lipe_find3_facet mds1 -type f + expect1 "$fid" lipe_find3_facet mds1 -type f,b + expect1 "$fid" lipe_find3_facet mds1 -type f,b,d + expect1 "$fid" lipe_find3_facet mds1 -type b,c,d,p,f,l,s + expect1 "$fid" lipe_find3_facet mds1 -not -type d + expect1 "$fid" lipe_find3_facet mds1 -not -type b,c,d,p,l,s + + expect_empty lipe_find3_facet mds1 -type d + expect_empty lipe_find3_facet mds1 -type b,c,d,p,l,s + expect_empty lipe_find3_facet mds1 -not -type b,c,d,p,f,l,s + + expect_error lipe_find3_facet mds1 -type + # expect_error lipe_find3_facet mds1 -type '' FIXME + expect_error lipe_find3_facet mds1 -type q + expect_error lipe_find3_facet mds1 -type b,c,d,p,l,s,q + +} +run_test 107 "lipe_find3 -type does the right thing" + +test_108() { + local file=$MOUNT/$tfile + local fid + + init_lipe_find3_env "$file" + fid=$($LFS path2fid "$file") + + # -name '' fails with "lipe_find3: FATAL: at argument 2: + # missing argument to '-name'". find is OK with -name ''. + expect1 "$fid" lipe_find3_facet mds1 -name "$tfile" + expect1 "$fid" lipe_find3_facet mds1 -name "$tfile*" + expect1 "$fid" lipe_find3_facet mds1 -name "*$tfile" + expect1 "$fid" lipe_find3_facet mds1 -name "$tfile*" + expect_empty lipe_find3_facet mds1 -name "dagobert.txt" + + mv "$file" "$MOUNT/zalf.x" + expect1 "$fid" lipe_find3_facet mds1 -name "zalf.x" + expect1 "$fid" lipe_find3_facet mds1 -name "*.x" + expect1 "$fid" lipe_find3_facet mds1 -name "zalf.?" + expect1 "$fid" lipe_find3_facet mds1 -name "[yz]alf.x" + expect1 "$fid" lipe_find3_facet mds1 -name "[yz]*.x" + expect_empty lipe_find3_facet mds1 -name "dagobert.txt" + + expect1 "$fid" lipe_find3_facet mds1 -iname "zalf.x" + expect1 "$fid" lipe_find3_facet mds1 -iname "*.x" + expect1 "$fid" lipe_find3_facet mds1 -iname "zalf.?" + expect1 "$fid" lipe_find3_facet mds1 -iname "[yz]alf.x" + expect1 "$fid" lipe_find3_facet mds1 -iname "[yz]*.x" + expect_empty lipe_find3_facet mds1 -iname "dagobert.txt" + + expect1 "$fid" lipe_find3_facet mds1 -iname "ZALF.X" + expect1 "$fid" lipe_find3_facet mds1 -iname "Zalf.x" + expect1 "$fid" lipe_find3_facet mds1 -iname "*.x" + expect1 "$fid" lipe_find3_facet mds1 -iname "ZALF.?" + expect1 "$fid" lipe_find3_facet mds1 -iname "[yz]ALF.x" + expect1 "$fid" lipe_find3_facet mds1 -iname "[YZ]*.X" + expect_empty lipe_find3_facet mds1 -iname "dagobert.txt" + expect_empty lipe_find3_facet mds1 -iname "DAGOBERT.TXT" + + ln "$MOUNT/zalf.x" "$MOUNT/schmerp.out" + expect1 "$fid" lipe_find3_facet mds1 -name "*.x" + expect1 "$fid" lipe_find3_facet mds1 -name "zalf.?" + expect1 "$fid" lipe_find3_facet mds1 -name "[yz]alf.x" + expect1 "$fid" lipe_find3_facet mds1 -name "[yz]*.x" + expect1 "$fid" lipe_find3_facet mds1 -name "schmerp.out" + expect1 "$fid" lipe_find3_facet mds1 -iname "SCHMERP.out" + expect1 "$fid" lipe_find3_facet mds1 -iname "SCHMERP.OUT" + + expect_error lipe_find3_facet mds1 -name +} +run_test 108 "lipe_find3 -name and -iname do the right thing" + +test_109() { + local file=$MOUNT/$tfile + local fid + + init_lipe_find3_env "$file" + fid=$($LFS path2fid "$file") + + # TODO What does -path match. + true +} +run_test 109 "lipe_find3 -path and -ipath do the right thing" + +test_130() { + local file=$MOUNT/$tfile + local xtime + local xmin + local now + local fid + + init_lipe_find3_env "$file" + fid=$($LFS path2fid "$file") + + # -atime n means last accessed n * 24 hours ago. When find + # figures out how many 24-hour periods ago the fil e was last + # accessed, any fractional part is ignored, so to match -atime + # +1, a file has to have been accessed at least two days ago. + + for x in a m c; do + xtime="-${x}time" + xmin="-${x}min" + + expect_empty lipe_find3_facet mds1 $xtime -0 + expect1 "$fid" lipe_find3_facet mds1 $xtime 0 + expect_empty lipe_find3_facet mds1 $xtime +0 + expect1 "$fid" lipe_find3_facet mds1 $xtime -1 + expect_empty lipe_find3_facet mds1 $xtime 1 + + expect_empty lipe_find3_facet mds1 $xmin -0 + expect1 "$fid" lipe_find3_facet mds1 $xmin -10 + expect_empty lipe_find3_facet mds1 $xmin 10 + expect_empty lipe_find3_facet mds1 $xmin +10 + + if [[ $x == c ]]; then + continue + fi + + now=$(date +%s) + touch -$x --date=@$((now - 25 * 3600)) $file + expect_empty lipe_find3_facet mds1 $xtime 0 + expect1 "$fid" lipe_find3_facet mds1 $xtime +0 + expect_empty lipe_find3_facet mds1 $xtime -1 + expect1 "$fid" lipe_find3_facet mds1 $xtime 1 + expect_empty lipe_find3_facet mds1 $xtime +1 + expect1 "$fid" lipe_find3_facet mds1 $xtime -2 + + expect_empty lipe_find3_facet mds1 $xtime -86400s + expect_empty lipe_find3_facet mds1 $xtime 86400s + expect1 "$fid" lipe_find3_facet mds1 $xtime +86400s + + expect_empty lipe_find3_facet mds1 $xtime -1440m + expect_empty lipe_find3_facet mds1 $xtime 1440m + expect1 "$fid" lipe_find3_facet mds1 $xtime +1440m + + expect_empty lipe_find3_facet mds1 $xmin -1440 + expect_empty lipe_find3_facet mds1 $xmin 1440 + expect1 "$fid" lipe_find3_facet mds1 $xmin +1440 + + expect_empty lipe_find3_facet mds1 $xtime -24h + expect_empty lipe_find3_facet mds1 $xtime 24h + expect1 "$fid" lipe_find3_facet mds1 $xtime +24h + + expect_empty lipe_find3_facet mds1 $xtime -25h + expect1 "$fid" lipe_find3_facet mds1 $xtime 25h + expect_empty lipe_find3_facet mds1 $xtime +25h + + expect_empty lipe_find3_facet mds1 $xtime -1d + expect1 "$fid" lipe_find3_facet mds1 $xtime 1d + expect_empty lipe_find3_facet mds1 $xtime +1d + + expect1 "$fid" lipe_find3_facet mds1 $xtime -93600s + expect1 "$fid" lipe_find3_facet mds1 $xtime -1560m + expect1 "$fid" lipe_find3_facet mds1 $xtime -26h + expect1 "$fid" lipe_find3_facet mds1 $xtime -2d + + now=$(date +%s) + touch -$x --date=@$((now - 30 * 86400 - 3600)) $file + expect_empty lipe_find3_facet mds1 $xtime 0 + expect_empty lipe_find3_facet mds1 $xtime -30 + expect1 "$fid" lipe_find3_facet mds1 $xtime 30 + expect_empty lipe_find3_facet mds1 $xtime +30 + done + + expect_error lipe_find3_facet mds1 -atime + expect_error lipe_find3_facet mds1 -atime '' + expect_error lipe_find3_facet mds1 -atime QQQ + expect_error lipe_find3_facet mds1 -atime 42Q + expect_error lipe_find3_facet mds1 -atime +42Q + expect_error lipe_find3_facet mds1 -atime + + expect_error lipe_find3_facet mds1 -atime - +} +run_test 130 "lipe_find3 {a,m,c}{time,min} do the right thing" + +# 200 printing +# print-file-fid +# print-self-fid +# print-json +# print-absolute-path +# print-relative-path + +# fprint +# fprint0 +# printf +# fprintf + +# 300 actions +# No implicit print when there is an explicit action + +# exec ... {} \; +# exec ... {} + +test_300() { + local file=$MOUNT/$tfile + local fid + + init_lipe_find3_env + + # Need quotes around ; to protect from do_facet. + # Does this work with ssh? + + expect_empty lipe_find3_facet mds1 -exec echo {} \; + expect_empty lipe_find3_facet mds1 -exec echo x{}x \; + expect_empty lipe_find3_facet mds1 -exec $LFS path2fid {} \; + expect_empty lipe_find3_facet mds1 -exec cat {} \; + + echo 4815162342 > "$file" + fid=$($LFS path2fid "$file") + + expect_empty lipe_find3_facet mds1 -exec true \; + expect_empty lipe_find3_facet mds1 -exec true {} \; + expect1 "$file" lipe_find3_facet mds1 -exec echo {} \; + expect1 "$file $file" lipe_find3_facet mds1 -exec echo {} {} \; + expect1 "$file $file" lipe_find3_facet mds1 -exec echo "{} {}" \; + expect1 "${file}x" lipe_find3_facet mds1 -exec echo "{}x" \; + expect1 "x${file}x" lipe_find3_facet mds1 -exec echo "x{}x" \; + expect1 "$fid" lipe_find3_facet mds1 -exec $LFS path2fid {} \; + expect1 4815162342 lipe_find3_facet mds1 -exec cat {} \; + + # $ find /mnt/lustre -exec zalp {} \; + # find: ‘zalp’: No such file or directory + # find: ‘zalp’: No such file or directory + # $ echo $? + # 0 + + expect_error lipe_find3_facet mds1 -exec cat + expect_error lipe_find3_facet mds1 -exec cat {} + expect_error lipe_find3_facet mds1 -exec cat { \; + expect_error lipe_find3_facet mds1 -exec cat xx{xxx \; +} +run_test 300 "lipe_find3 -exec does the right thing" + +test_301() { + local file=$MOUNT/$tfile + local fid + local count + + init_lipe_find3_env + + expect_empty lipe_find3_facet mds1 -exec echo {} + + expect_empty lipe_find3_facet mds1 -exec $LFS path2fid {} + + expect_empty lipe_find3_facet mds1 -exec cat {} + + + echo 4815162342 > "$file" + echo 4815162342 > "$file"-2 + + expect_empty lipe_find3_facet mds1 -exec true {} + + count=$(lipe_find3_facet mds1 -exec cat {} + | grep --count 4815162342) + ((count == 2)) || error "expected count 2, got $count" + + # {} must be present in -exec ... + + # {} must be the last argument in -exec ... + + # {} must occur by it self + # only one instance of {} is supported with -exec ... + + expect_error lipe_find3_facet mds1 -exec cat + + expect_error lipe_find3_facet mds1 -exec cat {} zzz + + expect_error lipe_find3_facet mds1 -exec cat {} {} + + expect_error lipe_find3_facet mds1 -exec cat x{}x + +} +run_test 301 "lipe_find3 -exec + does the right thing" + +test_350() { + local file=$MOUNT/$tfile + local dir=$MOUNT/$tdir + + init_lipe_find3_env + + expect_empty lipe_find3_facet mds1 -delete + + echo 4815162342 > "$file" + echo 4815162342 > "$file"-2 + + expect_empty lipe_find3_facet mds1 -delete + if [[ -f "$file" ]] || [[ -f "$file"-2 ]]; then + error "$file and $file-2 were not deleted" + fi + + lfs mkdir -i0 -c1 $dir + expect_empty lipe_find3_facet mds1 -delete + if [[ -d "$dir" ]]; then + error "$dir was not deleted" + fi + + # .... + # expect_error lipe_find3_facet mds1 -delete +} +run_test 350 "lipe_find3 -delete does the right thing" + +test_400() { + local file=$MOUNT/$tfile + local fid + + init_lipe_find3_env "$file" + fid=$($LFS path2fid "$file") + + expect1 "$fid" lipe_find3_facet mds1 -true + expect_empty lipe_find3_facet mds1 -false + + expect1 "$fid" lipe_find3_facet mds1 -not -false + expect_empty lipe_find3_facet mds1 -not -true + + expect1 "$fid" lipe_find3_facet mds1 \! -false + expect_empty lipe_find3_facet mds1 \! -true + + expect1 "$fid" lipe_find3_facet mds1 -not -not -true + expect_empty lipe_find3_facet mds1 -not -not -false + + # -and is implicit + expect1 "$fid" lipe_find3_facet mds1 -true -true + expect1 "$fid" lipe_find3_facet mds1 -true -and -true + expect1 "$fid" lipe_find3_facet mds1 -true -or -true + expect1 "$fid" lipe_find3_facet mds1 -true -or -false + expect1 "$fid" lipe_find3_facet mds1 -false -or -true + + expect1 "$fid" lipe_find3_facet mds1 -true -a -true + expect1 "$fid" lipe_find3_facet mds1 -true -o -true + expect1 "$fid" lipe_find3_facet mds1 -true -o -false + expect1 "$fid" lipe_find3_facet mds1 -false -o -true + + # -and is implicit + expect_empty lipe_find3_facet mds1 -true -false + expect_empty lipe_find3_facet mds1 -false -false + expect_empty lipe_find3_facet mds1 -false -true + + expect_empty lipe_find3_facet mds1 -true -and -false + expect_empty lipe_find3_facet mds1 -false -and -false + expect_empty lipe_find3_facet mds1 -false -and -true + expect_empty lipe_find3_facet mds1 -false -or -false + + expect_empty lipe_find3_facet mds1 -true -a -false + expect_empty lipe_find3_facet mds1 -false -a -false + expect_empty lipe_find3_facet mds1 -false -a -true + expect_empty lipe_find3_facet mds1 -false -o -false + + # -and has higher precedence than -or + # "X && Y || Z" means "(X && Y) || Z" + # + # The differences between "(X && Y) || Z" and "X && (Y || Z)" + # arises when the inputs are FTT or FFT. + # F && T || T => T + # F && F || T => T + + expect1 "$fid" lipe_find3_facet mds1 -false -and -true -or -true + expect1 "$fid" lipe_find3_facet mds1 -false -and -false -or -true + expect1 "$fid" lipe_find3_facet mds1 -true -or -false -and -true + expect1 "$fid" lipe_find3_facet mds1 -true -or -false -and -false + + expect_error lipe_find3_facet mds1 -and + expect_error lipe_find3_facet mds1 -true -and + expect_error lipe_find3_facet mds1 -and -true + expect_error lipe_find3_facet mds1 -true -and -and -true + + expect_error lipe_find3_facet mds1 -or + expect_error lipe_find3_facet mds1 -true -or + expect_error lipe_find3_facet mds1 -or -true + expect_error lipe_find3_facet mds1 -true -or -or -true + + expect_error lipe_find3_facet mds1 -true -and -or -true + expect_error lipe_find3_facet mds1 -true -or -and -true +} +run_test 400 "lipe_find3 -true, -false, -and, -or do the right thing" + +test_401() { + local file=$MOUNT/$tfile + local fid + + init_lipe_find3_env "$file" + fid=$($LFS path2fid "$file") + + expect1 "$fid" lipe_find3_facet mds1 \( -true \) + expect_empty lipe_find3_facet mds1 \( -false \) + + expect_empty lipe_find3_facet mds1 -false -and \( -true -or -true \) + expect_empty lipe_find3_facet mds1 -false -and \( -false -or -true \) + + expect1 "$fid" lipe_find3_facet mds1 \( -true -true \) + expect_empty lipe_find3_facet mds1 \( -false -true \) + + expect1 "$fid" lipe_find3_facet mds1 \! \( -false \) + expect_empty lipe_find3_facet mds1 \! \( -true \) + + expect1 "$fid" lipe_find3_facet mds1 \( \! -false \) + expect_empty lipe_find3_facet mds1 \( \! -true \) + + expect1 "$fid" lipe_find3_facet mds1 -false , -true + expect_empty lipe_find3_facet mds1 -true , -false + + expect_error lipe_find3_facet mds1 \( + expect_error lipe_find3_facet mds1 \( -true + expect_error lipe_find3_facet mds1 -true \) + + expect_error lipe_find3_facet mds1 -true , + expect_error lipe_find3_facet mds1 , -true + expect_error lipe_find3_facet mds1 , + expect_error lipe_find3_facet mds1 -true , , -true +} +run_test 401 "lipe_find3 parens and commas do the right thing" + +complete $SECONDS +check_and_cleanup_lustre +exit_status