├── strverscmp.c ├── .gitignore ├── tree.lsm ├── INSTALL ├── TODO ├── doc ├── global_info ├── xml.dtd └── tree.1 ├── hash.c ├── Makefile ├── unix.c ├── filter.c ├── xml.c ├── info.c ├── json.c ├── html.c ├── list.c ├── file.c ├── tree.h ├── README ├── LICENSE ├── color.c ├── CHANGES └── tree.c /strverscmp.c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Old-Man-Programmer/tree/HEAD/strverscmp.c -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore the things we would normally "clean": 2 | *.o 3 | tree 4 | *~ 5 | -------------------------------------------------------------------------------- /tree.lsm: -------------------------------------------------------------------------------- 1 | Begin3 2 | Title: tree 3 | Version: 2.2.1 4 | Entered-date: 11/25/2024 5 | Description: Depth-indented directory listing program with hacked in dir- 6 | colors and ANSI-line support. Includes HTML output support. 7 | Keywords: tree ls dircolors 8 | Author: Steve.Baker.llc@gmail.com (Steve Baker) 9 | Maintained-by: Steve.Baker.llc@gmail.com (Steve Baker) 10 | Primary-size: https://oldmanprogrammer.net/source.php?dir=projects/tree 11 | 65505 tree-2.2.1.tgz 12 | Platforms: Linux, FreeBSD, OS X, HP/UX, OS/2, Others. 13 | Copying-policy: GPLv2 14 | End 15 | -------------------------------------------------------------------------------- /INSTALL: -------------------------------------------------------------------------------- 1 | Installation instructions: 2 | 3 | 1. Edit the Makefile for your OS. Comment out the Linux options and un-comment 4 | the options for your OS. 5 | 2. Type: make 6 | 3. Type: make install 7 | 4. Enjoy colorful directory trees. 8 | 9 | I cannot test on non-Linux machines, so please feel free to contribute 10 | porting information, bug reports, compile options, patches, etc for porting to 11 | other OS's to Steve.Baker.llc@gmail.com. 12 | 13 | I would also welcome any localization efforts, particularly translating the 14 | man page to other languages. And of course feel free to suggest options and 15 | improvements you would like to see in tree. 16 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | Should do: 2 | 3 | - Use stdint.h and inttypes.h to standardize the int sizes and format strings. 4 | Not sure how cross-platform this would be. 5 | 6 | - Add --DU option to fully report disk usage, taking into account files that 7 | are not displayed in the output. 8 | 9 | - Make wide character support less of a hack. 10 | 11 | - Fully support HTML colorization properly and allow for an external stylesheet. 12 | 13 | - Might be nice to prune files by things like type, mode, access/modify/change 14 | time, uid/gid, etc. ala find. 15 | 16 | - Just incorporate the stat structure into _info, since we now need most of 17 | the structure anyway. 18 | 19 | - Move as many globals into a struct or structs to reduce namespace pollution. 20 | 21 | Maybe do: 22 | 23 | - With the addition of TREE_COLORS, add some custom options to perhaps colorize 24 | metadata like the permissions, date, username, etc, and change the color of 25 | the tree lines and so on. 26 | 27 | - Refactor color.c. 28 | 29 | - Output BSON on stddata instead of JSON. 30 | -------------------------------------------------------------------------------- /doc/global_info: -------------------------------------------------------------------------------- 1 | # An example of a possible global info file. 2 | /bin 3 | Binaries needed before a /usr is mounted (single user mode). 4 | /boot 5 | Kernels / boot loader files 6 | /dev 7 | Device files 8 | /etc 9 | System-wide configuration files. 10 | /etc/rc.d/ 11 | Slackware init scripts 12 | /etc/X11 13 | X windows system configuration files. 14 | /home 15 | Typically user home directories 16 | */lost+found 17 | Contains files recovered by fsck in the event of file-system damage 18 | /lib 19 | /lib64 20 | System libraries (essential for binaries in /bin, /sbin) 21 | /media 22 | /mnt 23 | Spare, usually temporary mount points, usually for removable media 24 | (/media) 25 | /opt 26 | Optional local packages 27 | /proc 28 | System & process information virtual pseudo-filesystem (man 5 proc) 29 | /root 30 | Root's home directory 31 | /run 32 | System runtime files 33 | /sbin 34 | System (admin) binaries needed before /usr is mounted 35 | /srv 36 | Files served by the system (e.g. web service) 37 | /sys 38 | System information pseudo-filesystem 39 | /tmp 40 | System wide temporary files, not guaranteed to be preserved between 41 | reboots 42 | /usr 43 | Read-only data, historically a secondary hierarchy to be mounted after 44 | '/', so binaries / data that might not be available during boot. 45 | /usr/bin 46 | User binaries, not needed at boot. 47 | /usr/include 48 | Standard include libraries (C, C++) 49 | /usr/lib 50 | /usr/lib64 51 | Libraries (for binaries in /usr/bin, /usr/sbin) 52 | /usr/local 53 | A tertiary hierarchy for local system additions 54 | /usr/sbin 55 | Non-essential system binaries (e.g. daemons) 56 | /usr/share 57 | Shared data 58 | /usr/src 59 | Source code (e.g. kernel source) 60 | /usr/X11R6 61 | X windows (Version 11, release 6) 62 | /var 63 | Variable files, files that will likely change during run-time. 64 | /var/cache 65 | Application cache data. 66 | /var/lib 67 | State information generated by programs such as databases and 68 | package managers 69 | /var/lib/pkgtools/packages 70 | Slackware package database 71 | /var/lib/pkgtools/setup 72 | Slackware setup/configuration scripts 73 | /var/lock 74 | Lock files 75 | /var/log 76 | log files (syslogd, klogd, httpd, other daemons) 77 | /var/mail 78 | User mailboxes 79 | /var/spool 80 | Spool directories for tasks waiting to be processed (cron, 81 | mail, print files) (depreciates /var/spool/mail) 82 | /var/tmp 83 | Temporary files to be preserved across reboots 84 | /proc/1 85 | Each one of these subdirectories contains files and subdirectories exposing 86 | information about the process with the corresponding process ID. Underneath 87 | each of the /proc/[pid] directories, a task subdirectory contains subdirectories 88 | of the form task/[tid], which contain corresponding information about each of the 89 | threads in the process, where tid is the kernel thread ID of the thread. The 90 | /proc/[pid] subdirectories are visible when iterating through /proc with getdents(2) 91 | (and thus are visible when one uses ls(1) to view the contents of /proc). 92 | /proc/self 93 | When a process accesses this magic symbolic link, it resolves to the process's own /proc/[pid] directory. 94 | /proc/thread-self 95 | When a thread accesses this magic symbolic link, it resolves to the process's own /proc/self/task/[tid] directory. 96 | # Could go on to add all of man 5 proc for example here. -------------------------------------------------------------------------------- /doc/xml.dtd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 30 | 43 | 55 | 67 | 79 | 91 | 103 | 115 | 127 | -------------------------------------------------------------------------------- /hash.c: -------------------------------------------------------------------------------- 1 | /* $Copyright: $ 2 | * Copyright (c) 1996 - 2024 by Steve Baker (steve.baker.llc@gmail.com) 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 | */ 18 | #include "tree.h" 19 | 20 | /* Faster uid/gid -> name lookup with hash(tm)(r)(c) tables! */ 21 | #define HASH(x) ((x)&255) 22 | struct xtable *gtable[256], *utable[256]; 23 | 24 | #define inohash(x) ((x)&255) 25 | struct inotable *itable[256]; 26 | 27 | char *uidtoname(uid_t uid) 28 | { 29 | struct xtable *o, *p, *t; 30 | struct passwd *ent; 31 | char ubuf[32]; 32 | int uent = HASH(uid); 33 | 34 | for(o = p = utable[uent]; p ; p=p->nxt) { 35 | if (uid == p->xid) return p->name; 36 | else if (uid < p->xid) break; 37 | o = p; 38 | } 39 | /* Not found, do a real lookup and add to table */ 40 | t = xmalloc(sizeof(struct xtable)); 41 | if ((ent = getpwuid(uid)) != NULL) t->name = scopy(ent->pw_name); 42 | else { 43 | snprintf(ubuf,30,"%d",uid); 44 | ubuf[31] = 0; 45 | t->name = scopy(ubuf); 46 | } 47 | t->xid = uid; 48 | t->nxt = p; 49 | if (p == utable[uent]) utable[uent] = t; 50 | else o->nxt = t; 51 | return t->name; 52 | } 53 | 54 | char *gidtoname(gid_t gid) 55 | { 56 | struct xtable *o, *p, *t; 57 | struct group *ent; 58 | char gbuf[32]; 59 | int gent = HASH(gid); 60 | 61 | for(o = p = gtable[gent]; p ; p=p->nxt) { 62 | if (gid == p->xid) return p->name; 63 | else if (gid < p->xid) break; 64 | o = p; 65 | } 66 | /* Not found, do a real lookup and add to table */ 67 | t = xmalloc(sizeof(struct xtable)); 68 | if ((ent = getgrgid(gid)) != NULL) t->name = scopy(ent->gr_name); 69 | else { 70 | snprintf(gbuf,30,"%d",gid); 71 | gbuf[31] = 0; 72 | t->name = scopy(gbuf); 73 | } 74 | t->xid = gid; 75 | t->nxt = p; 76 | if (p == gtable[gent]) gtable[gent] = t; 77 | else o->nxt = t; 78 | return t->name; 79 | } 80 | 81 | /* Record inode numbers of followed sym-links to avoid re-following them */ 82 | void saveino(ino_t inode, dev_t device) 83 | { 84 | struct inotable *it, *ip, *pp; 85 | int hp = inohash(inode); 86 | 87 | for(pp = ip = itable[hp];ip;ip = ip->nxt) { 88 | if (ip->inode > inode) break; 89 | if (ip->inode == inode && ip->device >= device) break; 90 | pp = ip; 91 | } 92 | 93 | if (ip && ip->inode == inode && ip->device == device) return; 94 | 95 | it = xmalloc(sizeof(struct inotable)); 96 | it->inode = inode; 97 | it->device = device; 98 | it->nxt = ip; 99 | if (ip == itable[hp]) itable[hp] = it; 100 | else pp->nxt = it; 101 | } 102 | 103 | bool findino(ino_t inode, dev_t device) 104 | { 105 | struct inotable *it; 106 | 107 | for(it=itable[inohash(inode)]; it; it=it->nxt) { 108 | if (it->inode > inode) break; 109 | if (it->inode == inode && it->device >= device) break; 110 | } 111 | 112 | if (it && it->inode == inode && it->device == device) return true; 113 | return false; 114 | } 115 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # $Copyright: $ 2 | # Copyright (c) 1996 - 2024 by Steve Baker (steve.baker.llc@gmail.com) 3 | # All Rights reserved 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the Free Software 17 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | 19 | PREFIX=/usr/local 20 | 21 | 22 | CC ?= gcc 23 | INSTALL ?= install 24 | 25 | VERSION=2.2.1 26 | TREE_DEST=tree 27 | DESTDIR=${PREFIX}/bin 28 | MAN=tree.1 29 | # Probably needs to be ${PREFIX}/share/man for most systems now 30 | MANDIR=${PREFIX}/man 31 | OBJS=tree.o list.o hash.o color.o file.o filter.o info.o unix.o xml.o json.o html.o strverscmp.o 32 | 33 | # Uncomment options below for your particular OS: 34 | 35 | # Linux defaults: 36 | LDFLAGS?=-s 37 | #CFLAGS?=-ggdb 38 | CFLAGS?=-O3 39 | CFLAGS+=-std=c11 -Wpedantic -Wall -Wextra -Wstrict-prototypes -Wshadow -Wconversion 40 | # _LARGEFILE64_SOURCE may be considered obsolete 41 | CPPFLAGS+=-DLARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 42 | 43 | # Uncomment for FreeBSD: 44 | #CC=cc 45 | #CFLAGS?=-O2 46 | #CFLAGS+=-Wall -fomit-frame-pointer 47 | #LDFLAGS+=-s 48 | 49 | # Uncomment for OpenBSD: 50 | #TREE_DEST=colortree 51 | #MAN=colortree.1 52 | #CFLAGS?=-O2 53 | #CFLAGS+=-Wall -fomit-frame-pointer 54 | #LDFLAGS+=-s 55 | 56 | # Uncomment for Solaris: 57 | #CC=cc 58 | #CFLAGS+=-xO0 -v 59 | #LDFLAGS+= 60 | #MANDIR=${prefix}/share/man 61 | 62 | # Uncomment for Cygwin: 63 | #CFLAGS?=-O2 64 | #CFLAGS+=-Wall -fomit-frame-pointer 65 | #LDFLAGS+=-s 66 | #TREE_DEST=tree.exe 67 | 68 | # Uncomment for MacOS: 69 | # It is not allowed to install to /usr/bin on MacOS any longer (SIP): 70 | #CC = cc 71 | #CFLAGS?=-O2 72 | #CFLAGS+=-Wall -fomit-frame-pointer -no-cpp-precomp 73 | #LDFLAGS+= 74 | #MANDIR=${PREFIX}/share/man 75 | 76 | # Uncomment for HP/UX: 77 | #prefix=/opt 78 | #CC=cc 79 | # manpage of mbsrtowcs() requires C99 and the two defines 80 | #CFLAGS+=+w -AC99 81 | #CPPFLAGS+=-D_XOPEN_SOURCE=500 -D_POSIX_C_SOURCE=200112 82 | #LDFLAGS+= 83 | #MANDIR=${PREFIX}/share/man 84 | 85 | # Uncomment for OS/2: 86 | #CFLAGS?=-O2 87 | #CFLAGS+=-Wall -fomit-frame-pointer -Zomf -Zsmall-conv 88 | #LDFLAGS+=-s -Zomf -Zsmall-conv 89 | 90 | # Uncomment for HP NonStop: 91 | #prefix = /opt 92 | #CC=c89 93 | #CFLAGS+=-Wextensions -WIEEE_float -g -Wnowarn=1506 -D_XOPEN_SOURCE_EXTENDED=1 \ 94 | # -Wallow_cplusplus_comments 95 | #LDFLAGS+= 96 | 97 | # AIX 98 | #CC=cc_r -q64 99 | #LD=ld -d64 100 | #LDFLAGS+=-lc 101 | 102 | # Android NDK 103 | #CC=aarch64-linux-android26-clang # Need >= 26 104 | #CFLAGS?=-O2 # Or: 105 | #CFLAGS?=-ggdb 106 | #CFLAGS+=-std=c89 -pedantic -Wall -Wno-error=int-conversion 107 | #CPPFLAGS+=-D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 108 | 109 | #------------------------------------------------------------ 110 | 111 | all: tree 112 | 113 | tree: $(OBJS) 114 | $(CC) $(LDFLAGS) -o $(TREE_DEST) $(OBJS) 115 | 116 | $(OBJS): %.o: %.c tree.h 117 | $(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $< 118 | 119 | clean: 120 | rm -f $(TREE_DEST) *.o *~ 121 | 122 | install: tree 123 | $(INSTALL) -d $(DESTDIR) 124 | $(INSTALL) -d $(MANDIR)/man1 125 | $(INSTALL) $(TREE_DEST) $(DESTDIR)/$(TREE_DEST); \ 126 | $(INSTALL) -m 644 doc/$(MAN) $(MANDIR)/man1/$(MAN) 127 | 128 | distclean: 129 | rm -f *.o *~ 130 | 131 | dist: distclean 132 | tar zcf ../tree-$(VERSION).tgz -C .. `cat .tarball` 133 | -------------------------------------------------------------------------------- /unix.c: -------------------------------------------------------------------------------- 1 | /* $Copyright: $ 2 | * Copyright (c) 1996 - 2024 by Steve Baker (steve.baker.llc@gmail.com) 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 | */ 18 | #include "tree.h" 19 | 20 | extern FILE *outfile; 21 | extern bool dflag, Fflag, duflag, metafirst, hflag, siflag, noindent; 22 | extern bool colorize, linktargetcolor, hyperflag; 23 | extern const struct linedraw *linedraw; 24 | extern int *dirs; 25 | extern char *scheme, *authority; 26 | static char info[512] = {0}; 27 | 28 | extern char realbasepath[PATH_MAX], xpattern[PATH_MAX]; 29 | extern size_t dirpathoffset; 30 | 31 | int unix_printinfo(char *dirname, struct _info *file, int level) 32 | { 33 | UNUSED(dirname); 34 | 35 | fillinfo(info, file); 36 | if (metafirst) { 37 | if (info[0] == '[') fprintf(outfile, "%s ",info); 38 | if (!noindent) indent(level); 39 | } else { 40 | if (!noindent) indent(level); 41 | if (info[0] == '[') fprintf(outfile, "%s ",info); 42 | } 43 | return 0; 44 | } 45 | 46 | void open_hyperlink(char *dirname, char *filename) 47 | { 48 | fprintf(outfile,"\033]8;;%s", scheme); 49 | url_encode(outfile, authority); 50 | url_encode(outfile, realbasepath); 51 | url_encode(outfile, dirname+dirpathoffset); 52 | fputc('/',outfile); 53 | url_encode(outfile, filename); 54 | fprintf(outfile,"\033\\"); 55 | } 56 | 57 | void close_hyperlink(void) 58 | { 59 | fprintf(outfile, "\033]8;;\033\\"); 60 | } 61 | 62 | int unix_printfile(char *dirname, char *filename, struct _info *file, int descend) 63 | { 64 | UNUSED(descend); 65 | 66 | bool colored = false; 67 | int c; 68 | 69 | if (hyperflag) open_hyperlink(dirname, file->name); 70 | 71 | if (file && colorize) { 72 | if (file->lnk && linktargetcolor) colored = color(file->lnkmode, file->name, file->orphan, false); 73 | else colored = color(file->mode, file->name, file->orphan, false); 74 | } 75 | 76 | printit(filename); 77 | 78 | if (colored) endcolor(); 79 | 80 | if (hyperflag) close_hyperlink(); 81 | 82 | if (file) { 83 | if (Fflag && !file->lnk) { 84 | if ((c = Ftype(file->mode))) fputc(c, outfile); 85 | } 86 | 87 | if (file->lnk) { 88 | fprintf(outfile," -> "); 89 | if (hyperflag) open_hyperlink(dirname, file->name); 90 | if (colorize) colored = color(file->lnkmode, file->lnk, file->orphan, true); 91 | printit(file->lnk); 92 | if (colored) endcolor(); 93 | if (hyperflag) close_hyperlink(); 94 | if (Fflag) { 95 | if ((c = Ftype(file->lnkmode))) fputc(c, outfile); 96 | } 97 | } 98 | } 99 | return 0; 100 | } 101 | 102 | int unix_error(char *error) 103 | { 104 | fprintf(outfile, " [%s]", error); 105 | return 0; 106 | } 107 | 108 | void unix_newline(struct _info *file, int level, int postdir, int needcomma) 109 | { 110 | UNUSED(needcomma); 111 | 112 | if (postdir <= 0) fprintf(outfile, "\n"); 113 | if (file && file->comment) { 114 | size_t infosize = 0, line, lines; 115 | if (metafirst) infosize = info[0] == '['? strlen(info)+2 : 0; 116 | 117 | for(lines = 0; file->comment[lines]; lines++); 118 | dirs[level+1] = 1; 119 | for(line = 0; line < lines; line++) { 120 | if (metafirst) { 121 | printf("%*s", (int)infosize, ""); 122 | } 123 | indent(level); 124 | printcomment(line, lines, file->comment[line]); 125 | } 126 | dirs[level+1] = 0; 127 | } 128 | } 129 | 130 | void unix_report(struct totals tot) 131 | { 132 | char buf[256]; 133 | 134 | fputc('\n', outfile); 135 | if (duflag) { 136 | psize(buf, tot.size); 137 | fprintf(outfile,"%s%s used in ", buf, hflag || siflag? "" : " bytes"); 138 | } 139 | if (dflag) 140 | fprintf(outfile,"%ld director%s\n",tot.dirs,(tot.dirs==1? "y":"ies")); 141 | else 142 | fprintf(outfile,"%ld director%s, %ld file%s\n",tot.dirs,(tot.dirs==1? "y":"ies"),tot.files,(tot.files==1? "":"s")); 143 | } 144 | -------------------------------------------------------------------------------- /filter.c: -------------------------------------------------------------------------------- 1 | /* $Copyright: $ 2 | * Copyright (c) 1996 - 2024 by Steve Baker (steve.baker.llc@gmail.com) 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 | */ 18 | #include "tree.h" 19 | 20 | extern char xpattern[PATH_MAX]; 21 | 22 | struct ignorefile *filterstack = NULL; 23 | 24 | void gittrim(char *s) 25 | { 26 | ssize_t i, e = (ssize_t)strlen(s)-1; 27 | 28 | if (e < 0) return; 29 | if (s[e] == '\n') e--; 30 | 31 | for(i = e; i >= 0; i--) { 32 | if (s[i] != ' ') break; 33 | if (i && s[i-1] != '\\') e--; 34 | } 35 | s[e+1] = '\0'; 36 | for(i = e = 0; s[i] != '\0';) { 37 | if (s[i] == '\\') i++; 38 | s[e++] = s[i++]; 39 | } 40 | s[e] = '\0'; 41 | } 42 | 43 | struct pattern *new_pattern(char *pattern) 44 | { 45 | struct pattern *p = xmalloc(sizeof(struct pattern)); 46 | char *sl; 47 | 48 | p->pattern = scopy(pattern + ((pattern[0] == '/')? 1 : 0)); 49 | sl = strchr(pattern, '/'); 50 | p->relative = (sl == NULL || (sl && !*(sl+1))); 51 | p->next = NULL; 52 | return p; 53 | } 54 | 55 | struct ignorefile *new_ignorefile(const char *path, bool checkparents) 56 | { 57 | struct stat st; 58 | char buf[PATH_MAX], rpath[PATH_MAX]; 59 | struct ignorefile *ig; 60 | struct pattern *remove = NULL, *remend, *p; 61 | struct pattern *reverse = NULL, *revend; 62 | int rev; 63 | FILE *fp; 64 | 65 | rev = stat(path, &st); 66 | if (rev < 0 || !S_ISREG(st.st_mode)) { 67 | snprintf(buf, PATH_MAX, "%s/.gitignore", path); 68 | fp = fopen(buf, "r"); 69 | 70 | if (fp == NULL && checkparents) { 71 | strcpy(rpath, path); 72 | while ((fp == NULL) && (strcmp(rpath, "/") != 0)) { 73 | snprintf(buf, PATH_MAX, "%.*s/..", PATH_MAX-4, rpath); 74 | if (realpath(buf, rpath) == NULL) break; 75 | snprintf(buf, PATH_MAX, "%.*s/.gitignore", PATH_MAX-12, rpath); 76 | fp = fopen(buf, "r"); 77 | } 78 | } 79 | } else fp = fopen(path, "r"); 80 | if (fp == NULL) return NULL; 81 | 82 | while (fgets(buf, PATH_MAX, fp) != NULL) { 83 | if (buf[0] == '#') continue; 84 | rev = (buf[0] == '!'); 85 | gittrim(buf); 86 | if (strlen(buf) == 0) continue; 87 | p = new_pattern(buf + (rev? 1 : 0)); 88 | if (rev) { 89 | if (reverse == NULL) reverse = revend = p; 90 | else { 91 | revend->next = p; 92 | revend = p; 93 | } 94 | } else { 95 | if (remove == NULL) remove = remend = p; 96 | else { 97 | remend->next = p; 98 | remend = p; 99 | } 100 | } 101 | } 102 | 103 | fclose(fp); 104 | 105 | ig = xmalloc(sizeof(struct ignorefile)); 106 | ig->remove = remove; 107 | ig->reverse = reverse; 108 | ig->path = scopy(path); 109 | ig->next = NULL; 110 | 111 | return ig; 112 | } 113 | 114 | void push_filterstack(struct ignorefile *ig) 115 | { 116 | if (ig == NULL) return; 117 | ig->next = filterstack; 118 | filterstack = ig; 119 | } 120 | 121 | struct ignorefile *pop_filterstack(void) 122 | { 123 | struct ignorefile *ig; 124 | struct pattern *p, *c; 125 | 126 | ig = filterstack; 127 | if (ig == NULL) return NULL; 128 | 129 | filterstack = filterstack->next; 130 | 131 | for(p=c=ig->remove; p != NULL; c = p) { 132 | p=p->next; 133 | free(c->pattern); 134 | } 135 | for(p=c=ig->reverse; p != NULL; c = p) { 136 | p=p->next; 137 | free(c->pattern); 138 | } 139 | free(ig->path); 140 | free(ig); 141 | return NULL; 142 | } 143 | 144 | /** 145 | * true if remove filter matches and no reverse filter matches. 146 | */ 147 | bool filtercheck(const char *path, const char *name, int isdir) 148 | { 149 | bool filter = false; 150 | struct ignorefile *ig; 151 | struct pattern *p; 152 | 153 | for(ig = filterstack; !filter && ig; ig = ig->next) { 154 | int fpos = sprintf(xpattern, "%s/", ig->path); 155 | 156 | for(p = ig->remove; p != NULL; p = p->next) { 157 | if (p->relative) { 158 | if (patmatch(name, p->pattern, isdir) == 1) { 159 | filter = true; 160 | break; 161 | } 162 | } else { 163 | sprintf(xpattern + fpos, "%s", p->pattern); 164 | if (patmatch(path, xpattern, isdir) == 1) { 165 | filter = true; 166 | break; 167 | } 168 | } 169 | } 170 | } 171 | if (!filter) return false; 172 | 173 | for(ig = filterstack; ig; ig = ig->next) { 174 | int fpos = sprintf(xpattern, "%s/", ig->path); 175 | 176 | for(p = ig->reverse; p != NULL; p = p->next) { 177 | if (p->relative) { 178 | if (patmatch(name, p->pattern, isdir) == 1) return false; 179 | } else { 180 | sprintf(xpattern + fpos, "%s", p->pattern); 181 | if (patmatch(path, xpattern, isdir) == 1) return false; 182 | } 183 | } 184 | } 185 | 186 | return true; 187 | } 188 | -------------------------------------------------------------------------------- /xml.c: -------------------------------------------------------------------------------- 1 | /* $Copyright: $ 2 | * Copyright (c) 1996 - 2024 by Steve Baker (steve.baker.llc@gmail.com) 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 | */ 18 | #include "tree.h" 19 | 20 | 21 | extern bool dflag, pflag, sflag, uflag, gflag; 22 | extern bool Dflag, inodeflag, devflag, cflag, duflag; 23 | extern bool noindent; 24 | 25 | extern const char *charset; 26 | extern const mode_t ifmt[]; 27 | extern const char *ftype[]; 28 | 29 | extern FILE *outfile; 30 | 31 | /* 32 | 33 | 34 | 35 | ... if link is followed, otherwise this is empty. 36 | 37 | 38 | some error 39 | 40 | 41 | 42 | 43 | 44 | ... 45 | 46 | 47 | # 48 | # 49 | # 50 | 51 | 52 | */ 53 | 54 | void xml_indent(int maxlevel) 55 | { 56 | int i; 57 | 58 | fprintf(outfile, " "); 59 | for(i=0; iinode); 67 | #else 68 | if (inodeflag) fprintf(outfile," inode=\"%ld\"",(long int)ent->inode); 69 | #endif 70 | if (devflag) fprintf(outfile, " dev=\"%d\"", (int)ent->dev); 71 | #ifdef __EMX__ 72 | if (pflag) fprintf(outfile, " mode=\"%04o\" prot=\"%s\"",ent->attr, prot(ent->attr)); 73 | #else 74 | if (pflag) fprintf(outfile, " mode=\"%04o\" prot=\"%s\"", ent->mode & (S_IRWXU|S_IRWXG|S_IRWXO|S_ISUID|S_ISGID|S_ISVTX), prot(ent->mode)); 75 | #endif 76 | if (uflag) fprintf(outfile, " user=\"%s\"", uidtoname(ent->uid)); 77 | if (gflag) fprintf(outfile, " group=\"%s\"", gidtoname(ent->gid)); 78 | if (sflag) fprintf(outfile, " size=\"%lld\"", (long long int)(ent->size)); 79 | if (Dflag) fprintf(outfile, " time=\"%s\"", do_date(cflag? ent->ctime : ent->mtime)); 80 | } 81 | 82 | void xml_intro(void) 83 | { 84 | extern char *_nl; 85 | 86 | fprintf(outfile,"%s%s",_nl,_nl); 89 | } 90 | 91 | void xml_outtro(void) 92 | { 93 | fprintf(outfile,"\n"); 94 | } 95 | 96 | int xml_printinfo(char *dirname, struct _info *file, int level) 97 | { 98 | UNUSED(dirname); 99 | 100 | mode_t mt; 101 | int t; 102 | 103 | if (!noindent) xml_indent(level); 104 | 105 | if (file != NULL) { 106 | if (file->lnk) mt = file->mode & S_IFMT; 107 | else mt = file->mode & S_IFMT; 108 | } else mt = 0; 109 | 110 | for(t=0;ifmt[t];t++) 111 | if (ifmt[t] == mt) break; 112 | fprintf(outfile,"<%s", (file->tag = ftype[t])); 113 | 114 | return 0; 115 | } 116 | 117 | int xml_printfile(char *dirname, char *filename, struct _info *file, int descend) 118 | { 119 | UNUSED(dirname);UNUSED(descend); 120 | 121 | int i; 122 | 123 | fprintf(outfile, " name=\""); 124 | html_encode(outfile, filename); 125 | fputc('"',outfile); 126 | 127 | if (file && file->comment) { 128 | fprintf(outfile, " info=\""); 129 | for(i=0; file->comment[i]; i++) { 130 | html_encode(outfile, file->comment[i]); 131 | if (file->comment[i+1]) fprintf(outfile, "\n"); 132 | } 133 | fputc('"', outfile); 134 | } 135 | 136 | if (file && file->lnk) { 137 | fprintf(outfile, " target=\""); 138 | html_encode(outfile,file->lnk); 139 | fputc('"',outfile); 140 | } 141 | if (file) xml_fillinfo(file); 142 | fputc('>',outfile); 143 | 144 | return 1; 145 | } 146 | 147 | int xml_error(char *error) 148 | { 149 | fprintf(outfile,"%s", error); 150 | 151 | return 0; 152 | } 153 | 154 | void xml_newline(struct _info *file, int level, int postdir, int needcomma) 155 | { 156 | UNUSED(file);UNUSED(level);UNUSED(needcomma); 157 | 158 | if (postdir >= 0) fprintf(outfile, "\n"); 159 | } 160 | 161 | void xml_close(struct _info *file, int level, int needcomma) 162 | { 163 | UNUSED(needcomma); 164 | 165 | if (!noindent && level >= 0) xml_indent(level); 166 | fprintf(outfile,"%s", file? file->tag : "unknown", noindent? "" : "\n"); 167 | } 168 | 169 | 170 | void xml_report(struct totals tot) 171 | { 172 | extern char *_nl; 173 | 174 | fprintf(outfile,"%s%s",noindent?"":" ", _nl); 175 | if (duflag) fprintf(outfile,"%s%lld%s", noindent?"":" ", (long long int)tot.size, _nl); 176 | fprintf(outfile,"%s%ld%s", noindent?"":" ", tot.dirs, _nl); 177 | if (!dflag) fprintf(outfile,"%s%ld%s", noindent?"":" ", tot.files, _nl); 178 | fprintf(outfile,"%s%s",noindent?"":" ", _nl); 179 | } 180 | -------------------------------------------------------------------------------- /info.c: -------------------------------------------------------------------------------- 1 | /* $Copyright: $ 2 | * Copyright (c) 1996 - 2024 by Steve Baker (steve.baker.llc@gmail.com) 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 | */ 18 | #include "tree.h" 19 | 20 | /** 21 | * TODO: Make a "filenote" command for info comments. 22 | * maybe TODO: Support language extensions (i.e. .info.en, .info.gr, etc) 23 | * # comments 24 | * pattern 25 | * pattern 26 | * info messages 27 | * more info 28 | */ 29 | extern FILE *outfile; 30 | extern const struct linedraw *linedraw; 31 | extern char xpattern[PATH_MAX]; 32 | 33 | struct infofile *infostack = NULL; 34 | 35 | struct comment *new_comment(struct pattern *phead, char **line, int lines) 36 | { 37 | int i; 38 | 39 | struct comment *com = xmalloc(sizeof(struct comment)); 40 | com->pattern = phead; 41 | com->desc = xmalloc(sizeof(char *) * (size_t)(lines+1)); 42 | for(i=0; i < lines; i++) com->desc[i] = line[i]; 43 | com->desc[i] = NULL; 44 | com->next = NULL; 45 | return com; 46 | } 47 | 48 | struct infofile *new_infofile(const char *path, bool checkparents) 49 | { 50 | struct stat st; 51 | char buf[PATH_MAX], rpath[PATH_MAX]; 52 | struct infofile *inf; 53 | struct comment *chead = NULL, *cend = NULL, *com; 54 | struct pattern *phead = NULL, *pend = NULL, *p; 55 | char *line[PATH_MAX]; 56 | FILE *fp; 57 | int i, lines = 0; 58 | 59 | i = stat(path, &st); 60 | if (i < 0 || !S_ISREG(st.st_mode)) { 61 | snprintf(buf, PATH_MAX, "%s/.info", path); 62 | fp = fopen(buf, "r"); 63 | 64 | if (fp == NULL && checkparents) { 65 | strcpy(rpath, path); 66 | while ((fp == NULL) && (strcmp(rpath, "/") != 0)) { 67 | snprintf(buf, PATH_MAX, "%.*s/..", PATH_MAX-4, rpath); 68 | if (realpath(buf, rpath) == NULL) break; 69 | snprintf(buf, PATH_MAX, "%.*s/.info", PATH_MAX-7, rpath); 70 | fp = fopen(buf, "r"); 71 | } 72 | } 73 | } else fp = fopen(path, "r"); 74 | if (fp == NULL) return NULL; 75 | 76 | while (fgets(buf, PATH_MAX, fp) != NULL) { 77 | if (buf[0] == '#') continue; 78 | gittrim(buf); 79 | if (strlen(buf) < 1) continue; 80 | 81 | if (buf[0] == '\t') { 82 | line[lines++] = scopy(buf+1); 83 | } else { 84 | if (lines) { 85 | /* Save previous pattern/message: */ 86 | if (phead) { 87 | com = new_comment(phead, line, lines); 88 | if (!chead) chead = cend = com; 89 | else cend = cend->next = com; 90 | } else { 91 | /* Accumulated info message lines w/ no associated pattern? */ 92 | for(i=0; i < lines; i++) free(line[i]); 93 | } 94 | /* Reset for next pattern/message: */ 95 | phead = pend = NULL; 96 | lines = 0; 97 | } 98 | p = new_pattern(buf); 99 | if (phead == NULL) phead = pend = p; 100 | else pend = pend->next = p; 101 | } 102 | } 103 | if (phead) { 104 | com = new_comment(phead, line, lines); 105 | if (!chead) chead = cend = com; 106 | else cend = cend->next = com; 107 | } else { 108 | for(i=0; i < lines; i++) free(line[i]); 109 | } 110 | 111 | fclose(fp); 112 | 113 | inf = xmalloc(sizeof(struct infofile)); 114 | inf->comments = chead; 115 | inf->path = scopy(path); 116 | inf->next = NULL; 117 | 118 | return inf; 119 | } 120 | 121 | void push_infostack(struct infofile *inf) 122 | { 123 | if (inf == NULL) return; 124 | inf->next = infostack; 125 | infostack = inf; 126 | } 127 | 128 | struct infofile *pop_infostack(void) 129 | { 130 | struct infofile *inf; 131 | struct comment *cn, *cc; 132 | struct pattern *p, *c; 133 | int i; 134 | 135 | inf = infostack; 136 | if (inf == NULL) return NULL; 137 | 138 | infostack = infostack->next; 139 | 140 | for(cn = cc = inf->comments; cn != NULL; cc = cn) { 141 | cn = cn->next; 142 | for(p=c=cc->pattern; p != NULL; c = p) { 143 | p=p->next; 144 | free(c->pattern); 145 | } 146 | for(i=0; cc->desc[i] != NULL; i++) free(cc->desc[i]); 147 | free(cc->desc); 148 | free(cc); 149 | } 150 | free(inf->path); 151 | free(inf); 152 | return NULL; 153 | } 154 | 155 | /** 156 | * Returns an info pointer if a path matches a pattern. 157 | * top == 1 if called in a directory with a .info file. 158 | */ 159 | struct comment *infocheck(const char *path, const char *name, int top, bool isdir) 160 | { 161 | struct infofile *inf = infostack; 162 | struct comment *com; 163 | struct pattern *p; 164 | 165 | if (inf == NULL) return NULL; 166 | 167 | for(inf = infostack; inf != NULL; inf = inf->next) { 168 | int fpos = sprintf(xpattern, "%s/", inf->path); 169 | 170 | for(com = inf->comments; com != NULL; com = com->next) { 171 | for(p = com->pattern; p != NULL; p = p->next) { 172 | if (patmatch(path, p->pattern, isdir) == 1) return com; 173 | if (top && patmatch(name, p->pattern, isdir) == 1) return com; 174 | 175 | sprintf(xpattern + fpos, "%s", p->pattern); 176 | if (patmatch(path, xpattern, isdir) == 1) return com; 177 | } 178 | } 179 | top = 0; 180 | } 181 | return NULL; 182 | } 183 | 184 | void printcomment(size_t line, size_t lines, char *s) 185 | { 186 | if (lines == 1) fprintf(outfile, "%s ", linedraw->csingle); 187 | else { 188 | if (line == 0) fprintf(outfile, "%s ", linedraw->ctop); 189 | else if (line < 2) { 190 | fprintf(outfile, "%s ", (lines==2)? linedraw->cbot : linedraw->cmid); 191 | } else { 192 | fprintf(outfile, "%s ", (line == lines-1)? linedraw->cbot : linedraw->cext); 193 | } 194 | } 195 | fprintf(outfile, "%s\n", s); 196 | } 197 | -------------------------------------------------------------------------------- /json.c: -------------------------------------------------------------------------------- 1 | /* $Copyright: $ 2 | * Copyright (c) 1996 - 2024 by Steve Baker (steve.baker.llc@gmail.com) 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 | */ 18 | #include "tree.h" 19 | 20 | extern bool dflag, pflag, sflag, uflag, gflag, Dflag, inodeflag, devflag; 21 | extern bool cflag, hflag, siflag, duflag, noindent; 22 | 23 | extern const mode_t ifmt[]; 24 | extern const char *ftype[]; 25 | 26 | extern FILE *outfile; 27 | 28 | /* JSON code courtesy of Florian Sesser 29 | [ 30 | {"type": "directory", "name": "name", "mode": "0777", "user": "user", "group": "group", "inode": ###, "dev": ####, "time": "00:00 00-00-0000", "info": "", "contents": [ 31 | {"type": "link", "name": "name", "target": "name", "info": "...", "contents": [... if link is followed, otherwise this is empty.]} 32 | {"type": "file", "name": "name", "mode": "0777", "size": ###, "group": "group", "inode": ###, "dev": ###, "time": "00:00 00-00-0000", "info": "..."} 33 | {"type": "socket", "name": "", "info": "...", "error": "some error" ...} 34 | {"type": "block", "name": "" ...}, 35 | {"type": "char", "name": "" ...}, 36 | {"type": "fifo", "name": "" ...}, 37 | {"type": "door", "name": "" ...}, 38 | {"type": "port", "name": "" ...} 39 | ]}, 40 | {"type": "report", "size": ###, "files": ###, "directories": ###} 41 | ] 42 | */ 43 | 44 | /** 45 | * JSON encoded strings are not HTML/XML strings: 46 | * https://tools.ietf.org/html/rfc8259#section-7 47 | * FIXME: Still not UTF-8 48 | */ 49 | void json_encode(FILE *fd, char *s) 50 | { 51 | char *ctrl = "0-------btn-fr------------------"; 52 | 53 | for(;*s;s++) { 54 | if ((unsigned char)*s < 32) { 55 | if (ctrl[(unsigned char)*s] != '-') fprintf(fd, "\\%c", ctrl[(unsigned char)*s]); 56 | else fprintf(fd, "\\u%04x", (unsigned char)*s); 57 | } else if (*s == '"' || *s == '\\') fprintf(fd, "\\%c", *s); 58 | else fprintf(fd, "%c", *s); 59 | } 60 | } 61 | 62 | void json_indent(int maxlevel) 63 | { 64 | int i; 65 | 66 | fprintf(outfile, " "); 67 | for(i=0; iinode); 75 | #else 76 | if (inodeflag) fprintf(outfile,",\"inode\":%ld",(long int)ent->inode); 77 | #endif 78 | if (devflag) fprintf(outfile, ",\"dev\":%d", (int)ent->dev); 79 | #ifdef __EMX__ 80 | if (pflag) fprintf(outfile, ",\"mode\":\"%04o\",\"prot\":\"%s\"",ent->attr, prot(ent->attr)); 81 | #else 82 | if (pflag) fprintf(outfile, ",\"mode\":\"%04o\",\"prot\":\"%s\"", ent->mode & (S_IRWXU|S_IRWXG|S_IRWXO|S_ISUID|S_ISGID|S_ISVTX), prot(ent->mode)); 83 | #endif 84 | if (uflag) fprintf(outfile, ",\"user\":\"%s\"", uidtoname(ent->uid)); 85 | if (gflag) fprintf(outfile, ",\"group\":\"%s\"", gidtoname(ent->gid)); 86 | if (sflag) { 87 | if (hflag || siflag) { 88 | char nbuf[64]; 89 | int i; 90 | psize(nbuf,ent->size); 91 | for(i=0; isspace(nbuf[i]); i++); /* trim() hack */ 92 | fprintf(outfile, ",\"size\":\"%s\"", nbuf+i); 93 | } else 94 | fprintf(outfile, ",\"size\":%lld", (long long int)ent->size); 95 | } 96 | if (Dflag) fprintf(outfile, ",\"time\":\"%s\"", do_date(cflag? ent->ctime : ent->mtime)); 97 | } 98 | 99 | 100 | void json_intro(void) 101 | { 102 | extern char *_nl; 103 | fprintf(outfile, "[%s", noindent? "" : _nl); 104 | } 105 | 106 | void json_outtro(void) 107 | { 108 | extern char *_nl; 109 | fprintf(outfile, "%s]\n", noindent? "" : _nl); 110 | } 111 | 112 | int json_printinfo(char *dirname, struct _info *file, int level) 113 | { 114 | UNUSED(dirname); 115 | mode_t mt; 116 | int t; 117 | 118 | if (!noindent) json_indent(level); 119 | 120 | if (file != NULL) { 121 | if (file->lnk) mt = file->mode & S_IFMT; 122 | else mt = file->mode & S_IFMT; 123 | } else mt = 0; 124 | 125 | for(t=0;ifmt[t];t++) 126 | if (ifmt[t] == mt) break; 127 | fprintf(outfile,"{\"type\":\"%s\"", ftype[t]); 128 | 129 | return 0; 130 | } 131 | 132 | int json_printfile(char *dirname, char *filename, struct _info *file, int descend) 133 | { 134 | UNUSED(dirname); 135 | int i; 136 | 137 | fprintf(outfile, ",\"name\":\""); 138 | json_encode(outfile, filename); 139 | fputc('"',outfile); 140 | 141 | if (file && file->comment) { 142 | fprintf(outfile, ",\"info\":\""); 143 | for(i=0; file->comment[i]; i++) { 144 | json_encode(outfile, file->comment[i]); 145 | if (file->comment[i+1]) fprintf(outfile, "\\n"); 146 | } 147 | fprintf(outfile, "\""); 148 | } 149 | 150 | if (file && file->lnk) { 151 | fprintf(outfile, ",\"target\":\""); 152 | json_encode(outfile, file->lnk); 153 | fputc('"',outfile); 154 | } 155 | if (file) json_fillinfo(file); 156 | 157 | // if (file && file->err) fprintf(outfile, ",\"error\": \"%s\"", file->err); 158 | if (descend || (file->isdir && file->err)) fprintf(outfile, ",\"contents\":["); 159 | else fputc('}',outfile); 160 | 161 | return descend || (file->isdir && file->err); 162 | } 163 | 164 | int json_error(char *error) 165 | { 166 | fprintf(outfile,"{\"error\": \"%s\"}%s",error, noindent?"":""); 167 | return 0; 168 | } 169 | 170 | void json_newline(struct _info *file, int level, int postdir, int needcomma) 171 | { 172 | UNUSED(file);UNUSED(level);UNUSED(postdir); 173 | extern char *_nl; 174 | 175 | fprintf(outfile, "%s%s", needcomma? "," : "", _nl); 176 | } 177 | 178 | void json_close(struct _info *file, int level, int needcomma) 179 | { 180 | UNUSED(file); 181 | if (!noindent) json_indent(level); 182 | fprintf(outfile,"]}%s%s", needcomma? ",":"", noindent? "":"\n"); 183 | } 184 | 185 | void json_report(struct totals tot) 186 | { 187 | fprintf(outfile, ",%s{\"type\":\"report\"",noindent?"":"\n "); 188 | if (duflag) fprintf(outfile,",\"size\":%lld", (long long int)tot.size); 189 | fprintf(outfile,",\"directories\":%ld", tot.dirs); 190 | if (!dflag) fprintf(outfile,",\"files\":%ld", tot.files); 191 | fprintf(outfile, "}"); 192 | } 193 | -------------------------------------------------------------------------------- /html.c: -------------------------------------------------------------------------------- 1 | /* $Copyright: $ 2 | * Copyright (c) 1996 - 2024 by Steve Baker (steve.baker.llc@gmail.com) 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 | */ 18 | #include "tree.h" 19 | 20 | 21 | extern bool duflag, dflag, hflag, siflag; 22 | extern bool metafirst, noindent, force_color, nolinks, htmloffset; 23 | 24 | extern char *hversion; 25 | extern char *host, *sp, *title, *Hintro, *Houtro; 26 | extern const char *charset; 27 | 28 | extern FILE *outfile; 29 | 30 | extern const struct linedraw *linedraw; 31 | 32 | size_t htmldirlen = 0; 33 | 34 | char *class(struct _info *info) 35 | { 36 | return 37 | info->isdir ? "DIR" : 38 | info->isexe ? "EXEC" : 39 | info->isfifo ? "FIFO" : 40 | info->issok ? "SOCK" : "NORM"; 41 | } 42 | 43 | void html_encode(FILE *fd, char *s) 44 | { 45 | for(;*s;s++) { 46 | switch(*s) { 47 | case '<': 48 | fputs("<",fd); 49 | break; 50 | case '>': 51 | fputs(">",fd); 52 | break; 53 | case '&': 54 | fputs("&",fd); 55 | break; 56 | case '"': 57 | fputs(""",fd); 58 | break; 59 | default: 60 | fputc(*s,fd); 61 | break; 62 | } 63 | } 64 | } 65 | 66 | void url_encode(FILE *fd, char *s) 67 | { 68 | // Removes / from the reserved list: 69 | static const char *reserved = "!#$&'()*+,:;=?@[]"; 70 | 71 | for(;*s;s++) { 72 | fprintf(fd, (isprint((u_int)*s) && (strchr(reserved, *s) == NULL))? "%c":"%%%02X", *s); 73 | } 74 | } 75 | 76 | void fcat(const char *filename) 77 | { 78 | FILE *fp; 79 | char buf[PATH_MAX]; 80 | size_t n; 81 | 82 | if ((fp = fopen(filename, "r")) == NULL) return; 83 | while((n = fread(buf, sizeof(char), PATH_MAX, fp)) > 0) { 84 | fwrite(buf, sizeof(char), n, outfile); 85 | } 86 | fclose(fp); 87 | } 88 | 89 | void html_intro(void) 90 | { 91 | if (Hintro) fcat(Hintro); 92 | else { 93 | fprintf(outfile, 94 | "\n" 95 | "\n" 96 | "\n" 97 | " \n" 98 | " \n" 99 | " \n" 102 | " %s\n" 103 | " \n" 120 | "\n" 121 | "\n" 122 | "\t

