pax_global_header00006660000000000000000000000064132036061670014516gustar00rootroot0000000000000052 comment=603c12cdf15afcbc9131369583dcdf5e8beadbe9 lr-1.2/000077500000000000000000000000001320360616700117755ustar00rootroot00000000000000lr-1.2/LICENSE000066400000000000000000000023071320360616700130040ustar00rootroot00000000000000/* lr - a better recursive ls/find */ Copyright (C) 2015-2017 Leah Neukirchen Parts of code derived from musl libc, which is Copyright (C) 2005-2014 Rich Felker, et al. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. lr-1.2/Makefile000066400000000000000000000007421320360616700134400ustar00rootroot00000000000000ALL=lr ZSHCOMP=_lr CFLAGS=-g -O2 -Wall -Wno-switch -Wextra -Wwrite-strings DESTDIR= PREFIX=/usr/local BINDIR=$(PREFIX)/bin MANDIR=$(PREFIX)/share/man ZSHCOMPDIR=$(PREFIX)/share/zsh/site-functions all: $(ALL) clean: FRC rm -f lr install: FRC all mkdir -p $(DESTDIR)$(BINDIR) $(DESTDIR)$(MANDIR)/man1 $(DESTDIR)$(ZSHCOMPDIR) install -m0755 $(ALL) $(DESTDIR)$(BINDIR) install -m0644 $(ALL:=.1) $(DESTDIR)$(MANDIR)/man1 install -m0644 $(ZSHCOMP) $(DESTDIR)$(ZSHCOMPDIR) FRC: lr-1.2/NEWS.md000066400000000000000000000037111320360616700130750ustar00rootroot00000000000000## 1.2 (2017-11-17) * Feature: new option `-B` for breadth first traversal. * Feature: new syntax `? :` for ternary operator. * Feature: new action `skip` which is always false. The common find(1) idiom `-name x -prune -o -print` is now best written as `name = "x" ? prune && skip : print`. * Significant speed-up as tsearch is not used anymore. * Lower memory usage for -U. * Default widths for -U. ## 1.1 (2017-10-29) * Feature: lr is substantially faster as files only are stat(2)ed if the output requires it. * Feature: new option `-X` to print OSC 8 hyperlinks. * Feature: new option `-e` for the common case of filtering file names. * Feature: support for DragonFlyBSD. * Bug: lr doesn't fail on symlinks refering to themselves anymore. ## 1.0 (2017-08-29) * **Breaking change**: the `-Q` flag changed meaning to *enable* quoting (as it does in GNU ls), since shell quoting is not so useful in many cases using a pipe. Filenames are quoted by default when printing to TTY. * Feature: lr now respects the locale, which mainly influences date format. * Feature: new option `-C` to change the color of files. * Feature: new action `color ` to change the color of files. * Feature: new argument `@file` to read file names from a file. * Feature: negated string operations `!=`, `!===`, `!~~`, `!~`, `!=~~`. * Bug: lr now reports errors and sets exit code when toplevel arguments can not be stat'ed. ## 0.4 (2017-04-25) * Feature: argument `-` means read files from standard input ## 0.3.2 (2016-05-20) * Bug: getopt was called in a wrong way from ARM platforms ## 0.3.1 (2016-03-31) * Bug: `=~` was not recognized (broken since 0.3) * Add emacs contrib (lr.el) ## 0.3 (2016-02-28) * Checking permissions against chmod-style symbolic modes * `-TA`/`-TC`/`-TM` to select which timestamp to show in `-l` * Colorize symlink targets * Show broken links * On Linux: Display of capabilities, ACL and xattrs in `-l` * zsh completion * Some small things in contrib/ lr-1.2/README.md000066400000000000000000000245771320360616700132730ustar00rootroot00000000000000## lr: list files, recursively `lr` is a new tool for generating file listings, which includes the best features of `ls(1)`, `find(1)`, `stat(1)` and `du(1)`. `lr` has been tested on Linux 4.1, FreeBSD 10.2, OpenBSD 5.7, NetBSD 5.2.3, DragonFlyBSD 5.0, Mac OS X 10.10, OmniOS 5.11 and Cygwin 1.7.32. It will likely work on other Unix-like systems with C99, but you'll need to port `scan_filesystems` for `fstype` to work. ## Screenshot ![Screenshot of lr -AFGl -ovh](lr.png) ## Benefits Over find: * friendly and logical C-style filter syntax * getopt is used, can mix filters and arguments in any order * can sort * compute directory sizes * can strip leading `./` * can do breadth first search Over ls: * sorts over all files, not per directory * copy & paste file names from the output since they are relative to pwd * ISO dates * powerful filters ## Rosetta stone * `ls`: `lr -1 | column` * `find .`: `lr` (or `lr -U` for speed.) * `ls -l`: `lr -1l` * `ls -ltrc`: `lr -l1Aoc` * `find . -name '*.c'`: `lr -t 'name ~~ "*.c"'` * `find . -regex '.*c'`: `lr -t 'path =~ "c$"'` * `find -L /proc/*/fd -maxdepth 1 -type f -links 0 -printf '%b %p\n'`: `lr -UL1 -t 'type == f && links == 0' -f '%b %p\n' /proc/*/fd` * `find "${@:-.}" -name HEAD -execdir sh -c 'git rev-parse --resolve-git-dir . >/dev/null 2>/dev/null && pwd' ';'`: `lr -0U -t 'name == "HEAD"' "$@" | xe -0 -s 'cd ${1%/*} && git rev-parse --resolve-git-dir . >/dev/null && pwd; true' 2>/dev/null` * Filter list of files for existence: `xe lr -dQU @|`). * `-l`: long output a la `ls -l` (implies `-Q`). * `-TA`: with `-l`, output atime. * `-TC`: with `-l`, output ctime. * `-TM`: with `-l`, output mtime (default). * `-S`: BSD stat(1)-inspired output (implies `-Q`). * `-f FMT`: custom formatting, see below. * `-B`: breadth first traversal. * `-D`: depth first traversal. `prune` does not work, but `entries` and `total` are computed on the fly. * `-H`: only follow symlinks on command line. * `-L`: follow all symlinks. * `-1`: don't go below one level of directories. * `-A`: don't list files starting with a dot. * `-G`: colorize output to tty. Use twice to force colorize. * `-X`: print OSC 8 hyperlinks to tty. Use twice to force. * `-Q`: shell quote file names (default for output to TTY). * `-d`: don't enter directories. * `-h`: print human readable size for `-l` (also `%s`). * `-s`: strip directory prefix passed on command line. * `-x`: don't enter other filesystems. * `-U`: don't sort results. * `-o ORD`: sort according to the string `ORD`, see below. * `-e REGEX`: only show files where basename matches `REGEX`. * `-t TEST`: only show files matching all `TEST`s, see below. ## Output formatting: * `\a`, `\b`, `\f`, `\n`, `\r`, `\v`, `\0` as in C. * `%%`: plain `%`. * `%s`: file size in bytes. * `%S`: file size, with human readable unit. * `%b`: file size in 512-byte blocks. * `%k`: file size in 1024-byte blocks. * `%d`: path depth. * `%D`: device number (`stat.st_dev`). * `%R`: device ID for special files (`stat.st_rdev`). * `%i`: inode number. * `%I`: one space character for every depth level. * `%p`: full path (`%P` if `-s`). * `%P`: full path without command line argument prefix. * `%l`: symlink target. * `%n`: number of hardlinks. * `%F`: file indicator type symbol (`*/=>@|`). * `%f`: file basename (everything after last `/`). * `%A-`, `%C-`, `%T-`: relative age for atime/ctime/mtime. * `%Ax`, `%Cx`, `%Tx`: result of `strftime` for `%x` on atime/ctime/mtime. * `%m`: octal file permissions. * `%M`: ls-style symbolic file permissions. * `%y`: ls-style symbolic file type (`bcdfls`). * `%g`: group name. * `%G`: numeric gid. * `%u`: user name. * `%U`: numeric uid. * `%e`: number of entries in directories. * `%t`: total size used by accepted files in directories (only with `-D`). * `%Y`: type of the filesystem the file resides on. * `%x`: Linux-only: a combination of: `#` for files with security capabilities, `+` for files with an ACL, `@` for files with other extended attributes. ## Sort order Sort order is string consisting of the following letters. Uppercase letters reverse sorting. E.g. `Sn` sorts first by size, smallest last, and then by name (in case sizes are equal). Default: `n`. * `a`: atime. * `c`: ctime. * `d`: path depth. * `e`: file extension. * `i`: inode number. * `m`: mtime. * `n`: file name. * `p`: directory name. * `s`: file size. * `t`: file type. This sorts all directories before other files. * `v`: file name as version numbers (sorts "2" before "10"). ## Filter expressions `lr` filters are given by the following EBNF: ::= || -- disjunction | && -- conjunction | ? : -- ternary operator | ! -- negation | ( | | | | | prune -- do not traverse into subdirectories | print -- always true value | skip -- always true value | color -- always true value, override 256-color ::= atime | ctime | mtime ::= depth | dev | entries | gid | inode | links | mode | rdev | size | total | uid ::= <= | < | >= | > | == | = | != ::= "./path" -- mtime of relative path | "/path" -- mtime of absolute path | "YYYY-MM-DD HH:MM:SS" | "YYYY-MM-DD" -- at midnight | "HH:MM:SS" -- today | "HH:MM" -- today | "-[0-9]+d" -- n days ago at midnight | "-[0-9]+h" -- n hours before now | "-[0-9]+m" -- n minutes before now | "-[0-9]+s" -- n seconds before now | [0-9]+ -- absolute epoch time ::= [0-9]+ ( c -- *1 | b -- *512 | k -- *1024 | M -- *1024*1024 | G -- *1024*1024*1024 | T )? -- *1024*1024*1024*1024 ::= fstype | group | name | path | target | user | xattr ::= == | = | != -- string (in)equality | === | !=== -- case insensitive string (in)equality | ~~ | !~~ -- glob (fnmatch) | ~~~ | !~~~ -- case insensitive glob (fnmatch) | =~ | !=~ | !~ -- POSIX Extended Regular Expressions | =~~ | !=~~ -- case insensitive POSIX Extended Regular Expressions ::= " ([^"] | "")+ " -- use "" for a single " inside " | $[A-Za-z0-9_] -- environment variable ::= type ( == | = | != ) ( b | c | d | p | f | l ) ::= mode ( == | = -- exact permissions | & -- check if all bits of set | | -- check if any bit of set ) | mode = "" -- check if symbolic mode is satisfied ::= [0-7]+ ::= (, )+ ::= [guoa]* [+-=] [rwxXstugo]* -- see chmod(1) ## EWONTFIX The following features won't be implemented: * `-exec`: use `-0` and `xargs` (or even better [xe](https://github.com/chneukirchen/xe)). * columns: use `column`, `git-column` (supports colors), Plan 9 `mc`. (e.g. `lr -1AGFs | git column --mode=dense --padding=2`) ## "Screenshots" Default output, sorted by name: ``` % lr . .git .git/HEAD .git/config [...] Makefile README.md lr.c ``` Long output format: ``` % lr -l drwxrwxr-x 3 chris users 120 2015-10-27 13:56 ./ drwxrwxr-x 7 chris users 240 2015-10-27 13:56 .git/ -rw-rw-r-- 1 chris users 23 2015-10-27 13:56 .git/HEAD -rw-rw-r-- 1 chris users 257 2015-10-27 13:56 .git/config [...] -rw-rw-r-- 1 chris users 297 2015-10-27 13:56 Makefile -rw-rw-r-- 1 chris users 5828 2015-10-27 13:56 README.md -rw-rw-r-- 1 chris users 27589 2015-10-27 13:56 lr.c ``` Simple test: ``` % lr -F -t 'type == d' ./ .git/ .git/hooks/ .git/info/ .git/logs/ .git/logs/refs/ .git/logs/refs/heads/ .git/logs/refs/remotes/ .git/logs/refs/remotes/origin/ .git/objects/ .git/objects/info/ .git/objects/pack/ .git/refs/ .git/refs/heads/ .git/refs/remotes/ .git/refs/remotes/origin/ .git/refs/tags/ ``` List regular files by size, largest first: ``` % lr -f '%S %f\n' -1 -t 'type == f' -oS 27K lr.c 5.7K README.md 297 Makefile ``` List directory total sizes, indented: ``` % lr -D -t 'type == d' -f '%I%I%t %p\n' 172 . 132 .git 40 .git/hooks 4 .git/info 12 .git/logs 8 .git/logs/refs 4 .git/logs/refs/heads 4 .git/logs/refs/remotes 4 .git/logs/refs/remotes/origin 48 .git/objects 0 .git/objects/info 48 .git/objects/pack 8 .git/refs 4 .git/refs/heads 4 .git/refs/remotes 4 .git/refs/remotes/origin 0 .git/refs/tags ``` List all files, but print them in red if they match "havoc": ``` % lr -G -t 'name =~ "havoc" && color 160 || print' ``` Do not enter `.git` or `.hg` directories: ``` % lr -t 'name = ".git" || name = ".hg" ? prune : print' . ``` ## Installation Use `make all` to build, `make install` to install relative to `PREFIX` (`/usr/local` by default). The `DESTDIR` convention is respected. You can also just copy the binary into your `PATH`. ## Copyright Copyright (C) 2015-2017 Leah Neukirchen Licensed under the terms of the MIT license, see lr.c. lr-1.2/_lr000066400000000000000000000055711320360616700125040ustar00rootroot00000000000000#compdef lr _lr_format() { if [[ ${(Q)PREFIX} = *% ]]; then local -a specs specs=( '%:percent sign' 's:file size in bytes' 'S:file size, with human readable unit' 'b:file size in 512-byte blocks' 'k:file size in 1024-byte blocks' 'd:path depth' 'D:device number' 'R:device ID for special files' 'i:inode number' 'I:one space character for every depth level' 'p:full path' 'P:full path without command line argument prefix' 'l:symlink target' 'n:number of hardlinks' 'F:file indicator type' 'f:file basename' 'A:atime prefix' 'C:ctime prefix' 'T:mtime prefix' 'm:octal file permissions' 'M:ls-style symbolic file permissions' 'y:ls-style symbolic file type' 'g:group name' 'G:numeric gid' 'u:user name' 'U:numeric uid' 'e:number of entries in directories' 't:total size used by accepted files' 'Y:file system type' 'x:extended attributes' ) compset -P "*" _describe -t lr-format-specifiers 'format specifier' specs -S '' else _wanted lr-format-specifiers expl 'format specifier' compadd -S '' % fi return 0 } _lr_order() { local -a specs specs=( 'a:atime' 'c:ctime' 'd:path depth' 'e:file extension' 'i:inode number' 'm:mtime' 'n:file name' 'p:directory name' 's:file size' 't:file type' 'v:file name as version number' 'A:atime (reversed)' 'C:ctime (reversed)' 'D:path depth (reversed)' 'E:file extension (reversed)' 'I:inode number (reversed)' 'M:mtime (reversed)' 'N:file name (reversed)' 'P:directory name (reversed)' 'S:file size (reversed)' 'T:file type (reversed)' 'V:file name as version number (reversed)' ) compset -P "*" _describe -t lr-order-specifiers 'order specifier' specs -S '' return 0 } if (( words[(I)-l] )); then local timeopt='-T+[show time]:time:((A\:access\ time C\:inode\ change\ time M\:file\ modification\ time))' fi _arguments -S : \ '(-F -l -S -f)-0[output filenames NUL-separated]' \ '(-0 -l -S -f)-F[output filenames with type indicator]' \ '(-0 -F -S -f)-l[long output]' \ '(-0 -F -l -f)-S[stat(1)-like output]' \ '(-0 -F -l -S)-f[output with custom format]:format:_lr_format' \ $timeopt \ '(-D)-B[use breadth-first traversal]' \ '(-B)-D[use depth-first traversal]' \ '(-L)-H[only follow symlinks on command line]' \ '(-H)-L[follow all symlinks]' \ '-1[don'\''t go below one level of directories]' \ '-A[don'\''t list files starting with a dot]' \ '-Q[shell quote file names]' \ '-d[don'\''t enter directories]' \ '-G[colorize output]' \ '-X[print OSC 8 hyperlinks]' \ '-h[print human readable size]' \ '-s[strip directory prefix passed on command line]' \ '-x[don'\''t enter other filesystems]' \ '(-o)-U[don'\''t sort results]' \ '(-U)-o[sort order]:order:_lr_order' \ '*-e[only show files where basename matches regexp]:pattern: ' \ '*-t[test expression]:test: ' \ '*-C[colorize path]:path:_files' \ '*:files:_files' lr-1.2/contrib/000077500000000000000000000000001320360616700134355ustar00rootroot00000000000000lr-1.2/contrib/lr.el000066400000000000000000000013401320360616700143720ustar00rootroot00000000000000;;; lr.el --- run `lr' and display the results (define-compilation-mode lr-mode "Lr" (set (make-local-variable 'compilation-error-regexp-alist) '(("\\([^ \n']+\\)[*/=>@|]$" 1) ("\\([^ \n']+\\)$" 1) ("'\\(.+\\)'$" 1))) (set (make-local-variable 'next-error-highlight) nil)) (defun lr (command-args) "Run lr, with user-specified args, and collect output in a buffer. While lr runs asynchronously, you can use \\[next-error] (M-x next-error), or RET in the *lr* buffer, to go to the files lr found." (interactive (list (read-shell-command "Run lr (like this): " "lr " 'lr-history))) (compilation-start command-args 'lr-mode)) (provide 'lr) ;;; lr.el ends here lr-1.2/contrib/lr.vim000066400000000000000000000003371320360616700145720ustar00rootroot00000000000000" :Lr to browse lr(1) results in a new window, " press return to open file in new window. command! -n=* -complete=file Lr silent exe "R" "lr" | res | silent f [lr] | map $hgF lr-1.2/contrib/lrls000077500000000000000000000002421320360616700143350ustar00rootroot00000000000000#!/bin/sh # lrls - lr with GNU ls style output color= [ -t 1 ] && color=-GG lr $color -A1s -t '!type == d || depth > 0' "$@" |git column --mode=dense --pad=2 lr-1.2/contrib/lrocate000077500000000000000000000002201320360616700150060ustar00rootroot00000000000000#!/bin/sh # lrocate [PATTERN] [LR OPTIONS...] - call lr(1) on locate(1) results pattern=$1; shift locate -0 "$pattern" | xe -0 -N0 lr -lG "$@" lr-1.2/lr.1000066400000000000000000000175611320360616700125060ustar00rootroot00000000000000.Dd July 27, 2017 .Dt LR 1 .Os .Sh NAME .Nm lr .Nd list files, recursively .Sh SYNOPSIS .Nm .Op Fl 0 | Fl F | Fl l Oo Fl TA | Fl TC | Fl TM Oc | Fl S | Fl f Ar fmt .br .Op Fl B | Fl D .Op Fl H | Fl L .Op Fl 1AGQXdhsx .Op Fl U | Fl o Ar ord .br .Op Fl e Ar regex .Op Fl t Ar test .Op Fl C Oo Ar color Ns Li \&: Oc Ns Ar path .Ar path\ ... .Sh DESCRIPTION .Nm is a versatile tool to generate file listings with configurable formatting, ordering and filtering. .Pp When no .Ar path is given or .Ar path is an empty string, the current directory is used by default. .Pp The special .Ar path argument .Sq Ic \&\- makes .Nm read file names from standard input, instead of traversing .Ar path . Likewise, the special .Ar path argument .Ic \&@ Ns Ar file makes .Nm read file names from .Ar file . .Pp The options are as follows: .Bl -tag -width Ds .It Fl 0 Output filenames separated by NUL bytes. Likewise, read input filenames separated by NUL bytes. .It Fl F Output filenames and an indicator of their file type (one of .Sq Li */=>@| ) . .It Fl l Long output a la .Sq Ic ls -l (implies .Fl Q ) . .It Fl TA With .Fl l , print atime. .It Fl TC With .Fl l , print ctime. .It Fl TM With .Fl l , print mtime. This is the default. .It Fl S Output inspired by BSD .Xr stat 1 (implies .Fl Q ) . .It Fl f Ar fmt Custom formatting, see .Sx FORMATTING . .It Fl B Use breadth first traversal. For each depth of the directory tree, files are sorted and printed, then the next depth is looked at. .It Fl D Use depth first traversal. .Ic prune will not work, but .Ic entries and .Ic total are computed on the fly. .It Fl H Only follow symlinks on command line (default: don't follow symlinks). .It Fl L Follow all symlinks. .It Fl 1 Don't go below one level of directories. .It Fl A Don't list files starting with a dot. .It Fl Q Quote file names (default for output to TTY). .It Fl d Don't enter directories. .It Fl G Colorize output to TTY. Use twice to force colorized output. .It Fl X Output OSC 8 hyperlinks to TTY. Use twice to force hyperlinks. .It Fl h Print human readable size for .Fl l (also .Ic %s ) . .It Fl s Strip directory prefix passed on command line. .It Fl x Don't enter other filesystems. .It Fl U Don't sort results. .It Fl o Ar ord Sort according to .Ar ord , see .Sx SORT ORDER . .It Fl e Ar regex Only show files where basename matches the POSIX ERE .Ar regex . .It Fl t Ar test Only show files matching the expression .Ar test , see .Sx TESTS . Multiple occurrences of .Fl t and .Fl e are regarded as a conjunction. .It Fl C Oo Ar color Ns Li \&: Oc Ns Ar path Behaves as if .Ar path was passed like an ordinary argument, but overrides the color of the file names to the number .Ar color which must be between 0 and 255 (default: 2, green). .Pp Implies .Fl GG . .El .Sh FORMATTING .Nm format strings support the following escape sequences: .Pp .Bl -tag -compact -width Ds .It Ic \ea , \eb , \ef , \en , \er , \ev , \e0 Special characters as in C. .It Ic \&%% A plain .Sq % . .It Ic \&%s File size in bytes .It Ic \&%S File size, with human readable unit .It Ic \&%b File size in 512-byte blocks .It Ic \&%k File size in 1024-byte blocks .It Ic \&%d Path depth .It Ic \&%D Device number .Va ( stat.st_dev ) .It Ic \&%R Device ID for special files .Va ( stat.st_rdev ) .It Ic \&%i Inode number .It Ic \&%I One space character for every depth level .It Ic \&%p Full path .Ic ( \&%P if .Fl s is used) .It Ic \&%P Full path without command line argument prefix .It Ic \&%l Symlink target .It Ic \&%n Number of hardlinks .It Ic \&%F File indicator type symbol (one of .Sq Li */=>@| ) .It Ic \&%f File basename (everything after last .Li / ) .It Ic \&%A- , %C- , %T- relative age for atime/ctime/mtime. .It Ic \&%A Ns Ar x , Ic \&%C Ns Ar x , Ic \&%T Ns Ar x result of .Xr strftime 3 for .Ic \&% Ns Ar x on atime/ctime/mtime .It Ic \&%m Octal file permissions .It Ic \&%M ls-style symbolic file permissions .It Ic \&%y ls-style symbolic file type .Sq ( Li bcdfls ) .It Ic \&%g Group name .It Ic \&%G Numeric gid .It Ic \&%u User name .It Ic \&%U Numeric uid .It Ic \&%e Number of entries in directories .It Ic \&%t Total size used by accepted files in directories (only with .Fl D ) .It Ic \&%Y Type of the filesystem the file resides on .It Ic \&%x Linux-only: Print a combination of .Sq Li \&# for files with security capabilities, .Sq Li \&+ for files with an ACL, .Sq Li \&@ for files with other extended attributes. .El .Sh SORT ORDER Sort order is string consisting of the following letters. Uppercase letters reverse sorting. Default sort order is .Sq Ic n . .Pp .Bl -tag -compact -width Ds .It Ic a atime .It Ic c ctime .It Ic d path depth .It Ic e file extension .It Ic i inode number .It Ic m mtime .It Ic n file name .It Ic p directory name .It Ic s file size .It Ic t file type. This sorts all directories before other files. .It Ic v File name as version numbers (sorts .Sq 2 before .Sq 10 ) . .El .Pp E.g.\& .Sq Ic Sn sorts first by size, smallest last, and then by name (in case sizes are equal). .Sh TESTS .Nm tests are given by the following EBNF: .Bd -literal ::= || -- disjunction | && -- conjunction | ? : -- ternary operator | ! -- negation | ( | | | | | prune -- do not traverse into subdirectories | print -- always true value | skip -- always false value | color -- always true value, override 256-color ::= atime | ctime | mtime ::= depth | dev | entries | gid | inode | links | mode | rdev | size | total | uid ::= <= | < | >= | > | == | = | != ::= "./path" -- mtime of relative path | "/path" -- mtime of absolute path | "YYYY-MM-DD HH:MM:SS" | "YYYY-MM-DD" -- at midnight | "HH:MM:SS" -- today | "HH:MM" -- today | "-[0-9]+d" -- n days ago at midnight | "-[0-9]+h" -- n hours before now | "-[0-9]+m" -- n minutes before now | "-[0-9]+s" -- n seconds before now | [0-9]+ -- absolute epoch time ::= [0-9]+ ( c -- *1 | b -- *512 | k -- *1024 | M -- *1024*1024 | G -- *1024*1024*1024 | T )? -- *1024*1024*1024*1024 ::= fstype | group | name | path | target | user | xattr ::= == | = | != -- string (in)equality | === | !=== -- case insensitive string (in)equality | ~~ | !~~ -- glob (fnmatch) | ~~~ | !~~~ -- case insensitive glob (fnmatch) | =~ | !=~ | !~ -- POSIX Extended Regular Expressions | =~~ | !=~~ -- case insensitive POSIX Extended Regular Expressions ::= " ([^"] | "")+ " -- use "" for a single " inside " | $[A-Za-z0-9_]+ -- environment variable ::= type ( == | = | != ) ( b | c | d | p | f | l ) ::= mode ( == | = -- exact permissions | & -- check if all bits of set | | -- check if any bit of set ) | mode = "" -- check if symbolic mode is satisfied ::= [0-7]+ ::= (, )+ ::= [guoa]* [+-=] [rwxXstugo]* -- see chmod(1) .Ed .Sh EXIT STATUS .Ex -std .Sh SEE ALSO .Xr du 1 , .Xr find 1 , .Xr ls 1 , .Xr stat 1 .Sh AUTHORS .An Leah Neukirchen Aq Mt leah@vuxu.org .Sh LICENSE .Nm is licensed under the terms of the MIT license. lr-1.2/lr.c000066400000000000000000001441121320360616700125610ustar00rootroot00000000000000/* lr - a better recursive ls/find */ /* * Copyright (C) 2015-2017 Leah Neukirchen * Parts of code derived from musl libc, which is * Copyright (C) 2005-2014 Rich Felker, et al. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* ##% gcc -Os -Wall -g -o $STEM $FILE -Wno-switch -Wextra -Wwrite-strings */ #define _GNU_SOURCE #include #include #include #ifdef __linux__ #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __has_include #if __has_include() #include #else #define noreturn /**/ #endif #else #define noreturn /**/ #endif /* For Solaris. */ #if !defined(FNM_CASEFOLD) && defined(FNM_IGNORECASE) #define FNM_CASEFOLD FNM_IGNORECASE #endif /* For Hurd. */ #if !defined(PATH_MAX) #define PATH_MAX 4096 #endif struct fitree; struct idtree; static int Bflag; static int Cflag; static char *Cflags[64]; static int Gflag; static int Dflag; static int Hflag; static int Lflag; static int Qflag; static int Uflag; static int Xflag; static int hflag; static int lflag; static int sflag; static int xflag; static char Tflag = 'T'; static char *argv0; static char *format; static char *ordering; static struct expr *expr; static int prune; static size_t prefixl; static char input_delim = '\n'; static int current_color; static int status; static char *basepath; static char host[1024]; static char default_ordering[] = "n"; static char default_format[] = "%p\\n"; static char type_format[] = "%p%F\\n"; static char long_format[] = "%M%x %n %u %g %s %\324F %\324R %p%F%l\n"; static char zero_format[] = "%p\\0"; static char stat_format[] = "%D %i %M %n %u %g %R %s \"%Ab %Ad %AT %AY\" \"%Tb %Td %TT %TY\" \"%Cb %Cd %CT %CY\" %b %p\n"; static struct fitree *root; static struct fitree *new_root; static struct idtree *users; static struct idtree *groups; static struct idtree *filesystems; static int scanned_filesystems; static int need_stat; static int need_group; static int need_user; static int need_fstype; static int need_xattr; static dev_t maxdev; static ino_t maxino; static nlink_t maxnlink; static uid_t maxuid; static gid_t maxgid; static dev_t maxrdev; static off_t maxsize; static blkcnt_t maxblocks; static unsigned int maxxattr; static int bflag_depth; static int maxdepth; static int uwid, gwid, fwid; static time_t now; static mode_t default_mask; // [^a-zA-Z0-9~._/-] static char rfc3986[128] = { 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,1 }; struct fileinfo { char *fpath; size_t prefixl; int depth; ino_t entries; struct stat sb; off_t total; char xattr[4]; int color; }; enum op { EXPR_OR = 1, EXPR_AND, EXPR_COND, EXPR_NOT, EXPR_LT, EXPR_LE, EXPR_EQ, EXPR_NEQ, EXPR_GE, EXPR_GT, EXPR_STREQ, EXPR_STREQI, EXPR_GLOB, EXPR_GLOBI, EXPR_REGEX, EXPR_REGEXI, EXPR_PRUNE, EXPR_PRINT, EXPR_COLOR, EXPR_TYPE, EXPR_ALLSET, EXPR_ANYSET, EXPR_CHMOD, }; enum prop { PROP_ATIME = 1, PROP_CTIME, PROP_DEPTH, PROP_DEV, PROP_ENTRIES, PROP_FSTYPE, PROP_GID, PROP_GROUP, PROP_INODE, PROP_LINKS, PROP_MODE, PROP_MTIME, PROP_NAME, PROP_PATH, PROP_RDEV, PROP_SIZE, PROP_TARGET, PROP_TOTAL, PROP_UID, PROP_USER, PROP_XATTR, }; enum filetype { TYPE_BLOCK = 'b', TYPE_CHAR = 'c', TYPE_DIR = 'd', TYPE_FIFO = 'p', TYPE_REGULAR = 'f', TYPE_SOCKET = 's', TYPE_SYMLINK = 'l', }; struct expr { enum op op; union { enum prop prop; enum filetype filetype; struct expr *expr; char *string; int64_t num; regex_t *regex; } a, b, c; }; static char *pos; noreturn static void parse_error(const char *msg, ...) { va_list ap; va_start(ap, msg); fprintf(stderr, "%s: parse error: ", argv0); vfprintf(stderr, msg, ap); fprintf(stderr, "\n"); exit(2); } int test_chmod(char *c, mode_t oldmode) { mode_t newmode = oldmode; mode_t whom, what; char op; do { whom = 0; what = 0; while (1) { switch (*c) { case 'u': whom |= 04700; break; case 'g': whom |= 02070; break; case 'o': whom |= 01007; break; case 'a': whom |= 07777; break; default: goto op; } c++; } op: if (whom == 0) whom = default_mask; op = *c++; if (!(op == '-' || op == '+' || op == '=')) parse_error("invalid mode operator"); switch (*c) { case 'u': what = 00111 * ((newmode >> 6) & 0007); c++; break; case 'g': what = 00111 * ((newmode >> 3) & 0007); c++; break; case 'o': what = 00111 * ((newmode ) & 0007); c++; break; default: while (1) { switch (*c) { case 'r': what |= 00444; break; case 'w': what |= 00222; break; case 'x': what |= 00111; break; case 'X': if (oldmode & 00111) what |= 00111; break; case 's': what |= 06000; break; case 't': what |= 01000; break; case ',': case 0: goto doit; default: parse_error("invalid permission"); } c++; } } doit: switch (op) { case '-': newmode &= ~(whom & what); break; case '+': newmode |= (whom & what); break; case '=': newmode = (newmode & ~whom) | (whom & what); break; } } while (*c == ',' && c++); if (*c) parse_error("trailing garbage in mode string '%s'", c); return newmode == oldmode; } static struct expr * mkexpr(enum op op) { struct expr *e = malloc(sizeof (struct expr)); if (!e) parse_error("out of memory"); e->op = op; return e; } static void ws() { while (isspace((unsigned char)*pos)) pos++; } static int token(const char *token) { if (strncmp(pos, token, strlen(token)) == 0) { pos += strlen(token); ws(); return 1; } else { return 0; } } static int64_t parse_num(int64_t *r) { char *s = pos; if (isdigit((unsigned char)*pos)) { int64_t n; for (n = 0; isdigit((unsigned char)*pos) && n <= INT64_MAX / 10 - 10; pos++) n = 10 * n + (*pos - '0'); if (isdigit((unsigned char)*pos)) parse_error("number too big: %s", s); if (token("c")) ; else if (token("b")) n *= 512LL; else if (token("k")) n *= 1024LL; else if (token("M")) n *= 1024LL * 1024; else if (token("G")) n *= 1024LL * 1024 * 1024; else if (token("T")) n *= 1024LL * 1024 * 1024 * 1024; ws(); *r = n; return 1; } else { return 0; } } int parse_octal(long *r) { char *s = pos; if (*pos >= '0' && *pos <= '7') { long n = 0; while (*pos >= '0' && *pos <= '7') { n *= 8; n += *pos - '0'; pos++; if (n > 07777) parse_error("number to big: %s", s); } ws(); *r = n; return 1; } else { return 0; } } static enum op parse_op() { if (token("<=")) return EXPR_LE; else if (token("<")) return EXPR_LT; else if (token(">=")) return EXPR_GE; else if (token(">")) return EXPR_GT; else if (token("==") || token("=")) return EXPR_EQ; else if (token("!=")) return EXPR_NEQ; return 0; } static struct expr *parse_cmp(); static struct expr *parse_cond(); static struct expr * parse_inner() { if (token("prune")) { struct expr *e = mkexpr(EXPR_PRUNE); return e; } else if (token("print")) { struct expr *e = mkexpr(EXPR_PRINT); return e; } else if (token("skip")) { struct expr *e = mkexpr(EXPR_PRINT); struct expr *not = mkexpr(EXPR_NOT); not->a.expr = e; return not; } else if (token("color")) { struct expr *e = mkexpr(EXPR_COLOR); int64_t n; if (parse_num(&n) && n >= 0 && n <= 255) { e->a.num = n; return e; } else { parse_error("invalid 256-color at '%.15s'", pos); return 0; } } else if (token("!")) { struct expr *e = parse_cmp(); struct expr *not = mkexpr(EXPR_NOT); not->a.expr = e; return not; } else if (token("(")) { struct expr *e = parse_cond(); if (token(")")) return e; parse_error("missing ) at '%.15s'", pos); return 0; } else { parse_error("unknown expression at '%.15s'", pos); return 0; } } static struct expr * parse_type() { int negate = 0; if (token("type")) { if (token("==") || token("=") || (token("!=") && ++negate)) { struct expr *e = mkexpr(EXPR_TYPE); if (token("b")) e->a.filetype = TYPE_BLOCK; else if (token("c")) e->a.filetype = TYPE_CHAR; else if (token("d")) e->a.filetype = TYPE_DIR; else if (token("p")) e->a.filetype = TYPE_FIFO; else if (token("f")) e->a.filetype = TYPE_REGULAR; else if (token("l")) e->a.filetype = TYPE_SYMLINK; else if (token("s")) e->a.filetype = TYPE_SOCKET; else if (*pos) parse_error("invalid file type '%c'", *pos); else parse_error("no file type given"); if (negate) { struct expr *not = mkexpr(EXPR_NOT); not->a.expr = e; return not; } else { return e; } } else { parse_error("invalid file type comparison at '%.15s'", pos); } } return parse_inner(); } static int parse_string(char **s) { char *buf = 0; size_t bufsiz = 0; size_t len = 0; if (*pos == '"') { pos++; while (*pos && (*pos != '"' || (*pos == '"' && *(pos+1) == '"'))) { if (len >= bufsiz) { bufsiz = 2*bufsiz + 16; buf = realloc(buf, bufsiz); if (!buf) parse_error("string too long"); } if (*pos == '"') pos++; buf[len++] = *pos++; } if (!*pos) parse_error("unterminated string"); if (buf) buf[len] = 0; pos++; ws(); *s = buf ? buf : (char *)""; return 1; } else if (*pos == '$') { char t; char *e = ++pos; while (isalnum((unsigned char)*pos) || *pos == '_') pos++; if (e == pos) parse_error("invalid environment variable name"); t = *pos; *pos = 0; *s = getenv(e); if (!*s) *s = (char *)""; *pos = t; ws(); return 1; } return 0; } static int parse_dur(int64_t *n) { char *s, *r; if (!parse_string(&s)) return 0; if (*s == '/' || *s == '.') { struct stat st; if (stat(s, &st) < 0) parse_error("can't stat file '%s': %s", s, strerror(errno)); *n = st.st_mtime; return 1; } struct tm tm = { 0 }; r = strptime(s, "%Y-%m-%d %H:%M:%S", &tm); if (r && !*r) { *n = mktime(&tm); return 1; } r = strptime(s, "%Y-%m-%d", &tm); if (r && !*r) { tm.tm_hour = tm.tm_min = tm.tm_sec = 0; *n = mktime(&tm); return 1; } r = strptime(s, "%H:%M:%S", &tm); if (r && !*r) { struct tm *tmnow = localtime(&now); tm.tm_year = tmnow->tm_year; tm.tm_mon = tmnow->tm_mon; tm.tm_mday = tmnow->tm_mday; *n = mktime(&tm); return 1; } r = strptime(s, "%H:%M", &tm); if (r && !*r) { struct tm *tmnow = localtime(&now); tm.tm_year = tmnow->tm_year; tm.tm_mon = tmnow->tm_mon; tm.tm_mday = tmnow->tm_mday; tm.tm_sec = 0; *n = mktime(&tm); return 1; } if (*s == '-') { s++; errno = 0; int64_t d; d = strtol(s, &r, 10); if (errno == 0 && r[0] == 'd' && !r[1]) { struct tm *tmnow = localtime(&now); tmnow->tm_mday -= d; tmnow->tm_hour = tmnow->tm_min = tmnow->tm_sec = 0; *n = mktime(tmnow); return 1; } if (errno == 0 && r[0] == 'h' && !r[1]) { *n = now - (d*60*60); return 1; } if (errno == 0 && r[0] == 'm' && !r[1]) { *n = now - (d*60); return 1; } if (errno == 0 && r[0] == 's' && !r[1]) { *n = now - d; return 1; } parse_error("invalid relative time format '%s'", s-1); } parse_error("invalid time format '%s'", s); return 0; } static struct expr * mkstrexpr(enum prop prop, enum op op, char *s, int negate) { int r = 0; struct expr *e = mkexpr(op); e->a.prop = prop; if (op == EXPR_REGEX) { e->b.regex = malloc(sizeof (regex_t)); r = regcomp(e->b.regex, s, REG_EXTENDED | REG_NOSUB); } else if (op == EXPR_REGEXI) { e->b.regex = malloc(sizeof (regex_t)); r = regcomp(e->b.regex, s, REG_EXTENDED | REG_NOSUB | REG_ICASE); } else { e->b.string = s; } if (r != 0) { char msg[256]; regerror(r, e->b.regex, msg, sizeof msg); parse_error("invalid regex '%s': %s", s, msg); exit(2); } if (negate) { struct expr *not = mkexpr(EXPR_NOT); not->a.expr = e; return not; } return e; } static struct expr * parse_strcmp() { enum prop prop; enum op op; int negate = 0; if (token("fstype")) prop = PROP_FSTYPE; else if (token("group")) prop = PROP_GROUP; else if (token("name")) prop = PROP_NAME; else if (token("path")) prop = PROP_PATH; else if (token("target")) prop = PROP_TARGET; else if (token("user")) prop = PROP_USER; else if (token("xattr")) prop = PROP_XATTR; else return parse_type(); if (token("~~~")) op = EXPR_GLOBI; else if (token("~~")) op = EXPR_GLOB; else if (token("=~~")) op = EXPR_REGEXI; else if (token("=~")) op = EXPR_REGEX; else if (token("===")) op = EXPR_STREQI; else if (token("==")) op = EXPR_STREQ; else if (token("=")) op = EXPR_STREQ; else if (token("!~~~")) negate = 1, op = EXPR_GLOBI; else if (token("!~~")) negate = 1, op = EXPR_GLOB; else if (token("!=~~")) negate = 1, op = EXPR_REGEXI; else if (token("!=~") || token("!~")) negate = 1, op = EXPR_REGEX; else if (token("!===")) negate = 1, op = EXPR_STREQI; else if (token("!==") || token("!=")) negate = 1, op = EXPR_STREQ; else parse_error("invalid string operator at '%.15s'", pos); char *s; if (parse_string(&s)) return mkstrexpr(prop, op, s, negate); parse_error("invalid string at '%.15s'", pos); return 0; } static struct expr * parse_mode() { struct expr *e = mkexpr(0); long n; char *s; e->a.prop = PROP_MODE; if (token("==") || token("=")) { e->op = EXPR_EQ; } else if (token("&")) { e->op = EXPR_ALLSET; } else if (token("|")) { e->op = EXPR_ANYSET; } else { parse_error("invalid mode comparison at '%.15s'", pos); } if (parse_octal(&n)) { e->b.num = n; } else if (e->op == EXPR_EQ && parse_string(&s)) { e->op = EXPR_CHMOD; e->b.string = s; umask(default_mask = 07777 & ~umask(0)); /* for future usage */ test_chmod(s, 0); /* run once to check for syntax */ } else { parse_error("invalid mode at '%.15s'", pos); } return e; } static struct expr * parse_cmp() { enum prop prop; enum op op; if (token("depth")) prop = PROP_DEPTH; else if (token("dev")) prop = PROP_DEV; else if (token("entries")) prop = PROP_ENTRIES; else if (token("gid")) prop = PROP_GID; else if (token("inode")) prop = PROP_INODE; else if (token("links")) prop = PROP_LINKS; else if (token("mode")) return parse_mode(); else if (token("rdev")) prop = PROP_RDEV; else if (token("size")) prop = PROP_SIZE; else if (token("total")) prop = PROP_TOTAL; else if (token("uid")) prop = PROP_UID; else return parse_strcmp(); op = parse_op(); if (!op) parse_error("invalid comparison at '%.15s'", pos); int64_t n; if (parse_num(&n)) { struct expr *e = mkexpr(op); e->a.prop = prop; e->b.num = n; return e; } return 0; } static struct expr * parse_timecmp() { enum prop prop; enum op op; if (token("atime")) prop = PROP_ATIME; else if (token("ctime")) prop = PROP_CTIME; else if (token("mtime")) prop = PROP_MTIME; else return parse_cmp(); op = parse_op(); if (!op) parse_error("invalid comparison at '%.15s'", pos); int64_t n; if (parse_num(&n) || parse_dur(&n)) { struct expr *e = mkexpr(op); e->a.prop = prop; e->b.num = n; return e; } return 0; } static struct expr * chain(struct expr *e1, enum op op, struct expr *e2) { struct expr *i, *j, *e; if (!e1) return e2; if (!e2) return e1; for (j = 0, i = e1; i->op == op; j = i, i = i->b.expr) ; if (!j) { e = mkexpr(op); e->a.expr = e1; e->b.expr = e2; return e; } else { e = mkexpr(op); e->a.expr = i; e->b.expr = e2; j->b.expr = e; return e1; } } static struct expr * parse_and() { struct expr *e1 = parse_timecmp(); struct expr *r = e1; while (token("&&")) { struct expr *e2 = parse_timecmp(); r = chain(r, EXPR_AND, e2); } return r; } static struct expr * parse_or() { struct expr *e1 = parse_and(); struct expr *r = e1; while (token("||")) { struct expr *e2 = parse_and(); r = chain(r, EXPR_OR, e2); } return r; } static struct expr * parse_cond() { struct expr *e1 = parse_or(); if (token("?")) { struct expr *e2 = parse_or(); if (token(":")) { struct expr *e3 = parse_cond(); struct expr *r = mkexpr(EXPR_COND); r->a.expr = e1; r->b.expr = e2; r->c.expr = e3; return r; } else { parse_error(": expected at '%.15s'", pos); } } return e1; } static struct expr * parse_expr(const char *s) { pos = (char *)s; struct expr *e = parse_cond(); if (*pos) parse_error("trailing garbage at '%.15s'", pos); return e; } static const char * basenam(const char *s) { char *r = strrchr(s, '/'); return r ? r + 1 : s; } static const char * extnam(const char *s) { char *r = strrchr(s, '/'); char *e = strrchr(s, '.'); if (!r || r < e) return e ? e + 1 : ""; return ""; } static const char * readlin(const char *p, const char *alt) { static char b[PATH_MAX]; ssize_t r = readlink(p, b, sizeof b - 1); if (r < 0 || (size_t)r >= sizeof b - 1) return alt; b[r] = 0; return b; } //// AA-tree implementation, adapted from https://github.com/ccxvii/minilibs struct idtree { long id; char *name; struct idtree *left, *right; int level; }; static struct idtree idtree_sentinel = { 0, 0, &idtree_sentinel, &idtree_sentinel, 0 }; static struct idtree * idtree_make(long id, char *name) { struct idtree *node = malloc(sizeof (struct idtree)); node->id = id; node->name = name; node->left = node->right = &idtree_sentinel; node->level = 1; return node; } char * idtree_lookup(struct idtree *node, long id) { if (node) { while (node != &idtree_sentinel) { if (id == node->id) return node->name; else if (id < node->id) node = node->left; else node = node->right; } } return 0; } static struct idtree * idtree_skew(struct idtree *node) { if (node->left->level == node->level) { struct idtree *save = node; node = node->left; save->left = node->right; node->right = save; } return node; } static struct idtree * idtree_split(struct idtree *node) { if (node->right->right->level == node->level) { struct idtree *save = node; node = node->right; save->right = node->left; node->left = save; node->level++; } return node; } struct idtree * idtree_insert(struct idtree *node, long id, char *name) { if (node && node != &idtree_sentinel) { if (id == node->id) return node; else if (id < node->id) node->left = idtree_insert(node->left, id, name); else node->right = idtree_insert(node->right, id, name); node = idtree_skew(node); node = idtree_split(node); return node; } return idtree_make(id, name); } //// static char * strid(long id) { static char buf[32]; snprintf(buf, sizeof buf, "%ld", id); return buf; } static char * groupname(gid_t gid) { char *name = idtree_lookup(groups, gid); if (name) return name; struct group *g = getgrgid(gid); if (g) { if ((int)strlen(g->gr_name) > gwid) gwid = strlen(g->gr_name); char *name = strdup(g->gr_name); groups = idtree_insert(groups, gid, name); return name; } return strid(gid); } static char * username(uid_t uid) { char *name = idtree_lookup(users, uid); if (name) return name; struct passwd *p = getpwuid(uid); if (p) { if ((int)strlen(p->pw_name) > uwid) uwid = strlen(p->pw_name); char *name = strdup(p->pw_name); users = idtree_insert(users, uid, name); return name; } return strid(uid); } #if defined(__linux__) || defined(__CYGWIN__) #include void scan_filesystems() { FILE *mtab; struct mntent *mnt; struct stat st; /* Approach: iterate over mtab and memorize st_dev for each mountpoint. * this will fail if we are not allowed to read the mountpoint, but then * we should not have to look up this st_dev value... */ mtab = setmntent(_PATH_MOUNTED, "r"); if (!mtab && errno == ENOENT) mtab = setmntent("/proc/mounts", "r"); if (!mtab) return; while ((mnt = getmntent(mtab))) { if (stat(mnt->mnt_dir, &st) < 0) continue; filesystems = idtree_insert(filesystems, st.st_dev, strdup(mnt->mnt_type)); }; endmntent(mtab); scanned_filesystems = 1; } #elif defined(__SVR4) #include void scan_filesystems() { FILE *mtab; struct mnttab mnt; struct stat st; mtab = fopen(MNTTAB, "r"); if (!mtab) return; while (getmntent(mtab, &mnt) == 0) { if (stat(mnt.mnt_mountp, &st) < 0) continue; filesystems = idtree_insert(filesystems, st.st_dev, strdup(mnt->mnt_fstype)); }; fclose(mtab); scanned_filesystems = 1; } #elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) || (defined(__APPLE__) && defined(__MACH__)) #include #include #include void scan_filesystems() { struct statfs *mnt; struct stat st; int i = getmntinfo(&mnt, MNT_NOWAIT); while (i-- > 0) { if (stat(mnt->f_mntonname, &st) < 0) continue; filesystems = idtree_insert(filesystems, st.st_dev, strdup(mnt->f_fstypename)); mnt++; }; scanned_filesystems = 1; } #elif defined(__NetBSD__) #include #include void scan_filesystems() { struct statvfs *mnt; struct stat st; int i = getmntinfo(&mnt, MNT_NOWAIT); while (i-- > 0) { if (stat(mnt->f_mntonname, &st) < 0) continue; filesystems = idtree_insert(filesystems, st.st_dev, strdup(mnt->f_fstypename)); mnt++; }; scanned_filesystems = 1; } #else #warning fstype lookup not implemented on this platform, keeping st_dev as number void scan_filesystems() { } #endif static char * fstype(dev_t devid) { if (!scanned_filesystems) scan_filesystems(); char *name = idtree_lookup(filesystems, devid); if (name) { if ((int)strlen(name) > fwid) fwid = strlen(name); return name; } return strid(devid); } static char * xattr_string(const char *f) { #ifdef __linux__ char xattr[1024]; int i, r; int have_xattr = 0, have_cap = 0, have_acl = 0; if (Lflag) r = listxattr(f, xattr, sizeof xattr); else r = llistxattr(f, xattr, sizeof xattr); if (r < 0 && errno == ERANGE) { /* just look at prefix */ r = sizeof xattr; xattr[r-1] = 0; } // ignoring ENOTSUP or r == 0 for (i = 0; i < r; i += strlen(xattr+i) + 1) if (strcmp(xattr+i, "security.capability") == 0) have_cap = 1; else if (strcmp(xattr+i, "system.posix_acl_access") == 0 || strcmp(xattr+i, "system.posix_acl_default") == 0) have_acl = 1; else have_xattr = 1; static char buf[4]; char *c = buf; if (have_cap) *c++ = '#'; if (have_acl) *c++ = '+'; if (have_xattr) *c++ = '@'; *c = 0; return buf; #else static char empty[] = ""; (void)f; return empty; // No support for xattrs on this platform. #endif } static ino_t count_entries(struct fileinfo *fi) { ino_t c = 0; struct dirent *de; DIR *d; if (Dflag) return fi->entries; if (!S_ISDIR(fi->sb.st_mode)) return 0; d = opendir(fi->fpath); if (!d) return 0; while ((de = readdir(d))) { if (de->d_name[0] == '.' && (!de->d_name[1] || (de->d_name[1] == '.' && !de->d_name[2]))) continue; c++; } closedir(d); return c; } int eval(struct expr *e, struct fileinfo *fi) { long v = 0; const char *s = ""; switch (e->op) { case EXPR_OR: return eval(e->a.expr, fi) || eval(e->b.expr, fi); case EXPR_AND: return eval(e->a.expr, fi) && eval(e->b.expr, fi); case EXPR_COND: return eval(e->a.expr, fi) ? eval(e->b.expr, fi) : eval(e->c.expr, fi); case EXPR_NOT: return !eval(e->a.expr, fi); case EXPR_PRUNE: prune = 1; return 1; case EXPR_PRINT: return 1; case EXPR_COLOR: fi->color = e->a.num; return 1; case EXPR_LT: case EXPR_LE: case EXPR_EQ: case EXPR_NEQ: case EXPR_GE: case EXPR_GT: case EXPR_ALLSET: case EXPR_ANYSET: switch (e->a.prop) { case PROP_ATIME: v = fi->sb.st_atime; break; case PROP_CTIME: v = fi->sb.st_ctime; break; case PROP_DEPTH: v = fi->depth; break; case PROP_DEV: v = fi->sb.st_dev; break; case PROP_ENTRIES: v = count_entries(fi); break; case PROP_GID: v = fi->sb.st_gid; break; case PROP_INODE: v = fi->sb.st_ino; break; case PROP_LINKS: v = fi->sb.st_nlink; break; case PROP_MODE: v = fi->sb.st_mode & 07777; break; case PROP_MTIME: v = fi->sb.st_mtime; break; case PROP_RDEV: v = fi->sb.st_rdev; break; case PROP_SIZE: v = fi->sb.st_size; break; case PROP_TOTAL: v = fi->total; break; case PROP_UID: v = fi->sb.st_uid; break; default: parse_error("unknown property"); } switch (e->op) { case EXPR_LT: return v < e->b.num; case EXPR_LE: return v <= e->b.num; case EXPR_EQ: return v == e->b.num; case EXPR_NEQ: return v != e->b.num; case EXPR_GE: return v >= e->b.num; case EXPR_GT: return v > e->b.num; case EXPR_ALLSET: return (v & e->b.num) == e->b.num; case EXPR_ANYSET: return (v & e->b.num) > 0; default: parse_error("invalid operator"); } case EXPR_CHMOD: return test_chmod(e->b.string, fi->sb.st_mode & 07777); case EXPR_TYPE: switch (e->a.filetype) { case TYPE_BLOCK: return S_ISBLK(fi->sb.st_mode); case TYPE_CHAR: return S_ISCHR(fi->sb.st_mode); case TYPE_DIR: return S_ISDIR(fi->sb.st_mode); case TYPE_FIFO: return S_ISFIFO(fi->sb.st_mode); case TYPE_REGULAR: return S_ISREG(fi->sb.st_mode); case TYPE_SOCKET: return S_ISSOCK(fi->sb.st_mode); case TYPE_SYMLINK: return S_ISLNK(fi->sb.st_mode); default: parse_error("invalid file type"); } case EXPR_STREQ: case EXPR_STREQI: case EXPR_GLOB: case EXPR_GLOBI: case EXPR_REGEX: case EXPR_REGEXI: switch (e->a.prop) { case PROP_FSTYPE: s = fstype(fi->sb.st_dev); break; case PROP_GROUP: s = groupname(fi->sb.st_gid); break; case PROP_NAME: s = basenam(fi->fpath); break; case PROP_PATH: s = fi->fpath; break; case PROP_TARGET: s = readlin(fi->fpath, ""); break; case PROP_USER: s = username(fi->sb.st_uid); break; case PROP_XATTR: s = xattr_string(fi->fpath); break; default: parse_error("unknown property"); } switch (e->op) { case EXPR_STREQ: return strcmp(e->b.string, s) == 0; case EXPR_STREQI: return strcasecmp(e->b.string, s) == 0; case EXPR_GLOB: return fnmatch(e->b.string, s, 0) == 0; case EXPR_GLOBI: return fnmatch(e->b.string, s, FNM_CASEFOLD) == 0; case EXPR_REGEX: case EXPR_REGEXI: return regexec(e->b.regex, s, 0, 0, 0) == 0; default: parse_error("invalid operator"); } default: parse_error("invalid operation %d, please file a bug.", e->op); } return 0; } int dircmp(char *a, char *b) { char *ea = strrchr(a, '/'); char *eb = strrchr(b, '/'); if (!ea) ea = a + strlen(a); if (!eb) eb = b + strlen(b); while (a != ea && b != eb && *a == *b) { a++; b++; } if (a == ea && b == eb) return 0; if (a == ea) return -1; if (b == eb) return 1; return *a - *b; } // taken straight from musl@a593414 int mystrverscmp(const char *l0, const char *r0) { const unsigned char *l = (const void *)l0; const unsigned char *r = (const void *)r0; size_t i, dp, j; int z = 1; /* Find maximal matching prefix and track its maximal digit * suffix and whether those digits are all zeros. */ for (dp = i = 0; l[i] == r[i]; i++) { int c = l[i]; if (!c) return 0; if (!isdigit(c)) dp = i+1, z = 1; else if (c != '0') z = 0; } if (l[dp] != '0' && r[dp] != '0') { /* If we're not looking at a digit sequence that began * with a zero, longest digit string is greater. */ for (j = i; isdigit(l[j]); j++) if (!isdigit(r[j])) return 1; if (isdigit(r[j])) return -1; } else if (z && dp < i && (isdigit(l[i]) || isdigit(r[i]))) { /* Otherwise, if common prefix of digit sequence is * all zeros, digits order less than non-digits. */ return (unsigned char)(l[i]-'0') - (unsigned char)(r[i]-'0'); } return l[i] - r[i]; } #define CMP(a, b) if ((a) == (b)) break; else if ((a) < (b)) return -1; else return 1 #define STRCMP(a, b) if (strcmp(a, b) == 0) break; else return (strcmp(a, b)); #define DIRCMP(a, b) { int r = dircmp(a, b); if (r == 0) break; else return r; }; #define VERCMP(a, b) if (mystrverscmp(a, b) == 0) break; else return (mystrverscmp(a, b)); int order(const void *a, const void *b) { struct fileinfo *fa = (struct fileinfo *)a; struct fileinfo *fb = (struct fileinfo *)b; char *s; for (s = ordering; *s; s++) { switch (*s) { // XXX use nanosecond timestamps case 'c': CMP(fa->sb.st_ctime, fb->sb.st_ctime); case 'C': CMP(fb->sb.st_ctime, fa->sb.st_ctime); case 'a': CMP(fa->sb.st_atime, fb->sb.st_atime); case 'A': CMP(fb->sb.st_atime, fa->sb.st_atime); case 'm': CMP(fa->sb.st_mtime, fb->sb.st_mtime); case 'M': CMP(fb->sb.st_mtime, fa->sb.st_mtime); case 's': CMP(fa->sb.st_size, fb->sb.st_size); case 'S': CMP(fb->sb.st_size, fa->sb.st_size); case 'i': CMP(fa->sb.st_ino, fb->sb.st_ino); case 'I': CMP(fb->sb.st_ino, fa->sb.st_ino); case 'd': CMP(fa->depth, fb->depth); case 'D': CMP(fb->depth, fa->depth); case 't': CMP("ZZZZAZZZZZZZZZZZ"[(fa->sb.st_mode >> 12) & 0x0f], "ZZZZAZZZZZZZZZZZ"[(fb->sb.st_mode >> 12) & 0x0f]); case 'T': CMP("ZZZZAZZZZZZZZZZZ"[(fb->sb.st_mode >> 12) & 0x0f], "ZZZZAZZZZZZZZZZZ"[(fa->sb.st_mode >> 12) & 0x0f]); case 'n': STRCMP(fa->fpath, fb->fpath); case 'N': STRCMP(fb->fpath, fa->fpath); case 'e': STRCMP(extnam(fa->fpath), extnam(fb->fpath)); case 'E': STRCMP(extnam(fb->fpath), extnam(fa->fpath)); case 'p': DIRCMP(fa->fpath, fb->fpath); case 'P': DIRCMP(fb->fpath, fa->fpath); case 'v': VERCMP(fa->fpath, fb->fpath); case 'V': VERCMP(fb->fpath, fa->fpath); default: STRCMP(fa->fpath, fb->fpath); } } return strcmp(fa->fpath, fb->fpath); } static void free_fi(struct fileinfo *fi) { if (fi) free(fi->fpath); free(fi); } //// AA-tree implementation, adapted from https://github.com/ccxvii/minilibs struct fitree { struct fileinfo *fi; struct fitree *left, *right; int level; }; static struct fitree fitree_sentinel = { 0, &fitree_sentinel, &fitree_sentinel, 0 }; static struct fitree * fitree_make(struct fileinfo *fi) { struct fitree *node = malloc(sizeof (struct fitree)); node->fi = fi; node->left = node->right = &fitree_sentinel; node->level = 1; return node; } static struct fitree * fitree_skew(struct fitree *node) { if (node->left->level == node->level) { struct fitree *save = node; node = node->left; save->left = node->right; node->right = save; } return node; } static struct fitree * fitree_split(struct fitree *node) { if (node->right->right->level == node->level) { struct fitree *save = node; node = node->right; save->right = node->left; node->left = save; node->level++; } return node; } struct fitree * fitree_insert(struct fitree *node, struct fileinfo *fi) { if (node && node != &fitree_sentinel) { int c = order(fi, node->fi); if (c == 0) return node; if (c < 0) node->left = fitree_insert(node->left, fi); else node->right = fitree_insert(node->right, fi); node = fitree_skew(node); node = fitree_split(node); return node; } return fitree_make(fi); } void fitree_walk(struct fitree *node, void (*visit)(struct fileinfo *)) { if (!node) return; if (node->left != &fitree_sentinel) fitree_walk(node->left, visit); visit(node->fi); if (node->right != &fitree_sentinel) fitree_walk(node->right, visit); } void fitree_free(struct fitree *node) { if (node && node != &fitree_sentinel) { fitree_free(node->left); fitree_free(node->right); free_fi(node->fi); free(node); } } //// static int intlen(intmax_t i) { int s; for (s = 1; i > 9; i /= 10) s++; return s; } static void print_mode(int mode) { putchar("0pcCd?bB-?l?s???"[(mode >> 12) & 0x0f]); putchar(mode & 00400 ? 'r' : '-'); putchar(mode & 00200 ? 'w' : '-'); putchar(mode & 04000 ? (mode & 00100 ? 's' : 'S') : (mode & 00100 ? 'x' : '-')); putchar(mode & 00040 ? 'r' : '-'); putchar(mode & 00020 ? 'w' : '-'); putchar(mode & 02000 ? (mode & 00010 ? 's' : 'S') : (mode & 00010 ? 'x' : '-')); putchar(mode & 00004 ? 'r' : '-'); putchar(mode & 00002 ? 'w' : '-'); putchar(mode & 01000 ? (mode & 00001 ? 't' : 'T') : (mode & 00001 ? 'x' : '-')); } static void fgbold() { printf("\033[1m"); } static void fg256(int c) { printf("\033[38;5;%dm", c); } static void fgdefault() { if (Gflag) printf("\033[0m"); } static void color_size_on(off_t s) { int c; if (!Gflag) return; if (s < 1024LL) c = 46; else if (s < 4*1024LL) c = 82; else if (s < 16*1024LL) c = 118; else if (s < 32*1024LL) c = 154; else if (s < 128*1024LL) c = 190; else if (s < 512*1024LL) c = 226; else if (s < 1024*1024LL) c = 220; else if (s < 700*1024*1024LL) c = 214; else if (s < 2*1048*1024*1024LL) c = 208; else if (s < 50*1024*1024*1024LL) c = 202; else c = 196; fg256(c); } static void print_human(intmax_t i) { double d = i; const char *u = "\0\0K\0M\0G\0T\0P\0E\0Z\0Y\0"; while (d >= 1024) { u += 2; d /= 1024.0; } color_size_on(i); if (!*u) printf("%5.0f", d); else if (d < 10.0) printf("%4.1f%s", d, u); else printf("%4.0f%s", d, u); fgdefault(); } static void print_shquoted(const char *s) { if (!Qflag || !strpbrk(s, "\001\002\003\004\005\006\007\010" "\011\012\013\014\015\016\017\020" "\021\022\023\024\025\026\027\030" "\031\032\033\034\035\036\037\040" "`^#*[]=|\\?${}()'\"<>&;\177")) { printf("%s", s); return; } putchar('\''); for (; *s; s++) if (*s == '\'') printf("'\\''"); else putchar(*s); putchar('\''); } void print_noprefix(struct fileinfo *fi) { if (fi->prefixl == 0 && fi->fpath[0]) print_shquoted(fi->fpath); else if (strlen(fi->fpath) > fi->prefixl + 1) /* strip prefix */ print_shquoted(fi->fpath + fi->prefixl + 1); else if (S_ISDIR(fi->sb.st_mode)) /* turn empty string into "." */ printf("."); else /* turn empty string into basename */ print_shquoted(basenam(fi->fpath)); } void analyze_format() { char *s; for (s = format; *s; s++) { if (*s == '\\') { s++; continue; } if (*s != '%') continue; switch (*++s) { case 'g': need_group++; break; case 'u': need_user++; break; case 'Y': need_fstype++; break; case 'x': need_xattr++; break; } switch (*s) { case 'd': case 'I': case 'p': case 'P': case 'f': /* all good without stat */ break; case 'e': if (!Dflag) need_stat++; break; default: need_stat++; } } for (s = ordering; *s; s++) { switch (*s) { case 'd': case 'D': case 'n': case 'N': case 'e': case 'E': case 'p': case 'P': case 'v': case 'V': /* all good without stat */ break; default: need_stat++; } } if (Gflag) need_stat++; } static void color_age_on(time_t t) { time_t age = now - t; int c; if (!Gflag) return; if (age < 0LL) c = 196; else if (age < 60LL) c = 255; else if (age < 60*60LL) c = 252; else if (age < 24*60*60LL) c = 250; else if (age < 7*24*60*60LL) c = 244; else if (age < 4*7*24*60*60LL) c = 244; else if (age < 26*7*24*60*60LL) c = 242; else if (age < 52*7*24*60*60LL) c = 240; else if (age < 2*52*7*24*60*60LL) c = 238; else c = 236; fg256(c); } static void color_name_on(int c, const char *f, mode_t m) { const char *b; if (!Gflag) return; if (c != -1) { fg256(c); return; } b = basenam(f); if (m & S_IXUSR) fgbold(); if (*b == '.' || (S_ISREG(m) && b[strlen(b)-1] == '~')) fg256(238); else if (S_ISDIR(m)) fg256(242); else if (S_ISREG(m) && (m & S_IXUSR)) fg256(154); else if (m == 0) /* broken link */ fg256(196); } static void print_urlquoted(unsigned char *s) { for (; *s; s++) if (*s > 127 || rfc3986[*s]) printf("%%%02x", *s); else putchar(*s); } static void hyperlink_on(char *fpath) { // OSC 8 hyperlink format if (Xflag) { printf("\033]8;;file://%s", host); if (*fpath != '/') { print_urlquoted((unsigned char *)basepath); putchar('/'); } print_urlquoted((unsigned char *)fpath); putchar('\007'); } } static void hyperlink_off() { if (Xflag) printf("\033]8;;\007"); } // unused format codes: BEHJKLNOQVWXZ achjoqrvwz void print_format(struct fileinfo *fi) { char *s; for (s = format; *s; s++) { if (*s == '\\') { switch (*++s) { case 'a': putchar('\a'); break; case 'b': putchar('\b'); break; case 'f': putchar('\f'); break; case 'n': putchar('\n'); break; case 'r': putchar('\r'); break; case 't': putchar('\t'); break; case 'v': putchar('\v'); break; case '0': putchar('\0'); break; // TODO: \NNN default: putchar(*s); } continue; } if (*s != '%') { putchar(*s); continue; } switch (*++s) { case '%': putchar('%'); break; case 's': if (!hflag) { color_size_on(fi->sb.st_size); printf("%*jd", intlen(maxsize), (intmax_t)fi->sb.st_size); fgdefault(); break; } /* FALLTHRU */ case 'S': print_human((intmax_t)fi->sb.st_size); break; case 'b': printf("%*jd", intlen(maxblocks), (intmax_t)fi->sb.st_blocks); break; case 'k': printf("%*jd", intlen(maxblocks/2), (intmax_t)fi->sb.st_blocks / 2); break; case 'd': printf("%*d", intlen(maxdepth), fi->depth); break; case 'D': printf("%*jd", intlen(maxdev), (intmax_t)fi->sb.st_dev); break; case 'R': printf("%*jd", intlen(maxrdev), (intmax_t)fi->sb.st_rdev); break; case 'i': printf("%*jd", intlen(maxino), (intmax_t)fi->sb.st_ino); break; case 'I': { int i; for (i = 0; i < fi->depth; i++) printf(" "); break; } case 'p': if (!sflag) { color_name_on(fi->color, fi->fpath, fi->sb.st_mode); hyperlink_on(fi->fpath); if (!fi->fpath[0] && S_ISDIR(fi->sb.st_mode)) print_shquoted("."); else print_shquoted(fi->fpath); hyperlink_off(); fgdefault(); break; } /* FALLTHRU */ case 'P': color_name_on(fi->color, fi->fpath, fi->sb.st_mode); hyperlink_on(fi->fpath); print_noprefix(fi); hyperlink_off(); fgdefault(); break; case 'l': if (S_ISLNK(fi->sb.st_mode)) { char target[PATH_MAX]; size_t j = strlen(fi->fpath); struct stat st; st.st_mode = 0; snprintf(target, sizeof target, "%s", fi->fpath); while (j && target[j-1] != '/') j--; ssize_t l = readlink(fi->fpath, target+j, sizeof target - j); if (l > 0 && (size_t)l < sizeof target - j) { target[j+l] = 0; if (Gflag) lstat(target[j] == '/' ? target + j : target, &st); } else { *target = 0; } color_name_on(-1, target, st.st_mode); hyperlink_on(target); print_shquoted(target + j); hyperlink_off(); fgdefault(); } break; case 'n': printf("%*jd", intlen(maxnlink), (intmax_t)fi->sb.st_nlink); break; case 'F': if (S_ISDIR(fi->sb.st_mode)) { putchar('/'); } else if (S_ISSOCK(fi->sb.st_mode)) { putchar('='); } else if (S_ISFIFO(fi->sb.st_mode)) { putchar('|'); } else if (S_ISLNK(fi->sb.st_mode)) { if (lflag) printf(" -> "); else putchar('@'); } else if (fi->sb.st_mode & S_IXUSR) { putchar('*'); } break; case 'f': color_name_on(fi->color, fi->fpath, fi->sb.st_mode); hyperlink_on(fi->fpath); print_shquoted(basenam(fi->fpath)); hyperlink_off(); fgdefault(); break; case 'A': case 'C': case 'T': case '\324': /* Meta-T */ { char tfmt[3] = "%\0\0"; char buf[256]; if (*s == '\324') *s = Tflag; time_t t = (*s == 'A' ? fi->sb.st_atime : *s == 'C' ? fi->sb.st_ctime : fi->sb.st_mtime); s++; if (!*s) break; color_age_on(t); if (*s == '-') { long diff = now - t; printf("%4ldd%3ldh%3ldm%3lds", diff / (60*60*24), (diff / (60*60)) % 24, (diff / 60) % 60, diff % 60); } else { tfmt[1] = *s; strftime(buf, sizeof buf, tfmt, localtime(&t)); printf("%s", buf); } fgdefault(); break; } case 'm': printf("%04o", (unsigned int)fi->sb.st_mode & 07777); break; case 'M': print_mode(fi->sb.st_mode); break; case 'y': putchar("0pcCd?bBf?l?s???"[(fi->sb.st_mode >> 12) & 0x0f]); break; case 'g': printf("%*s", -gwid, groupname(fi->sb.st_gid)); break; case 'G': printf("%*ld", intlen(maxgid), (long)fi->sb.st_gid); break; case 'u': printf("%*s", -uwid, username(fi->sb.st_uid)); break; case 'U': printf("%*ld", intlen(maxuid), (long)fi->sb.st_uid); break; case 'e': printf("%ld", (long)count_entries(fi)); break; case 't': printf("%jd", (intmax_t)fi->total); break; case 'Y': printf("%*s", -fwid, fstype(fi->sb.st_dev)); break; case 'x': printf("%*s", -maxxattr, fi->xattr); break; default: putchar('%'); putchar(*s); } } } static int initial; int callback(const char *fpath, const struct stat *sb, int depth, ino_t entries, off_t total) { struct fileinfo *fi = malloc(sizeof (struct fileinfo)); fi->fpath = strdup(fpath); fi->prefixl = prefixl; fi->depth = Bflag ? (depth > 0 ? bflag_depth + 1 : 0) : depth; fi->entries = entries; fi->total = total; fi->color = current_color; memcpy((char *)&fi->sb, (char *)sb, sizeof (struct stat)); if (expr && !eval(expr, fi)) { free_fi(fi); return 0; } if (need_xattr) { strncpy(fi->xattr, xattr_string(fi->fpath), sizeof fi->xattr); if (strlen(fi->xattr) > maxxattr) maxxattr = strlen(fi->xattr); } else memset(fi->xattr, 0, sizeof fi->xattr); if (Uflag) { print_format(fi); free_fi(fi); return 0; } else if (Bflag) { if (initial && fi->depth == 0) { root = fitree_insert(root, fi); } else if (!initial && fi->depth == bflag_depth + 1) { new_root = fitree_insert(new_root, fi); } else { free_fi(fi); return 0; } } else { // add to the tree, note that this will eliminate duplicate files root = fitree_insert(root, fi); } if (depth > maxdepth) maxdepth = depth; if (need_stat) { if (fi->sb.st_nlink > maxnlink) maxnlink = fi->sb.st_nlink; if (fi->sb.st_size > maxsize) maxsize = fi->sb.st_size; if (fi->sb.st_rdev > maxrdev) maxrdev = fi->sb.st_rdev; if (fi->sb.st_dev > maxdev) maxdev = fi->sb.st_dev; if (fi->sb.st_blocks > maxblocks) maxblocks = fi->sb.st_blocks; if (fi->sb.st_uid > maxuid) maxuid = fi->sb.st_uid; if (fi->sb.st_gid > maxuid) maxgid = fi->sb.st_gid; if (fi->sb.st_ino > maxino) maxino = fi->sb.st_ino; } /* prefetch user/group/fs name for correct column widths. */ if (need_user) username(fi->sb.st_uid); if (need_group) groupname(fi->sb.st_gid); if (need_fstype) fstype(fi->sb.st_dev); return 0; } // lifted from musl nftw. struct history { struct history *chain; dev_t dev; ino_t ino; int level; off_t total; }; static int recurse(char *path, struct history *h, int guessdir) { size_t l = strlen(path), j = l && path[l-1] == '/' ? l - 1 : l; struct stat st; struct history new; int r; ino_t entries; const char *fpath = *path ? path : "."; int resolve = Lflag || (Hflag && !h); int root = (path[0] == '/' && path[1] == 0); if (Bflag && h && h->chain) return 0; if (need_stat) guessdir = 1; if (guessdir && (resolve ? stat(fpath, &st) : lstat(fpath, &st) < 0)) { if (resolve && (errno == ENOENT || errno == ELOOP) && !lstat(fpath, &st)) { /* ignore */ } else if (!h) { /* warn for toplevel arguments */ fprintf(stderr, "lr: cannot %sstat '%s': %s\n", resolve ? "" : "l", fpath, strerror(errno)); status = 1; return -1; } else if (errno != EACCES) { return -1; } } if (guessdir && xflag && h && st.st_dev != h->dev) return 0; new.chain = h; new.level = h ? h->level + 1 : 0; if (guessdir) { new.dev = st.st_dev; new.ino = st.st_ino; new.total = st.st_blocks / 2; } entries = 0; if (!Dflag) { prune = 0; r = callback(path, &st, new.level, 0, 0); if (prune) return 0; if (r) return r; } if (guessdir) for (; h; h = h->chain) { if (h->dev == st.st_dev && h->ino == st.st_ino) return 0; h->total += st.st_blocks / 2; } if (guessdir && S_ISDIR(st.st_mode)) { DIR *d = opendir(fpath); if (d) { struct dirent *de; while ((de = readdir(d))) { if (de->d_name[0] == '.' && (!de->d_name[1] || (de->d_name[1] == '.' && !de->d_name[2]))) continue; entries++; if (strlen(de->d_name) >= PATH_MAX-l) { errno = ENAMETOOLONG; closedir(d); return -1; } if (j > 0 || root) { path[j] = '/'; strcpy(path + j + 1, de->d_name); } else { strcpy(path, de->d_name); } #if defined(DT_DIR) && defined(DT_UNKNOWN) int guesssubdir = de->d_type == DT_DIR || (de->d_type == DT_LNK && resolve) || de->d_type == DT_UNKNOWN; #else int guesssubdir = 1; #endif if ((r = recurse(path, &new, guesssubdir))) { closedir(d); return r; } } closedir(d); } else if (errno != EACCES && errno != ENOTDIR) { return -1; } } path[l] = 0; if (Dflag && (r = callback(path, &st, new.level, entries, new.total))) return r; return 0; } void tree_recurse(struct fileinfo *fi) { if (S_ISDIR(fi->sb.st_mode)) { char path[PATH_MAX]; strcpy(path, fi->fpath); bflag_depth = fi->depth; recurse(path, 0, 0); } } int traverse_file(FILE *file) { char *line = 0; size_t linelen = 0; struct stat st; ssize_t rd; prefixl = 0; while (1) { errno = 0; rd = getdelim(&line, &linelen, input_delim, file); if (rd == -1) { if (errno != 0) return -1; break; } if (rd > 0 && line[rd-1] == input_delim) // strip delimiter line[rd-1] = 0; if (Lflag ? stat(line, &st) : lstat(line, &st) < 0) continue; callback(line, &st, 0, 0, 0); } free(line); return 0; } int traverse(const char *path) { char pathbuf[PATH_MAX + 1]; if (path[0] == '-' && !path[1]) return traverse_file(stdin); if (path[0] == '@') { FILE *f = fopen(path + 1, "r"); if (!f) parse_error("can't open input file '%s'", path + 1); int r = traverse_file(f); fclose(f); return r; } prefixl = strlen(path); while (prefixl && path[prefixl-1] == '/') prefixl--; if (prefixl > PATH_MAX) { errno = ENAMETOOLONG; return -1; } memcpy(pathbuf, path, prefixl + 1); pathbuf[prefixl + 1] = 0; return recurse(pathbuf, 0, 1); } static char timeflag(char *arg) { if ((arg[0] == 'A' || arg[0] == 'C' || arg[0] == 'M') && !arg[1]) return arg[0] == 'M' ? 'T' : arg[0]; fprintf(stderr, "%s: -T only accepts A, C or M as argument.\n", argv0); exit(2); } int main(int argc, char *argv[]) { int i, c; format = default_format; ordering = default_ordering; argv0 = argv[0]; now = time(0); status = 0; setlocale(LC_ALL, ""); while ((c = getopt(argc, argv, "01ABC:DFGHLQST:UXde:f:lho:st:x")) != -1) switch (c) { case '0': format = zero_format; input_delim = 0; Qflag = 0; break; case '1': expr = chain(parse_expr("depth > 0 ? prune : print"), EXPR_AND, expr); break; case 'A': expr = chain(expr, EXPR_AND, parse_expr("name =~ \"^\\.\" && path != \".\" ? prune : print")); break; case 'B': Bflag++; Dflag = 0; Uflag = 0; need_stat++; break; case 'C': if ((unsigned int)Cflag < sizeof Cflags / sizeof Cflags[0]) { Cflags[Cflag++] = optarg; } else { fprintf(stderr, "%s: too many -C\n", argv0); exit(111); } Gflag += 2; // force color on break; case 'D': Dflag++; Bflag = 0; break; case 'F': format = type_format; break; case 'G': Gflag++; break; case 'H': Hflag++; break; case 'L': Lflag++; break; case 'Q': Qflag++; break; case 'S': Qflag++; format = stat_format; break; case 'T': Tflag = timeflag(optarg); break; case 'U': Uflag++; Bflag = 0; break; case 'X': Xflag++; break; case 'd': expr = chain(parse_expr("type == d && prune || print"), EXPR_AND, expr); break; case 'e': expr = chain(expr, EXPR_AND, mkstrexpr(PROP_NAME, EXPR_REGEX, optarg, 0)); break; case 'f': format = optarg; break; case 'h': hflag++; break; case 'l': lflag++; Qflag++; format = long_format; break; case 'o': ordering = optarg; break; case 's': sflag++; break; case 't': need_stat++; // overapproximation expr = chain(expr, EXPR_AND, parse_expr(optarg)); break; case 'x': xflag++; break; default: fprintf(stderr, "Usage: %s [-0|-F|-l [-TA|-TC|-TM]|-S|-f FMT] [-B|-D] [-H|-L] [-1AGQdhsx]\n" " [-U|-o ORD] [-e REGEX]* [-t TEST]* [-C [COLOR:]PATH]* PATH...\n", argv0); exit(2); } if (isatty(1)) { Qflag = 1; } else { if (Gflag == 1) Gflag = 0; if (Xflag == 1) Xflag = 0; } analyze_format(); if (Uflag) { maxnlink = 99; maxsize = 4*1024*1024; maxblocks = maxsize / 512; maxrdev = maxdev = 255; maxuid = maxgid = 65536; maxino = 9999999; maxdepth = 99; uwid = gwid = fwid = 8; } if (Xflag) { basepath = realpath(".", 0); if (!basepath) { fprintf(stderr, "%s: cannot resolve base path, disabling -X: %s\n", argv0, strerror(errno)); Xflag = 0; } if (gethostname(host, sizeof host) == 0) { // termination not posix guaranteed host[sizeof host - 1] = 0; } else { // ignore errors, use no host *host = 0; } } for (i = 0; i < Cflag; i++) { char *r; errno = 0; current_color = strtol(Cflags[i], &r, 10); if (errno == 0 && r != Cflags[i] && *r == ':') { current_color = current_color & 0xff; traverse(r + 1); } else { current_color = 2; traverse(Cflags[i]); } } current_color = -1; initial = 1; if (!Cflag && optind == argc) { traverse(""); } else { for (i = optind; i < argc; i++) traverse(argv[i]); } initial = 0; if (Bflag) { while (root) { fitree_walk(root, print_format); fitree_walk(root, tree_recurse); fitree_free(root); root = new_root; new_root = 0; } } else if (!Uflag) { fitree_walk(root, print_format); // no need to destroy here, we are done } return status; } lr-1.2/lr.png000066400000000000000000000272601320360616700131270ustar00rootroot00000000000000PNG  IHDR|tsBITO.hIDATx1嶱),@UL2%(q)T5L؉/qpU)B.`f~Ǣ/&@9J"Xap1jiRFg_Lܐdw?xd#T?j&!i6QڲMJjOC/X*L5LoUҿ'AdϚÞR2'Vg%X$jKqiʟ=B%aWUZ~ ]p,5h%`&khv[xP_/\GGSH=0Nd9)opamlݡeQ~@I,[,SR9ړS`_fBB_!f%TV.I E{cK+~wnDh]9w'~\pMzzI? ?3Kv[tjIx|x!(wFe ?$kGe۰ތ;kv̼̙ mg]=?Gbݻ"U*<)v\T6MqQ|cTv?ɡ<\YD?To-7`_]pjo9OsܓL..a|z=jh3qxJ!1g]F%[OY}G_90dAKu,-T.VpW&|J?0U2>11-̪[W$'z$eZ'sQJXc+홵;Lǩw-J>k)w6q50g*٧jAQ%ÜAJu^ ٨cuߗo._Ը+cj@WI=r6ke 8{K: HyU=Z{)t]Σ۷vnsԵnvOgH}4ݵ̯r,y*ǧnZ&iy7;JWy;Fmo0泌C(,e}|Nv_I(ToU:dg ye[T1n,VH_駖}'~!@7@?طu'k͖5i7 bosԕmط) l`f-j%m()|ط,goc=ct=ľլM nn[4}^oþmp7 %|oþ(۲o&osҏ}WľM}ӿ}[,s9H?_{七}eo1k¾mAn`4طc/oߖfK,fm}}nMKQJ[-]֓۰oO+m md~"0C n"oʷ?e6G]6<}[kfo Gɧaz^J ط0RJ|5}J۷moڷ1?c߆}`SՙyL -da{ mv"cf(o3N̾gOjߦ<Ϗ(|mAͻg~ore3 žmZo^޾0xO-ƾmۥRqۙ\`߆}[sGf~t)ľmScv22NH?tΥwJ֓䇏rv_9Hþmc<ʾK?moEuoImRi(82}mJ';Ǘobۖ ٷH?\n}O/wb?o~}6>cֵl^on-mL2EZQ\u?JgjQװo};W/)yHo,mQmH;طUu_O8J6fB}^N/,m;4.E٣삎`vS6z|J6H!mo3ޥAD02NH?tm7O/xEc"bmvmqAUmeKU.o}>M94߹&W<ʾK?mwo 'gii6l&4H nkd=R&fxAnMT mڷ9 ݸ>8LoS۷Dٗ_[Jp*mg!]3pt +p׳oJK7طmf29ْo;o7o?ޮh Ǿmۥj[mA`vSEϮS~c]?mܾ-۞u}z~!@7@?[m(Ȩ,^H6ݪQue{;3٧aطY ?ڃ?_ 0()|ط,'o ziIy@/RL{U! }}[y}`@~ۮgf` =BݺOM쏦mc>ط־2`9%#g=U}ۄm/I ~%mMIxPU6]EjN=r\о-ZcO7]UՅ}: mS۷dN|75?nQqV4y"nV moK$[O$yR-Ǿq^۷Y~],=^`sŝ7qxgm%[Nh/'Kfi9ݢ|r;+oK$84fDYRNU.oٷ10LT0{"Ar,sgzxǾm܎_ vnݧW]Cp}W\-}5۲|W^o |[jQfEzJ?m4}=Tα۾K~1}EC}p;5}.miՇXD^+/ݩ꾥>b֫?`vl.>d/ J H8q^JP3\_`{)x puo#+:O?==?);X_7olTCBl:E6JJO2(ㅢ٨RIJ|% }]1_q_]p uR(@ҏ}5f"pDWN# #.Qƞkr$|o}IinɒG,reTn-ܢs;cf(fo2-|Q}<,Ң,>zϵOz`"+pyn;5.i&;oRv6طco;pw׸'ΊlAY@_#OեdD>DC"iBzDINsBS-ILX9L_)0^]#Wv/f̭˺D#Tδh8 ZKDoMrQhI3NH?tzo Z8|BQV=*E5}>U]ӂ}p(4龾!s_jT(? /}Ϟ2WQY1}T4ڷ\WDdrFnH3zK]wqpc߆}h,JK }t<0LeV24 ۰o; o"JE)jI%y$߹Ohߦa'W I?mu?5׵veW!m T }u2u;۔6,mY,mRKjWξ-i$f cTm:Fm JureufrݾmWV&izcZiF0ٹzx""\3klbNɯWH?K\KT->lU5s75[۬l}ݾq޽K^/Q“bEoJ:.K65[c68V_ۂRXA]r|7EU?9ZqH?mط"pDWN# #ܾ-^:$|oþ@Z*._N)mKVXTv~%m;~k=<COdFg^+p7_v5ܧo}[}>]:)R/ʑRଳ~3$TٔK4';eҒJ^dsMR 52X;2.%P9ҢUyZKDo=N^v,#@ >H? 'yZ8|BQV^R=G]߽fߧ Q`f-% M/I~ 1iCWQ•r,c+~Vt{=ۀY Zs tUF _HuKպ~Ɵod2tAǾ XFRmk>طav D@|lFZ䤒Oak\C#ط?JF#vXؾu)ٽ2uDoJ.t+p=vSq>}e& $`ž-žMj_rFCЍuѾMD2d#pՈcTWJ+*m,jmVN/X)JveEfճZxu'w,|EEx)-vK Kg$"olecW?%3طY gAm?^_$9jO1*~V:xY3+sϢطMK}[U YGNOo`žM2-~7vai /\Ҿ>m~mطoQEO*I(%+uMkf|`t<7lH?mu_oc}Q}rtDoJxn'~ȁ}0ao w5(j7iP6a?طeطeue.˱/ʗڮ3طI]^1kOk}Q8ʦ\Z9!)SPB^ZYNH_)0^]#Wv/(s벮.Q#C,Z_Mi]Z\͟<|&nJ۳H?fH? mH%7v_lUWQ_oOѧ6v"Q`f-e|2?&GըRJ9ƨR^!JKgO1m&*4ڷ\WwE[)E5sue| ~۰oEzr0Le&[6(rX6u›oei8x$;i۶yrPH?mu?57Tږ$ mt)lA~wþmgݿy;۪m طcobotWV_;ʑQ}$ @7 mo_0z]8Oi\5b3(Օʡ-6U:c@o+eە"Y-2 U*a}EE*-, mg~>H? 'yZ8 F JTjqc]sOWNmY{Ф}C~"( eDwҹS7f֬@;nѾ-િL7%:y7arҏ}m(2=g\U&,Len%|oþ@ $;Txlv[6Z}Z6.wpǾQ2a߶2;Fݯ6۲mq f~wþmO?sپ$`ž-žMj%K,~zKF(u۶K3nǂ}mCJveeg3W_TUGX`0}[]69.>H?\K-l)Mqy|_5M( Ote<6.طͦ'r^_(JZت&QّC7332oo5wA,X1:Z4]vc߆}h,Jjyl,9d(c9SٷɮmJ?_a߶_qdz}E}m;}?,6},Ǣ٨AGݟľmTc !KV[*|4!{ ɨS=©۲SZž8 Ѻ$%Z8 F[5*[QsO۬ҠIuOJ^/Q“bEa)tq|zH3-UF ꂻf,W{STJ9ЛN#طa6RC_90d(c9ڷYXv;O i&̦JcÕ,9۪]2\TuH?mu?5a-I2:K?_b߶\׸#Wmp"obߖb&xw2Nʱrd<}r]K`߆}tR]Yٙ z{f5x#ܾ-{?AD0dx~0WR;?mAy|a}ݻxcVnFe ?$*T}QHFT}ݾ-BlTqQ%N>R=M`f7ٷy]W\E+e9ڛ8Loþm4륏r:aɪ>֒W->طav SD@q2>$U=%k6icqχJ?mu?5waI:~ȁ}κs]{F5W@rsobߖb&xw2Nʱjܟ6ސ/ ,f28.j'TٔK*#'jHXYNH_).^]#Wv/ḙ˺D#TTih:RKHZE6t D<|v`eS&4` ,?u$0`p;owط-|k3< }[iɦ,hQ} t1ǾUtvC~۰om"!^G }Lm)uS:L->(ط?JF#vBmrxOJ?_{mg߹qQNHnmX۲`ߖ=_[W(rr? &Ǿ-hx^dRC s!#ݲ峷Y?g0}fZZ8|ilU%F݌Gx[$nи秧_,۬˨RI#>Kx3}/ %طͦK}*22Zڿ,W{_efOpC6FcN |0FDC˩1 #iU kTzQ._T>J"A^$L_fIcf(oYce qG](gkL"'|W<ط;KҞV`ž-ž-;/ݵ\$X Eu_Y41˻S\p֏}ۂ}[mƫkd{}}dsz/JSڒ]DTb_] 5l#1 H?\ <|~:nw/`1O{"l[$ߥxatZK`E?إsfXr2f}[)rwW/}I{%o}}Xoq3?{5.*xOxO%B6o6?sTԞ{֯D>w>]xw2NʱrJ`6ʿbM)VԳ\T:~2d+9}c/e۫kǭwD4iPƛx^ˍoi3NU@.N v`-:طe֫(`߶0}[Uoyh>mۘ3_7Y^Qz۪TJW G(Y}h J5|biߦ<9,z՗QJI92:c߆}oLhVz,ԝ!~֯7o+̂}`f>8о͘YwP J?XF۶}[jfNdR=6ω'yطڇ}>m>Qz.mط5g~oӏpf8Ho`ŒSUwCZ{Z}uo3FӾWo"?/ط< `Vz`fokZnr}ۂ}So[o}[âˬm.+طa߶۪!ooY_3e֯7o+̂}`f>}[RmfX!\lzѷL{}[%䅚t?N'3x1b|7T*f~۰o[okS-Wo0?طa֜M?;طYY0}\&8U7oCov\ľmq妓یQطSKn2WSطU6>&oKҺ/`f7ґcf-5E1 ٷb΂~鿌}[uǾmطUCoSjGl-#3W&'okcf۳:طٯnk߶q ~Ǿm;ط%[osHlmM}1}[G?ط5^*c.\ľطu`ߦ!mF ^F}H-LqJn`߆1`H?i^J'Xí!^n_^(Ȩ,ط-.KrԮү%C>޳1=/M9>5Q9R|--J<[J۔q:T{AImu F90YYt-+}'+ץ,6be 5B}[j^[%lb{3W~|/_a֒Zط5 }j6$㜔ciMg RrbFQ>⿓dzPB^6ZYZe+{6oRUxu\ٹXѲ^e]]*gZZ"Xu^kSKqOիG*!%9LqJ!-v8F,=b2y8e`p;or{˗۳r˺omuF]nK{I?mu?Vk% @]u}.f,:__^@oJł o1b鼲8B`߶㼾z}#|\EO e%ףaطUe='`߶诉ǿoNDY(}-;U8 {u|>=r=Tێ6-y^޾2IBa `߆ñdN0uRBOӏJtIvcru` DO)^]#?n#t%Cҝ+;BoXj¼~8<|~:^yxGoOo?bfro#EQ>_o&!Oطůn?~`fmmc4,X`kfrK3m}vBi[?IX Q e߾q}$S~ےᡵNCyKZ±8یQ8طmjm[چDhݷ8OXo2Ǿm?"sk}?m{jJb}%oLL_ bt/+j G E,kˮ ֗Y1־=~WD.fǩ⃉տj|&2-YZUoKJg$糘Uь_>\AHхWi6`B>vat$v[)hM O(')~20׬_A/ItP`I^$!d1Cz9W:f0oKƑ[6-&闦Ͱ'[~؇\GIENDB`