-SUBDIRS = src src/lipe_scan3 pybuild pylipe .
+SUBDIRS = src src/lipe_find3 src/lipe_scan3 pybuild pylipe .
build_dir = `pwd`/build
rpmbuild_opt =
AC_CONFIG_FILES([Makefile
lipe.spec
src/Makefile
+ src/lipe_find3/Makefile
src/lipe_scan3/Makefile
pybuild/Makefile
pylipe/Makefile])
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}
%{_bindir}/lipe_delete
%{_bindir}/lipe_find
%{_bindir}/lipe_find2
+%{_bindir}/lipe_find3
%{_bindir}/lipe_launch
%{_bindir}/lipe_purge
%{_bindir}/lipe_scan
--- /dev/null
+*~
+*.gcda
+*.gcno
+*.gcov
+*.o
+*.tab.c
+*.tab.h
+lf3_convert_expr
+lf3_lexer.c
+lf3_printf
+lipe_find3
+bison-3.8.2/
+opt/
--- /dev/null
+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
--- /dev/null
+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))
--- /dev/null
+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
+
--- /dev/null
+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
--- /dev/null
+#ifndef _LF3_DEBUG_H_
+#define _LF3_DEBUG_H_
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+
+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
--- /dev/null
+#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_ */
--- /dev/null
+%option noinput
+%option nounput
+%{
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+#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;
+}
+
+<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. */
+ }
+}
+
+<ARG><<EOF>> {
+ 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;
+}
+
+<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;
+}
+
+<EXEC_ARG>[^\0]*\0 {
+ LF3_DEBUG("exec-arg '%s'\n", yytext);
+ /* Continue in EXEC_ARG state/ */
+}
+
+<EXEC_ARG><<EOF>> {
+ 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;
+}
--- /dev/null
+%{
+#include <assert.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <getopt.h>
+#include <grp.h>
+#include <limits.h>
+#include <printf.h>
+#include <pwd.h>
+#include <signal.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#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 <u_range> TOKEN_ACTION
+%token <u_range> TOKEN_TEST
+
+/* We use three levels of expr to handle shift reduce conflicts around
+ * 'not' and implicit 'and'. */
+%type <u_expr> expr
+%type <u_expr> expr1
+%type <u_expr> 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);
+ }
+}
--- /dev/null
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <errno.h>
+#include <malloc.h>
+#include <ctype.h>
+#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
--- /dev/null
+#ifndef _LF3_PARSE_FORMAT_H_
+#define _LF3_PARSE_FORMAT_H_
+#include <stdio.h>
+
+int lf3_parse_format(int index, const char *fmt, FILE *s_fmt, FILE *s_arg);
+
+#endif
--- /dev/null
+#include <ctype.h>
+#include <stdio.h>
+#include <printf.h>
+#include <limits.h> /* 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;
+}
--- /dev/null
+#ifndef _LF3_PRINTF_H_
+#define _LF3_PRINTF_H_
+#include "lf3_debug.h"
+#include <stdio.h>
+#include <printf.h>
+#include <stdarg.h>
+#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
--- /dev/null
+(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())))
--- /dev/null
+#!/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))))))
--- /dev/null
+#ifndef _XMALLOC_H_
+#define _XMALLOC_H_
+#include <malloc.h>
+#include <string.h>
+
+#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
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
--- /dev/null
+#!/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