xref: /plan9/sys/src/cmd/ls.c (revision ec59a3ddbfceee0efe34584c2c9981a5e5ff1ec4)
1 #include <u.h>
2 #include <libc.h>
3 #include <bio.h>
4 #include <fcall.h>
5 
6 typedef struct NDir NDir;
7 struct NDir
8 {
9 	Dir *d;
10 	char	*prefix;
11 };
12 
13 int	errs = 0;
14 int	dflag;
15 int	lflag;
16 int	mflag;
17 int	nflag;
18 int	pflag;
19 int	qflag;
20 int	Qflag;
21 int	rflag;
22 int	sflag;
23 int	tflag;
24 int	Tflag;
25 int	uflag;
26 int	Fflag;
27 int	ndirbuf;
28 int	ndir;
29 NDir*	dirbuf;
30 int	ls(char*, int);
31 int	compar(NDir*, NDir*);
32 char*	asciitime(long);
33 char*	darwx(long);
34 void	rwx(long, char*);
35 void	growto(long);
36 void	dowidths(Dir*);
37 void	format(Dir*, char*);
38 void	output(void);
39 char*	xcleanname(char*);
40 ulong	clk;
41 int	swidth;			/* max width of -s size */
42 int	qwidth;			/* max width of -q version */
43 int	vwidth;			/* max width of dev */
44 int	uwidth;			/* max width of userid */
45 int	mwidth;			/* max width of muid */
46 int	glwidth;		/* max width of groupid and length */
47 Biobuf	bin;
48 
49 void
50 main(int argc, char *argv[])
51 {
52 	int i;
53 
54 	Binit(&bin, 1, OWRITE);
55 	ARGBEGIN{
56 	case 'F':	Fflag++; break;
57 	case 'd':	dflag++; break;
58 	case 'l':	lflag++; break;
59 	case 'm':	mflag++; break;
60 	case 'n':	nflag++; break;
61 	case 'p':	pflag++; break;
62 	case 'q':	qflag++; break;
63 	case 'Q':	Qflag++; break;
64 	case 'r':	rflag++; break;
65 	case 's':	sflag++; break;
66 	case 't':	tflag++; break;
67 	case 'T':	Tflag++; break;
68 	case 'u':	uflag++; break;
69 	default:	fprint(2, "usage: ls [-dlmnpqrstuFQT] [file ...]\n");
70 			exits("usage");
71 	}ARGEND
72 
73 	doquote = needsrcquote;
74 	quotefmtinstall();
75 	fmtinstall('M', dirmodefmt);
76 
77 	if(lflag)
78 		clk = time(0);
79 	if(argc == 0)
80 		errs = ls(".", 0);
81 	else for(i=0; i<argc; i++)
82 		errs |= ls(argv[i], 1);
83 	output();
84 	exits(errs? "errors" : 0);
85 }
86 
87 int
88 ls(char *s, int multi)
89 {
90 	int fd;
91 	long i, n;
92 	char *p;
93 	Dir *db;
94 
95 	db = dirstat(s);
96 	if(db == nil){
97     error:
98 		fprint(2, "ls: %s: %r\n", s);
99 		return 1;
100 	}
101 	if((db->qid.type&QTDIR) && dflag==0){
102 		free(db);
103 		output();
104 		fd = open(s, OREAD);
105 		if(fd == -1)
106 			goto error;
107 		n = dirreadall(fd, &db);
108 		if(n < 0)
109 			goto error;
110 		xcleanname(s);
111 		growto(ndir+n);
112 		for(i=0; i<n; i++){
113 			dirbuf[ndir+i].d = db+i;
114 			dirbuf[ndir+i].prefix = multi? s : 0;
115 		}
116 		ndir += n;
117 		close(fd);
118 		output();
119 	}else{
120 		growto(ndir+1);
121 		dirbuf[ndir].d = db;
122 		dirbuf[ndir].prefix = 0;
123 		xcleanname(s);
124 		p = utfrrune(s, '/');
125 		if(p){
126 			dirbuf[ndir].prefix = s;
127 			*p = 0;
128 		}
129 		ndir++;
130 	}
131 	return 0;
132 }
133 
134 void
135 output(void)
136 {
137 	int i;
138 	char buf[4096];
139 	char *s;
140 
141 	if(!nflag)
142 		qsort(dirbuf, ndir, sizeof dirbuf[0], (int (*)(void*, void*))compar);
143 	for(i=0; i<ndir; i++)
144 		dowidths(dirbuf[i].d);
145 	for(i=0; i<ndir; i++) {
146 		if(!pflag && (s = dirbuf[i].prefix)) {
147 			if(strcmp(s, "/") ==0)	/* / is a special case */
148 				s = "";
149 			sprint(buf, "%s/%s", s, dirbuf[i].d->name);
150 			format(dirbuf[i].d, buf);
151 		} else
152 			format(dirbuf[i].d, dirbuf[i].d->name);
153 	}
154 	ndir = 0;
155 	Bflush(&bin);
156 }
157 
158 void
159 dowidths(Dir *db)
160 {
161 	char buf[256];
162 	int n;
163 
164 	if(sflag) {
165 		n = sprint(buf, "%llud", (db->length+1023)/1024);
166 		if(n > swidth)
167 			swidth = n;
168 	}
169 	if(qflag) {
170 		n = sprint(buf, "%lud", db->qid.vers);
171 		if(n > qwidth)
172 			qwidth = n;
173 	}
174 	if(mflag) {
175 		n = snprint(buf, sizeof buf, "[%s]", db->muid);
176 		if(n > mwidth)
177 			mwidth = n;
178 	}
179 	if(lflag) {
180 		n = sprint(buf, "%ud", db->dev);
181 		if(n > vwidth)
182 			vwidth = n;
183 		n = strlen(db->uid);
184 		if(n > uwidth)
185 			uwidth = n;
186 		n = sprint(buf, "%llud", db->length);
187 		n += strlen(db->gid);
188 		if(n > glwidth)
189 			glwidth = n;
190 	}
191 }
192 
193 char*
194 fileflag(Dir *db)
195 {
196 	if(Fflag == 0)
197 		return "";
198 	if(QTDIR & db->qid.type)
199 		return "/";
200 	if(0111 & db->mode)
201 		return "*";
202 	return "";
203 }
204 
205 void
206 format(Dir *db, char *name)
207 {
208 	int i;
209 
210 	if(sflag)
211 		Bprint(&bin, "%*llud ",
212 			swidth, (db->length+1023)/1024);
213 	if(mflag){
214 		Bprint(&bin, "[%s] ", db->muid);
215 		for(i=2+strlen(db->muid); i<mwidth; i++)
216 			Bprint(&bin, " ");
217 	}
218 	if(qflag)
219 		Bprint(&bin, "(%.16llux %*lud %.2ux) ",
220 			db->qid.path,
221 			qwidth, db->qid.vers,
222 			db->qid.type);
223 	if(Tflag)
224 		Bprint(&bin, "%c ", (db->mode&DMTMP) ? 't' : '-');
225 
226 	if(lflag)
227 		Bprint(&bin,
228 			Qflag? "%M %C %*ud %*s %s %*llud %s %s\n" : "%M %C %*ud %*s %s %*llud %s %q\n",
229 			db->mode, db->type,
230 			vwidth, db->dev,
231 			-uwidth, db->uid,
232 			db->gid,
233 			(int)(glwidth-strlen(db->gid)), db->length,
234 			asciitime(uflag? db->atime : db->mtime), name);
235 	else
236 		Bprint(&bin,
237 			Qflag? "%s%s\n" : "%q%s\n",
238 			name, fileflag(db));
239 }
240 
241 void
242 growto(long n)
243 {
244 	if(n <= ndirbuf)
245 		return;
246 	ndirbuf = n;
247 	dirbuf=(NDir *)realloc(dirbuf, ndirbuf*sizeof(NDir));
248 	if(dirbuf == 0){
249 		fprint(2, "ls: malloc fail\n");
250 		exits("malloc fail");
251 	}
252 }
253 
254 int
255 compar(NDir *a, NDir *b)
256 {
257 	long i;
258 	Dir *ad, *bd;
259 
260 	ad = a->d;
261 	bd = b->d;
262 
263 	if(tflag){
264 		if(uflag)
265 			i = bd->atime-ad->atime;
266 		else
267 			i = bd->mtime-ad->mtime;
268 	}else{
269 		if(a->prefix && b->prefix){
270 			i = strcmp(a->prefix, b->prefix);
271 			if(i == 0)
272 				i = strcmp(ad->name, bd->name);
273 		}else if(a->prefix){
274 			i = strcmp(a->prefix, bd->name);
275 			if(i == 0)
276 				i = 1;	/* a is longer than b */
277 		}else if(b->prefix){
278 			i = strcmp(ad->name, b->prefix);
279 			if(i == 0)
280 				i = -1;	/* b is longer than a */
281 		}else
282 			i = strcmp(ad->name, bd->name);
283 	}
284 	if(i == 0)
285 		i = (a<b? -1 : 1);
286 	if(rflag)
287 		i = -i;
288 	return i;
289 }
290 
291 char*
292 asciitime(long l)
293 {
294 	static char buf[32];
295 	char *t;
296 
297 	t = ctime(l);
298 	/* 6 months in the past or a day in the future */
299 	if(l<clk-180L*24*60*60 || clk+24L*60*60<l){
300 		memmove(buf, t+4, 7);		/* month and day */
301 		memmove(buf+7, t+23, 5);		/* year */
302 	}else
303 		memmove(buf, t+4, 12);		/* skip day of week */
304 	buf[12] = 0;
305 	return buf;
306 }
307 
308 /*
309  * Compress slashes, remove trailing slash.  Don't worry about . and ..
310  */
311 char*
312 xcleanname(char *name)
313 {
314 	char *r, *w;
315 
316 	for(r=w=name; *r; r++){
317 		if(*r=='/' && r>name && *(r-1)=='/')
318 			continue;
319 		*w++ = *r;
320 	}
321 	while(w-1>name && *(w-1)=='/')
322 		*--w = 0;
323 	return name;
324 }
325