1 /*
2  * Copyright (c) 2014 by David I. Bell
3  * Permission is granted to use, distribute, or modify this source,
4  * provided that this copyright notice remains intact.
5  *
6  * The "ls" built-in command.
7  */
8 
9 #include "sash.h"
10 
11 #include <sys/types.h>
12 #include <sys/stat.h>
13 #include <dirent.h>
14 #include <pwd.h>
15 #include <grp.h>
16 
17 
18 #define	LISTSIZE	8192
19 
20 
21 #ifdef	S_ISLNK
22 #define	LSTAT	lstat
23 #else
24 #define	LSTAT	stat
25 #endif
26 
27 
28 /*
29  * Flags for the LS command.
30  */
31 #define	LSF_LONG	0x01
32 #define	LSF_DIR		0x02
33 #define	LSF_INODE	0x04
34 #define	LSF_MULT	0x08
35 #define	LSF_FLAG	0x10
36 #define	LSF_COLUMN	0x20
37 #define	LSF_NUMERIC	0x40
38 
39 
40 /*
41  * Data holding list of files.
42  */
43 static	char **	list;
44 static	int	listSize;
45 static	int	listUsed;
46 
47 /*
48  * Cached user and group name data.
49  */
50 static	char	userName[12];
51 static	int	userId;
52 static	BOOL	userIdKnown;
53 static	char	groupName[12];
54 static	int	groupId;
55 static	BOOL	groupIdKnown;
56 
57 
58 /*
59  * Local procedures.
60  */
61 static	void	listFile(
62 	const char *		name,
63 	const struct stat *	statBuf,
64 	int			flags,
65 	int			width
66 );
67 
68 static	BOOL	addListName(const char * fileName);
69 static	void	listAllFiles(int flags, int displayWidth);
70 static	void	clearListNames(void);
71 
72 
73 int
do_ls(int argc,const char ** argv)74 do_ls(int argc, const char ** argv)
75 {
76 	const char *	cp;
77 	const char *	name;
78 	int		flags;
79 	int		i;
80 	int		displayWidth;
81 	BOOL		endSlash;
82 	DIR *		dirp;
83 	struct dirent *	dp;
84 	char		fullName[PATH_LEN];
85 	struct	stat	statBuf;
86 	int		r;
87 
88 	static const char *	def[] = {"."};
89 
90 	/*
91 	 * Reset for a new listing run.
92 	 */
93 	clearListNames();
94 
95 	userIdKnown = FALSE;
96 	groupIdKnown = FALSE;
97 
98 	displayWidth = 0;
99 	flags = 0;
100 
101 	/*
102 	 * Handle options.
103 	 */
104 	argc--;
105 	argv++;
106 
107 	while ((argc > 0) && (**argv == '-'))
108 	{
109 		cp = *argv++ + 1;
110 		argc--;
111 
112 		while (*cp) switch (*cp++)
113 		{
114 			case 'l':	flags |= LSF_LONG; break;
115 			case 'n':	flags |= LSF_NUMERIC; break;
116 			case 'd':	flags |= LSF_DIR; break;
117 			case 'i':	flags |= LSF_INODE; break;
118 			case 'F':	flags |= LSF_FLAG; break;
119 			case 'C':	flags |= LSF_COLUMN; break;
120 
121 			default:
122 				fprintf(stderr, "Unknown option -%c\n", cp[-1]);
123 
124 				return 1;
125 		}
126 	}
127 
128 	/*
129 	 * If long or numeric listing is specified then turn off column listing.
130 	 */
131 	if (flags & (LSF_LONG | LSF_NUMERIC))
132 		flags &= ~LSF_COLUMN;
133 
134 	/*
135 	 * If column listing is specified then calculate the maximum
136 	 * width available for the columns of file names.
137 	 * This is settable using the COLS environment variable.
138 	 */
139 	if (flags & LSF_COLUMN)
140 	{
141 		name = getenv("COLS");
142 
143 		if (name)
144 			displayWidth = atoi(name);
145 
146 		if (displayWidth <= 0)
147 			displayWidth = 80;
148 	}
149 
150 	/*
151 	 * If no arguments are given set up to show the current directory.
152 	 */
153 	if (argc <= 0)
154 	{
155 		argc = 1;
156 		argv = def;
157 	}
158 
159 	if (argc > 1)
160 		flags |= LSF_MULT;
161 
162 	/*
163 	 * Make one pass over the file names to collect together
164 	 * all of the files which are not directories.
165 	 * We will process them all as one list.
166 	 */
167 	for (i = 0; i < argc; i++)
168 	{
169 		if ((flags & LSF_DIR) || !isDirectory(argv[i]))
170 		{
171 			if (!addListName(argv[i]))
172 				return 1;
173 		}
174 	}
175 
176 	/*
177 	 * List those file names, and then clear the list.
178 	 */
179 	listAllFiles(flags, displayWidth);
180 	clearListNames();
181 
182 	/*
183 	 * If directories were being listed as themselves, then we are done.
184 	 */
185 	if (flags & LSF_DIR)
186 		return r;
187 
188 	/*
189 	 * Now iterate over the file names processing the directories.
190 	 */
191 	while (!intFlag && (argc-- > 0))
192 	{
193 		name = *argv++;
194 		endSlash = (*name && (name[strlen(name) - 1] == '/'));
195 
196 		if (LSTAT(name, &statBuf) < 0)
197 		{
198 			perror(name);
199 			r = 1;
200 
201 			continue;
202 		}
203 
204 		/*
205 		 * If this file name is not a directory, then ignore it.
206 		 */
207 		if (!S_ISDIR(statBuf.st_mode))
208 			continue;
209 
210 		/*
211 		 * Collect all the files in the directory.
212 		 */
213 		dirp = opendir(name);
214 
215 		if (dirp == NULL)
216 		{
217 			perror(name);
218 
219 			continue;
220 		}
221 
222 		if (flags & LSF_MULT)
223 			printf("\n%s:\n", name);
224 
225 		while (!intFlag && ((dp = readdir(dirp)) != NULL))
226 		{
227 			fullName[0] = '\0';
228 
229 			if ((*name != '.') || (name[1] != '\0'))
230 			{
231 				strcpy(fullName, name);
232 
233 				if (!endSlash)
234 					strcat(fullName, "/");
235 			}
236 
237 			strcat(fullName, dp->d_name);
238 
239 			/*
240 			 * Save the file name in the list.
241 			 */
242 			if (!addListName(fullName))
243 			{
244 				closedir(dirp);
245 
246 				return 1;
247 			}
248 		}
249 
250 		closedir(dirp);
251 
252 		/*
253 		 * List the files we collected in this directory,
254 		 * and then clear the list.
255 		 */
256 		listAllFiles(flags, displayWidth);
257 		clearListNames();
258 	}
259 
260 	return r;
261 }
262 
263 
264 /*
265  * List all of the files in the current list of files.
266  * The files are displayed according to the specified flags,
267  * in the specified display width.
268  */
269 static void
listAllFiles(int flags,int displayWidth)270 listAllFiles(int flags, int displayWidth)
271 {
272 	const char *	name;
273 	const char *	cp;
274 	int		fileWidth;
275 	int		column;
276 	int		len;
277 	int		i;
278 	struct stat	statBuf;
279 
280 	/*
281 	 * Initialise width data until we need it.
282 	 */
283 	fileWidth = 0;
284 	column = 0;
285 
286 	/*
287 	 * Sort the files in the list.
288 	 */
289 	qsort((void *) list, listUsed, sizeof(char *), nameSort);
290 
291 	/*
292 	 * If we are showing the files in columns then calculate the
293 	 * maximum width of all of the file names, taking into account
294 	 * various factors.
295 	 */
296 	if (flags & LSF_COLUMN)
297 	{
298 		for (i = 0; i < listUsed; i++)
299 		{
300 			len = strlen(list[i]);
301 
302 			if (fileWidth < len)
303 				fileWidth = len;
304 		}
305 
306 		if (flags & LSF_FLAG)
307 			fileWidth++;
308 
309 		if (flags & LSF_INODE)
310 			fileWidth += 8;
311 
312 		fileWidth += 2;
313 	}
314 
315 	/*
316 	 * Now list the fileNames.
317 	 */
318 	for (i = 0; i < listUsed; i++)
319 	{
320 		name = list[i];
321 
322 		if (LSTAT(name, &statBuf) < 0)
323 		{
324 			perror(name);
325 
326 			continue;
327 		}
328 
329 		cp = strrchr(name, '/');
330 
331 		if (cp)
332 			cp++;
333 		else
334 			cp = name;
335 
336 		/*
337 		 * List the file in the next column or at the end
338 		 * of a line depending on the width left.
339 		 */
340 		if (column + fileWidth * 2 >= displayWidth)
341 		{
342 			listFile(cp, &statBuf, flags, 0);
343 			column = 0;
344 		}
345 		else
346 		{
347 			listFile(cp, &statBuf, flags, fileWidth);
348 			column += fileWidth;
349 		}
350 	}
351 
352 	/*
353 	 * Terminate the last file name if necessary.
354 	 */
355 	if (column > 0)
356 		fputc('\n', stdout);
357 }
358 
359 
360 /*
361  * Do a listing of a particular file name according to the flags.
362  * The output is shown within the specified width if it is nonzero,
363  * or on its own line if the width is zero.
364  */
365 static void
listFile(const char * name,const struct stat * statBuf,int flags,int width)366 listFile(
367 	const char *		name,
368 	const struct stat *	statBuf,
369 	int			flags,
370 	int			width
371 )
372 {
373 	char *		cp;
374 	struct passwd *	pwd;
375 	struct group *	grp;
376 	int		len;
377 	int		mode;
378 	int		flagChar;
379 	int		usedWidth;
380 	char		buf[PATH_LEN];
381 
382 	mode = statBuf->st_mode;
383 
384 	/*
385 	 * Initialise buffers for use.
386 	 */
387 	cp = buf;
388 	buf[0] = '\0';
389 	flagChar = '\0';
390 
391 	/*
392 	 * Show the inode number if requested.
393 	 */
394 	if (flags & LSF_INODE)
395 	{
396 		sprintf(cp, "%7d ", statBuf->st_ino);
397 		cp += strlen(cp);
398 	}
399 
400 	/*
401 	 * Create the long or numeric status line if requested.
402 	 */
403 	if (flags & (LSF_LONG | LSF_NUMERIC))
404 	{
405 		strcpy(cp, modeString(mode));
406 		cp += strlen(cp);
407 
408 		sprintf(cp, "%3ld ", (long) statBuf->st_nlink);
409 		cp += strlen(cp);
410 
411 		if (!userIdKnown || (statBuf->st_uid != userId))
412 		{
413 			if (flags & LSF_NUMERIC)
414 				pwd = 0;
415 			else
416 				pwd = getpwuid(statBuf->st_uid);
417 
418 			if (pwd)
419 				strcpy(userName, pwd->pw_name);
420 			else
421 				sprintf(userName, "%d", statBuf->st_uid);
422 
423 			userId = statBuf->st_uid;
424 			userIdKnown = TRUE;
425 		}
426 
427 		sprintf(cp, "%-8s ", userName);
428 		cp += strlen(cp);
429 
430 		if (!groupIdKnown || (statBuf->st_gid != groupId))
431 		{
432 			if (flags & LSF_NUMERIC)
433 				grp = 0;
434 			else
435 				grp = getgrgid(statBuf->st_gid);
436 
437 			if (grp)
438 				strcpy(groupName, grp->gr_name);
439 			else
440 				sprintf(groupName, "%d", statBuf->st_gid);
441 
442 			groupId = statBuf->st_gid;
443 			groupIdKnown = TRUE;
444 		}
445 
446 		sprintf(cp, "%-8s ", groupName);
447 		cp += strlen(cp);
448 
449 		if (S_ISBLK(mode) || S_ISCHR(mode))
450 		{
451 			sprintf(cp, "%3lu, %3lu ",
452 				((unsigned long) statBuf->st_rdev) >> 8,
453 				((unsigned long) statBuf->st_rdev) & 0xff);
454 		}
455 		else
456 			sprintf(cp, "%8lld ", statBuf->st_size);
457 
458 		cp += strlen(cp);
459 
460 		sprintf(cp, " %-12s ", timeString(statBuf->st_mtime));
461 	}
462 
463 	/*
464 	 * Set the special character if the file is a directory or
465 	 * symbolic link or executable and the display was requested.
466 	 */
467 	if (flags & LSF_FLAG)
468 	{
469 		if (S_ISDIR(mode))
470 			flagChar = '/';
471 #ifdef S_ISLNK
472 		else if (S_ISLNK(mode))
473 			flagChar = '@';
474 #endif
475 		else if ((mode & 0111) != 0)
476 			flagChar = '*';
477 	}
478 
479 	/*
480 	 * Print the status info followed by the file name.
481 	 */
482 	fputs(buf, stdout);
483 	fputs(name, stdout);
484 
485 	if (flagChar)
486 		fputc(flagChar, stdout);
487 
488 	/*
489 	 * Calculate the width used so far.
490 	 */
491 	usedWidth = strlen(buf) + strlen(name);
492 
493 	if (flagChar)
494 		usedWidth++;
495 
496 	/*
497 	 * Show where a symbolic link points.
498 	 */
499 #ifdef	S_ISLNK
500 	if ((flags & LSF_LONG) && S_ISLNK(mode))
501 	{
502 		len = readlink(name, buf, PATH_LEN - 1);
503 
504 		if (len >= 0)
505 		{
506 			buf[len] = '\0';
507 			printf(" -> %s", buf);
508 		}
509 
510 		usedWidth += strlen(buf) + 4;
511 	}
512 #endif
513 
514 	/*
515 	 * If no width was given then just end the line with a newline.
516 	 */
517 	if (width == 0)
518 	{
519 		fputc('\n', stdout);
520 
521 		return;
522 	}
523 
524 	/*
525 	 * There is a width given.
526 	 * Print as many spaces as it takes to reach that width.
527 	 */
528 	while (usedWidth++ < width)
529 		fputc(' ', stdout);
530 }
531 
532 
533 /*
534  * Save a file name to the end of the static list, reallocating if necessary.
535  * The file name is copied into allocated memory owned by the list.
536  * Returns TRUE on success.
537  */
538 static BOOL
addListName(const char * fileName)539 addListName(const char * fileName)
540 {
541 	char **	newList;
542 
543 	/*
544 	 * Reallocate the list if necessary.
545 	 */
546 	if (listUsed >= listSize)
547 	{
548 		newList = realloc(list,
549 			((sizeof(char **)) * (listSize + LISTSIZE)));
550 
551 		if (newList == NULL)
552 		{
553 			fprintf(stderr, "No memory for file name buffer\n");
554 
555 			return FALSE;
556 		}
557 
558 		list = newList;
559 		listSize += LISTSIZE;
560 	}
561 
562 	/*
563 	 * Copy the file name into the next entry.
564 	 */
565 	list[listUsed] = strdup(fileName);
566 
567 	if (list[listUsed] == NULL)
568 	{
569 		fprintf(stderr, "No memory for file name\n");
570 
571 		return FALSE;
572 	}
573 
574 	/*
575 	 * Increment the amount of space used.
576 	 */
577 	listUsed++;
578 
579 	return TRUE;
580 }
581 
582 
583 /*
584  * Free all of the names from the list of file names.
585  */
586 static void
clearListNames(void)587 clearListNames(void)
588 {
589 	while (listUsed > 0)
590 	{
591 		listUsed--;
592 
593 		free(list[listUsed]);
594 	}
595 }
596 
597 /* END CODE */
598