%s

\n", title, title); 123 | } 124 | } 125 | 126 | void html_outtro(void) 127 | { 128 | if (Houtro) fcat(Houtro); 129 | else { 130 | fprintf(outfile,"\t


\n"); 131 | fprintf(outfile,"\t

\n"); 132 | fprintf(outfile,hversion,linedraw->copy, linedraw->copy, linedraw->copy, linedraw->copy); 133 | fprintf(outfile,"\t

\n"); 134 | fprintf(outfile,"\n"); 135 | fprintf(outfile,"\n"); 136 | } 137 | } 138 | 139 | void html_print(char *s) 140 | { 141 | int i; 142 | for(i=0; s[i]; i++) { 143 | if (s[i] == ' ') fprintf(outfile,"%s",sp); 144 | else fprintf(outfile,"%c", s[i]); 145 | } 146 | fprintf(outfile,"%s%s", sp, sp); 147 | } 148 | 149 | int html_printinfo(char *dirname, struct _info *file, int level) 150 | { 151 | UNUSED(dirname); 152 | 153 | char info[512]; 154 | 155 | fillinfo(info,file); 156 | if (metafirst) { 157 | if (info[0] == '[') { 158 | html_print(info); 159 | fprintf(outfile,"%s%s", sp, sp); 160 | } 161 | if (!noindent) indent(level); 162 | } else { 163 | if (!noindent) indent(level); 164 | if (info[0] == '[') { 165 | html_print(info); 166 | fprintf(outfile,"%s%s", sp, sp); 167 | } 168 | } 169 | 170 | return 0; 171 | } 172 | 173 | /* descend == add 00Tree.html to the link */ 174 | int html_printfile(char *dirname, char *filename, struct _info *file, int descend) 175 | { 176 | int i; 177 | /* Switch to using 'a' elements only. Omit href attribute if not a link */ 178 | fprintf(outfile,"comment) { 182 | fprintf(outfile," title=\""); 183 | for(i=0; file->comment[i]; i++) { 184 | html_encode(outfile, file->comment[i]); 185 | if (file->comment[i+1]) fprintf(outfile, "\n"); 186 | } 187 | fprintf(outfile, "\""); 188 | } 189 | 190 | if (!nolinks) { 191 | fprintf(outfile," href=\"%s",host); 192 | if (dirname != NULL) { 193 | size_t len = strlen(dirname); 194 | size_t off = (len >= htmldirlen? htmldirlen : 0); 195 | url_encode(outfile, dirname + (htmloffset? off : 0)); 196 | if (strcmp(dirname, filename) != 0) { 197 | if (dirname[strlen(dirname)-1] != '/') putc('/', outfile); 198 | url_encode(outfile, filename); 199 | } 200 | fprintf(outfile,"%s%s\"",(descend > 1? "/00Tree.html" : ""), (file->isdir && descend < 2?"/":"")); 201 | } else { 202 | if (host[strlen(host)-1] != '/') putc('/', outfile); 203 | url_encode(outfile, filename); 204 | fprintf(outfile,"%s\"",(descend > 1? "/00Tree.html" : "")); 205 | } 206 | } 207 | } 208 | fprintf(outfile, ">"); 209 | 210 | if (dirname) html_encode(outfile,filename); 211 | else html_encode(outfile, host); 212 | 213 | fprintf(outfile,""); 214 | return 0; 215 | } 216 | 217 | int html_error(char *error) 218 | { 219 | fprintf(outfile, " [%s]", error); 220 | return 0; 221 | } 222 | 223 | void html_newline(struct _info *file, int level, int postdir, int needcomma) 224 | { 225 | UNUSED(file);UNUSED(level);UNUSED(postdir);UNUSED(needcomma); 226 | 227 | fprintf(outfile, "
\n"); 228 | } 229 | 230 | void html_close(struct _info *file, int level, int needcomma) 231 | { 232 | UNUSED(level);UNUSED(needcomma); 233 | 234 | fprintf(outfile, "
\n", file->tag); 235 | } 236 | 237 | void html_report(struct totals tot) 238 | { 239 | char buf[256]; 240 | 241 | fprintf(outfile,"

\n\n"); 242 | 243 | if (duflag) { 244 | psize(buf, tot.size); 245 | fprintf(outfile,"%s%s used in ", buf, hflag || siflag? "" : " bytes"); 246 | } 247 | if (dflag) 248 | fprintf(outfile,"%ld director%s\n",tot.dirs,(tot.dirs==1? "y":"ies")); 249 | else 250 | fprintf(outfile,"%ld director%s, %ld file%s\n",tot.dirs,(tot.dirs==1? "y":"ies"),tot.files,(tot.files==1? "":"s")); 251 | 252 | fprintf(outfile, "\n

\n"); 253 | } 254 | -------------------------------------------------------------------------------- /list.c: -------------------------------------------------------------------------------- 1 | /* $Copyright: $ 2 | * Copyright (c) 1996 - 2024 by Steve Baker (steve.baker.llc@gmail.com) 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 | */ 18 | #include "tree.h" 19 | 20 | extern bool fflag, duflag, lflag, Rflag, Jflag, xdev, noreport, hyperflag, Hflag; 21 | 22 | extern struct _info **(*getfulltree)(char *d, u_long lev, dev_t dev, off_t *size, char **err); 23 | extern int (*topsort)(struct _info **, struct _info **); 24 | extern FILE *outfile; 25 | extern int flimit, *dirs, errors; 26 | extern ssize_t Level; 27 | extern size_t htmldirlen; 28 | 29 | static char errbuf[256]; 30 | char realbasepath[PATH_MAX]; 31 | size_t dirpathoffset = 0; 32 | 33 | /** 34 | * Maybe TODO: Refactor the listing calls / when they are called. A more thorough 35 | * analysis of the different outputs is required. This all is not as clean as I 36 | * had hoped it to be. 37 | */ 38 | 39 | extern struct listingcalls lc; 40 | 41 | void null_intro(void) 42 | { 43 | return; 44 | } 45 | 46 | void null_outtro(void) 47 | { 48 | return; 49 | } 50 | 51 | void null_close(struct _info *file, int level, int needcomma) 52 | { 53 | UNUSED(file);UNUSED(level);UNUSED(needcomma); 54 | } 55 | 56 | void emit_tree(char **dirname, bool needfulltree) 57 | { 58 | struct totals tot = { 0 }, subtotal; 59 | struct ignorefile *ig = NULL; 60 | struct infofile *inf = NULL; 61 | struct _info **dir = NULL, *info = NULL; 62 | char *err; 63 | ssize_t n; 64 | int i, needsclosed; 65 | size_t j; 66 | struct stat st; 67 | 68 | lc.intro(); 69 | 70 | for(i=0; dirname[i]; i++) { 71 | if (hyperflag) { 72 | if (realpath(dirname[i], realbasepath) == NULL) { realbasepath[0] = '\0'; dirpathoffset = 0; } 73 | else dirpathoffset = strlen(dirname[i]); 74 | } 75 | 76 | if (fflag) { 77 | j=strlen(dirname[i]); 78 | do { 79 | if (j > 1 && dirname[i][j-1] == '/') dirname[i][--j] = 0; 80 | } while (j > 1 && dirname[i][j-1] == '/'); 81 | } 82 | if (Hflag) htmldirlen = strlen(dirname[i]); 83 | 84 | if ((n = lstat(dirname[i],&st)) >= 0) { 85 | saveino(st.st_ino, st.st_dev); 86 | info = stat2info(&st); 87 | info->name = ""; //dirname[i]; 88 | 89 | if (needfulltree) { 90 | dir = getfulltree(dirname[i], 0, st.st_dev, &(info->size), &err); 91 | n = err? -1 : 0; 92 | } else { 93 | push_files(dirname[i], &ig, &inf, true); 94 | dir = read_dir(dirname[i], &n, inf != NULL); 95 | } 96 | 97 | lc.printinfo(dirname[i], info, 0); 98 | } else info = NULL; 99 | 100 | needsclosed = lc.printfile(dirname[i], dirname[i], info, (dir != NULL) || (!dir && n)); 101 | subtotal = (struct totals){0, 0, 0}; 102 | 103 | if (!dir && n) { 104 | lc.error("error opening dir"); 105 | lc.newline(info, 0, 0, dirname[i+1] != NULL); 106 | if (!info) errors++; 107 | else subtotal.files++; 108 | } else if (flimit > 0 && n > flimit) { 109 | sprintf(errbuf,"%ld entries exceeds filelimit, not opening dir", n); 110 | lc.error(errbuf); 111 | lc.newline(info, 0, 0, dirname[i+1] != NULL); 112 | subtotal.dirs++; 113 | } else { 114 | lc.newline(info, 0, 0, 0); 115 | if (dir) { 116 | subtotal = listdir(dirname[i], dir, 1, st.st_dev, needfulltree); 117 | subtotal.dirs++; 118 | } 119 | } 120 | if (dir) { 121 | free_dir(dir); 122 | dir = NULL; 123 | } 124 | if (needsclosed) lc.close(info, 0, dirname[i+1] != NULL); 125 | 126 | tot.files += subtotal.files; 127 | tot.dirs += subtotal.dirs; 128 | // Do not bother to accumulate tot.size in listdir. 129 | // This is already done in getfulltree() 130 | if (duflag) tot.size += info? info->size : 0; 131 | 132 | if (ig != NULL) ig = pop_filterstack(); 133 | if (inf != NULL) inf = pop_infostack(); 134 | } 135 | 136 | if (!noreport) lc.report(tot); 137 | 138 | lc.outtro(); 139 | } 140 | 141 | struct totals listdir(char *dirname, struct _info **dir, int lev, dev_t dev, bool hasfulltree) 142 | { 143 | struct totals tot = {0}, subtotal; 144 | struct ignorefile *ig = NULL; 145 | struct infofile *inf = NULL; 146 | struct _info **subdir = NULL; 147 | size_t namemax = 257, namelen; 148 | int descend, htmldescend = 0; 149 | int needsclosed; 150 | ssize_t n; 151 | size_t dirlen = strlen(dirname)+2, pathlen = dirlen + 257; 152 | bool found; 153 | char *path, *newpath, *filename, *err = NULL; 154 | 155 | int es = (dirname[strlen(dirname) - 1] == '/'); 156 | 157 | // Sanity check on dir, may or may not be necessary when using --fromfile: 158 | if (dir == NULL || *dir == NULL) return tot; 159 | 160 | for(n=0; dir[n]; n++); 161 | if (topsort) qsort(dir, (size_t)n, sizeof(struct _info *), (int (*)(const void *, const void *))topsort); 162 | 163 | dirs[lev] = *(dir+1)? 1 : 2; 164 | 165 | path = xmalloc(sizeof(char) * pathlen); 166 | 167 | for (;*dir != NULL; dir++) { 168 | lc.printinfo(dirname, *dir, lev); 169 | 170 | namelen = strlen((*dir)->name) + 1; 171 | if (namemax < namelen) 172 | path = xrealloc(path, dirlen + (namemax = namelen)); 173 | if (es) sprintf(path,"%s%s",dirname,(*dir)->name); 174 | else sprintf(path,"%s/%s",dirname,(*dir)->name); 175 | if (fflag) filename = path; 176 | else filename = (*dir)->name; 177 | 178 | descend = 0; 179 | err = NULL; 180 | newpath = path; 181 | 182 | if ((*dir)->isdir) { 183 | tot.dirs++; 184 | 185 | if (!hasfulltree) { 186 | found = findino((*dir)->inode,(*dir)->dev); 187 | if (!found) { 188 | saveino((*dir)->inode, (*dir)->dev); 189 | } 190 | } else found = false; 191 | 192 | if (!(xdev && dev != (*dir)->dev) && (!(*dir)->lnk || ((*dir)->lnk && lflag))) { 193 | descend = 1; 194 | 195 | if ((*dir)->lnk) { 196 | if (*(*dir)->lnk == '/') newpath = (*dir)->lnk; 197 | else { 198 | if (fflag && !strcmp(dirname,"/")) sprintf(path,"%s%s",dirname,(*dir)->lnk); 199 | else sprintf(path,"%s/%s",dirname,(*dir)->lnk); 200 | } 201 | if (found) { 202 | err = "recursive, not followed"; 203 | /* Not actually a problem if we weren't going to descend anyway: */ 204 | if (Level >= 0 && lev > Level) err = NULL; 205 | descend = -1; 206 | } 207 | } 208 | 209 | if ((Level >= 0) && (lev > Level)) { 210 | if (Rflag) { 211 | FILE *outsave = outfile; 212 | char *paths[2] = {newpath, NULL}, *output = xmalloc(strlen(newpath) + 13); 213 | int *dirsave = xmalloc(sizeof(int) * (size_t)(lev + 2)); 214 | 215 | memcpy(dirsave, dirs, sizeof(int) * (size_t)(lev+1)); 216 | sprintf(output, "%s/00Tree.html", newpath); 217 | setoutput(output); 218 | emit_tree(paths, hasfulltree); 219 | 220 | free(output); 221 | fclose(outfile); 222 | outfile = outsave; 223 | 224 | memcpy(dirs, dirsave, sizeof(int) * (size_t)(lev+1)); 225 | free(dirsave); 226 | htmldescend = 10; 227 | } else htmldescend = 0; 228 | descend = 0; 229 | } 230 | 231 | if (descend > 0) { 232 | if (hasfulltree) { 233 | subdir = (*dir)->child; 234 | err = (*dir)->err; 235 | } else { 236 | push_files(newpath, &ig, &inf, false); 237 | subdir = read_dir(newpath, &n, inf != NULL); 238 | if (!subdir && n) { 239 | err = "error opening dir"; 240 | errors++; 241 | } if (flimit > 0 && n > flimit) { 242 | sprintf(err = errbuf,"%ld entries exceeds filelimit, not opening dir", n); 243 | errors++; 244 | free_dir(subdir); 245 | subdir = NULL; 246 | } 247 | } 248 | if (subdir == NULL) descend = 0; 249 | } 250 | } 251 | } else tot.files++; 252 | 253 | needsclosed = lc.printfile(dirname, filename, *dir, descend + htmldescend + (Jflag && errors)); 254 | if (err) lc.error(err); 255 | 256 | if (descend > 0) { 257 | lc.newline(*dir, lev, 0, 0); 258 | 259 | subtotal = listdir(newpath, subdir, lev+1, dev, hasfulltree); 260 | tot.dirs += subtotal.dirs; 261 | tot.files += subtotal.files; 262 | } else if (!needsclosed) lc.newline(*dir, lev, 0, *(dir+1)!=NULL); 263 | 264 | if (subdir) { 265 | free_dir(subdir); 266 | subdir = NULL; 267 | } 268 | if (needsclosed) lc.close(*dir, descend? lev : -1, *(dir+1)!=NULL); 269 | 270 | if (*(dir+1) && !*(dir+2)) dirs[lev] = 2; 271 | 272 | if (ig != NULL) ig = pop_filterstack(); 273 | if (inf != NULL) inf = pop_infostack(); 274 | } 275 | 276 | dirs[lev] = 0; 277 | free(path); 278 | return tot; 279 | } 280 | -------------------------------------------------------------------------------- /file.c: -------------------------------------------------------------------------------- 1 | /* $Copyright: $ 2 | * Copyright (c) 1996 - 2024 by Steve Baker (steve.baker.llc@gmail.com) 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 | */ 18 | #include "tree.h" 19 | 20 | extern bool dflag, aflag, pruneflag, gitignore, showinfo; 21 | extern bool matchdirs, fflinks; 22 | extern int pattern, ipattern; 23 | 24 | extern int (*topsort)(const struct _info **, const struct _info **); 25 | extern FILE *outfile; 26 | 27 | extern char *file_comment, *file_pathsep; 28 | 29 | /* 64K paths maximum */ 30 | #define MAXPATH 64*1024 31 | 32 | enum ftok { T_PATHSEP, T_DIR, T_FILE, T_EOP }; 33 | 34 | char *nextpc(char **p, int *tok) 35 | { 36 | static char prev = 0; 37 | char *s = *p; 38 | if (!**p) { 39 | *tok = T_EOP; /* Shouldn't happen. */ 40 | return NULL; 41 | } 42 | if (prev) { 43 | prev = 0; 44 | *tok = T_PATHSEP; 45 | return NULL; 46 | } 47 | if (strchr(file_pathsep, **p) != NULL) { 48 | (*p)++; 49 | *tok = T_PATHSEP; 50 | return NULL; 51 | } 52 | while(**p && strchr(file_pathsep,**p) == NULL) (*p)++; 53 | 54 | if (**p) { 55 | *tok = T_DIR; 56 | prev = **p; 57 | *(*p)++ = '\0'; 58 | } else *tok = T_FILE; 59 | return s; 60 | } 61 | 62 | struct _info *newent(const char *name) { 63 | struct _info *n = xmalloc(sizeof(struct _info)); 64 | memset(n,0,sizeof(struct _info)); 65 | n->name = scopy(name); 66 | n->child = NULL; 67 | n->tchild = n->next = NULL; 68 | return n; 69 | } 70 | 71 | /* Don't insertion sort, let fprune() do the sort if necessary */ 72 | struct _info *search(struct _info **dir, const char *name) 73 | { 74 | struct _info *ptr, *prev, *n; 75 | int cmp; 76 | 77 | if (*dir == NULL) return (*dir = newent(name)); 78 | 79 | for(prev = ptr = *dir; ptr != NULL; ptr=ptr->next) { 80 | cmp = strcmp(ptr->name,name); 81 | if (cmp == 0) return ptr; 82 | prev = ptr; 83 | } 84 | n = newent(name); 85 | n->next = ptr; 86 | if (prev == ptr) *dir = n; 87 | else prev->next = n; 88 | return n; 89 | } 90 | 91 | void freefiletree(struct _info *ent) 92 | { 93 | struct _info *ptr = ent, *t; 94 | 95 | while (ptr != NULL) { 96 | if (ptr->tchild) freefiletree(ptr->tchild); 97 | t = ptr; 98 | ptr = ptr->next; 99 | free(t); 100 | } 101 | } 102 | 103 | /** 104 | * Recursively prune (unset show flag) files/directories of matches/ignored 105 | * patterns: 106 | */ 107 | struct _info **fprune(struct _info *head, const char *path, bool matched, bool root) 108 | { 109 | struct _info **dir, *new = NULL, *end = NULL, *ent, *t; 110 | struct comment *com; 111 | struct ignorefile *ig = NULL; 112 | struct infofile *inf = NULL; 113 | char *cur, *fpath = xmalloc(sizeof(char) * MAXPATH); 114 | size_t i, count = 0; 115 | bool show; 116 | 117 | strcpy(fpath, path); 118 | cur = fpath + strlen(fpath); 119 | *(cur++) = '/'; 120 | 121 | push_files(path, &ig, &inf, root); 122 | 123 | for(ent = head; ent != NULL;) { 124 | strcpy(cur, ent->name); 125 | if (ent->tchild) ent->isdir = 1; 126 | 127 | show = true; 128 | if (dflag && !ent->isdir) show = false; 129 | if (!aflag && !root && ent->name[0] == '.') show = false; 130 | if (show && !matched) { 131 | if (!ent->isdir) { 132 | if (pattern && !patinclude(ent->name, 0)) show = false; 133 | if (ipattern && patignore(ent->name, 0)) show = false; 134 | } 135 | if (ent->isdir && show && matchdirs && pattern) { 136 | if (patinclude(ent->name, 1)) matched = true; 137 | } 138 | } 139 | if (pruneflag && !matched && ent->isdir && ent->tchild == NULL) show = false; 140 | if (gitignore && filtercheck(path, ent->name, ent->isdir)) show = false; 141 | if (show && showinfo && (com = infocheck(path, ent->name, inf != NULL, ent->isdir))) { 142 | for(i = 0; com->desc[i] != NULL; i++); 143 | ent->comment = xmalloc(sizeof(char *) * (i+1)); 144 | for(i = 0; com->desc[i] != NULL; i++) ent->comment[i] = scopy(com->desc[i]); 145 | ent->comment[i] = NULL; 146 | } 147 | if (show && ent->tchild != NULL) ent->child = fprune(ent->tchild, fpath, matched, false); 148 | 149 | 150 | t = ent; 151 | ent = ent->next; 152 | if (show) { 153 | if (end) end = end->next = t; 154 | else new = end = t; 155 | count++; 156 | } else { 157 | t->next = NULL; 158 | freefiletree(t); 159 | } 160 | } 161 | if (end) end->next = NULL; 162 | 163 | dir = xmalloc(sizeof(struct _info *) * (count+1)); 164 | for(count = 0, ent = new; ent != NULL; ent = ent->next, count++) { 165 | dir[count] = ent; 166 | } 167 | dir[count] = NULL; 168 | 169 | if (topsort) qsort(dir,count,sizeof(struct _info *), (int (*)(const void *, const void *))topsort); 170 | 171 | if (ig != NULL) ig = pop_filterstack(); 172 | if (inf != NULL) inf = pop_infostack(); 173 | free(fpath); 174 | 175 | return dir; 176 | } 177 | 178 | struct _info **file_getfulltree(char *d, u_long lev, dev_t dev, off_t *size, char **err) 179 | { 180 | UNUSED(lev);UNUSED(dev);UNUSED(err); 181 | FILE *fp = (strcmp(d,".")? fopen(d,"r") : stdin); 182 | char *path, *spath, *s, *link; 183 | struct _info *root = NULL, **cwd, *ent; 184 | int tok; 185 | size_t l; 186 | 187 | *size = 0; 188 | if (fp == NULL) { 189 | fprintf(stderr,"tree: Error opening %s for reading.\n", d); 190 | return NULL; 191 | } 192 | path = xmalloc(sizeof(char) * MAXPATH); 193 | 194 | while(fgets(path, MAXPATH, fp) != NULL) { 195 | if (file_comment != NULL && strncmp(path,file_comment,strlen(file_comment)) == 0) continue; 196 | l = strlen(path); 197 | while(l && (path[l-1] == '\n' || path[l-1] == '\r')) path[--l] = '\0'; 198 | if (l == 0) continue; 199 | 200 | spath = path; 201 | cwd = &root; 202 | 203 | link = fflinks? strstr(path, " -> ") : NULL; 204 | if (link) { 205 | *link = '\0'; 206 | link += 4; 207 | } 208 | ent = NULL; 209 | do { 210 | s = nextpc(&spath, &tok); 211 | switch(tok) { 212 | case T_PATHSEP: continue; 213 | case T_FILE: 214 | case T_DIR: 215 | /* Should probably handle '.' and '..' entries here */ 216 | ent = search(cwd, s); 217 | /* Might be empty, but should definitely be considered a directory: */ 218 | if (tok == T_DIR) { 219 | ent->isdir = 1; 220 | ent->mode = S_IFDIR; 221 | } else { 222 | ent->mode = S_IFREG; 223 | } 224 | 225 | cwd = &(ent->tchild); 226 | break; 227 | } 228 | } while (tok != T_FILE && tok != T_EOP); 229 | 230 | if (ent && link) { 231 | ent->isdir = 0; 232 | ent->mode = S_IFLNK; 233 | ent->lnk = scopy(link); 234 | } 235 | } 236 | if (fp != stdin) fclose(fp); 237 | 238 | free(path); 239 | 240 | /* Prune accumulated directory tree: */ 241 | return fprune(root, "", false, true); 242 | } 243 | 244 | struct _info **tabedfile_getfulltree(char *d, u_long lev, dev_t dev, off_t *size, char **err) 245 | { 246 | UNUSED(lev);UNUSED(dev);UNUSED(err); 247 | FILE *fp = (strcmp(d,".")? fopen(d,"r") : stdin); 248 | char *path, *spath, *link; 249 | struct _info *root = NULL, **istack, *ent; 250 | size_t line = 0, l, tabs, top = 0, maxstack = 2048; 251 | 252 | *size = 0; 253 | if (fp == NULL) { 254 | fprintf(stderr,"tree: Error opening %s for reading.\n", d); 255 | return NULL; 256 | } 257 | path = xmalloc(sizeof(char) * MAXPATH); 258 | istack = xmalloc(sizeof(struct _info *) * maxstack); 259 | memset(istack, 0, sizeof(struct _info *) * maxstack); 260 | 261 | while(fgets(path, MAXPATH, fp) != NULL) { 262 | line++; 263 | if (file_comment != NULL && strncmp(path,file_comment,strlen(file_comment)) == 0) continue; 264 | l = strlen(path); 265 | while(l && (path[l-1] == '\n' || path[l-1] == '\r')) path[--l] = '\0'; 266 | if (l == 0) continue; 267 | 268 | for(tabs=0; path[tabs] == '\t'; tabs++); 269 | if (tabs >= maxstack) { 270 | fprintf(stderr, "tree: Tab depth exceeds maximum path depth (%ld >= %ld) on line %ld\n", tabs, maxstack, line); 271 | continue; 272 | } 273 | 274 | spath = path+tabs; 275 | 276 | link = fflinks? strstr(spath, " -> ") : NULL; 277 | if (link) { 278 | *link = '\0'; 279 | link += 4; 280 | } 281 | if (tabs > 0 && ((tabs-1 > top) || (istack[tabs-1] == NULL))) { 282 | fprintf(stderr, "tree: Orphaned file [%s] on line %ld, check tab depth in file.\n", spath, line); 283 | continue; 284 | } 285 | ent = istack[tabs] = search(tabs? &(istack[tabs-1]->tchild) : &root, spath); 286 | ent->mode = S_IFREG; 287 | if (tabs) { 288 | istack[tabs-1]->isdir = 1; 289 | istack[tabs-1]->mode = S_IFDIR; 290 | } 291 | if (link) { 292 | ent->isdir = 0; 293 | ent->mode = S_IFLNK; 294 | ent->lnk = scopy(link); 295 | } 296 | top = tabs; 297 | } 298 | if (fp != stdin) fclose(fp); 299 | 300 | free(path); 301 | free(istack); 302 | 303 | /* Prune accumulated directory tree: */ 304 | return fprune(root, "", false, true); 305 | } 306 | -------------------------------------------------------------------------------- /tree.h: -------------------------------------------------------------------------------- 1 | /* $Copyright: $ 2 | * Copyright (c) 1996 - 2024 by Steve Baker (steve.baker.llc@gmail.com) 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 | */ 18 | 19 | #define _GNU_SOURCE 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #ifdef __EMX__ /* for OS/2 systems */ 36 | # define INCL_DOSFILEMGR 37 | # define INCL_DOSNLS 38 | # include 39 | # include 40 | # include 41 | /* On many systems stat() function is identical to lstat() function. 42 | * But the OS/2 does not support symbolic links and doesn't have lstat() function. 43 | */ 44 | # define lstat stat 45 | # define strcasecmp stricmp 46 | /* Following two functions, getcwd() and chdir() don't support for drive letters. 47 | * To implement support them, use _getcwd2() and _chdir2(). 48 | */ 49 | # define getcwd _getcwd2 50 | # define chdir _chdir2 51 | #endif 52 | 53 | #include 54 | #include 55 | #include 56 | #include 57 | #include 58 | 59 | #ifdef __ANDROID 60 | #define mbstowcs(w,m,x) mbsrtowcs(w,(const char**)(& #m),x,NULL) 61 | #endif 62 | 63 | /* Start using PATH_MAX instead of the magic number 4096 everywhere. */ 64 | #ifndef PATH_MAX 65 | #define PATH_MAX 4096 66 | #endif 67 | 68 | #ifndef INFO_PATH 69 | #define INFO_PATH "/usr/share/finfo/global_info" 70 | #endif 71 | 72 | #ifdef __linux__ 73 | #include 74 | # define ENV_STDDATA_FD "STDDATA_FD" 75 | # ifndef STDDATA_FILENO 76 | # define STDDATA_FILENO 3 77 | # endif 78 | #endif 79 | 80 | /* Should probably use strdup(), but we like our xmalloc() */ 81 | #define scopy(x) strcpy(xmalloc(strlen(x)+1),(x)) 82 | #define MINIT 30 /* number of dir entries to initially allocate */ 83 | #define MINC 20 /* allocation increment */ 84 | 85 | #define UNUSED(x) ((void)x) 86 | 87 | struct _info { 88 | char *name; 89 | char *lnk; 90 | bool isdir; 91 | bool issok; 92 | bool isfifo; 93 | bool isexe; 94 | bool orphan; 95 | mode_t mode, lnkmode; 96 | uid_t uid; 97 | gid_t gid; 98 | off_t size; 99 | time_t atime, ctime, mtime; 100 | dev_t dev, ldev; 101 | ino_t inode, linode; 102 | #ifdef __EMX__ 103 | long attr; 104 | #endif 105 | char *err; 106 | const char *tag; 107 | char **comment; 108 | struct _info **child, *next, *tchild; 109 | }; 110 | 111 | /* list.c */ 112 | struct totals { 113 | size_t files, dirs; 114 | off_t size; 115 | }; 116 | 117 | struct listingcalls { 118 | void (*intro)(void); 119 | void (*outtro)(void); 120 | int (*printinfo)(char *dirname, struct _info *file, int level); 121 | int (*printfile)(char *dirname, char *filename, struct _info *file, int descend); 122 | int (*error)(char *error); 123 | void (*newline)(struct _info *file, int level, int postdir, int needcomma); 124 | void (*close)(struct _info *file, int level, int needcomma); 125 | void (*report)(struct totals tot); 126 | }; 127 | 128 | 129 | /* hash.c */ 130 | struct xtable { 131 | unsigned int xid; 132 | char *name; 133 | struct xtable *nxt; 134 | }; 135 | struct inotable { 136 | ino_t inode; 137 | dev_t device; 138 | struct inotable *nxt; 139 | }; 140 | 141 | /* color.c */ 142 | struct extensions { 143 | char *ext; 144 | char *term_flg; 145 | //char *CSS_name, *web_fg, *web_bg, *web_extattr; 146 | struct extensions *nxt; 147 | }; 148 | struct linedraw { 149 | const char **name, *vert, *vert_left, *corner, *copy; 150 | const char *ctop, *cbot, *cmid, *cext, *csingle; 151 | }; 152 | struct meta_ids { 153 | char *name; 154 | char *term_flg; 155 | }; 156 | 157 | /* filter.c */ 158 | struct pattern { 159 | char *pattern; 160 | int relative; 161 | struct pattern *next; 162 | }; 163 | 164 | struct ignorefile { 165 | char *path; 166 | struct pattern *remove, *reverse; 167 | struct ignorefile *next; 168 | }; 169 | 170 | /* info.c */ 171 | struct comment { 172 | struct pattern *pattern; 173 | char **desc; 174 | struct comment *next; 175 | }; 176 | 177 | struct infofile { 178 | char *path; 179 | struct comment *comments; 180 | struct infofile *next; 181 | }; 182 | 183 | 184 | /* Function prototypes: */ 185 | /* tree.c */ 186 | void setoutput(const char *filename); 187 | void print_version(int nl); 188 | void usage(int); 189 | void push_files(const char *dir, struct ignorefile **ig, struct infofile **inf, bool top); 190 | int patignore(const char *name, bool isdir); 191 | int patinclude(const char *name, bool isdir); 192 | struct _info **unix_getfulltree(char *d, u_long lev, dev_t dev, off_t *size, char **err); 193 | struct _info **read_dir(char *dir, ssize_t *n, int infotop); 194 | 195 | int filesfirst(struct _info **, struct _info **); 196 | int dirsfirst(struct _info **, struct _info **); 197 | int alnumsort(struct _info **, struct _info **); 198 | int versort(struct _info **a, struct _info **b); 199 | int reversealnumsort(struct _info **, struct _info **); 200 | int mtimesort(struct _info **, struct _info **); 201 | int ctimesort(struct _info **, struct _info **); 202 | int sizecmp(off_t a, off_t b); 203 | int fsizesort(struct _info **a, struct _info **b); 204 | 205 | void *xmalloc(size_t), *xrealloc(void *, size_t); 206 | char *gnu_getcwd(void); 207 | int patmatch(const char *buf, const char *pat, bool isdir); 208 | void indent(int maxlevel); 209 | void free_dir(struct _info **); 210 | #ifdef __EMX__ 211 | char *prot(long); 212 | #else 213 | char *prot(mode_t); 214 | #endif 215 | char *do_date(time_t); 216 | void printit(const char *); 217 | int psize(char *buf, off_t size); 218 | char Ftype(mode_t mode); 219 | struct _info *stat2info(const struct stat *st); 220 | char *fillinfo(char *buf, const struct _info *ent); 221 | 222 | /* list.c */ 223 | void null_intro(void); 224 | void null_outtro(void); 225 | void null_close(struct _info *file, int level, int needcomma); 226 | void emit_tree(char **dirname, bool needfulltree); 227 | struct totals listdir(char *dirname, struct _info **dir, int lev, dev_t dev, bool hasfulltree); 228 | 229 | /* unix.c */ 230 | int unix_printinfo(char *dirname, struct _info *file, int level); 231 | int unix_printfile(char *dirname, char *filename, struct _info *file, int descend); 232 | int unix_error(char *error); 233 | void unix_newline(struct _info *file, int level, int postdir, int needcomma); 234 | void unix_report(struct totals tot); 235 | 236 | /* html.c */ 237 | void url_encode(FILE *fd, char *s); 238 | void html_intro(void); 239 | void html_outtro(void); 240 | int html_printinfo(char *dirname, struct _info *file, int level); 241 | int html_printfile(char *dirname, char *filename, struct _info *file, int descend); 242 | int html_error(char *error); 243 | void html_newline(struct _info *file, int level, int postdir, int needcomma); 244 | void html_close(struct _info *file, int level, int needcomma); 245 | void html_report(struct totals tot); 246 | void html_encode(FILE *fd, char *s); 247 | 248 | /* xml.c */ 249 | void xml_intro(void); 250 | void xml_outtro(void); 251 | int xml_printinfo(char *dirname, struct _info *file, int level); 252 | int xml_printfile(char *dirname, char *filename, struct _info *file, int descend); 253 | int xml_error(char *error); 254 | void xml_newline(struct _info *file, int level, int postdir, int needcomma); 255 | void xml_close(struct _info *file, int level, int needcomma); 256 | void xml_report(struct totals tot); 257 | 258 | /* json.c */ 259 | void json_indent(int maxlevel); 260 | void json_fillinfo(struct _info *ent); 261 | void json_intro(void); 262 | void json_outtro(void); 263 | int json_printinfo(char *dirname, struct _info *file, int level); 264 | int json_printfile(char *dirname, char *filename, struct _info *file, int descend); 265 | int json_error(char *error); 266 | void json_newline(struct _info *file, int level, int postdir, int needcomma); 267 | void json_close(struct _info *file, int level, int needcomma); 268 | void json_report(struct totals tot); 269 | 270 | /* color.c */ 271 | void parse_dir_colors(void); 272 | bool color(mode_t mode, const char *name, bool orphan, bool islink); 273 | void endcolor(void); 274 | void fancy(FILE *out, char *s); 275 | const char *getcharset(void); 276 | void initlinedraw(bool flag); 277 | 278 | /* hash.c */ 279 | char *uidtoname(uid_t uid); 280 | char *gidtoname(gid_t gid); 281 | bool findino(ino_t, dev_t); 282 | void saveino(ino_t, dev_t); 283 | 284 | /* file.c */ 285 | struct _info **file_getfulltree(char *d, u_long lev, dev_t dev, off_t *size, char **err); 286 | struct _info **tabedfile_getfulltree(char *d, u_long lev, dev_t dev, off_t *size, char **err); 287 | 288 | /* filter.c */ 289 | void gittrim(char *s); 290 | struct pattern *new_pattern(char *pattern); 291 | bool filtercheck(const char *path, const char *name, int isdir); 292 | struct ignorefile *new_ignorefile(const char *path, bool checkparents); 293 | void push_filterstack(struct ignorefile *ig); 294 | struct ignorefile *pop_filterstack(void); 295 | 296 | /* info.c */ 297 | struct infofile *new_infofile(const char *path, bool checkparents); 298 | void push_infostack(struct infofile *inf); 299 | struct infofile *pop_infostack(void); 300 | struct comment *infocheck(const char *path, const char *name, int top, bool isdir); 301 | void printcomment(size_t line, size_t lines, char *s); 302 | 303 | /* list.c */ 304 | void new_emit_unix(char **dirname, bool needfulltree); 305 | 306 | 307 | /* We use the strverscmp.c file if we're not linux: */ 308 | #if !defined(__linux__) || defined(__ANDROID__) 309 | int strverscmp (const char *s1, const char *s2); 310 | #endif 311 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Please read the INSTALL file for installation instructions, particularly if 2 | you are installing on a non-Linux machine. 3 | 4 | This is a handy little utility to display a tree view of directories that 5 | I wrote some time ago and just added color support to. I've decided that 6 | since no one else has done something similar I would go ahead and release 7 | it, even though it's barely a 1st year CS student hack. I've found it damn 8 | handy to peruse a directory tree though, especially when someone is trying to 9 | hide something from you. 10 | 11 | The main distribution site for tree is here: 12 | http://oldmanprogrammer.net/source.php?dir=projects/tree 13 | 14 | Backup GIT sites are: 15 | https://gitlab.com/OldManProgrammer/unix-tree 16 | https://github.com/Old-Man-Programmer/tree 17 | 18 | Current e-mail address to reach me at: steve.baker.llc@gmail.com 19 | 20 | If you don't like the way it looks let me know how you think it should be 21 | formatted. Feel free to suggest modifications and additions. 22 | 23 | Thanks go out so the following people who have helped bring tree to the 24 | pinnacle of perfection that it is: ;) 25 | 26 | Francesc Rocher 27 | - Added HTML output (-H). 28 | - Added options -o, -L and -R. 29 | 30 | Gerald Scheidl 31 | - Added -S option to print ASCII graphics lines for use under linux 32 | console when an alternate console font has been selected (might also 33 | work under DOS telnet). 34 | 35 | Guido Socher (and others) 36 | - Made tree more portable. Should compile under solaris. 37 | 38 | Mitja Lacen 39 | - Discovered bug where tree will segmentation fault on long pathnames. 40 | - Discovered in -L argument processing. 41 | 42 | Nathaniel Delage 43 | - Discovered problem with recursive symlink detection 44 | 45 | A. Karthik 46 | - Suggested option to remove file and directory report at end of tree 47 | listing. 48 | 49 | Roger Luethi 50 | - Spotted memory over-allocation bug in read_dir(). 51 | - Submitted several patches to fix various memory leaks. 52 | 53 | Daniel Lee 54 | - Reported that Tru64 defines TRUE/FALSE in sys/types.h (OSF1 standard?) 55 | 56 | Paolo Violini 57 | - Found bug in tree that caused it to seg-fault if 50 file arguments where 58 | given and directory coloring was turned on. 59 | 60 | Mitsuaki Masuhara 61 | - Discovered tree crashed on missing arguments. 62 | - Discovered that tree did not properly encode characters in filenames 63 | when used as URLs when using the -H option. 64 | - Fixed issue with --charset option processing. 65 | 66 | Johan Fredrik 67 | - Pointed out that tree did not list large files. 68 | 69 | Ted Tiberio 70 | - Submitted patch which fixed a compiler issue and cleaned up HTML and CSS 71 | code, applied CSS to all output, and fixed up HTML to 4.01 strict 72 | standards. 73 | 74 | David MacMahon 75 | - Added '|' support to the pattern matching routines. 76 | 77 | Dan Jacobson 78 | - Pointed out that -t did not sort properly for files with the same 79 | timestamp. 80 | - Suggested option to change HTML title and H1 string. 81 | - Suggested -r option for reversed alphanumeric sort ala 'ls -r'. 82 | 83 | Kyosuke Tokoro 84 | - Provided patch to support OS/2, fix HTML encoding, provide charset 85 | support. Added to authors list. 86 | 87 | Florian Ernst 88 | - Debian maintainer who pointed out problems and applied fire to feet to fix 89 | stuff. 90 | 91 | Jack Cuyler 92 | - Suggested -h option for human readable output for -s, ala ls -lh. 93 | 94 | Jonathon Cranford 95 | - Supplied patch to make tree under cygwin. 96 | 97 | Richard Houser 98 | - Provided patch to fix a colorization bug when dealing with special 99 | files and directories that seem to have an extension. 100 | 101 | Zurd (?) 102 | - Suggested removing trailing slash on user supplied directory names if -f 103 | option is used. 104 | 105 | John Nintendud 106 | - Pointed out broken HTML output in 1.5.1. 107 | 108 | Mark Braker 109 | - Suggested --filelimit option. 110 | 111 | Michael Vogt 112 | - Suggested -v option (version sort). 113 | 114 | Wang Quanhong 115 | - Provided build options for Solaris. 116 | 117 | Craig McDaniel 118 | - Provided build options and source mods for HP NonStop support. 119 | 120 | Christian Grigis 121 | - Noted that setlocale() should come before MB_CUR_MAX check. 122 | 123 | Kamaraju Kusumanchi 124 | - Submitted patch to remove compiler warnings for Solaris. 125 | 126 | Martin Nagy 127 | - Provided patch which fixes issue where indent may output more than it 128 | should when dirs[*] is not properly cleared before use. 129 | 130 | William C. Lathan III 131 | - Showed that tree was not properly quoting arguments to recursively called 132 | tree instances when using -R. 133 | 134 | Ulrich Eckhardt 135 | - Submitted patch for --si option. 136 | 137 | Tim Waugh (redhat) 138 | - Pointed out a potential memory leak in listdir(). 139 | 140 | Markus Schnalke 141 | - Tracked down bug where tree would print "argetm" before the filename of a 142 | symbolic link when the "target" option was specified for LINK in dircolors. 143 | 144 | Ujjwal Kumar 145 | - Suggested that tree backslash spaces like ls does for script use. Made 146 | output more like ls. 147 | 148 | Ivan Shmakov 149 | - Pointed out multiple CLI defenciencies (via Debian) 150 | 151 | Mantas Mikulnas 152 | - Provided patch to make tree more reliably detect the UTF-8 locale. 153 | 154 | Tim Mooney 155 | - Noticed S_ISDOOR/S_IFDOOR spelling mistake for under Solaris. 156 | 157 | Han Hui 158 | - Pointed out possible memory overflow in read_dir (path/lbuf not equal in size 159 | to pathsize/lbufsize.) 160 | 161 | Ryan Hollis 162 | - Pointed out problems with the Makefile w/ respect to OSX. 163 | 164 | Philipp M?ller 165 | - Provided patch for filesize sorting. 166 | 167 | Sascha Zorn 168 | - Pointed out that the HTML output was broken when -L 1 option was used. 169 | 170 | Alexandre Wendling 171 | - Pointed out that modern systems may use 32 bit uid/gids which could lead 172 | to a potential buffer overflow in the uid/gid to name mapping functions. 173 | 174 | Florian Sesser 175 | - Provided patch to add JSON support. 176 | 177 | Brian Mattern & Jason A. Donenfeld 178 | - Provided patch to add --matchdirs functionality. 179 | 180 | Jason A. Donenfeld 181 | - Added --caseinsentive, renamed --ignore-case option. 182 | - Bugged me a lot. 183 | 184 | Stephan Gabert 185 | - Found a bug where the wrong inode (and device) information would be printed 186 | for symbolic links. 187 | 188 | Nick Craig-Wood 189 | - Fixed issue where mbstowcs() fails to null terminate the string due to 190 | improper UTF-8 encoding leading to garbage being printed. 191 | 192 | Mantas Mikulėnas 193 | - Fixed issue with malformed multibyte string handling. 194 | 195 | Wagner Camarao 196 | - Pointed out that JSON size output ignored -h/--si flags 197 | 198 | John Lane, Tad, others 199 | - Fixed JSON output hanging commas 200 | 201 | Jacob Wahlgren 202 | - Improved command line switch error reporting. 203 | - Symbolic links not displayed if a -P pattern is active 204 | - Missing argument error reporting fixes on long format switches. 205 | 206 | Shawn Mehan 207 | - Update BINDIR in Makefile for MacOS X -- It is not allowed to install 208 | programs to /usr/bin on MacOS X any longer due to System Integrity 209 | Protection (SIP) 210 | 211 | Kirill Kolyshkin 212 | - Some man page fixes and cleanups 213 | 214 | Alyssa Ross 215 | - Suggested adding support for BSD's CLICOLOR and CLICOLOR_FORCE environment 216 | variables. 217 | 218 | Tomáš Beránek 219 | - Make sure we always use xmalloc / xrealloc 220 | 221 | Sergei Maximov 222 | - Make XML/HTML/JSON output mutually exclusive. 223 | 224 | Jonas Stein 225 | - Deprecate using local -DLINUX / -DCYGWIN and use the OS provided defines 226 | 227 | John A. Fedoruk 228 | - Suggested --filesfirst option. 229 | 230 | Michael Osipov 231 | - Optimized makefile, HP/UX support. 232 | 233 | Richard Mitchell 234 | - Suggested --metafirst option 235 | 236 | Paul Seyfert 237 | - Honor -n (no color) even if the CLICOLOR_FORCE environment variable is set 238 | 239 | Filips Romāns via Debian 240 | - Make tree colorization use reset (rs code in dir_colors,) not normal color 241 | when resetting attributes. 242 | 243 | Chentao Credungtao via Debian 244 | - Properly sort --fromfile input 245 | 246 | Jake Zimmerman (and others) 247 | - Suggest support for .gitignore files (--gitignore option) 248 | 249 | Eric Pruitt 250 | - Always HTML escape filenames in HTML output even when -C is used. 251 | 252 | Michiel Beijen (and others) 253 | - Suggest Support multiple -I and -P instances. 254 | - Suggest that / match directories in patterns (also Taylor Faubion) 255 | - Suggested to update MANPATH for OS X 256 | 257 | Michal Vasilek 258 | - Various Makefile fixes 259 | 260 | Josey Smith 261 | - Reported an error with * in the patchmatch code where *foo*bar would match 262 | *foo alone. 263 | 264 | Maxim Cournoyer 265 | - Reported HTML url output issue w/ 2.0.0-2.0.1 266 | 267 | Ben Brown 268 | - Updates to the Makefile 269 | - Reported use after free error 270 | 271 | Erik Skultety 272 | - Reported same use after error 273 | 274 | Saniya Maheshwari / Mig-hub ? / Carlos Pinto 275 | - Reported various issues with --gitignore 276 | 277 | Piotr Andruszkow 278 | - Suggested adding support for --info and --gitignore for the --fromfile 279 | option. 280 | 281 | Sebastian Rose 282 | - Another attempt at fixing extraneous /'s in HTML URLs/output. 283 | 284 | Dave Rice 285 | - Fixed XML output 286 | 287 | Timm Fitschen 288 | - Suggest adding support for the NO_COLOR environment variable. 289 | 290 | Chentao Credungtao 291 | - Suggested supporting symbolic links in --fromfile (--fflinks option) 292 | 293 | Sith Wijesinghe and Matthew Sessions 294 | - Remove many C90 isms to make compiling with C90 compilers easier. 295 | 296 | simonpmind (gitlab) 297 | - Reported issue where following links while doing JSON output would lead to 298 | incorrect JSON output. 299 | - Suggested suppressing 'recursive, not followed' when using -L, fixing JSON 300 | error. 301 | 302 | German Lashevich 303 | - Reported an issue where .info patterns relative to the .info file that did 304 | not use a wildcard for matching the prefix were not matching files properly. 305 | 306 | Javier Jaramago Fernández 307 | - Reported a buffer overflow in listdir() when file names are allowed to be 308 | longer than 256 characters (like when using fromfile.) 309 | 310 | 6ramr (gitlab) 311 | - Reported issue with following symbolic links when a full tree was gathered. 312 | 313 | Clinton 314 | - Reported issue where --gitignore does not think a pattern with a singular 315 | terminal '/' (indicating it matches only directories,) is a relative path. 316 | 317 | jack6th (gitlab) 318 | - Don't prematurely sort files/directories with --from*file. 319 | 320 | Hanqin Guan (The OSLab of Peking University): 321 | - Fuzzing testing that identified several problems in --fromfile and 322 | --fromtabfile processing. 323 | - -U should disable --dirsfirst / --filesfirst sorting. 324 | - Make sure gittrim() function can handle a null string. 325 | 326 | Trevor Gross 327 | - Suggested adding support for immediate values to -L with no spacing. 328 | 329 | Christoph Anton Mitterer 330 | - Suggested option negation, which led to --opt-toggle. 331 | 332 | Nicolai Dagestad 333 | - Suggested --hyperlink option. 334 | 335 | Alchemyst (github) 336 | - Reported JSON error when using the --du option and unable to open a 337 | directory. 338 | - Reported that the reported total was incorrect when using the --du option. 339 | 340 | Ivan Ivanovich 341 | - Reported small rounding error in human readable size output. 342 | 343 | Kenta Arai 344 | - Reported Segfault with --filelimit option 345 | - Add NULL guard for json_printinfo() and xml_printinfo() (and fix ftype 346 | printing for XML) 347 | - Fix getcharset() to not return a getenv() pointer. 348 | - Suggest adding .gitignore file to the distribution. 349 | - Clean up some warnings issued by -Wextra 350 | 351 | freemedom (github) 352 | - Provided Makefile information for cross compiling for Android. 353 | 354 | David Seifert 355 | - Provided updates for Makefile 356 | - Suggested move to stdbool.h to avoid problems with newer compilers. 357 | 358 | Daniel Li / Landon Bourma 359 | - Reported segfault from incorrect free. 360 | 361 | And many others whom I've failed to keep track of. I should have started 362 | this list years ago. 363 | 364 | - Steve Baker 365 | Steve.Baker.llc@gmail.com 366 | or 367 | oldmanprogrammer.llc@gmail.com 368 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 5 | 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Library General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License 307 | along with this program; if not, write to the Free Software 308 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 309 | 310 | 311 | Also add information on how to contact you by electronic and paper mail. 312 | 313 | If the program is interactive, make it output a short notice like this 314 | when it starts in an interactive mode: 315 | 316 | Gnomovision version 69, Copyright (C) year name of author 317 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 318 | This is free software, and you are welcome to redistribute it 319 | under certain conditions; type `show c' for details. 320 | 321 | The hypothetical commands `show w' and `show c' should show the appropriate 322 | parts of the General Public License. Of course, the commands you use may 323 | be called something other than `show w' and `show c'; they could even be 324 | mouse-clicks or menu items--whatever suits your program. 325 | 326 | You should also get your employer (if you work as a programmer) or your 327 | school, if any, to sign a "copyright disclaimer" for the program, if 328 | necessary. Here is a sample; alter the names: 329 | 330 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 331 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 332 | 333 | , 1 April 1989 334 | Ty Coon, President of Vice 335 | 336 | This General Public License does not permit incorporating your program into 337 | proprietary programs. If your program is a subroutine library, you may 338 | consider it more useful to permit linking proprietary applications with the 339 | library. If this is what you want to do, use the GNU Library General 340 | Public License instead of this License. 341 | -------------------------------------------------------------------------------- /color.c: -------------------------------------------------------------------------------- 1 | /* $Copyright: $ 2 | * Copyright (c) 1996 - 2024 by Steve Baker (steve.baker.llc@gmail.com) 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 | */ 18 | #include "tree.h" 19 | 20 | /* 21 | * Hacked in DIR_COLORS support for linux. ------------------------------ 22 | */ 23 | /* 24 | * If someone asked me, I'd extend dircolors, to provide more generic 25 | * color support so that more programs could take advantage of it. This 26 | * is really just hacked in support. The dircolors program should: 27 | * 1) Put the valid terms in a environment var, like: 28 | * COLOR_TERMS=linux:console:xterm:vt100... 29 | * 2) Put the COLOR and OPTIONS directives in a env var too. 30 | * 3) Have an option to dircolors to silently ignore directives that it 31 | * doesn't understand (directives that other programs would 32 | * understand). 33 | * 4) Perhaps even make those unknown directives environment variables. 34 | * 35 | * The environment is the place for cryptic crap no one looks at, but 36 | * programs. No one is going to care if it takes 30 variables to do 37 | * something. 38 | */ 39 | enum { 40 | ERROR = -1, CMD_COLOR = 0, CMD_OPTIONS, CMD_TERM, CMD_EIGHTBIT, COL_RESET, 41 | COL_NORMAL, COL_FILE, COL_DIR, COL_LINK, COL_FIFO, COL_DOOR, COL_BLK, COL_CHR, 42 | COL_ORPHAN, COL_SOCK, COL_SETUID, COL_SETGID, COL_STICKY_OTHER_WRITABLE, 43 | COL_OTHER_WRITABLE, COL_STICKY, COL_EXEC, COL_MISSING, 44 | COL_LEFTCODE, COL_RIGHTCODE, COL_ENDCODE, COL_BOLD, COL_ITALIC, 45 | /* Keep this one last, sets the size of the color_code array: */ 46 | DOT_EXTENSION 47 | }; 48 | 49 | enum { 50 | MCOL_INODE, MCOL_PERMS, MCOL_USER, MCOL_GROUP, MCOL_SIZE, MCOL_DATE, 51 | MCOL_INDENTLINES 52 | }; 53 | 54 | bool colorize = false, ansilines = false, linktargetcolor = false; 55 | 56 | char *color_code[DOT_EXTENSION+1] = {NULL}; 57 | 58 | /* 59 | char *vgacolor[] = { 60 | "black", "red", "green", "yellow", "blue", "fuchsia", "aqua", "white", 61 | NULL, NULL, 62 | "transparent", "red", "green", "yellow", "blue", "fuchsia", "aqua", "black" 63 | }; 64 | struct colortable { 65 | char *term_flg, *CSS_name, *font_fg, *font_bg; 66 | } colortable[11]; 67 | */ 68 | 69 | struct extensions *ext = NULL; 70 | const struct linedraw *linedraw; 71 | 72 | char **split(char *str, const char *delim, size_t *nwrds); 73 | int cmd(char *s); 74 | 75 | extern FILE *outfile; 76 | extern bool Hflag, force_color, nocolor; 77 | extern const char *charset; 78 | 79 | void parse_dir_colors(void) 80 | { 81 | char **arg, **c, *colors, *s; 82 | int i, col, cc; 83 | size_t n; 84 | struct extensions *e; 85 | 86 | if (Hflag) return; 87 | 88 | s = getenv("NO_COLOR"); 89 | if (s && s[0]) nocolor = true; 90 | 91 | if (getenv("TERM") == NULL) { 92 | colorize = false; 93 | return; 94 | } 95 | 96 | cc = getenv("CLICOLOR") != NULL; 97 | if (getenv("CLICOLOR_FORCE") != NULL && !nocolor) force_color=true; 98 | s = getenv("TREE_COLORS"); 99 | if (s == NULL) s = getenv("LS_COLORS"); 100 | if ((s == NULL || strlen(s) == 0) && (force_color || cc)) s = ":no=00:rs=0:fi=00:di=01;34:ln=01;36:pi=40;33:so=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:ex=01;32:*.bat=01;32:*.BAT=01;32:*.btm=01;32:*.BTM=01;32:*.cmd=01;32:*.CMD=01;32:*.com=01;32:*.COM=01;32:*.dll=01;32:*.DLL=01;32:*.exe=01;32:*.EXE=01;32:*.arj=01;31:*.bz2=01;31:*.deb=01;31:*.gz=01;31:*.lzh=01;31:*.rpm=01;31:*.tar=01;31:*.taz=01;31:*.tb2=01;31:*.tbz2=01;31:*.tbz=01;31:*.tgz=01;31:*.tz2=01;31:*.z=01;31:*.Z=01;31:*.zip=01;31:*.ZIP=01;31:*.zoo=01;31:*.asf=01;35:*.ASF=01;35:*.avi=01;35:*.AVI=01;35:*.bmp=01;35:*.BMP=01;35:*.flac=01;35:*.FLAC=01;35:*.gif=01;35:*.GIF=01;35:*.jpg=01;35:*.JPG=01;35:*.jpeg=01;35:*.JPEG=01;35:*.m2a=01;35:*.M2a=01;35:*.m2v=01;35:*.M2V=01;35:*.mov=01;35:*.MOV=01;35:*.mp3=01;35:*.MP3=01;35:*.mpeg=01;35:*.MPEG=01;35:*.mpg=01;35:*.MPG=01;35:*.ogg=01;35:*.OGG=01;35:*.ppm=01;35:*.rm=01;35:*.RM=01;35:*.tga=01;35:*.TGA=01;35:*.tif=01;35:*.TIF=01;35:*.wav=01;35:*.WAV=01;35:*.wmv=01;35:*.WMV=01;35:*.xbm=01;35:*.xpm=01;35:"; 101 | 102 | if (s == NULL || (!force_color && (nocolor || !isatty(1)))) { 103 | colorize = false; 104 | return; 105 | } 106 | 107 | colorize = true; 108 | 109 | for(i=0; i < DOT_EXTENSION; i++) color_code[i] = NULL; 110 | 111 | colors = scopy(s); 112 | 113 | arg = split(colors,":",&n); 114 | 115 | for(i=0;arg[i];i++) { 116 | c = split(arg[i],"=",&n); 117 | 118 | switch(col = cmd(c[0])) { 119 | case ERROR: 120 | break; 121 | case DOT_EXTENSION: 122 | if (c[1]) { 123 | e = xmalloc(sizeof(struct extensions)); 124 | e->ext = scopy(c[0]+1); 125 | e->term_flg = scopy(c[1]); 126 | e->nxt = ext; 127 | ext = e; 128 | } 129 | break; 130 | case COL_LINK: 131 | if (c[1] && (strcasecmp("target",c[1]) == 0)) { 132 | linktargetcolor = true; 133 | color_code[COL_LINK] = "01;36"; /* Should never actually be used */ 134 | break; 135 | } 136 | /* Falls through */ 137 | default: 138 | if (c[1]) color_code[col] = scopy(c[1]); 139 | break; 140 | } 141 | 142 | free(c); 143 | } 144 | free(arg); 145 | 146 | /** 147 | * Make sure at least reset (not normal) is defined. We're going to assume 148 | * ANSI/vt100 support: 149 | */ 150 | if (!color_code[COL_LEFTCODE]) color_code[COL_LEFTCODE] = scopy("\033["); 151 | if (!color_code[COL_RIGHTCODE]) color_code[COL_RIGHTCODE] = scopy("m"); 152 | if (!color_code[COL_RESET]) color_code[COL_RESET] = scopy("0"); 153 | if (!color_code[COL_BOLD]) { 154 | color_code[COL_BOLD] = xmalloc(strlen(color_code[COL_LEFTCODE])+strlen(color_code[COL_RIGHTCODE])+2); 155 | sprintf(color_code[COL_BOLD],"%s1%s",color_code[COL_LEFTCODE],color_code[COL_RIGHTCODE]); 156 | } 157 | if (!color_code[COL_ITALIC]) { 158 | color_code[COL_ITALIC] = xmalloc(strlen(color_code[COL_LEFTCODE])+strlen(color_code[COL_RIGHTCODE])+2); 159 | sprintf(color_code[COL_ITALIC],"%s3%s",color_code[COL_LEFTCODE],color_code[COL_RIGHTCODE]); 160 | } 161 | if (!color_code[COL_ENDCODE]) { 162 | color_code[COL_ENDCODE] = xmalloc(strlen(color_code[COL_LEFTCODE])+strlen(color_code[COL_RESET])+strlen(color_code[COL_RIGHTCODE])+1); 163 | sprintf(color_code[COL_ENDCODE],"%s%s%s",color_code[COL_LEFTCODE],color_code[COL_RESET],color_code[COL_RIGHTCODE]); 164 | } 165 | 166 | free(colors); 167 | } 168 | 169 | /* 170 | * You must free the pointer that is allocated by split() after you 171 | * are done using the array. 172 | */ 173 | char **split(char *str, const char *delim, size_t *nwrds) 174 | { 175 | size_t n = 128; 176 | char **w = xmalloc(sizeof(char *) * n); 177 | 178 | w[*nwrds = 0] = strtok(str,delim); 179 | 180 | while (w[*nwrds]) { 181 | if (*nwrds == (n-2)) w = xrealloc(w,sizeof(char *) * (n+=256)); 182 | w[++(*nwrds)] = strtok(NULL,delim); 183 | } 184 | 185 | w[*nwrds] = NULL; 186 | return w; 187 | } 188 | 189 | int cmd(char *s) 190 | { 191 | static struct { 192 | char *cmd; 193 | char cmdnum; 194 | } cmds[] = { 195 | {"rs", COL_RESET}, {"no", COL_NORMAL}, {"fi", COL_FILE}, {"di", COL_DIR}, 196 | {"ln", COL_LINK}, {"pi", COL_FIFO}, {"do", COL_DOOR}, {"bd", COL_BLK}, 197 | {"cd", COL_CHR}, {"or", COL_ORPHAN}, {"so", COL_SOCK}, {"su", COL_SETUID}, 198 | {"sg", COL_SETGID}, {"tw", COL_STICKY_OTHER_WRITABLE}, 199 | {"ow", COL_OTHER_WRITABLE}, {"st", COL_STICKY}, {"ex", COL_EXEC}, 200 | {"mi", COL_MISSING}, {"lc", COL_LEFTCODE}, {"rc", COL_RIGHTCODE}, 201 | {"ec", COL_ENDCODE}, {NULL, 0} 202 | }; 203 | int i; 204 | 205 | if (s == NULL) return ERROR; /* Probably can't happen */ 206 | 207 | if (s[0] == '*') return DOT_EXTENSION; 208 | for(i=0;cmds[i].cmdnum;i++) { 209 | if (!strcmp(cmds[i].cmd,s)) return cmds[i].cmdnum; 210 | } 211 | return ERROR; 212 | } 213 | 214 | bool print_color(int color) 215 | { 216 | if (!color_code[color]) return false; 217 | 218 | fputs(color_code[COL_LEFTCODE],outfile); 219 | fputs(color_code[color],outfile); 220 | fputs(color_code[COL_RIGHTCODE],outfile); 221 | return true; 222 | } 223 | 224 | void endcolor(void) 225 | { 226 | if (color_code[COL_ENDCODE]) 227 | fputs(color_code[COL_ENDCODE],outfile); 228 | } 229 | 230 | 231 | void fancy(FILE *out, char *s) 232 | { 233 | for (; *s; s++) { 234 | switch(*s) { 235 | case '\b': if (colorize && color_code[COL_BOLD]) fputs(color_code[COL_BOLD] , out); break; 236 | case '\f': if (colorize && color_code[COL_ITALIC]) fputs(color_code[COL_ITALIC] , out); break; 237 | case '\r': if (colorize && color_code[COL_ENDCODE]) fputs(color_code[COL_ENDCODE], out); break; 238 | default: 239 | fputc(*s,out); 240 | } 241 | } 242 | } 243 | 244 | bool color(mode_t mode, const char *name, bool orphan, bool islink) 245 | { 246 | struct extensions *e; 247 | size_t l, xl; 248 | 249 | if (orphan) { 250 | if (islink) { 251 | if (print_color(COL_MISSING)) return true; 252 | } else { 253 | if (print_color(COL_ORPHAN)) return true; 254 | } 255 | } 256 | 257 | /* It's probably safe to assume short-circuit evaluation, but we'll do it this way: */ 258 | switch(mode & S_IFMT) { 259 | case S_IFIFO: 260 | return print_color(COL_FIFO); 261 | case S_IFCHR: 262 | return print_color(COL_CHR); 263 | case S_IFDIR: 264 | if (mode & S_ISVTX) { 265 | if ((mode & S_IWOTH)) 266 | if (print_color(COL_STICKY_OTHER_WRITABLE)) return true; 267 | if (!(mode & S_IWOTH)) 268 | if (print_color(COL_STICKY)) return true; 269 | } 270 | if ((mode & S_IWOTH)) 271 | if (print_color(COL_OTHER_WRITABLE)) return true; 272 | return print_color(COL_DIR); 273 | #ifndef __EMX__ 274 | case S_IFBLK: 275 | return print_color(COL_BLK); 276 | case S_IFLNK: 277 | return print_color(COL_LINK); 278 | #ifdef S_IFDOOR 279 | case S_IFDOOR: 280 | return print_color(COL_DOOR); 281 | #endif 282 | #endif 283 | case S_IFSOCK: 284 | return print_color(COL_SOCK); 285 | case S_IFREG: 286 | if ((mode & S_ISUID)) 287 | if (print_color(COL_SETUID)) return true; 288 | if ((mode & S_ISGID)) 289 | if (print_color(COL_SETGID)) return true; 290 | if (mode & (S_IXUSR | S_IXGRP | S_IXOTH)) 291 | if (print_color(COL_EXEC)) return true; 292 | 293 | /* not a directory, link, special device, etc, so check for extension match */ 294 | l = strlen(name); 295 | for(e=ext;e;e=e->nxt) { 296 | xl = strlen(e->ext); 297 | if (!strcmp((l>xl)?name+(l-xl):name,e->ext)) { 298 | fputs(color_code[COL_LEFTCODE], outfile); 299 | fputs(e->term_flg, outfile); 300 | fputs(color_code[COL_RIGHTCODE], outfile); 301 | return true; 302 | } 303 | } 304 | /* colorize just normal files too */ 305 | return print_color(COL_FILE); 306 | } 307 | return print_color(COL_NORMAL); 308 | } 309 | 310 | /* 311 | * Charsets provided by Kyosuke Tokoro (NBG01720@nifty.ne.jp) 312 | */ 313 | const char *getcharset(void) 314 | { 315 | char *cs; 316 | static char buffer[256]; 317 | 318 | cs = getenv("TREE_CHARSET"); 319 | if (cs) return strncpy(buffer,cs,255); 320 | 321 | #ifndef __EMX__ 322 | return NULL; 323 | #else 324 | ULONG aulCpList[3],ulListSize,codepage=0; 325 | 326 | if(!getenv("WINDOWID")) 327 | if(!DosQueryCp(sizeof aulCpList,aulCpList,&ulListSize)) 328 | if(ulListSize>=sizeof*aulCpList) 329 | codepage=*aulCpList; 330 | 331 | switch(codepage) { 332 | case 437: case 775: case 850: case 851: case 852: case 855: 333 | case 857: case 860: case 861: case 862: case 863: case 864: 334 | case 865: case 866: case 868: case 869: case 891: case 903: 335 | case 904: 336 | sprintf(buffer,"IBM%03lu",codepage); 337 | break; 338 | case 367: 339 | return"US-ASCII"; 340 | case 813: 341 | return"ISO-8859-7"; 342 | case 819: 343 | return"ISO-8859-1"; 344 | case 881: case 882: case 883: case 884: case 885: 345 | sprintf(buffer,"ISO-8859-%lu",codepage-880); 346 | break; 347 | case 858: case 924: 348 | sprintf(buffer,"IBM%05lu",codepage); 349 | break; 350 | case 874: 351 | return"TIS-620"; 352 | case 897: case 932: case 942: case 943: 353 | return"Shift_JIS"; 354 | case 912: 355 | return"ISO-8859-2"; 356 | case 915: 357 | return"ISO-8859-5"; 358 | case 916: 359 | return"ISO-8859-8"; 360 | case 949: case 970: 361 | return"EUC-KR"; 362 | case 950: 363 | return"Big5"; 364 | case 954: 365 | return"EUC-JP"; 366 | case 1051: 367 | return"hp-roman8"; 368 | case 1089: 369 | return"ISO-8859-6"; 370 | case 1250: case 1251: case 1253: case 1254: case 1255: case 1256: 371 | case 1257: case 1258: 372 | sprintf(buffer,"windows-%lu",codepage); 373 | break; 374 | case 1252: 375 | return"ISO-8859-1-Windows-3.1-Latin-1"; 376 | default: 377 | return NULL; 378 | } 379 | return buffer; 380 | #endif 381 | } 382 | 383 | void initlinedraw(bool flag) 384 | { 385 | static const char*latin1_3[]={ 386 | "ISO-8859-1", "ISO-8859-1:1987", "ISO_8859-1", "latin1", "l1", "IBM819", 387 | "CP819", "csISOLatin1", "ISO-8859-3", "ISO_8859-3:1988", "ISO_8859-3", 388 | "latin3", "ls", "csISOLatin3", NULL 389 | }; 390 | static const char*iso8859_789[]={ 391 | "ISO-8859-7", "ISO_8859-7:1987", "ISO_8859-7", "ELOT_928", "ECMA-118", 392 | "greek", "greek8", "csISOLatinGreek", "ISO-8859-8", "ISO_8859-8:1988", 393 | "iso-ir-138", "ISO_8859-8", "hebrew", "csISOLatinHebrew", "ISO-8859-9", 394 | "ISO_8859-9:1989", "iso-ir-148", "ISO_8859-9", "latin5", "l5", 395 | "csISOLatin5", NULL 396 | }; 397 | static const char*shift_jis[]={ 398 | "Shift_JIS", "MS_Kanji", "csShiftJIS", NULL 399 | }; 400 | static const char*euc_jp[]={ 401 | "EUC-JP", "Extended_UNIX_Code_Packed_Format_for_Japanese", 402 | "csEUCPkdFmtJapanese", NULL 403 | }; 404 | static const char*euc_kr[]={ 405 | "EUC-KR", "csEUCKR", NULL 406 | }; 407 | static const char*iso2022jp[]={ 408 | "ISO-2022-JP", "csISO2022JP", "ISO-2022-JP-2", "csISO2022JP2", NULL 409 | }; 410 | static const char*ibm_pc[]={ 411 | "IBM437", "cp437", "437", "csPC8CodePage437", "IBM852", "cp852", "852", 412 | "csPCp852", "IBM863", "cp863", "863", "csIBM863", "IBM855", "cp855", 413 | "855", "csIBM855", "IBM865", "cp865", "865", "csIBM865", "IBM866", 414 | "cp866", "866", "csIBM866", NULL 415 | }; 416 | static const char*ibm_ps2[]={ 417 | "IBM850", "cp850", "850", "csPC850Multilingual", "IBM00858", "CCSID00858", 418 | "CP00858", "PC-Multilingual-850+euro", NULL 419 | }; 420 | static const char*ibm_gr[]={ 421 | "IBM869", "cp869", "869", "cp-gr", "csIBM869", NULL 422 | }; 423 | static const char*gb[]={ 424 | "GB2312", "csGB2312", NULL 425 | }; 426 | static const char*utf8[]={ 427 | "UTF-8", "utf8", NULL 428 | }; 429 | static const char*big5[]={ 430 | "Big5", "csBig5", NULL 431 | }; 432 | static const char*viscii[]={ 433 | "VISCII", "csVISCII", NULL 434 | }; 435 | static const char*koi8ru[]={ 436 | "KOI8-R", "csKOI8R", "KOI8-U", NULL 437 | }; 438 | static const char*windows[]={ 439 | "ISO-8859-1-Windows-3.1-Latin-1", "csWindows31Latin1", 440 | "ISO-8859-2-Windows-Latin-2", "csWindows31Latin2", "windows-1250", 441 | "windows-1251", "windows-1253", "windows-1254", "windows-1255", 442 | "windows-1256", "windows-1256", "windows-1257", NULL 443 | }; 444 | 445 | static const struct linedraw cstable[]={ 446 | { latin1_3, "| ", "|--", "·--", "©", 447 | " [", " [", " [", " [", " [" }, 448 | { iso8859_789, "| ", "|--", "·--", "(c)", 449 | " [", " [", " [", " [", " [" }, 450 | { shift_jis, "\204\240 ", "\204\245", "\204\244", "(c)", 451 | " [", " [", " [", " [", " [" }, 452 | { euc_jp, "\250\242 ", "\250\247", "\250\246", "(c)", 453 | " [", " [", " [", " [", " [" }, 454 | { euc_kr, "\246\242 ", "\246\247", "\246\246", "(c)", 455 | " [", " [", " [", " [", " [" }, 456 | { iso2022jp, "\033$B(\"\033(B ", "\033$B('\033(B", "\033$B(&\033(B", "(c)", 457 | " [", " [", " [", " [", " [" }, 458 | { ibm_pc, "\263 ", "\303\304\304", "\300\304\304", "(c)", 459 | " [", " [", " [", " [", " [" }, 460 | { ibm_ps2, "\263 ", "\303\304\304", "\300\304\304", "\227", 461 | " [", " [", " [", " [", " [" }, 462 | { ibm_gr, "\263 ", "\303\304\304", "\300\304\304", "\270", 463 | " [", " [", " [", " [", " [" }, 464 | { gb, "\251\246 ", "\251\300", "\251\270", "(c)", 465 | " [", " [", " [", " [", " [" }, 466 | { utf8, "\342\224\202\302\240\302\240", "\342\224\234\342\224\200\342\224\200", 467 | "\342\224\224\342\224\200\342\224\200", "\302\251", 468 | " \342\216\247", " \342\216\251", " \342\216\250", " \342\216\252", " {" }, 469 | { big5, "\242x ", "\242u", "\242|", "(c)", 470 | " [", " [", " [", " [", " [" }, 471 | { viscii, "| ", "|--", "`--", "\371", 472 | " [", " [", " [", " [", " [" }, 473 | { koi8ru, "\201 ", "\206\200\200", "\204\200\200", "\277", 474 | " [", " [", " [", " [", " [" }, 475 | { windows, "| ", "|--", "`--", "\251", 476 | " [", " [", " [", " [", " [" }, 477 | { NULL, "| ", "|--", "`--", "(c)", 478 | " [", " [", " [", " [", " [" }, 479 | }; 480 | const char**s; 481 | 482 | if (flag) { 483 | fprintf(stderr,"Valid charsets include:\n"); 484 | for(linedraw=cstable;linedraw->name;++linedraw) { 485 | for(s=linedraw->name;*s;++s) { 486 | fprintf(stderr," %s\n",*s); 487 | } 488 | } 489 | return; 490 | } 491 | if (charset) { 492 | for(linedraw=cstable;linedraw->name;++linedraw) 493 | for(s=linedraw->name;*s;++s) 494 | if(!strcasecmp(charset,*s)) return; 495 | } 496 | linedraw=cstable+sizeof cstable/sizeof*cstable-1; 497 | } 498 | -------------------------------------------------------------------------------- /doc/tree.1: -------------------------------------------------------------------------------- 1 | .\" $Copyright: $ 2 | .\" Copyright (c) 1996 - 2024 by Steve Baker 3 | .\" All Rights reserved 4 | .\" 5 | .\" This program is free software; you can redistribute it and/or modify 6 | .\" it under the terms of the GNU General Public License as published by 7 | .\" the Free Software Foundation; either version 2 of the License, or 8 | .\" (at your option) any later version. 9 | .\" 10 | .\" This program is distributed in the hope that it will be useful, 11 | .\" but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | .\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | .\" GNU General Public License for more details. 14 | .\" 15 | .\" You should have received a copy of the GNU General Public License 16 | .\" along with this program; if not, write to the Free Software 17 | .\" Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | .\" 19 | ... 20 | .TH TREE 1 "" "Tree 2.2.1" 21 | .SH NAME 22 | tree \- list contents of directories in a tree-like format. 23 | .SH SYNOPSIS 24 | \fBtree\fP 25 | [\fB-acdfghilnpqrstuvxACDFJQNSUX\fP] 26 | [\fB-L\fP \fIlevel\fP [\fB-R\fP]] 27 | [\fB-H\fP [-]\fIbaseHREF\fP] 28 | [\fB-T\fP \fItitle\fP] 29 | [\fB-o\fP \fIfilename\fP] 30 | [\fB-P\fP \fIpattern\fP] 31 | [\fB-I\fP \fIpattern\fP] 32 | [\fB--gitignore\fP] 33 | [\fB--gitfile\fP[\fB=\fP]\fIfile\fP] 34 | [\fB--matchdirs\fP] 35 | [\fB--metafirst\fP] 36 | [\fB--ignore-case\fP] 37 | [\fB--nolinks\fP] 38 | [\fB--hintro\fP[\fB=\fP]\fIfile\fP] 39 | [\fB--houtro\fP[\fB=\fP]\fIfile\fP] 40 | [\fB--inodes\fP] 41 | [\fB--device\fP] 42 | [\fB--sort\fP[\fB=\fP]\fIname\fP] 43 | [\fB--dirsfirst\fP] 44 | [\fB--filesfirst\fP] 45 | [\fB--filelimit\fP[\fB=\fP]\fI#\fP] 46 | [\fB--si\fP] 47 | [\fB--du\fP] 48 | [\fB--prune\fP] 49 | [\fB--charset\fP[\fB=\fP]\fIX\fP] 50 | [\fB--timefmt\fP[\fB=\fP]\fIformat\fP] 51 | [\fB--fromfile\fP] 52 | [\fB--fromtabfile\fP] 53 | [\fB--fflinks\fP] 54 | [\fB--info\fP] 55 | [\fB--infofile\fP[\fB=\fP]\fIfile\fP] 56 | [\fB--noreport\fP] 57 | [\fB--hyperlink\fP] 58 | [\fB--scheme\fP[\fB=\fP]\fIschema\fP] 59 | [\fB--authority\fP[\fB=\fP]\fIhostname\fP] 60 | [\fB--opt-toggle\fP] 61 | [\fB--version\fP] 62 | [\fB--help\fP] 63 | [\fB--\fP] [\fIdirectory\fP ...] 64 | 65 | .br 66 | .SH DESCRIPTION 67 | \fITree\fP is a recursive directory listing program that produces a depth 68 | indented listing of files, which is colorized ala \fIdircolors\fP if the 69 | \fBLS_COLORS\fP environment variable is set and output is to tty. With no 70 | arguments, \fItree\fP lists the files in the current directory. When directory 71 | arguments are given, \fItree\fP lists all the files and/or directories found in 72 | the given directories each in turn. Upon completion of listing all 73 | files/directories found, \fItree\fP returns the total number of files and/or 74 | directories listed. 75 | 76 | By default, when a symbolic link is encountered, the path that the symbolic 77 | link refers to is printed after the name of the link in the format: 78 | .br 79 | 80 | name -> real-path 81 | .br 82 | 83 | If the `\fB-l\fP' option is given and the symbolic link refers to an actual 84 | directory, then \fItree\fP will follow the path of the symbolic link as if 85 | it were a real directory. 86 | .br 87 | 88 | .SH OPTIONS 89 | \fITree\fP understands the following command line switches: 90 | 91 | .SH LISTING OPTIONS 92 | 93 | .TP 94 | .B -a 95 | All files are printed. By default tree does not print hidden files (those 96 | beginning with a dot `.'). In no event does tree print the file system 97 | constructs `.' (current directory) and `..' (previous directory). 98 | .PP 99 | .TP 100 | .B -d 101 | List directories only. 102 | .PP 103 | .TP 104 | .B -l 105 | Follows symbolic links if they point to directories, as if they were 106 | directories. Symbolic links that will result in recursion are avoided when 107 | detected. 108 | .PP 109 | .TP 110 | .B -f 111 | Prints the full path prefix for each file. 112 | .PP 113 | .TP 114 | .B -x 115 | Stay on the current file-system only. Ala \fBfind \fI-xdev\fP. 116 | .PP 117 | .TP 118 | .B -L \fIlevel\fP 119 | Max display depth of the directory tree. 120 | .PP 121 | .TP 122 | .B -R 123 | Recursively cross down the tree each \fIlevel\fP directories (see \fB-L\fP 124 | option), and at each level outputting to a file named \fB00Tree.html\fP 125 | (ala \fB-o\fP). 126 | .PP 127 | .TP 128 | .B -P \fIpattern\fP 129 | List only those files that match the wild-card \fIpattern\fP. You may have 130 | multiple -P options. Note: you must use the \fI-a\fP option to also consider 131 | those files beginning with a dot `.' for matching. Valid wildcard operators 132 | are `*' (any zero or more characters), `**` (any zero or more characters as 133 | well as null /'s, i.e. /**/ may match a single /), `?' (any single character), 134 | `[...]' (any single character listed between brackets (optional - (dash) for 135 | character range may be used: ex: [A-Z]), and `[^...]' (any single character 136 | not listed in brackets) and `|' separates alternate patterns. A '/' at the 137 | end of the pattern matches directories, but not files. 138 | .PP 139 | .TP 140 | .B -I \fIpattern\fP 141 | Do not list those files that match the wild-card \fIpattern\fP. You may have 142 | multiple -I options. See \fI-P\fP above for information on wildcard patterns. 143 | .PP 144 | .TP 145 | .B --gitignore 146 | Uses git \fB.gitignore\fP files for filtering files and directories. Also 147 | uses \fB$GIT_DIR/info/exclude\fP if present. 148 | .PP 149 | .TP 150 | .B --gitfile\fR[\fB=\fR]\fIfile\fP 151 | Use \fIfile\fP explicitly as a gitignore file. 152 | .PP 153 | .TP 154 | .B --ignore-case 155 | If a match pattern is specified by the \fB-P\fP or \fB-I\fP option, this will 156 | cause the pattern to match without regard to the case of each letter. 157 | .PP 158 | .TP 159 | .B --matchdirs 160 | If a match pattern is specified by the \fB-P\fP option, this will cause the 161 | pattern to be applied to directory names (in addition to filenames). In the 162 | event of a match on the directory name, matching is disabled for the directory's 163 | contents. If the \fB--prune\fP option is used, empty folders that match the 164 | pattern will not be pruned. 165 | .PP 166 | .TP 167 | .B --metafirst 168 | Print the meta-data information at the beginning of the line rather than 169 | after the indentation lines. 170 | .PP 171 | .TP 172 | .B --prune 173 | Makes tree prune empty directories from the output, useful when used in 174 | conjunction with \fB-P\fP or \fB-I\fP. See \fBBUGS AND NOTES\fP below for 175 | more information on this option. 176 | .PP 177 | .TP 178 | .B --info 179 | Prints file comments found in .info files. See \fB.INFO FILES\fP below 180 | for more information on the format of .info files. 181 | .PP 182 | .TP 183 | .B --infofile\fR[\fB=\fR]\fIfile\fP 184 | Use \fIfile\fP explicitly as a info file. 185 | .PP 186 | .TP 187 | .B --noreport 188 | Omits printing of the file and directory report at the end of the tree 189 | listing. 190 | .PP 191 | .TP 192 | .B --charset\fR[\fB=\fR]\fIcharset\fP 193 | Set the character set to use when outputting HTML and for line drawing. 194 | .PP 195 | .TP 196 | .B --filelimit\fR[\fB=\fR]\fI#\fP 197 | Do not descend directories that contain more than \fI#\fP entries. 198 | .PP 199 | .TP 200 | .B --timefmt\fR[\fB=\fR]\fIformat\fP 201 | Prints (implies -D) and formats the date according to the format string 202 | which uses the \fBstrftime\fP(3) syntax. 203 | .PP 204 | .TP 205 | .B -o \fIfilename\fP 206 | Send output to \fIfilename\fP. 207 | .PP 208 | 209 | .SH FILE OPTIONS 210 | 211 | .TP 212 | .B -q 213 | Print non-printable characters in filenames as question marks instead of the 214 | default. 215 | .PP 216 | .TP 217 | .B -N 218 | Print non-printable characters as is instead of as escaped octal numbers. 219 | .PP 220 | .TP 221 | .B -Q 222 | Quote the names of files in double quotes. 223 | .PP 224 | .TP 225 | .B -p 226 | Print the file type and permissions for each file (as per ls -l). 227 | .PP 228 | .TP 229 | .B -u 230 | Print the username, or UID # if no username is available, of the file. 231 | .PP 232 | .TP 233 | .B -g 234 | Print the group name, or GID # if no group name is available, of the file. 235 | .PP 236 | .TP 237 | .B -s 238 | Print the size of each file in bytes along with the name. 239 | .PP 240 | .TP 241 | .B -h 242 | Print the size of each file but in a more human readable way, e.g. appending a 243 | size letter for kilobytes (K), megabytes (M), gigabytes (G), terabytes (T), 244 | petabytes (P) and exabytes (E). 245 | .PP 246 | .TP 247 | .B --si 248 | Like \fB-h\fP but use SI units (powers of 1000) instead. 249 | .PP 250 | .TP 251 | .B --du 252 | For each directory report its size as the accumulation of sizes of all its files 253 | and sub-directories (and their files, and so on). The total amount of used 254 | space is also given in the final report (like the 'du -c' command.) This option 255 | requires tree to read the entire directory tree before emitting it, see 256 | \fBBUGS AND NOTES\fP below. Implies \fB-s\fP. 257 | .PP 258 | .TP 259 | .B -D 260 | Print the date of the last modification time or if \fB-c\fP is used, the last 261 | status change time for the file listed. 262 | .PP 263 | .TP 264 | .B -F 265 | Append a `/' for directories, a `=' for socket files, a `*' for executable 266 | files, a `>' for doors (Solaris) and a `|' for FIFO's, as per ls -F 267 | .PP 268 | .TP 269 | .B --inodes 270 | Prints the inode number of the file or directory 271 | .PP 272 | .TP 273 | .B --device 274 | Prints the device number to which the file or directory belongs 275 | .PP 276 | 277 | .SH SORTING OPTIONS 278 | 279 | .TP 280 | .B -v 281 | Sort the output by version. 282 | .PP 283 | .TP 284 | .B -t 285 | Sort the output by last modification time instead of alphabetically. 286 | .PP 287 | .TP 288 | .B -c 289 | Sort the output by last status change instead of alphabetically. Modifies the 290 | \fB-D\fP option (if used) to print the last status change instead of 291 | modification time. 292 | .PP 293 | .TP 294 | .B -U 295 | Do not sort. Lists files in directory order. Disables \fB--dirsfirst\fP. 296 | .PP 297 | .TP 298 | .B -r 299 | Sort the output in reverse order. This is a meta-sort that alters the above sorts. 300 | This option is disabled when \fB-U\fP is used. 301 | .PP 302 | .TP 303 | .B --dirsfirst 304 | List directories before files. This is a meta-sort that alters the above sorts. 305 | This option is disabled when \fB-U\fP is used. 306 | .PP 307 | .TP 308 | .B --filesfirst 309 | List files before directories. This is a meta-sort that alters the above sorts. 310 | This option is disabled when \fB-U\fP is used. 311 | .PP 312 | .TP 313 | .B --sort\fR[\fB=\fR]\fItype\fR 314 | Sort the output by \fItype\fR instead of name. Possible values are: 315 | \fBctime\fR (\fB-c\fP), 316 | \fBmtime\fR (\fB-t\fB), \fBsize\fR, \fBversion\fR (\fB-v\fR) or \fBnone\fR 317 | (\fB-U\fR). 318 | 319 | .SH GRAPHICS OPTIONS 320 | 321 | .TP 322 | .B -i 323 | Makes tree not print the indentation lines, useful when used in conjunction 324 | with the \fB-f\fP option. Also removes as much whitespace as possible when used 325 | with the \fB-J\fP or \fB-X\fP options. 326 | .PP 327 | .TP 328 | .B -A 329 | Turn on ANSI line graphics hack when printing the indentation lines. 330 | .PP 331 | .TP 332 | .B -S 333 | Turn on CP437 line graphics (useful when using Linux console mode fonts). This 334 | option is now equivalent to `\fB--charset=IBM437\fP' and may eventually be depreciated. 335 | .PP 336 | .TP 337 | .B -n 338 | Turn colorization off always, over-ridden by the \fB-C\fP option, however 339 | overrides CLICOLOR_FORCE if present. 340 | .PP 341 | .TP 342 | .B -C 343 | Turn colorization on always, using built-in color defaults if the LS_COLORS or 344 | TREE_COLORS environment variables are not set. Useful to colorize output to a 345 | pipe. 346 | .PP 347 | 348 | .SH XML/JSON/HTML/HYPERLINKS OPTIONS 349 | 350 | .TP 351 | .B -X 352 | Turn on XML output. Outputs the directory tree as an XML formatted file. 353 | .PP 354 | .TP 355 | .B -J 356 | Turn on JSON output. Outputs the directory tree as a JSON formatted array. 357 | .PP 358 | .TP 359 | .B -H [-]\fIbaseHREF\fP 360 | Turn on HTML output, including HTTP references. Useful for ftp sites. 361 | \fIbaseHREF\fP gives the base ftp location when using HTML output. That is, the 362 | local directory may be `/local/ftp/pub', but it must be referenced as 363 | `ftp://hostname.organization.domain/pub' (\fIbaseHREF\fP should be 364 | `ftp://hostname.organization.domain'). If \fIbaseHREF\fP begins with a dash 365 | (\fB-\fP), then the dash is removed and indicates that tree should remove the 366 | first directory name from the href URL. Hint: don't use ANSI lines with this 367 | option, and don't give more than one directory in the directory list. If you 368 | wish to use colors via CSS style-sheet, use the -C option in addition to this 369 | option to force color output. 370 | .PP 371 | .TP 372 | .B --hintro\fR[\fB=\fR]\fIfile\fP 373 | Use \fIfile\fP as the HTML intro in place of the default one. Use an empty 374 | file or \fI/dev/null\fP to eliminate the intro altogether. 375 | .PP 376 | .TP 377 | .B --houtro\fR[\fB=\fR]\fIfile\fP 378 | Use \fIfile\fP as the HTML outro in place of the default one. Use an empty 379 | file or \fI/dev/null\fP to eliminate the outro altogether. 380 | .PP 381 | .TP 382 | .B -T \fItitle\fP 383 | Sets the title and H1 header string in HTML output mode. 384 | .PP 385 | .TP 386 | .B --nolinks 387 | Turns off hyperlinks in HTML output. 388 | .PP 389 | .TP 390 | .B --hyperlink 391 | Enable OSC 8 terminal hyperlinks for terminals that support them. See \fBBUGS 392 | AND NOTES\fR below. 393 | .PP 394 | .TP 395 | .B --scheme\fR[\fB=\fR]\fIschema\fP 396 | Sets the schema used in the OSC 8 hyperlinks. The default schema is '\fBfile://\fR'. 397 | If the schema omits the colon (:), then \fB://\fR will be appended to the schema. 398 | .TP 399 | .B --authority\fR[\fB=\fR]\fIhostname|authority\fP 400 | Sets the authority (hostname) to use for the OSC 8 hyperlinks. By default the 401 | local hostname of the machine as returned by gethostname() is used as the 402 | authority. A dot (\fB.\fP) or a set of ""'s, sans '=', (i.e. the empty string) 403 | can be used to indicate a null authority. 404 | .PP 405 | 406 | .SH INPUT OPTIONS 407 | 408 | .TP 409 | .B --fromfile 410 | Reads a directory listing from a file rather than the file-system. Paths 411 | provided on the command line are files to read from rather than directories to 412 | search. The dot (.) directory indicates that tree should read paths from 413 | standard input. NOTE: this is only suitable for reading the output of a program 414 | such as find, not 'tree -fi' as symlinks are not distinguished from files that 415 | simply contain ' -> ' as part of the filename unless the \fB--fflinks\fP option 416 | is used. 417 | .PP 418 | .TP 419 | .B --fromtabfile 420 | Like \fB--fromfile\fP, tree reads a directory tree from a text file where the 421 | files are tab indented in a tree like format to indicate the directory nesting 422 | level. 423 | .PP 424 | .TP 425 | .B --fflinks 426 | Processes symbolic link information found in a file, as from the output of 427 | \fB'tree -fi --noreport'\fP. Only the first occurrence of the string \fB' -> '\fP 428 | is used to denote the separation of the filename from the link. 429 | .PP 430 | 431 | .SH MISC OPTIONS 432 | 433 | .TP 434 | .B --opt-toggle 435 | Enables option "toggling". Turns on the ability to toggle options such as -a, 436 | -h, etc. Useful to add to an alias when you wish to disable options enabled in 437 | the alias. 438 | .PP 439 | .TP 440 | .B --help 441 | Outputs a verbose usage listing. 442 | .PP 443 | .TP 444 | .B --version 445 | Outputs the version of tree. 446 | .PP 447 | .TP 448 | .B -- 449 | Option processing terminator. No further options will be processed after this. 450 | .PP 451 | 452 | .SH .INFO FILES 453 | 454 | \fB.info\fP files are similar to \.gitignore files, if a .info file is found 455 | while scanning a directory it is read and added to a stack of .info 456 | information. Each file is composed of comments (lines starting with hash marks 457 | (#),) or wild-card patterns which may match a file relative to the directory 458 | the \.info file is found in. If a file should match a pattern, the tab indented 459 | comment that follows the pattern is used as the file comment. A comment is 460 | terminated by a non-tab indented line. Multiple patterns, each to a line, may 461 | share the same comment. 462 | 463 | .br 464 | .SH FILES 465 | \fB/etc/DIR_COLORS\fP System color database. 466 | .br 467 | \fB~/.dircolors\fP Users color database. 468 | .br 469 | \fB.gitignore\fP Git exclusion file 470 | .br 471 | \fB$GIT_DIR/info/exclude\fP Global git file exclusion list 472 | .br 473 | \fB.info\fP File comment file 474 | .br 475 | \fB/usr/share/finfo/global_info\fP Global file comment file 476 | 477 | 478 | .SH ENVIRONMENT 479 | \fBLS_COLORS\fP Color information created by dircolors 480 | .br 481 | \fBTREE_COLORS\fP Uses this for color information over LS_COLORS if it is set. 482 | .br 483 | \fBTREE_CHARSET\fP Character set for tree to use in HTML mode. 484 | .br 485 | \fBCLICOLOR\fP Enables colorization even if TREE_COLORS or LS_COLORS is not set. 486 | .br 487 | \fBCLICOLOR_FORCE\fP Always enables colorization (effectively -C) 488 | .br 489 | \fBNO_COLOR\fP Disable colorization (effectively -n) (see \fBhttps://no-color.org/\fP) 490 | .br 491 | \fBLC_CTYPE\fP Locale for filename output. 492 | .br 493 | \fBLC_TIME\fP Locale for timefmt output, see \fBstrftime\fP(3). 494 | .br 495 | \fBTZ\fP Timezone for timefmt output, see \fBstrftime\fP(3). 496 | .br 497 | \fBSTDDATA_FD\fP Enable the stddata feature, optionally set descriptor to use. 498 | 499 | .SH AUTHOR 500 | Steve Baker (Steve.Baker.llc@gmail.com) 501 | .br 502 | HTML output hacked by Francesc Rocher (rocher@econ.udg.es) 503 | .br 504 | Charsets and OS/2 support by Kyosuke Tokoro (NBG01720@nifty.ne.jp) 505 | 506 | .SH BUGS AND NOTES 507 | Tree does not prune "empty" directories when the -P and -I options are used by 508 | default. Use the --prune option. 509 | 510 | The -h and --si options round to the nearest whole number unlike the ls 511 | implementations which rounds up always. 512 | 513 | Pruning files and directories with the -I, -P and --filelimit options will 514 | lead to incorrect file/directory count reports. 515 | 516 | The --prune and --du options cause tree to accumulate the entire tree in memory 517 | before emitting it. For large directory trees this can cause a significant delay 518 | in output and the use of large amounts of memory. 519 | 520 | The timefmt expansion buffer is limited to a ridiculously large 255 characters. 521 | Output of time strings longer than this will be undefined, but are guaranteed 522 | to not exceed 255 characters. 523 | 524 | XML/JSON trees are not colored, which is a bit of a shame. The jq utility 525 | can colorize the JSON however, just not the filenames by file-type ala LS_COLORS. 526 | 527 | OSC 8 hyperlinks may be poorly supported by your terminal. For my version of 528 | Konsole it is necessary to set the schema to file: (no //) and use a null 529 | authority. It may also be necessary to spend 3.5 hours finding the option to 530 | turn on hyperlinks. 531 | 532 | Probably more. 533 | 534 | As of version 2.0.0, in Linux, tree will attempt to automatically output a 535 | compact JSON tree on file descriptor 3 (what I call stddata,) if present and the 536 | environment variable STDDATA_FD is defined or set to a positive non-zero file 537 | descriptor value to use to output on. It is hoped that some day a better 538 | Linux/Unix shell may take advantage of this feature, though BSON would probably 539 | be a better format for this. 540 | 541 | .SH SEE ALSO 542 | .BR dircolors (1), 543 | .BR ls (1), 544 | .BR find (1), 545 | .BR du (1), 546 | .BR jq (1), 547 | .BR strftime (3), 548 | .BR gitignore (5) 549 | -------------------------------------------------------------------------------- /CHANGES: -------------------------------------------------------------------------------- 1 | Version 2.2.1 (11/25/2024) 2 | - Yet another brown-paper bag release. I seem to like doing these. 3 | - Fix regression where I free a pointer that should not have been freed due 4 | to a variable renaming that wasn't completed. (Daniel Li / Landon Bourma) 5 | - Put back, in a modified form, HTML href directory path fixing that was 6 | removed in 2.1.2. If the "baseHREF" part after the -H begins with a dash 7 | (-), the dash is removed and the lead directory name is removed from the 8 | href, otherwise it is left as-is. It can be very difficult to know how to 9 | handle the host and directory part given the plethora of protocols and 10 | what-not, so hopefully this should give enough control over that to suit 11 | most requirements. 12 | 13 | Version 2.2.0 (11/24/2024) 14 | - Add option --opt-toggle which turns on the ability to toggle options such 15 | as -a, -p, etc. Useful to add to an alias for turning an option off when 16 | using said alias. (Christoph Anton Mitterer) 17 | - Add --hyperlink option to print OSC 8 terminal hyperlinks for files. Also 18 | adds the --scheme and --authority options to modify the schema and hostname/ 19 | authority of the links. (Nicolai Dagestad) 20 | OSC 8 Terminal hyperlinks: 21 | https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda 22 | - Maybe finally fix JSON error reporting when unable to open a directory and a 23 | full tree is required, such as when using --du. (Alchemyst@github) 24 | - Fix small rounding error in human readable size (-h) output, where 9.99K is 25 | rounded to 10.0K rather than 10K (Ivan Ivanovich) 26 | - Fix the totals report for sizes when --du option is used. The directory 27 | size total was correct, but the final report was an accumulation of all 28 | the directory totals rather than just the top most directory total. 29 | (Alchemyst@github) 30 | - Add .gitignore file to distribution for those wanting that. (Kenta Arai) 31 | - Add 'none' as a valid --sort option (i.e. -U). 32 | - Add ability to cross compile for Android (freemedom@github) 33 | - List charsets again if --charsets is not given an argument. 34 | - Allow --help and usage to use ANSI bold and italic if colorization is 35 | enabled. 36 | - General code cleanups: 37 | - Removed unused externs where possible. 38 | - Clean up some warnings issued by -Wextra (Kenta Arai) 39 | - Update Makefile to allow CC and the CFLAGS -O3 option to be overridden, 40 | move CPPFLAGS into their own variable, add -Wstrict-prototypes 41 | (David Seifert) 42 | - Long over-due move to stdbool.h, removes custom bool type and changes all 43 | occurrences of TRUE/FALSE to true/false. (David Seifert / others) This 44 | likely makes C99+ even more of a requirement now. Please let me know if 45 | this requires a work-around for your system. 46 | - Went ahead and added -Wconversion to the Makefile as well. This required a 47 | large number of type conversion fixing which may have unexpected side 48 | effects, but should hopefully help with tree safely dealing with absurd 49 | sizes/number of things in the future as this promotes using size_t more. 50 | This probably needs more work to do properly however. 51 | - Apply the const constraint on parameter strings wherever possible. 52 | 53 | Version 2.1.3 (07/09/2024) 54 | - Mostly a brown-paper bag release to fix the below regression and add a 55 | feature I forgot to add. 56 | - Fix regression in search() function that broke --fromfile (Florian Ernst) 57 | (caused by removing too much code while fixing premature sort for 58 | --fromfile) 59 | - Allow the -L option to accept its parameter immediately (with no space) 60 | instead of requiring it be the next option word. (Trevor Gross) 61 | 62 | Version 2.1.2 (07/01/2024) 63 | - Fix issue where --gitignore does not think a pattern with a singular 64 | terminal '/' (indicating it matches only directories,) is a relative path. 65 | (Clinton) 66 | - Don't emit the error 'recursive, not followed' if when using -L, the depth 67 | would prevent descending anyway. This also fixes up a JSON output error 68 | (missing comma) when this happens. (simonpmind) 69 | - Don't prematurely sort files/directories with --from*file. (gitlab @jack6th) 70 | - Various seg-faults fixed (Hanqin Guan (The OSLab of Peking University)): 71 | - Make doubly sure that there is actually a previous path entry when reading 72 | from a tabbed file. 73 | - Make sure there is actually a file entity when applying the link info to 74 | it when reading fromfile using --fflinks. 75 | - Increase space for the path a little in listdir(), just to be sure. 76 | - Make sure that there is no topsort (--dirsfirst / --filesfirst) if there 77 | is no basesort (-U). 78 | - Make sure gittrim() function can handle a null string. 79 | - Update email address to steve.baker.llc@gmail.com from ice@mama.indstate.edu 80 | which has been permanently disabled. 81 | 82 | Version 2.1.1 (05/31/2023) 83 | - Various spelling corrections. 84 | - Fix issue where following links while doing JSON output would lead to 85 | incorrect JSON output. (simonpmind) 86 | - Fix issue where .info patterns relative to the .info file that did not use 87 | a wildcard for matching the prefix were not matching files properly. 88 | (German Lashevich) 89 | - Added support for making trees from tab indented files (--fromtabfile) 90 | (gitlab @AvidSeeker), also cleaned up some other issues in the fromfile 91 | code. 92 | - Fix buffer overflow in listdir() when file names are allowed to be longer 93 | than 256 characters (like when using fromfile.) (Javier Jaramago Fernández) 94 | - If when attempting to open a .gitignore or .info file from a top level 95 | directory and failing, recursively check the parents for such a file. This 96 | stops when successful at opening such a file. This behavior might in the 97 | future be modified to open all such files in all parents to until root is 98 | reached. (Damien Bezborodov) Note that this requires the use of realpath() 99 | which I think may be an issue for some OSes. 100 | - Fix issue where tree would never descend (-l) a symbolic link when a full 101 | tree is gathered (--du/matchdirs/prune) (gitlab @6ramr) 102 | 103 | Version 2.1.0 (12/26/2022) 104 | This is a bit bigger release, due to not realizing that gitlab/github was 105 | not sending me email and ice+tree@mama.indstate.edu was broken. This has 106 | been fixed and I think I have gone through most all of the outstanding issues 107 | that were submitted over the last few months. Note to those that are sending 108 | me git merge requests: because I distribute this in multiple ways and places, 109 | and I tend to manually go over patches and often re-work them in any event, I 110 | will not likely ever directly accept any merge requests. I will still use the 111 | patch information from them for inclusion into the code, so you are more than 112 | welcome to still submit such merges. 113 | - Add support for --info and --gitignore for the --fromfile option. 114 | (Suggested by Piotr Andruszkow) 115 | - Add options --infofile and --gitfile to load .info and .gitignore files 116 | explicitly. Each implies --info or --gitignore respectively. 117 | - Add NULL guard for json_printinfo() and xml_printinfo() (and fix ftype 118 | printing for XML) (Kenta Arai) 119 | - Fix getcharset() to not return a getenv() pointer (fix for ENV34-C issue.) 120 | (Kenta Arai) 121 | - Another attempt at fixing extraneous /'s in HTML URLs/output. (Sebastian 122 | Rose) 123 | - Fixed XML output (Dave Rice) 124 | - Remove the (very outdated) French version of the manpage. Look to 125 | localization projects such as Debian's 'manpages-l10n' for localized 126 | translations. (hmartink) 127 | - Add support for the NO_COLOR environment variable (https://no-color.org/). 128 | Equivalent to the -n option (can be still be overridden with -C). 129 | (Timm Fitschen) 130 | - Removed many C99isms to enable compiling on C90 compilers with fewer 131 | warnings. (Sith Wijesinghe and Matthew Sessions) It should not be 132 | necessary to avoid using a standard that is old enough to drink, 133 | furthermore it is all but impossible to remove the remaining warnings and 134 | have modern features like compound literals. In the meantime I've added 135 | -std=c11 to the default CFLAGS for Linux and will likely not worry about 136 | C90 compatibility going forward unless there is some other reason for it. 137 | - Added a helper function for long command line arguments to clean up option 138 | processing (and fixes the processing for a few of the options such as 139 | --timefmt= (наб?).) 140 | - Added --hintro and --houtro options to select files to use as the HTML 141 | intro and outro. Use /dev/null or an empty file to eliminate them 142 | entirely. This should make it much easier to create your own custom CSS 143 | or embed one or more trees into a web page. 144 | - Defer printing the version until the character set is known so we can use 145 | the linedraw copyright symbol. 146 | - Revert change to the error code to not return an error (code 2) when 147 | attempting to list a non-directory that actually exists. Tree will still 148 | return an error when attempting to list a non-existing directory/file. 149 | - Added option --fflinks which will process symbolic link information from 150 | a file generated with 'tree -if --noreport' when using --fromfile. 151 | (Suggested by Chentao Credungtao) 152 | - Updated the totals reporting code to also include in the total the file or 153 | directory that is being listed. This should make a correct report when 154 | doing something like 'tree *'. 155 | 156 | Version 2.0.4 (09/06/2022) 157 | - Brown paper bag release: 158 | - Fix missing comma in JSON output. (jogbear?) 159 | 160 | Version 2.0.3 (08/26/2022) 161 | - Fix segfault when filelimit is used and tree encounters a directory it 162 | cannot enter. (Kenta Arai) 163 | - Use += when assigning CFLAGS and LDFLAGS in the Makefile allowing 164 | them to be modified by environment variables during make. (Ben Brown) 165 | Possibly assumes GNU make. 166 | - Fixed broken -x option (stops recursing.) (balping) 167 | - Fix use after free (causing segfault) for dir/subdir in list.c (Erik 168 | Skultety / Ben Brown) 169 | - Fixes for .gitignore functionality (Saniya Maheshwari / Mig-hub ? / Carlos 170 | Pinto) 171 | - Fixed * handing in patmatch. Worked almost like ** before, now properly 172 | stops at /'s. These issues were the result of forgetting that patmatch() 173 | was just to match filenames to patterns, not paths. 174 | - Patterns starting with / are actually relative to the .gitignore file, 175 | not the root of the filesystem, go figure. 176 | - Patterns without /'s in .gitignore apply to any file in any directory 177 | under the .gitignore, not just the .gitignore directory 178 | - Remove "All rights reserved" from copyright statements. A left-over from 179 | trees original artistic license. 180 | - Add in --du and --prune to --help output (Nxueyamao?) 181 | - Fixed segfault when an unknown directory is given with -X 182 | - Fixed output up for -X and -J options. 183 | - Remove one reference to strnlen which isn't necessary since it may not 184 | be available on some OS's. 185 | 186 | Version 2.0.2 (02/16/2022) 187 | - Okay, apparently the stddata addition is causing havoc (who knew how many 188 | scripts just haphazardly hand programs random file descriptors, that's 189 | surely not a problem.) Going forward the stddata option will only work 190 | if the environment variable STDDATA_FD is present or set to the descriptor 191 | to produce the JSON output on. 192 | - Fix HTML url output issue. (Maxim Cournoyer) It was definitely broken in 193 | the 2.0.0 release, and this should normalize it with respect to older 194 | versions, however I think it needs to be updated to work better. 195 | - Update MANPATH for OS X (Michiel Beijen) 196 | - Fixed an error with * in the patchmatch code where *foo*bar would match 197 | *foo alone. (Josey Smith) 198 | 199 | Version 2.0.1 (01/03/2022) 200 | - Simplify Makefile and the following changes: prefix -> PREFIX, 201 | BINDIR -> DESTDIR, -O4 -> -O3, mode 644 for man page installation 202 | (Michal Vasilek) 203 | - Make patterns ending in '/' match directories (but not files) for -I / -P 204 | (Michiel Beijen) should also fix issues with --gitignore as well 205 | (Taylor Faubion) 206 | - Fix --gitignore not matching files relative to the path of the .gitignore 207 | (Taylor Faubion) I did say it was hacked together. 208 | - Refactored color.c a bit to simplify the code as a prelude to meta coloring. 209 | 210 | Version 2.0.0 (12/21/2021) 211 | - This started out as a 1.9.0 release but then I got fed up with the 212 | abundance of directory listers (8 in total, 2 each for each output mode). 213 | Nothing is terribly well tested since there are a lot of changes and I would 214 | like to get this out the door finally, please report breakage. This reduced 215 | so much code that all the below additions only resulted in a code base that 216 | is only 54 lines larger than 1.8.0. 217 | - Rolled all the directory listers into 2 functions that call output specific 218 | functions (removes one TODO). 219 | - -R option now recursively calls the emit_tree() function rather than using 220 | system() to re-call tree. Also removes a TODO. 221 | - Adds --info to print information about files/directories from information 222 | found in .info files (removes a maybe do) In HTML output, comments show as 223 | mouse over tooltips, which I imagine will be the most useful use of this 224 | "feature". 225 | - Output un-indented JSON on file descriptor 3 ("stddata") automatically if 226 | file descriptor 3 is present (currently Linux only.) Maybe switch to BSON. 227 | - Always HTML escape filenames in HTML output even when -C is used. 228 | (Eric Pruitt) 229 | - Return a non-zero exit status if there is a failure to open any directory. 230 | - Added --gitignore option to filter out files specified by .gitignore files. 231 | (also reads $GIT_DIR/info/exclude if present.) To facilitate gitignore, 232 | adds support for ** on pattern matching to allow /**/ to match a single /. 233 | This is not well tested and kind of hacked together, so may not work 234 | correctly. (Jake Zimmerman and others) 235 | - Now also supports multiple -I and -P instances. (Michiel Beijen and others) 236 | - Now prints meta data for the top level directory as well. 237 | - Split spaghetti code in main into individual functions. 238 | - Properly sort --fromfile input (Chentao Credungtao via Debian) 239 | - Make tree colorization use reset (rs code in dir_colors,) not normal color 240 | when resetting attributes (Filips Romāns via Debian). 241 | - Honor -n (no color) even if the CLICOLOR_FORCE environment variable is set 242 | (Paul Seyfert) 243 | - Added --metafirst to print the metadata before the indentation lines 244 | (suggested by Richard Mitchell) 245 | - Fix --sort option to not require = 246 | - Defer sorting for --du until the entire sub-directory tree has been 247 | processed. 248 | - Optimized makefile, HP/UX support (Osipov, Michael). Note that this changes 249 | the prefix default to /usr/local, which is becoming required for many 250 | systems now. 251 | - Renamed (the by now very obsolete) doc/tree.1.fr to doc/tree.fr.1 (Jonas 252 | Stein) 253 | - Fix JSON string escaping such that it is not using the HTML escaping (Fox 254 | & others) 255 | - Add --filesfirst option (John A. Fedoruk). Cleaned up sorting code to make 256 | --dirsfirst and --filesfirst top level meta-sorts. 257 | - "arial" not "ariel" (Mark), HTML style-sheet cleaned up in any event. 258 | - Deprecate using local -DLINUX / -DCYGWIN and use the OS provided 259 | __linux__ or __CYGWIN__ (Jonas Stein) 260 | - XML/HTML/JSON output needs to be mutually exclusive, last command line 261 | switch wins. (Sergei Maximov) 262 | - Make sure we use xmalloc instead of malloc in a number of places (Tomáš 263 | Beránek) 264 | 265 | Version 1.8.0 (11/16/2018) 266 | - Added an experimental --fromfile option (suggested by several people.) 267 | This may eventually be replaced or supplimented by a --fromjson option. 268 | - Added support for BSD's CLICOLOR and CLICOLOR_FORCE environment variables. 269 | (Suggested by Alyssa Ross) 270 | - Use strftime() exclusively when formatting date/time to respect locale. 271 | - Some man page fixes and cleanups curtsey of Kirill Kolyshkin 272 | - Update BINDIR in Makefile for MacOS X -- It is not allowed to install 273 | programs to /usr/bin on MacOS X any longer due to System Integrity 274 | Protection (SIP) (Shawn Mehan) 275 | - Misc patches from Jacob Wahlgren: 276 | - Improved command line switch error reporting. 277 | - Symbolic links not displayed if a -P pattern is active 278 | - Missing argument error reporting fixes on long format switches. 279 | - Fixed JSON output hanging commas (John Lane, Tad, others) 280 | - JSON size output ignored -h/--si flags (Wagner Camarao) 281 | - Fixed issue with malformed multibyte string handling. (Mantas 282 | Mikulėnas) 283 | - Fixed issue where mbstowcs() fails to null terminate the string due to 284 | improper UTF-8 encoding leading to garbage being printed. (Nick Craig-Wood) 285 | - Found a bug where the wrong inode (and device) information would be printed 286 | for symbolic links. (Stephan Gabert) 287 | 288 | Version 1.7.0 (04/23/2014) 289 | - Allow user/group names up to 32 characters before clipping. 290 | - Made -i compress XML and JSON output as much as possible by eliminating 291 | extraneous whitespace. 292 | - Added --caseinsensitive (renamed --ignore-case ala grep) flag so patterns 293 | match without regard to case, courtesy of Jason A Donenfeld. 294 | - Added --matchdirs option courtesy of Brian Mattern & Jason A. Donenfeld 295 | . 296 | - Fixed possible buffer overflow on large uid/gids w/o user names/group 297 | names (Alexandre Wendling ) 298 | - Added JSON support courtesy of Florian Sesser . 299 | - Fixed formatting error with HTML output when -L 1 specified. (Sascha Zorn 300 | ) 301 | - Added file size sorting (Philipp M?ller ) 302 | - Added '--sort[=]' option, ala ls. 303 | - Fixed OS X makefile problems (Ryan Hollis ) 304 | - Fixed possible memory overflow in read_dir (path/lbuf not equal in size 305 | to pathsize/lbufsize.) (Han Hui ) 306 | - Fix S_ISDOOR/S_IFDOOR spelling mistake for Solaris. (Tim Mooney 307 | ) 308 | - Make tree more reliably detect UTF-8 locales. (Mantas Mikulnas 309 | and others.) 310 | - Return non-zero exit status on option errors, print usage to stdout when 311 | not an error, add the posix '--' option terminator, Change -S description 312 | to mean CP437 (console) output codes, not ASCII. (Ivan Shmakov 313 | ) 314 | 315 | Version 1.6.0 (05/24/11) 316 | - Re-org of code into multiple files, split HTML and Unix listdir() into 317 | separate functions, various code cleanups and optimizations. 318 | - Fixed a memory leak in listdir() when memory was allocated early and not 319 | freed before function exit. 320 | - Fixed possible buffer overflow where symbolic links are followed. 321 | - Fixed links printing "argetm" before the name of the link when the LINK 322 | setting for DIR_COLORS is set to target (Markus Schnalke 323 | ) 324 | - More fully support dir colors -- added support for su, sg, tw, ow, & st 325 | options (and "do" in theory). 326 | - Use the environment variable "TREE_COLORS" instead of "LS_COLORS" for 327 | color information if it exists. 328 | - Added --si flag to print filesizes in SI (powers of 1000) units (Ulrich 329 | Eckhardt) 330 | - Added -Q to quote filenames in double quotes. Does not override -N or -q. 331 | - Control characters are no longer printed in carrot notation, but as 332 | backslashed octal, ala ls, except for codes 7-13 which are printed as 333 | \a, \b, \t, \n, \v, \f and \r respectively. Spaces and backslashes are 334 | also now backslashed as per ls, for better input to scripts unless -Q 335 | is in use (where "'s are backslashed.) (Ujjwal Kumar) 336 | - Added -U for unsorted listings (directory order). 337 | - Added -c for sorting by last status change (ala ls -c). 338 | - --dirsfirst is now a meta-sort and does not override -c, -v, -r or -t, but 339 | is disabled by -U. 340 | - After many requests, added the ability to process the entire tree before 341 | emitting output. Used for the new options --du, which works like the du 342 | command: sums the amount of space under each directory and prints a total 343 | amount used in the report and the --prune option which will prune all empty 344 | directories from the output (makes the -P option output much more readable.) 345 | It should be noted that this will be slow to output when processing large 346 | directory trees and can consume copious amounts of memory, use at your own 347 | peril. 348 | - Added -X option to emit the directory tree in XML format (turns colorization 349 | off always.) 350 | - Added --timefmt option to specify the format of time display (implies -D). 351 | Uses the strftime format. 352 | 353 | Version 1.5.3 (11/24/09) 354 | - Properly quote directories for the system command when tree is relaunched 355 | using the -R option. 356 | - Fixed possible indentation problem if dirs[*] is not properly zeroed 357 | (Martin Nagy). 358 | - Use strcoll() instead of strcmp() to sort files based on locale if set. 359 | - Change "const static" to "static const" to remove some compiler warnings 360 | for Solaris (Kamaraju Kusumanchi). 361 | - Actually use TREE_CHARSET if it's defined. 362 | - Automatically select UTF-8 charset if TREE_CHARSET is not set, and the 363 | locale is set to *UTF-8 (overridden with --charset option.) 364 | 365 | Version 1.5.2.2 (01/22/09) 366 | - Set locale before checking MB_CUR_MAX. 367 | - Added HP-NonStop platform support (Craig McDaniel ) 368 | - Fixed to support 32 bit UID/GIDs. 369 | - Added Solaris build options to Makefile (edit and uncomment to use). 370 | Provided by Wang Quanhong 371 | 372 | Version 1.5.2.1 (08/29/08) 373 | - Added strverscmp.c file for os's without strverscmp. Source file is 374 | attributed to: Jean-Franois Bignolles 375 | - Try different approach to MB_CUR_MAX problem. 376 | - Changed the argument to printit() to be signed char to avoid warnings. 377 | 378 | Version 1.5.2 (06/06/08) 379 | - Added --filelimit X option to not descend directories that have more than 380 | X number of files in them. 381 | - Added -v option for version sorting (also called natural sorting) ala ls. 382 | 383 | Version 1.5.1.2 (06/04/08) 384 | - Fixed compile issues related to MB_CUR_MAX on non-linux machines. 385 | - Removed unecessary features.h 386 | 387 | Version 1.5.1.1 (06/11/07) 388 | - Regression in HTML output, fixed formatting issues. 389 | 390 | Version 1.5.1 (?) 391 | - Remove extraneous / at end of user input directory names when using -f 392 | option (Zurd) 393 | - List available charsets if --charset option is missing charset argument. 394 | - Fixed --charset option processing bug. 395 | - Fixed missing
's when -i option used with -H. 396 | - Added -h option for human readable output. 397 | - Colorization bugfix for special files and directories (make tree behave as 398 | ls does) 399 | 400 | Version 1.5.0 (08/15/04) 401 | - Added -T option to change title and H1 header in HTML output. 402 | - Added -r option to reverse alpha sort output, ala. 'ls -r'. 403 | - '|' wildcard support added by David MacMahon . 404 | - Remove extraneous '/' at the end of dirs and dir-symlinks in HTML output. 405 | - Removed several possible overflow problems by dynamically allocating 406 | arrays in several places. 407 | - Better support for Locales and printing utf-8 encoded characters in 408 | filenames (still hackish). 409 | - Fixed -t to alphasort files with same time-stamps. 410 | - Fixed encoding of filenames in HTML output, Kyosuke and others. 411 | - Patches by Kyosuke Tokoro : 412 | - Now, runs OS/2 systems. 413 | + Print the file attributes in 'adhrs' format for each file, instead 414 | of the protections in 'drwxrwxrwx' format, when running the tree 415 | on OS/2 with option -p. 416 | - Added --charset option, to specify which character set is used for 417 | output. 418 | + You can specify any IANA registered character set name. But I have 419 | tested only following character sets: 420 | Shift_JIS EUC-JP IBM850 421 | UTF-8 ISO-8859-1 US-ASCII 422 | + Now, `-S' option is equal to `--charset=IBM437'. 423 | + When running on OS/2 systems, the default value of this option 424 | is according to current codepage. On the other systems, no default. 425 | - Change font-weight to font-size in CSS .VERSION. 426 | - Change version to standard major.minor.patch format. 427 | - Switch from artistic license to GPLv2. 428 | 429 | Version 1.4 (02/21/02 (b1), 03/24/02 (b2), 02/06/03 (b3)) 430 | - Added large file support under Linux. 431 | - Fixed crashing on missing command line arguments. 432 | - Fixed several memory leaks 433 | - Added --dirsfirst option to list directories first. 434 | - Fixed formatting error when unable to open directories. 435 | - Fixed bug in parse_dir_colors(). 436 | - Changed -I to also ignore directories. 437 | - Added --nolinks command to turn off hyperlinks with the HTML output. 438 | - Fixed several memory leaks in listdir(). 439 | - Some additional code cleanup in listdir(). 440 | - Some systems may define TRUE/FALSE, so don't create the enums for TRUE 441 | and FALSE if that's the case. 442 | - Fixed over-allocation bug in read_dir(). 443 | - Added crude beginnings of color output for HTML via CSS (Ted Tiberio 444 | ttiberio@rochester.rr.com). 445 | - Fixed buffer overflow problem in dircolors parsing. 446 | - Fixed recursive symlink detection. 447 | - Added --inodes and --device options. 448 | - Added --noreport option. 449 | 450 | Version 1.3 (02/15/99) 451 | - Fixed long pathname problem by dynamically allocating the path. 452 | - Added recursive symlink detection. 453 | - Added --help and --version options. 454 | - When -C is used and LS_COLORS is undefined, tree uses a default color 455 | scheme (thus -C always forces color output now). 456 | - Added -S to show ASCII tree lines (Gerald Scheidl) 457 | - Made tree more portable (Guido Socher and others) 458 | 459 | Following options courtesy of Francesc Rocher: 460 | - Added -o to redirect the output. 461 | - Added -H to print the tree in HTML format. 462 | - Added -L to set the maximum level of directories to print. 463 | - Added -R to recursively restart the search at the level given by `-L' 464 | option (adding as well `-o 00Tree.html'). 465 | 466 | Version 1.2 (01/05/97) 467 | - Added -D to print the date of the last modification. 468 | - Added -t option to sort by last modification time (ala ls -t). 469 | - Added -I , similar to the -P option except tree does not print 470 | those files which match the pattern. 471 | - Made tree print non-printable characters in filenames in standard unix 472 | carrot notation. 473 | - Added -N option to make tree print filenames without any processing. 474 | - Added -q option to make tree print non-printable characters in filenames 475 | as question marks. 476 | - Added `|' to -F output and made it only print character type after the 477 | link on sym-links, not on the symlink name itself. 478 | - Added -u option to display username/uid, and -g option to display group 479 | name/gid. 480 | - Fully (pass the salt) implemented dircolors support. 481 | 482 | Version 1.1 (07/09/96) 483 | - Made some changes to the Makefile to insure proper installation and for 484 | multi-architecture support and a bug-fix. 485 | - Made root directory colorized if dircolors is enabled. 486 | - Put []'s around permission and size info, 'cause I think it looks better. 487 | - Added -A option to enable ANSI-lines hack. 488 | - Added some sanity checks for dircolors support. 489 | - Added -P to list only those files that match the wildcard 490 | given in . 491 | - Fixed error where relative symbolic links to directories would not be 492 | followed when the -l option was used. 493 | - Made uid 0 the same as anyone else (-a was default for uid 0) 494 | - Added -x directive to stay on one filesystem (ala find -xdev). 495 | 496 | Version 1.0 (??/??/90?) 497 | - The original, a model of perfection... 498 | -------------------------------------------------------------------------------- /tree.c: -------------------------------------------------------------------------------- 1 | /* $Copyright: $ 2 | * Copyright (c) 1996 - 2024 by Steve Baker (steve.baker.llc@gmail.com) 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 | */ 18 | 19 | #include "tree.h" 20 | 21 | char *version = "$Version: $ tree v2.2.1 %s 1996 - 2024 by Steve Baker, Thomas Moore, Francesc Rocher, Florian Sesser, Kyosuke Tokoro $"; 22 | char *hversion= "\t\t tree v2.2.1 %s 1996 - 2024 by Steve Baker and Thomas Moore
\n" 23 | "\t\t HTML output hacked and copyleft %s 1998 by Francesc Rocher
\n" 24 | "\t\t JSON output hacked and copyleft %s 2014 by Florian Sesser
\n" 25 | "\t\t Charsets / OS/2 support %s 2001 by Kyosuke Tokoro\n"; 26 | 27 | /* Globals */ 28 | bool dflag, lflag, pflag, sflag, Fflag, aflag, fflag, uflag, gflag; 29 | bool qflag, Nflag, Qflag, Dflag, inodeflag, devflag, hflag, Rflag; 30 | bool Hflag, siflag, cflag, Xflag, Jflag, duflag, pruneflag, hyperflag; 31 | bool noindent, force_color, nocolor, xdev, noreport, nolinks; 32 | bool ignorecase, matchdirs, fromfile, metafirst, gitignore, showinfo; 33 | bool reverse, fflinks, htmloffset; 34 | int flimit; 35 | 36 | struct listingcalls lc; 37 | 38 | int pattern = 0, maxpattern = 0, ipattern = 0, maxipattern = 0; 39 | char **patterns = NULL, **ipatterns = NULL; 40 | 41 | char *host = NULL, *title = "Directory Tree", *sp = " ", *_nl = "\n"; 42 | char *Hintro = NULL, *Houtro = NULL, *scheme = "file://", *authority = NULL; 43 | char *file_comment = "#", *file_pathsep = "/"; 44 | char *timefmt = NULL; 45 | const char *charset = NULL; 46 | 47 | struct _info **(*getfulltree)(char *d, u_long lev, dev_t dev, off_t *size, char **err) = unix_getfulltree; 48 | /* off_t (*listdir)(char *, int *, int *, u_long, dev_t) = unix_listdir; */ 49 | int (*basesort)(struct _info **, struct _info **) = alnumsort; 50 | int (*topsort)(struct _info **, struct _info **) = NULL; 51 | 52 | char *sLevel, *curdir; 53 | FILE *outfile = NULL; 54 | int *dirs; 55 | ssize_t Level; 56 | size_t maxdirs; 57 | int errors; 58 | 59 | char xpattern[PATH_MAX]; 60 | 61 | int mb_cur_max; 62 | 63 | #ifdef __EMX__ 64 | const u_short ifmt[]={ FILE_ARCHIVED, FILE_DIRECTORY, FILE_SYSTEM, FILE_HIDDEN, FILE_READONLY, 0}; 65 | #else 66 | #ifdef S_IFPORT 67 | const mode_t ifmt[] = {S_IFREG, S_IFDIR, S_IFLNK, S_IFCHR, S_IFBLK, S_IFSOCK, S_IFIFO, S_IFDOOR, S_IFPORT, 0}; 68 | const char fmt[] = "-dlcbspDP?"; 69 | const char *ftype[] = {"file", "directory", "link", "char", "block", "socket", "fifo", "door", "port", "unknown", NULL}; 70 | #else 71 | const mode_t ifmt[] = {S_IFREG, S_IFDIR, S_IFLNK, S_IFCHR, S_IFBLK, S_IFSOCK, S_IFIFO, 0}; 72 | const char fmt[] = "-dlcbsp?"; 73 | const char *ftype[] = {"file", "directory", "link", "char", "block", "socket", "fifo", "unknown", NULL}; 74 | #endif 75 | #endif 76 | 77 | struct sorts { 78 | char *name; 79 | int (*cmpfunc)(struct _info **, struct _info **); 80 | } sorts[] = { 81 | {"name", alnumsort}, 82 | {"version", versort}, 83 | {"size", fsizesort}, 84 | {"mtime", mtimesort}, 85 | {"ctime", ctimesort}, 86 | {"none", NULL}, 87 | {NULL, NULL} 88 | }; 89 | 90 | /* Externs */ 91 | /* hash.c */ 92 | extern struct xtable *gtable[256], *utable[256]; 93 | extern struct inotable *itable[256]; 94 | 95 | /* color.c */ 96 | extern bool colorize, ansilines, linktargetcolor; 97 | extern char *leftcode, *rightcode, *endcode; 98 | extern const struct linedraw *linedraw; 99 | 100 | /* Time to switch to getopt()? */ 101 | char *long_arg(char *argv[], size_t i, size_t *j, size_t *n, char *prefix) { 102 | char *ret = NULL; 103 | size_t len = strlen(prefix); 104 | 105 | if (!strncmp(prefix,argv[i], len)) { 106 | *j = len; 107 | if (*(argv[i]+(*j)) == '=') { 108 | if (*(argv[i]+ (++(*j)))) { 109 | ret=(argv[i] + (*j)); 110 | *j = strlen(argv[i])-1; 111 | } else { 112 | fprintf(stderr,"tree: Missing argument to %s=\n", prefix); 113 | if (strcmp(prefix, "--charset=") == 0) initlinedraw(true); 114 | exit(1); 115 | } 116 | } else if (argv[*n] != NULL) { 117 | ret = argv[*n]; 118 | (*n)++; 119 | *j = strlen(argv[i])-1; 120 | } else { 121 | fprintf(stderr,"tree: Missing argument to %s\n", prefix); 122 | if (strcmp(prefix, "--charset") == 0) initlinedraw(true); 123 | exit(1); 124 | } 125 | } 126 | return ret; 127 | } 128 | 129 | int main(int argc, char **argv) 130 | { 131 | struct ignorefile *ig; 132 | struct infofile *inf; 133 | char **dirname = NULL; 134 | size_t i, j=0, k, n, p = 0, q = 0; 135 | bool optf = true; 136 | char *stmp, *outfilename = NULL, *arg; 137 | char *stddata_fd; 138 | bool needfulltree, showversion = false, opt_toggle = false; 139 | 140 | aflag = dflag = fflag = lflag = pflag = sflag = Fflag = uflag = gflag = false; 141 | Dflag = qflag = Nflag = Qflag = Rflag = hflag = Hflag = siflag = cflag = false; 142 | noindent = force_color = nocolor = xdev = noreport = nolinks = reverse = false; 143 | ignorecase = matchdirs = inodeflag = devflag = Xflag = Jflag = fflinks = false; 144 | duflag = pruneflag = metafirst = gitignore = hyperflag = htmloffset = false; 145 | 146 | flimit = 0; 147 | dirs = xmalloc(sizeof(int) * (size_t)(maxdirs=PATH_MAX)); 148 | memset(dirs, 0, sizeof(int) * (size_t)maxdirs); 149 | dirs[0] = 0; 150 | Level = -1; 151 | 152 | setlocale(LC_CTYPE, ""); 153 | setlocale(LC_COLLATE, ""); 154 | 155 | charset = getcharset(); 156 | if (charset == NULL && 157 | (strcmp(nl_langinfo(CODESET), "UTF-8") == 0 || 158 | strcmp(nl_langinfo(CODESET), "utf8") == 0)) { 159 | charset = "UTF-8"; 160 | } 161 | 162 | lc = (struct listingcalls){ 163 | null_intro, null_outtro, unix_printinfo, unix_printfile, unix_error, unix_newline, 164 | null_close, unix_report 165 | }; 166 | 167 | /* Still a hack, but assume that if the macro is defined, we can use it: */ 168 | #ifdef MB_CUR_MAX 169 | mb_cur_max = (int)MB_CUR_MAX; 170 | #else 171 | mb_cur_max = 1; 172 | #endif 173 | 174 | #ifdef __linux__ 175 | /* Output JSON automatically to "stddata" if present: */ 176 | stddata_fd = getenv(ENV_STDDATA_FD); 177 | if (stddata_fd != NULL) { 178 | int std_fd = atoi(stddata_fd); 179 | if (std_fd <= 0) std_fd = STDDATA_FILENO; 180 | if (fcntl(std_fd, F_GETFD) >= 0) { 181 | Jflag = noindent = true; 182 | _nl = ""; 183 | lc = (struct listingcalls){ 184 | json_intro, json_outtro, json_printinfo, json_printfile, json_error, json_newline, 185 | json_close, json_report 186 | }; 187 | outfile = fdopen(std_fd, "w"); 188 | } 189 | } 190 | #endif 191 | 192 | memset(utable,0,sizeof(utable)); 193 | memset(gtable,0,sizeof(gtable)); 194 | memset(itable,0,sizeof(itable)); 195 | 196 | for(n=i=1;i<(size_t)argc;i=n) { 197 | n++; 198 | if (optf && argv[i][0] == '-' && argv[i][1]) { 199 | for(j=1;argv[i][j];j++) { 200 | switch(argv[i][j]) { 201 | case 'N': 202 | Nflag = (opt_toggle? !Nflag : true); 203 | break; 204 | case 'q': 205 | qflag = (opt_toggle? !qflag : true); 206 | break; 207 | case 'Q': 208 | Qflag = (opt_toggle? !Qflag : true); 209 | break; 210 | case 'd': 211 | dflag = (opt_toggle? !dflag : true); 212 | break; 213 | case 'l': 214 | lflag = (opt_toggle? !lflag : true); 215 | break; 216 | case 's': 217 | sflag = (opt_toggle? !sflag : true); 218 | break; 219 | case 'h': 220 | /* Assume they also want -s */ 221 | sflag = (hflag = (opt_toggle? !hflag : true)); 222 | break; 223 | case 'u': 224 | uflag = (opt_toggle? !uflag : true); 225 | break; 226 | case 'g': 227 | gflag = (opt_toggle? !gflag : true); 228 | break; 229 | case 'f': 230 | fflag = (opt_toggle? !fflag : true); 231 | break; 232 | case 'F': 233 | Fflag = (opt_toggle? !Fflag : true); 234 | break; 235 | case 'a': 236 | aflag = (opt_toggle? !aflag : true); 237 | break; 238 | case 'p': 239 | pflag = (opt_toggle? !pflag : true); 240 | break; 241 | case 'i': 242 | noindent = (opt_toggle? !noindent : true); 243 | _nl = ""; 244 | break; 245 | case 'C': 246 | force_color = (opt_toggle? !force_color : true); 247 | break; 248 | case 'n': 249 | nocolor = (opt_toggle? !nocolor : true); 250 | break; 251 | case 'x': 252 | xdev = (opt_toggle? !xdev : true); 253 | break; 254 | case 'P': 255 | if (argv[n] == NULL) { 256 | fprintf(stderr,"tree: Missing argument to -P option.\n"); 257 | exit(1); 258 | } 259 | if (pattern >= maxpattern-1) patterns = xrealloc(patterns, sizeof(char *) * (size_t)(maxpattern += 10)); 260 | patterns[pattern++] = argv[n++]; 261 | patterns[pattern] = NULL; 262 | break; 263 | case 'I': 264 | if (argv[n] == NULL) { 265 | fprintf(stderr,"tree: Missing argument to -I option.\n"); 266 | exit(1); 267 | } 268 | if (ipattern >= maxipattern-1) ipatterns = xrealloc(ipatterns, sizeof(char *) * (size_t)(maxipattern += 10)); 269 | ipatterns[ipattern++] = argv[n++]; 270 | ipatterns[ipattern] = NULL; 271 | break; 272 | case 'A': 273 | ansilines = (opt_toggle? !ansilines : true); 274 | break; 275 | case 'S': 276 | charset = "IBM437"; 277 | break; 278 | case 'D': 279 | Dflag = (opt_toggle? !Dflag : true); 280 | break; 281 | case 't': 282 | basesort = mtimesort; 283 | break; 284 | case 'c': 285 | basesort = ctimesort; 286 | cflag = true; 287 | break; 288 | case 'r': 289 | reverse = (opt_toggle? !reverse : true); 290 | break; 291 | case 'v': 292 | basesort = versort; 293 | break; 294 | case 'U': 295 | basesort = NULL; 296 | break; 297 | case 'X': 298 | Xflag = true; 299 | Hflag = Jflag = false; 300 | lc = (struct listingcalls){ 301 | xml_intro, xml_outtro, xml_printinfo, xml_printfile, xml_error, xml_newline, 302 | xml_close, xml_report 303 | }; 304 | break; 305 | case 'J': 306 | Jflag = true; 307 | Xflag = Hflag = false; 308 | lc = (struct listingcalls){ 309 | json_intro, json_outtro, json_printinfo, json_printfile, json_error, json_newline, 310 | json_close, json_report 311 | }; 312 | break; 313 | case 'H': 314 | Hflag = true; 315 | Xflag = Jflag = false; 316 | lc = (struct listingcalls){ 317 | html_intro, html_outtro, html_printinfo, html_printfile, html_error, html_newline, 318 | html_close, html_report 319 | }; 320 | if (argv[n] == NULL) { 321 | fprintf(stderr,"tree: Missing argument to -H option.\n"); 322 | exit(1); 323 | } 324 | host = argv[n++]; 325 | k = strlen(host)-1; 326 | if (host[0] == '-') { 327 | htmloffset = true; 328 | host++; 329 | } 330 | /* Allows a / if that is the only character as the 'host': */ 331 | // if (k && host[k] == '/') host[k] = '\0'; 332 | sp = " "; 333 | break; 334 | case 'T': 335 | if (argv[n] == NULL) { 336 | fprintf(stderr,"tree: Missing argument to -T option.\n"); 337 | exit(1); 338 | } 339 | title = argv[n++]; 340 | break; 341 | case 'R': 342 | Rflag = (opt_toggle? !Rflag : true); 343 | break; 344 | case 'L': 345 | if (isdigit(argv[i][j+1])) { 346 | for(k=0; (argv[i][j+1+k] != '\0') && (isdigit(argv[i][j+1+k])) && (k < PATH_MAX-1); k++) { 347 | xpattern[k] = argv[i][j+1+k]; 348 | } 349 | xpattern[k] = '\0'; 350 | j += k; 351 | sLevel = xpattern; 352 | } else { 353 | if ((sLevel = argv[n++]) == NULL) { 354 | fprintf(stderr,"tree: Missing argument to -L option.\n"); 355 | exit(1); 356 | } 357 | } 358 | Level = (int)strtoul(sLevel,NULL,0)-1; 359 | if (Level < 0) { 360 | fprintf(stderr,"tree: Invalid level, must be greater than 0.\n"); 361 | exit(1); 362 | } 363 | break; 364 | case 'o': 365 | if (argv[n] == NULL) { 366 | fprintf(stderr,"tree: Missing argument to -o option.\n"); 367 | exit(1); 368 | } 369 | outfilename = argv[n++]; 370 | break; 371 | case '-': 372 | if (j == 1) { 373 | if (!strcmp("--", argv[i])) { 374 | optf = false; 375 | break; 376 | } 377 | /* Long options that don't take parameters should just use strcmp: */ 378 | if (!strcmp("--help",argv[i])) { 379 | usage(2); 380 | exit(0); 381 | } 382 | if (!strcmp("--version",argv[i])) { 383 | j = strlen(argv[i])-1; 384 | showversion = true; 385 | break; 386 | } 387 | if (!strcmp("--inodes",argv[i])) { 388 | j = strlen(argv[i])-1; 389 | inodeflag = (opt_toggle? !inodeflag : true); 390 | break; 391 | } 392 | if (!strcmp("--device",argv[i])) { 393 | j = strlen(argv[i])-1; 394 | devflag = (opt_toggle? !devflag : true); 395 | break; 396 | } 397 | if (!strcmp("--noreport",argv[i])) { 398 | j = strlen(argv[i])-1; 399 | noreport = (opt_toggle? !noreport : true); 400 | break; 401 | } 402 | if (!strcmp("--nolinks",argv[i])) { 403 | j = strlen(argv[i])-1; 404 | nolinks = (opt_toggle? !nolinks : true); 405 | break; 406 | } 407 | if (!strcmp("--dirsfirst",argv[i])) { 408 | j = strlen(argv[i])-1; 409 | topsort = dirsfirst; 410 | break; 411 | } 412 | if (!strcmp("--filesfirst",argv[i])) { 413 | j = strlen(argv[i])-1; 414 | topsort = filesfirst; 415 | break; 416 | } 417 | if ((arg = long_arg(argv, i, &j, &n, "--filelimit")) != NULL) { 418 | flimit = atoi(arg); 419 | break; 420 | } 421 | if ((arg = long_arg(argv, i, &j, &n, "--charset")) != NULL) { 422 | charset = arg; 423 | break; 424 | } 425 | if (!strcmp("--si", argv[i])) { 426 | j = strlen(argv[i])-1; 427 | sflag = hflag = siflag = (opt_toggle? !siflag : true); 428 | break; 429 | } 430 | if (!strcmp("--du",argv[i])) { 431 | j = strlen(argv[i])-1; 432 | sflag = duflag = (opt_toggle? !duflag : true); 433 | break; 434 | } 435 | if (!strcmp("--prune",argv[i])) { 436 | j = strlen(argv[i])-1; 437 | pruneflag = (opt_toggle? !pruneflag : true); 438 | break; 439 | } 440 | if ((arg = long_arg(argv, i, &j, &n, "--timefmt")) != NULL) { 441 | timefmt = scopy(arg); 442 | Dflag = true; 443 | break; 444 | } 445 | if (!strcmp("--ignore-case",argv[i])) { 446 | j = strlen(argv[i])-1; 447 | ignorecase = (opt_toggle? !ignorecase : true); 448 | break; 449 | } 450 | if (!strcmp("--matchdirs",argv[i])) { 451 | j = strlen(argv[i])-1; 452 | matchdirs = (opt_toggle? !matchdirs : true); 453 | break; 454 | } 455 | if ((arg = long_arg(argv, i, &j, &n, "--sort")) != NULL) { 456 | basesort = NULL; 457 | for(k=0;sorts[k].name;k++) { 458 | if (strcasecmp(sorts[k].name,arg) == 0) { 459 | basesort = sorts[k].cmpfunc; 460 | break; 461 | } 462 | } 463 | if (sorts[k].name == NULL) { 464 | fprintf(stderr,"tree: Sort type '%s' not valid, should be one of: ", arg); 465 | for(k=0; sorts[k].name; k++) 466 | printf("%s%c", sorts[k].name, sorts[k+1].name? ',': '\n'); 467 | exit(1); 468 | } 469 | break; 470 | } 471 | if (!strcmp("--fromtabfile", argv[i])) { 472 | j = strlen(argv[i])-1; 473 | fromfile=true; 474 | getfulltree = tabedfile_getfulltree; 475 | break; 476 | } 477 | if (!strcmp("--fromfile",argv[i])) { 478 | j = strlen(argv[i])-1; 479 | fromfile=true; 480 | getfulltree = file_getfulltree; 481 | break; 482 | } 483 | if (!strcmp("--metafirst",argv[i])) { 484 | j = strlen(argv[i])-1; 485 | metafirst = (opt_toggle? !metafirst : true); 486 | break; 487 | } 488 | if ((arg = long_arg(argv, i, &j, &n, "--gitfile")) != NULL) { 489 | gitignore=true; 490 | ig = new_ignorefile(arg, false); 491 | if (ig != NULL) push_filterstack(ig); 492 | else { 493 | fprintf(stderr,"tree: Could not load gitignore file\n"); 494 | exit(1); 495 | } 496 | break; 497 | } 498 | if (!strcmp("--gitignore",argv[i])) { 499 | j = strlen(argv[i])-1; 500 | gitignore = (opt_toggle? !gitignore : true); 501 | break; 502 | } 503 | if (!strcmp("--info",argv[i])) { 504 | j = strlen(argv[i])-1; 505 | showinfo = (opt_toggle? !showinfo : true); 506 | break; 507 | } 508 | if ((arg = long_arg(argv, i, &j, &n, "--infofile")) != NULL) { 509 | showinfo = true; 510 | inf = new_infofile(arg, false); 511 | if (inf != NULL) push_infostack(inf); 512 | else { 513 | fprintf(stderr,"tree: Could not load infofile\n"); 514 | exit(1); 515 | } 516 | break; 517 | } 518 | if ((arg = long_arg(argv, i, &j, &n, "--hintro")) != NULL) { 519 | Hintro = scopy(arg); 520 | break; 521 | } 522 | if ((arg = long_arg(argv, i, &j, &n, "--houtro")) != NULL) { 523 | Houtro = scopy(arg); 524 | break; 525 | } 526 | if (!strcmp("--fflinks",argv[i])) { 527 | j = strlen(argv[i])-1; 528 | fflinks = (opt_toggle? !fflinks : true); 529 | break; 530 | } 531 | if (!strcmp("--hyperlink", argv[i])) { 532 | j = strlen(argv[i])-1; 533 | hyperflag = (opt_toggle? !hyperflag : true); 534 | break; 535 | } 536 | if ((arg = long_arg(argv, i, &j, &n, "--scheme")) != NULL) { 537 | if (strchr(arg, ':') == NULL) { 538 | sprintf(xpattern, "%s://", arg); 539 | arg = scopy(xpattern); 540 | } else scheme = scopy(arg); 541 | break; 542 | } 543 | if ((arg = long_arg(argv, i, &j, &n, "--authority")) != NULL) { 544 | // I don't believe that . by itself can be a valid hostname, 545 | // so it will do as a null authority. 546 | if (strcmp(arg, ".") == 0) authority = scopy(""); 547 | else authority = scopy(arg); 548 | break; 549 | } 550 | if (!strcmp("--opt-toggle", argv[i])) { 551 | j = strlen(argv[i])-1; 552 | opt_toggle = !opt_toggle; 553 | break; 554 | } 555 | 556 | fprintf(stderr,"tree: Invalid argument `%s'.\n",argv[i]); 557 | usage(1); 558 | exit(1); 559 | } 560 | /* Falls through */ 561 | default: 562 | /* printf("here i = %d, n = %d\n", i, n); */ 563 | fprintf(stderr,"tree: Invalid argument -`%c'.\n",argv[i][j]); 564 | usage(1); 565 | exit(1); 566 | break; 567 | } 568 | } 569 | } else { 570 | if (!dirname) dirname = (char **)xmalloc(sizeof(char *) * (q=MINIT)); 571 | else if (p == (q-2)) dirname = (char **)xrealloc(dirname,sizeof(char *) * (q+=MINC)); 572 | dirname[p++] = scopy(argv[i]); 573 | } 574 | } 575 | if (p) dirname[p] = NULL; 576 | 577 | setoutput(outfilename); 578 | 579 | parse_dir_colors(); 580 | initlinedraw(false); 581 | 582 | if (showversion) { 583 | print_version(true); 584 | exit(0); 585 | } 586 | 587 | /* Insure sensible defaults and sanity check options: */ 588 | if (dirname == NULL) { 589 | dirname = xmalloc(sizeof(char *) * 2); 590 | dirname[0] = scopy("."); 591 | dirname[1] = NULL; 592 | } 593 | if (topsort == NULL) topsort = basesort; 594 | if (basesort == NULL) topsort = NULL; 595 | if (timefmt) setlocale(LC_TIME,""); 596 | if (dflag) pruneflag = false; /* You'll just get nothing otherwise. */ 597 | if (Rflag && (Level == -1)) Rflag = false; 598 | 599 | if (hyperflag && authority == NULL) { 600 | // If the hostname is longer than PATH_MAX, maybe it's just as well we don't 601 | // try to use it. 602 | if (gethostname(xpattern,PATH_MAX) < 0) { 603 | fprintf(stderr,"Unable to get hostname, using 'localhost'.\n"); 604 | authority = "localhost"; 605 | } else authority = scopy(xpattern); 606 | } 607 | 608 | /* Not going to implement git configs so no core.excludesFile support. */ 609 | if (gitignore && (stmp = getenv("GIT_DIR"))) { 610 | char *path = xmalloc(PATH_MAX); 611 | snprintf(path, PATH_MAX, "%s/info/exclude", stmp); 612 | push_filterstack(new_ignorefile(path, false)); 613 | free(path); 614 | } 615 | if (showinfo) { 616 | push_infostack(new_infofile(INFO_PATH, false)); 617 | } 618 | 619 | needfulltree = duflag || pruneflag || matchdirs || fromfile; 620 | 621 | emit_tree(dirname, needfulltree); 622 | 623 | if (outfilename != NULL) fclose(outfile); 624 | 625 | return errors ? 2 : 0; 626 | } 627 | 628 | void print_version(int nl) 629 | { 630 | char buf[PATH_MAX], *v; 631 | v = version+12; 632 | sprintf(buf, "%.*s%s", (int)strlen(v)-2, v, nl?"\n":""); 633 | fprintf(outfile, buf, linedraw->copy); 634 | } 635 | 636 | void setoutput(const char *filename) 637 | { 638 | if (filename == NULL) { 639 | #ifdef __EMX__ 640 | _fsetmode(outfile=stdout,Hflag?"b":"t"); 641 | #else 642 | if (outfile == NULL) outfile = stdout; 643 | #endif 644 | } else { 645 | #ifdef __EMX__ 646 | outfile = fopen(filename, Hflag? "wb":"wt"); 647 | #else 648 | outfile = fopen(filename, "w"); 649 | #endif 650 | if (outfile == NULL) { 651 | fprintf(stderr,"tree: invalid filename '%s'\n", filename); 652 | exit(1); 653 | } 654 | } 655 | } 656 | 657 | void usage(int n) 658 | { 659 | parse_dir_colors(); 660 | initlinedraw(false); 661 | 662 | /* 123456789!123456789!123456789!123456789!123456789!123456789!123456789!123456789! */ 663 | /* \t9!123456789!123456789!123456789!123456789!123456789!123456789!123456789! */ 664 | fancy(n < 2? stderr: stdout, 665 | "usage: \btree\r [\b-acdfghilnpqrstuvxACDFJQNSUX\r] [\b-L\r \flevel\r [\b-R\r]] [\b-H\r [-]\fbaseHREF\r]\n" 666 | "\t[\b-T\r \ftitle\r] [\b-o\r \ffilename\r] [\b-P\r \fpattern\r] [\b-I\r \fpattern\r] [\b--gitignore\r]\n" 667 | "\t[\b--gitfile\r[\b=\r]\ffile\r] [\b--matchdirs\r] [\b--metafirst\r] [\b--ignore-case\r]\n" 668 | "\t[\b--nolinks\r] [\b--hintro\r[\b=\r]\ffile\r] [\b--houtro\r[\b=\r]\ffile\r] [\b--inodes\r] [\b--device\r]\n" 669 | "\t[\b--sort\r[\b=\r]\fname\r] [\b--dirsfirst\r] [\b--filesfirst\r] [\b--filelimit\r[\b=\r]\f#\r] [\b--si\r]\n" 670 | "\t[\b--du\r] [\b--prune\r] [\b--charset\r[\b=\r]\fX\r] [\b--timefmt\r[\b=\r]\fformat\r] [\b--fromfile\r]\n" 671 | "\t[\b--fromtabfile\r] [\b--fflinks\r] [\b--info\r] [\b--infofile\r[\b=\r]\ffile\r] [\b--noreport\r]\n" 672 | "\t[\b--hyperlink\r] [\b--scheme\r[\b=\r]\fschema\r] [\b--authority\r[\b=\r]\fhost\r] [\b--opt-toggle\r]\n" 673 | "\t[\b--version\r] [\b--help\r] [\b--\r] [\fdirectory\r \b...\r]\n"); 674 | 675 | if (n < 2) return; 676 | fancy(stdout, 677 | " \b------- Listing options -------\r\n" 678 | " \b-a\r All files are listed.\n" 679 | " \b-d\r List directories only.\n" 680 | " \b-l\r Follow symbolic links like directories.\n" 681 | " \b-f\r Print the full path prefix for each file.\n" 682 | " \b-x\r Stay on current filesystem only.\n" 683 | " \b-L\r \flevel\r Descend only \flevel\r directories deep.\n" 684 | " \b-R\r Rerun tree when max dir level reached.\n" 685 | " \b-P\r \fpattern\r List only those files that match the pattern given.\n" 686 | " \b-I\r \fpattern\r Do not list files that match the given pattern.\n" 687 | " \b--gitignore\r Filter by using \b.gitignore\r files.\n" 688 | " \b--gitfile\r \fX\r Explicitly read a gitignore file.\n" 689 | " \b--ignore-case\r Ignore case when pattern matching.\n" 690 | " \b--matchdirs\r Include directory names in \b-P\r pattern matching.\n" 691 | " \b--metafirst\r Print meta-data at the beginning of each line.\n" 692 | " \b--prune\r Prune empty directories from the output.\n" 693 | " \b--info\r Print information about files found in \b.info\r files.\n" 694 | " \b--infofile\r \fX\r Explicitly read info file.\n" 695 | " \b--noreport\r Turn off file/directory count at end of tree listing.\n" 696 | " \b--charset\r \fX\r Use charset \fX\r for terminal/HTML and indentation line output.\n" 697 | " \b--filelimit\r \f#\r Do not descend dirs with more than \f#\r files in them.\n" 698 | " \b-o\r \ffilename\r Output to file instead of stdout.\n" 699 | " \b------- File options -------\r\n" 700 | " \b-q\r Print non-printable characters as '\b?\r'.\n" 701 | " \b-N\r Print non-printable characters as is.\n" 702 | " \b-Q\r Quote filenames with double quotes.\n" 703 | " \b-p\r Print the protections for each file.\n" 704 | " \b-u\r Displays file owner or UID number.\n" 705 | " \b-g\r Displays file group owner or GID number.\n" 706 | " \b-s\r Print the size in bytes of each file.\n" 707 | " \b-h\r Print the size in a more human readable way.\n" 708 | " \b--si\r Like \b-h\r, but use in SI units (powers of 1000).\n" 709 | " \b--du\r Compute size of directories by their contents.\n" 710 | " \b-D\r Print the date of last modification or (-c) status change.\n" 711 | " \b--timefmt\r \ffmt\r Print and format time according to the format \ffmt\r.\n" 712 | " \b-F\r Appends '\b/\r', '\b=\r', '\b*\r', '\b@\r', '\b|\r' or '\b>\r' as per \bls -F\r.\n" 713 | " \b--inodes\r Print inode number of each file.\n" 714 | " \b--device\r Print device ID number to which each file belongs.\n"); 715 | fancy(stdout, 716 | " \b------- Sorting options -------\r\n" 717 | " \b-v\r Sort files alphanumerically by version.\n" 718 | " \b-t\r Sort files by last modification time.\n" 719 | " \b-c\r Sort files by last status change time.\n" 720 | " \b-U\r Leave files unsorted.\n" 721 | " \b-r\r Reverse the order of the sort.\n" 722 | " \b--dirsfirst\r List directories before files (\b-U\r disables).\n" 723 | " \b--filesfirst\r List files before directories (\b-U\r disables).\n" 724 | " \b--sort\r \fX\r Select sort: \b\fname\r,\b\fversion\r,\b\fsize\r,\b\fmtime\r,\b\fctime\r,\b\fnone\r.\n" 725 | " \b------- Graphics options -------\r\n" 726 | " \b-i\r Don't print indentation lines.\n" 727 | " \b-A\r Print ANSI lines graphic indentation lines.\n" 728 | " \b-S\r Print with CP437 (console) graphics indentation lines.\n" 729 | " \b-n\r Turn colorization off always (\b-C\r overrides).\n" 730 | " \b-C\r Turn colorization on always.\n" 731 | " \b------- XML/HTML/JSON/HYPERLINK options -------\r\n" 732 | " \b-X\r Prints out an XML representation of the tree.\n" 733 | " \b-J\r Prints out an JSON representation of the tree.\n" 734 | " \b-H\r \fbaseHREF\r Prints out HTML format with \fbaseHREF\r as top directory.\n" 735 | " \b-T\r \fstring\r Replace the default HTML title and H1 header with \fstring\r.\n" 736 | " \b--nolinks\r Turn off hyperlinks in HTML output.\n" 737 | " \b--hintro\r \fX\r Use file \fX\r as the HTML intro.\n" 738 | " \b--houtro\r \fX\r Use file \fX\r as the HTML outro.\n" 739 | " \b--hyperlink\r Turn on OSC 8 terminal hyperlinks.\n" 740 | " \b--scheme\r \fX\r Set OSC 8 hyperlink scheme, default \b\ffile://\r\n" 741 | " \b--authority\r \fX\r Set OSC 8 hyperlink authority/hostname.\n" 742 | " \b------- Input options -------\r\n" 743 | " \b--fromfile\r Reads paths from files (\b.\r=stdin)\n" 744 | " \b--fromtabfile\r Reads trees from tab indented files (\b.\r=stdin)\n" 745 | " \b--fflinks\r Process link information when using \b--fromfile\r.\n" 746 | " \b------- Miscellaneous options -------\r\n" 747 | " \b--opt-toggle\r Enable option toggling.\n" 748 | " \b--version\r Print version and exit.\n" 749 | " \b--help\r Print usage and this help message and exit.\n" 750 | " \b--\r Options processing terminator.\n"); 751 | exit(0); 752 | } 753 | 754 | /** 755 | * True if file matches an -I pattern 756 | */ 757 | int patignore(const char *name, bool isdir) 758 | { 759 | int i; 760 | for(i=0; i < ipattern; i++) 761 | if (patmatch(name, ipatterns[i], isdir)) return 1; 762 | return 0; 763 | } 764 | 765 | /** 766 | * True if name matches a -P pattern 767 | */ 768 | int patinclude(const char *name, bool isdir) 769 | { 770 | int i; 771 | for(i=0; i < pattern; i++) { 772 | if (patmatch(name, patterns[i], isdir)) { 773 | return 1; 774 | } 775 | } 776 | return 0; 777 | } 778 | 779 | /** 780 | * Split out stat portion from read_dir as prelude to just using stat structure directly. 781 | */ 782 | struct _info *getinfo(const char *name, char *path) 783 | { 784 | static char *lbuf = NULL; 785 | static size_t lbufsize = 0; 786 | struct _info *ent; 787 | struct stat st, lst; 788 | ssize_t len; 789 | int rs; 790 | bool isdir; 791 | 792 | if (lbuf == NULL) lbuf = xmalloc(lbufsize = PATH_MAX); 793 | 794 | if (lstat(path,&lst) < 0) return NULL; 795 | 796 | if ((lst.st_mode & S_IFMT) == S_IFLNK) { 797 | if ((rs = stat(path,&st)) < 0) memset(&st, 0, sizeof(st)); 798 | } else { 799 | rs = 0; 800 | st.st_mode = lst.st_mode; 801 | st.st_dev = lst.st_dev; 802 | st.st_ino = lst.st_ino; 803 | } 804 | 805 | isdir = (st.st_mode & S_IFMT) == S_IFDIR; 806 | 807 | #ifndef __EMX__ 808 | if (gitignore && filtercheck(path, name, isdir)) return NULL; 809 | 810 | if ((lst.st_mode & S_IFMT) != S_IFDIR && !(lflag && ((st.st_mode & S_IFMT) == S_IFDIR))) { 811 | if (pattern && !patinclude(name, isdir)) return NULL; 812 | } 813 | if (ipattern && patignore(name, isdir)) return NULL; 814 | #endif 815 | 816 | if (dflag && ((st.st_mode & S_IFMT) != S_IFDIR)) return NULL; 817 | 818 | #ifndef __EMX__ 819 | /* if (pattern && ((lst.st_mode & S_IFMT) == S_IFLNK) && !lflag) continue; */ 820 | #endif 821 | 822 | ent = (struct _info *)xmalloc(sizeof(struct _info)); 823 | memset(ent, 0, sizeof(struct _info)); 824 | 825 | ent->name = scopy(name); 826 | /* We should just incorporate struct stat into _info, and eliminate this unnecessary copying. 827 | * Made sense long ago when we had fewer options and didn't need half of stat. 828 | */ 829 | ent->mode = lst.st_mode; 830 | ent->uid = lst.st_uid; 831 | ent->gid = lst.st_gid; 832 | ent->size = lst.st_size; 833 | ent->dev = st.st_dev; 834 | ent->inode = st.st_ino; 835 | ent->ldev = lst.st_dev; 836 | ent->linode = lst.st_ino; 837 | ent->lnk = NULL; 838 | ent->orphan = false; 839 | ent->err = NULL; 840 | ent->child = NULL; 841 | 842 | ent->atime = lst.st_atime; 843 | ent->ctime = lst.st_ctime; 844 | ent->mtime = lst.st_mtime; 845 | 846 | #ifdef __EMX__ 847 | ent->attr = lst.st_attr; 848 | #else 849 | 850 | /* These should be eliminated, as they're barely used: */ 851 | ent->isdir = isdir; 852 | ent->issok = ((st.st_mode & S_IFMT) == S_IFSOCK); 853 | ent->isfifo = ((st.st_mode & S_IFMT) == S_IFIFO); 854 | ent->isexe = (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) ? 1 : 0; 855 | 856 | if ((lst.st_mode & S_IFMT) == S_IFLNK) { 857 | if ((size_t)lst.st_size+1 > lbufsize) lbuf = xrealloc(lbuf,lbufsize=((size_t)lst.st_size+8192)); 858 | if ((len=readlink(path,lbuf,lbufsize-1)) < 0) { 859 | ent->lnk = scopy("[Error reading symbolic link information]"); 860 | ent->isdir = false; 861 | ent->lnkmode = st.st_mode; 862 | } else { 863 | lbuf[len] = 0; 864 | ent->lnk = scopy(lbuf); 865 | if (rs < 0) ent->orphan = true; 866 | ent->lnkmode = st.st_mode; 867 | } 868 | } 869 | #endif 870 | 871 | ent->comment = NULL; 872 | 873 | return ent; 874 | } 875 | 876 | struct _info **read_dir(char *dir, ssize_t *n, int infotop) 877 | { 878 | struct comment *com; 879 | static char *path = NULL; 880 | static size_t pathsize; 881 | struct _info **dl, *info; 882 | struct dirent *ent; 883 | DIR *d; 884 | size_t ne, p = 0, i; 885 | bool es = (dir[strlen(dir)-1] == '/'); 886 | 887 | if (path == NULL) { 888 | path=xmalloc(pathsize = strlen(dir)+PATH_MAX); 889 | } 890 | 891 | *n = -1; 892 | if ((d=opendir(dir)) == NULL) return NULL; 893 | 894 | dl = (struct _info **)xmalloc(sizeof(struct _info *) * (ne = MINIT)); 895 | 896 | while((ent = (struct dirent *)readdir(d))) { 897 | if (!strcmp("..",ent->d_name) || !strcmp(".",ent->d_name)) continue; 898 | if (Hflag && !strcmp(ent->d_name,"00Tree.html")) continue; 899 | if (!aflag && ent->d_name[0] == '.') continue; 900 | 901 | if (strlen(dir)+strlen(ent->d_name)+2 > pathsize) path = xrealloc(path,pathsize=(strlen(dir)+strlen(ent->d_name)+PATH_MAX)); 902 | if (es) sprintf(path, "%s%s", dir, ent->d_name); 903 | else sprintf(path,"%s/%s",dir,ent->d_name); 904 | 905 | info = getinfo(ent->d_name, path); 906 | if (info) { 907 | if (showinfo && (com = infocheck(path, ent->d_name, infotop, info->isdir))) { 908 | for(i = 0; com->desc[i] != NULL; i++); 909 | info->comment = xmalloc(sizeof(char *) * (i+1)); 910 | for(i = 0; com->desc[i] != NULL; i++) info->comment[i] = scopy(com->desc[i]); 911 | info->comment[i] = NULL; 912 | } 913 | if (p == (ne-1)) dl = (struct _info **)xrealloc(dl,sizeof(struct _info *) * (ne += MINC)); 914 | dl[p++] = info; 915 | } 916 | } 917 | closedir(d); 918 | 919 | if ((*n = (ssize_t)p) == 0) { 920 | free(dl); 921 | return NULL; 922 | } 923 | 924 | dl[p] = NULL; 925 | return dl; 926 | } 927 | 928 | void push_files(const char *dir, struct ignorefile **ig, struct infofile **inf, bool top) 929 | { 930 | if (gitignore) { 931 | *ig = new_ignorefile(dir, top); 932 | if (*ig != NULL) push_filterstack(*ig); 933 | } 934 | if (showinfo) { 935 | *inf = new_infofile(dir, top); 936 | if (*inf != NULL) push_infostack(*inf); 937 | } 938 | } 939 | 940 | /* This is for all the impossible things people wanted the old tree to do. 941 | * This can and will use a large amount of memory for large directory trees 942 | * and also take some time. 943 | */ 944 | struct _info **unix_getfulltree(char *d, u_long lev, dev_t dev, off_t *size, char **err) 945 | { 946 | char *path; 947 | size_t pathsize = 0; 948 | struct ignorefile *ig = NULL; 949 | struct infofile *inf = NULL; 950 | struct _info **dir, **sav, **p, *xp; 951 | struct stat sb; 952 | ssize_t n; 953 | u_long lev_tmp; 954 | int tmp_pattern = 0; 955 | char *start_rel_path; 956 | 957 | *err = NULL; 958 | if (Level >= 0 && lev > (u_long)Level) return NULL; 959 | if (xdev && lev == 0) { 960 | stat(d,&sb); 961 | dev = sb.st_dev; 962 | } 963 | /* if the directory name matches, turn off pattern matching for contents */ 964 | if (matchdirs && pattern) { 965 | lev_tmp = lev; 966 | start_rel_path = d + strlen(d); 967 | for (start_rel_path = d + strlen(d); start_rel_path != d; --start_rel_path) { 968 | if (*start_rel_path == '/') 969 | --lev_tmp; 970 | if (lev_tmp <= 0) { 971 | if (*start_rel_path) 972 | ++start_rel_path; 973 | break; 974 | } 975 | } 976 | if (*start_rel_path && patinclude(start_rel_path, 1)) { 977 | tmp_pattern = pattern; 978 | pattern = 0; 979 | } 980 | } 981 | 982 | push_files(d, &ig, &inf, lev==0); 983 | 984 | sav = dir = read_dir(d, &n, inf != NULL); 985 | if (tmp_pattern) { 986 | pattern = tmp_pattern; 987 | tmp_pattern = 0; 988 | } 989 | if (dir == NULL && n) { 990 | *err = scopy("error opening dir"); 991 | return NULL; 992 | } 993 | if (n == 0) { 994 | if (sav != NULL) free_dir(sav); 995 | return NULL; 996 | } 997 | path = xmalloc(pathsize=PATH_MAX); 998 | 999 | if (flimit > 0 && n > flimit) { 1000 | sprintf(path,"%ld entries exceeds filelimit, not opening dir",n); 1001 | *err = scopy(path); 1002 | free_dir(sav); 1003 | free(path); 1004 | return NULL; 1005 | } 1006 | 1007 | if (lev >= (u_long)maxdirs-1) { 1008 | dirs = xrealloc(dirs,sizeof(int) * (maxdirs += 1024)); 1009 | } 1010 | 1011 | while (*dir) { 1012 | if ((*dir)->isdir && !(xdev && dev != (*dir)->dev)) { 1013 | if ((*dir)->lnk) { 1014 | if (lflag) { 1015 | if (findino((*dir)->inode,(*dir)->dev)) { 1016 | (*dir)->err = scopy("recursive, not followed"); 1017 | } else { 1018 | saveino((*dir)->inode, (*dir)->dev); 1019 | if (*(*dir)->lnk == '/') 1020 | (*dir)->child = unix_getfulltree((*dir)->lnk,lev+1,dev,&((*dir)->size),&((*dir)->err)); 1021 | else { 1022 | if (strlen(d)+strlen((*dir)->lnk)+2 > pathsize) path=xrealloc(path,pathsize=(strlen(d)+strlen((*dir)->name)+1024)); 1023 | if (fflag && !strcmp(d,"/")) sprintf(path,"%s%s",d,(*dir)->lnk); 1024 | else sprintf(path,"%s/%s",d,(*dir)->lnk); 1025 | (*dir)->child = unix_getfulltree(path,lev+1,dev,&((*dir)->size),&((*dir)->err)); 1026 | } 1027 | } 1028 | } 1029 | } else { 1030 | if (strlen(d)+strlen((*dir)->name)+2 > pathsize) path=xrealloc(path,pathsize=(strlen(d)+strlen((*dir)->name)+1024)); 1031 | if (fflag && !strcmp(d,"/")) sprintf(path,"%s%s",d,(*dir)->name); 1032 | else sprintf(path,"%s/%s",d,(*dir)->name); 1033 | saveino((*dir)->inode, (*dir)->dev); 1034 | (*dir)->child = unix_getfulltree(path,lev+1,dev,&((*dir)->size),&((*dir)->err)); 1035 | } 1036 | /* prune empty folders, unless they match the requested pattern */ 1037 | if (pruneflag && (*dir)->child == NULL && 1038 | !(matchdirs && pattern && patinclude((*dir)->name, (*dir)->isdir))) { 1039 | xp = *dir; 1040 | for(p=dir;*p;p++) *p = *(p+1); 1041 | n--; 1042 | free(xp->name); 1043 | if (xp->lnk) free(xp->lnk); 1044 | free(xp); 1045 | continue; 1046 | } 1047 | } 1048 | if (duflag) *size += (*dir)->size; 1049 | dir++; 1050 | } 1051 | 1052 | /* sorting needs to be deferred for --du: */ 1053 | if (topsort) qsort(sav,(size_t)n,sizeof(struct _info *), (int (*)(const void *, const void *))topsort); 1054 | 1055 | free(path); 1056 | if (n == 0) { 1057 | free_dir(sav); 1058 | return NULL; 1059 | } 1060 | if (ig != NULL) pop_filterstack(); 1061 | if (inf != NULL) pop_infostack(); 1062 | return sav; 1063 | } 1064 | 1065 | /** 1066 | * filesfirst and dirsfirst are now top-level meta-sorts. 1067 | */ 1068 | int filesfirst(struct _info **a, struct _info **b) 1069 | { 1070 | if ((*a)->isdir != (*b)->isdir) { 1071 | return (*a)->isdir ? 1 : -1; 1072 | } 1073 | return basesort(a, b); 1074 | } 1075 | 1076 | int dirsfirst(struct _info **a, struct _info **b) 1077 | { 1078 | if ((*a)->isdir != (*b)->isdir) { 1079 | return (*a)->isdir ? -1 : 1; 1080 | } 1081 | return basesort(a, b); 1082 | } 1083 | 1084 | /* Sorting functions */ 1085 | int alnumsort(struct _info **a, struct _info **b) 1086 | { 1087 | int v = strcoll((*a)->name,(*b)->name); 1088 | return reverse? -v : v; 1089 | } 1090 | 1091 | int versort(struct _info **a, struct _info **b) 1092 | { 1093 | int v = strverscmp((*a)->name,(*b)->name); 1094 | return reverse? -v : v; 1095 | } 1096 | 1097 | int mtimesort(struct _info **a, struct _info **b) 1098 | { 1099 | int v; 1100 | 1101 | if ((*a)->mtime == (*b)->mtime) { 1102 | v = strcoll((*a)->name,(*b)->name); 1103 | return reverse? -v : v; 1104 | } 1105 | v = (*a)->mtime == (*b)->mtime? 0 : ((*a)->mtime < (*b)->mtime ? -1 : 1); 1106 | return reverse? -v : v; 1107 | } 1108 | 1109 | int ctimesort(struct _info **a, struct _info **b) 1110 | { 1111 | int v; 1112 | 1113 | if ((*a)->ctime == (*b)->ctime) { 1114 | v = strcoll((*a)->name,(*b)->name); 1115 | return reverse? -v : v; 1116 | } 1117 | v = (*a)->ctime == (*b)->ctime? 0 : ((*a)->ctime < (*b)->ctime? -1 : 1); 1118 | return reverse? -v : v; 1119 | } 1120 | 1121 | int sizecmp(off_t a, off_t b) 1122 | { 1123 | return (a == b)? 0 : ((a < b)? 1 : -1); 1124 | } 1125 | 1126 | int fsizesort(struct _info **a, struct _info **b) 1127 | { 1128 | int v = sizecmp((*a)->size, (*b)->size); 1129 | if (v == 0) v = strcoll((*a)->name,(*b)->name); 1130 | return reverse? -v : v; 1131 | } 1132 | 1133 | void *xmalloc (size_t size) 1134 | { 1135 | register void *value = malloc (size); 1136 | if (value == NULL) { 1137 | fprintf(stderr,"tree: virtual memory exhausted.\n"); 1138 | exit(1); 1139 | } 1140 | return value; 1141 | } 1142 | 1143 | void *xrealloc (void *ptr, size_t size) 1144 | { 1145 | register void *value = realloc (ptr,size); 1146 | if (value == NULL) { 1147 | fprintf(stderr,"tree: virtual memory exhausted.\n"); 1148 | exit(1); 1149 | } 1150 | return value; 1151 | } 1152 | 1153 | void free_dir(struct _info **d) 1154 | { 1155 | int i; 1156 | 1157 | for(i=0;d[i];i++) { 1158 | free(d[i]->name); 1159 | if (d[i]->lnk) free(d[i]->lnk); 1160 | free(d[i]); 1161 | } 1162 | free(d); 1163 | } 1164 | 1165 | char *gnu_getcwd(void) 1166 | { 1167 | size_t size = 100; 1168 | char *buffer = (char *) xmalloc (size); 1169 | 1170 | while (1) { 1171 | char *value = getcwd (buffer, size); 1172 | if (value != 0) return buffer; 1173 | size *= 2; 1174 | free (buffer); 1175 | buffer = (char *) xmalloc (size); 1176 | } 1177 | } 1178 | 1179 | static char cond_lower(char c) 1180 | { 1181 | return ignorecase ? (char)tolower(c) : c; 1182 | } 1183 | 1184 | /* 1185 | * Patmatch() code courtesy of Thomas Moore (dark@mama.indstate.edu) 1186 | * '|' support added by David MacMahon (davidm@astron.Berkeley.EDU) 1187 | * Case insensitive support added by Jason A. Donenfeld (Jason@zx2c4.com) 1188 | * returns: 1189 | * 1 on a match 1190 | * 0 on a mismatch 1191 | * -1 on a syntax error in the pattern 1192 | */ 1193 | int patmatch(const char *buf, const char *pat, bool isdir) 1194 | { 1195 | int match = 1, n; 1196 | char *bar = strchr(pat, '|'); 1197 | char m, pprev = 0; 1198 | 1199 | /* If a bar is found, call patmatch recursively on the two sub-patterns */ 1200 | if (bar) { 1201 | /* If the bar is the first or last character, it's a syntax error */ 1202 | if (bar == pat || !bar[1]) { 1203 | return -1; 1204 | } 1205 | /* Break pattern into two sub-patterns */ 1206 | *bar = '\0'; 1207 | match = patmatch(buf, pat, isdir); 1208 | if (!match) { 1209 | match = patmatch(buf, bar+1, isdir); 1210 | } 1211 | /* Join sub-patterns back into one pattern */ 1212 | *bar = '|'; 1213 | return match; 1214 | } 1215 | 1216 | while(*pat && match) { 1217 | switch(*pat) { 1218 | case '[': 1219 | pat++; 1220 | if(*pat != '^') { 1221 | n = 1; 1222 | match = 0; 1223 | } else { 1224 | pat++; 1225 | n = 0; 1226 | } 1227 | while(*pat != ']'){ 1228 | if(*pat == '\\') pat++; 1229 | if(!*pat /* || *pat == '/' */ ) return -1; 1230 | if(pat[1] == '-'){ 1231 | m = *pat; 1232 | pat += 2; 1233 | if(*pat == '\\' && *pat) 1234 | pat++; 1235 | if(cond_lower(*buf) >= cond_lower(m) && cond_lower(*buf) <= cond_lower(*pat)) 1236 | match = n; 1237 | if(!*pat) 1238 | pat--; 1239 | } else if(cond_lower(*buf) == cond_lower(*pat)) match = n; 1240 | pat++; 1241 | } 1242 | buf++; 1243 | break; 1244 | case '*': 1245 | pat++; 1246 | if(!*pat) { 1247 | int f = (strchr(buf, '/') == NULL); 1248 | return f; 1249 | } 1250 | match = 0; 1251 | /* "Support" ** for .gitignore support, mostly the same as *: */ 1252 | if (*pat == '*') { 1253 | pat++; 1254 | if(!*pat) return 1; 1255 | 1256 | while(*buf && !(match = patmatch(buf, pat, isdir))) { 1257 | /* ** between two /'s is allowed to match a null /: */ 1258 | if (pprev == '/' && *pat == '/' && *(pat+1) && (match = patmatch(buf, pat+1, isdir))) return match; 1259 | buf++; 1260 | while(*buf && *buf != '/') buf++; 1261 | } 1262 | } else { 1263 | while(*buf && !(match = patmatch(buf++, pat, isdir))) 1264 | if (*buf == '/') break; 1265 | } 1266 | if (!match && (!*buf || *buf == '/')) match = patmatch(buf, pat, isdir); 1267 | return match; 1268 | case '?': 1269 | if(!*buf) return 0; 1270 | buf++; 1271 | break; 1272 | case '/': 1273 | if (!*(pat+1) && !*buf) return isdir; 1274 | match = (*buf++ == *pat); 1275 | break; 1276 | case '\\': 1277 | if(*pat) 1278 | pat++; 1279 | /* Falls through */ 1280 | default: 1281 | match = (cond_lower(*buf++) == cond_lower(*pat)); 1282 | break; 1283 | } 1284 | pprev = *pat++; 1285 | if(match<1) return match; 1286 | } 1287 | if(!*buf) return match; 1288 | return 0; 1289 | } 1290 | 1291 | 1292 | /** 1293 | * They cried out for ANSI-lines (not really), but here they are, as an option 1294 | * for the xterm and console capable among you, as a run-time option. 1295 | */ 1296 | void indent(int maxlevel) 1297 | { 1298 | int i; 1299 | 1300 | if (ansilines) { 1301 | if (dirs[1]) fprintf(outfile,"\033(0"); 1302 | for(i=1; (i <= maxlevel) && dirs[i]; i++) { 1303 | if (dirs[i+1]) { 1304 | if (dirs[i] == 1) fprintf(outfile,"\170 "); 1305 | else printf(" "); 1306 | } else { 1307 | if (dirs[i] == 1) fprintf(outfile,"\164\161\161 "); 1308 | else fprintf(outfile,"\155\161\161 "); 1309 | } 1310 | } 1311 | if (dirs[1]) fprintf(outfile,"\033(B"); 1312 | } else { 1313 | if (Hflag) fprintf(outfile,"\t"); 1314 | for(i=1; (i <= maxlevel) && dirs[i]; i++) { 1315 | fprintf(outfile,"%s ", 1316 | dirs[i+1] ? (dirs[i]==1 ? linedraw->vert : (Hflag? "   " : " ") ) 1317 | : (dirs[i]==1 ? linedraw->vert_left:linedraw->corner)); 1318 | } 1319 | } 1320 | } 1321 | 1322 | 1323 | #ifdef __EMX__ 1324 | char *prot(long m) 1325 | #else 1326 | char *prot(mode_t m) 1327 | #endif 1328 | { 1329 | #ifdef __EMX__ 1330 | const u_short *p; 1331 | static char buf[6]; 1332 | char*cp; 1333 | 1334 | for(p=ifmt,cp=strcpy(buf,"adshr");*cp;++p,++cp) 1335 | if(!(m&*p)) 1336 | *cp='-'; 1337 | #else 1338 | static char buf[11], perms[] = "rwxrwxrwx"; 1339 | int i; 1340 | mode_t b; 1341 | 1342 | for(i=0;ifmt[i] && (m&S_IFMT) != ifmt[i];i++); 1343 | buf[0] = fmt[i]; 1344 | 1345 | /** 1346 | * Nice, but maybe not so portable, it is should be no less portable than the 1347 | * old code. 1348 | */ 1349 | for(b=S_IRUSR,i=0; i<9; b>>=1,i++) 1350 | buf[i+1] = (m & (b)) ? perms[i] : '-'; 1351 | if (m & S_ISUID) buf[3] = (buf[3]=='-')? 'S' : 's'; 1352 | if (m & S_ISGID) buf[6] = (buf[6]=='-')? 'S' : 's'; 1353 | if (m & S_ISVTX) buf[9] = (buf[9]=='-')? 'T' : 't'; 1354 | 1355 | buf[10] = 0; 1356 | #endif 1357 | return buf; 1358 | } 1359 | 1360 | #define SIXMONTHS (6*31*24*60*60) 1361 | 1362 | char *do_date(time_t t) 1363 | { 1364 | static char buf[256]; 1365 | struct tm *tm; 1366 | 1367 | tm = localtime(&t); 1368 | 1369 | if (timefmt) { 1370 | strftime(buf,255,timefmt,tm); 1371 | buf[255] = 0; 1372 | } else { 1373 | time_t c = time(0); 1374 | /* Use strftime() so that locale is respected: */ 1375 | if (t > c || (t+SIXMONTHS) < c) 1376 | strftime(buf,255,"%b %e %Y",tm); 1377 | else 1378 | strftime(buf,255,"%b %e %R", tm); 1379 | } 1380 | return buf; 1381 | } 1382 | 1383 | /** 1384 | * Must fix this someday 1385 | */ 1386 | void printit(const char *s) 1387 | { 1388 | int c; 1389 | size_t cs; 1390 | 1391 | if (Nflag) { 1392 | if (Qflag) fprintf(outfile, "\"%s\"",s); 1393 | else fprintf(outfile,"%s",s); 1394 | return; 1395 | } 1396 | if (mb_cur_max > 1) { 1397 | wchar_t *ws, *tp; 1398 | ws = xmalloc(sizeof(wchar_t)* (cs=(strlen(s)+1))); 1399 | if (mbstowcs(ws,s,cs) != (size_t)-1) { 1400 | if (Qflag) putc('"',outfile); 1401 | for(tp=ws;*tp && cs > 1;tp++, cs--) { 1402 | if (iswprint((wint_t)*tp)) fprintf(outfile,"%lc",(wint_t)*tp); 1403 | else { 1404 | if (qflag) putc('?',outfile); 1405 | else fprintf(outfile,"\\%03o",(unsigned int)*tp); 1406 | } 1407 | } 1408 | if (Qflag) putc('"',outfile); 1409 | free(ws); 1410 | return; 1411 | } 1412 | free(ws); 1413 | } 1414 | if (Qflag) putc('"',outfile); 1415 | for(;*s;s++) { 1416 | c = (unsigned char)*s; 1417 | #ifdef __EMX__ 1418 | if(_nls_is_dbcs_lead(*(unsigned char*)s)){ 1419 | putc(*s,outfile); 1420 | putc(*++s,outfile); 1421 | continue; 1422 | } 1423 | #endif 1424 | if((c >= 7 && c <= 13) || c == '\\' || (c == '"' && Qflag) || (c == ' ' && !Qflag)) { 1425 | putc('\\',outfile); 1426 | if (c > 13) putc(c, outfile); 1427 | else putc("abtnvfr"[c-7], outfile); 1428 | } else if (isprint(c)) putc(c,outfile); 1429 | else { 1430 | if (qflag) { 1431 | if (mb_cur_max > 1 && c > 127) putc(c,outfile); 1432 | else putc('?',outfile); 1433 | } else fprintf(outfile,"\\%03o",c); 1434 | } 1435 | } 1436 | if (Qflag) putc('"',outfile); 1437 | } 1438 | 1439 | int psize(char *buf, off_t size) 1440 | { 1441 | static char *iec_unit="BKMGTPEZY", *si_unit = "dkMGTPEZY"; 1442 | char *unit = siflag ? si_unit : iec_unit; 1443 | int idx, usize = siflag ? 1000 : 1024; 1444 | 1445 | if (hflag || siflag) { 1446 | for (idx=size= (usize*usize); idx++,size/=usize); 1447 | if (!idx) return sprintf(buf, " %4d", (int)size); 1448 | else return sprintf(buf, (((size+52)/usize) >= 10)? " %3.0f%c" : " %3.1f%c" , (float)size/(float)usize,unit[idx]); 1449 | } else return sprintf(buf, sizeof(off_t) == sizeof(long long)? " %11lld" : " %9lld", (long long int)size); 1450 | } 1451 | 1452 | char Ftype(mode_t mode) 1453 | { 1454 | int m = mode & S_IFMT; 1455 | if (!dflag && m == S_IFDIR) return '/'; 1456 | else if (m == S_IFSOCK) return '='; 1457 | else if (m == S_IFIFO) return '|'; 1458 | else if (m == S_IFLNK) return '@'; /* Here, but never actually used though. */ 1459 | #ifdef S_IFDOOR 1460 | else if (m == S_IFDOOR) return '>'; 1461 | #endif 1462 | else if ((m == S_IFREG) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH))) return '*'; 1463 | return 0; 1464 | } 1465 | 1466 | struct _info *stat2info(const struct stat *st) 1467 | { 1468 | static struct _info info; 1469 | 1470 | info.linode = st->st_ino; 1471 | info.ldev = st->st_dev; 1472 | #ifdef __EMX__ 1473 | info.attr = st->st_attr 1474 | #endif 1475 | info.mode = st->st_mode; 1476 | info.uid = st->st_uid; 1477 | info.gid = st->st_gid; 1478 | info.size = st->st_size; 1479 | info.atime = st->st_atime; 1480 | info.ctime = st->st_ctime; 1481 | info.mtime = st->st_mtime; 1482 | 1483 | info.isdir = ((st->st_mode & S_IFMT) == S_IFDIR); 1484 | info.issok = ((st->st_mode & S_IFMT) == S_IFSOCK); 1485 | info.isfifo = ((st->st_mode & S_IFMT) == S_IFIFO); 1486 | info.isexe = (st->st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) ? 1 : 0; 1487 | 1488 | return &info; 1489 | } 1490 | 1491 | char *fillinfo(char *buf, const struct _info *ent) 1492 | { 1493 | int n; 1494 | buf[n=0] = 0; 1495 | #ifdef __USE_FILE_OFFSET64 1496 | if (inodeflag) n += sprintf(buf," %7lld",(long long)ent->linode); 1497 | #else 1498 | if (inodeflag) n += sprintf(buf," %7ld",(long int)ent->linode); 1499 | #endif 1500 | if (devflag) n += sprintf(buf+n, " %3d", (int)ent->ldev); 1501 | #ifdef __EMX__ 1502 | if (pflag) n += sprintf(buf+n, " %s",prot(ent->attr)); 1503 | #else 1504 | if (pflag) n += sprintf(buf+n, " %s", prot(ent->mode)); 1505 | #endif 1506 | if (uflag) n += sprintf(buf+n, " %-8.32s", uidtoname(ent->uid)); 1507 | if (gflag) n += sprintf(buf+n, " %-8.32s", gidtoname(ent->gid)); 1508 | if (sflag) n += psize(buf+n,ent->size); 1509 | if (Dflag) n += sprintf(buf+n, " %s", do_date(cflag? ent->ctime : ent->mtime)); 1510 | 1511 | if (buf[0] == ' ') { 1512 | buf[0] = '['; 1513 | sprintf(buf+n, "]"); 1514 | } 1515 | 1516 | return buf; 1517 | } 1518 | --------------------------------------------------------------------------------