1/****************************************************************************
2 *
3 *	Program:	dir.c
4 *	Author:		Marc van Kempen
5 *	desc:		Directory routines, sorting and reading
6 *
7 * Copyright (c) 1995, Marc van Kempen
8 *
9 * All rights reserved.
10 *
11 * This software may be used, modified, copied, distributed, and
12 * sold, in both source and binary form provided that the above
13 * copyright and these terms are retained, verbatim, as the first
14 * lines of this file.  Under no circumstances is the author
15 * responsible for the proper functioning of this software, nor does
16 * the author assume any responsibility for damages incurred with
17 * its use.
18 *
19 ****************************************************************************/
20
21#include <sys/types.h>
22#include <sys/stat.h>
23
24#include <unistd.h>		/* XXX for _POSIX_VERSION ifdefs */
25
26#if !defined sgi && !defined _POSIX_VERSION
27#include <sys/dir.h>
28#endif
29#if defined __sun__
30#include <sys/dirent.h>
31#endif
32#if defined sgi || defined _POSIX_VERSION
33#include <dirent.h>
34#endif
35
36#include <stdlib.h>
37#include <stdio.h>
38#include <string.h>
39#include <fnmatch.h>
40#include <sys/param.h>
41#include "dir.h"
42
43/****************************************************************************
44 *
45 *	local prototypes
46 *
47 ****************************************************************************/
48
49void toggle_dotfiles(void);
50int  show_dotfiles(void);
51int  dir_alphasort(const void *d1, const void *d2);
52int  dir_sizesort(const void *d1, const void *d2);
53int  dir_datesort(const void *d1, const void *d2);
54int  dir_extsort(const void *d1, const void *d2);
55
56/****************************************************************************
57 *
58 *	global variables
59 *
60 ****************************************************************************/
61
62
63/* This is user-selectable, I've set them fixed for now however */
64
65void		*_sort_func = dir_alphasort;
66static int	_showdotfiles = TRUE;
67
68/****************************************************************************
69 *
70 *	Functions
71 *
72 ****************************************************************************/
73
74int
75dir_select_nd(
76#if defined __linux__
77  const struct dirent *d
78#else
79  struct dirent *d
80#endif
81)
82/*
83 *	desc:	allways include a directory entry <d>, except
84 *		for the current directory and other dot-files
85 *		keep '..' however.
86 *	pre:	<d> points to a dirent
87 *	post:	returns TRUE if d->d_name != "." else FALSE
88 */
89{
90    if (strcmp(d->d_name, ".")==0 ||
91	  (d->d_name[0] == '.' && strlen(d->d_name) > 1 && d->d_name[1] != '.')) {
92	return(FALSE);
93    } else {
94	return(TRUE);
95    }
96}/* dir_select_nd() */
97
98
99int
100dir_select(
101#ifdef __linux__
102  const struct dirent *d
103#else
104  struct dirent *d
105#endif
106)
107/*
108 *	desc:	allways include a directory entry <d>, except
109 *		for the current directory
110 *	pre:	<d> points to a dirent
111 *	post:	returns TRUE if d->d_name != "." else FALSE
112 */
113{
114	if (strcmp(d->d_name, ".")==0) {	/* don't include the current directory */
115		return(FALSE);
116	} else {
117	    return(TRUE);
118	}
119} /* dir_select() */
120
121int
122dir_select_root_nd(
123#ifdef __linux__
124  const struct dirent *d
125#else
126  struct dirent *d
127#endif
128)
129/*
130 *	desc:	allways include a directory entry <d>, except
131 *		for the current directory and the parent directory.
132 *		Also skip any other dot-files.
133 *	pre:	<d> points to a dirent
134 *	post:	returns TRUE if d->d_name[0] != "." else FALSE
135 */
136{
137	if (d->d_name[0] == '.') {	/* don't include the current directory */
138		return(FALSE);		/* nor the parent directory */
139	} else {
140	    return(TRUE);
141	}
142} /* dir_select_root_nd() */
143
144
145int
146dir_select_root(
147#ifdef __linux__
148  const struct dirent *d
149#else
150  struct dirent *d
151#endif
152)
153/*
154 *	desc:	allways include a directory entry <d>, except
155 *		for the current directory and the parent directory
156 *	pre:	<d> points to a dirent
157 *	post:	returns TRUE if d->d_name[0] != "." else FALSE
158 */
159{
160	if (strcmp(d->d_name, ".") == 0 || strcmp(d->d_name, "..") == 0) {
161		return(FALSE);
162	} else {
163	    return(TRUE);
164	}
165}/* dir_select_root() */
166
167
168#ifdef NO_ALPHA_SORT
169int
170alphasort(const void *d1, const void *d2)
171/*
172 *	desc:	a replacement for what should be in the library
173 */
174{
175    return(strcmp(((struct dirent *) d1)->d_name,
176		  ((struct dirent *) d2)->d_name));
177} /* alphasort() */
178#endif
179
180int
181dir_alphasort(const void *d1, const void *d2)
182/*
183 *	desc:	compare d1 and d2, but put directories always first
184 *		put '..' always on top
185 *
186 */
187{
188    DirList	*f1 = ((DirList *) d1),
189		*f2 = ((DirList *) d2);
190    struct stat	*s1 = &(f1->filestatus);
191    struct stat	*s2 = &(f2->filestatus);
192
193    /* check for '..' */
194    if (strcmp(((DirList *) d1)->filename, "..") == 0) {
195	return(-1);
196    }
197    if (strcmp(((DirList *) d2)->filename, "..") == 0) {
198	return(1);
199    }
200
201    /* put directories first */
202    if ((s1->st_mode & S_IFDIR) && (s2->st_mode & S_IFDIR)) {
203	return(strcmp(f1->filename, f2->filename));
204    };
205    if (s1->st_mode & S_IFDIR) {
206	return(-1);
207    }
208    if (s2->st_mode & S_IFDIR) {
209	return(1);
210    }
211    return(strcmp(f1->filename, f2->filename));
212
213} /* dir_alphasort() */
214
215
216int
217dir_sizesort(const void *d1, const void *d2)
218/*
219 *	desc:	compare d1 and d2, but put directories always first
220 *
221 */
222{
223    DirList	*f1 = ((DirList *) d1),
224		*f2 = ((DirList *) d2);
225    struct stat	*s1 = &(f1->filestatus);
226    struct stat	*s2 = &(f2->filestatus);
227
228    /* check for '..' */
229    if (strcmp(((DirList *) d1)->filename, "..") == 0) {
230	return(-1);
231    }
232    if (strcmp(((DirList *) d2)->filename, "..") == 0) {
233	return(1);
234    }
235
236    /* put directories first */
237    if ((s1->st_mode & S_IFDIR) && (s2->st_mode & S_IFDIR)) {
238	return(s1->st_size < s2->st_size ?
239	       -1
240	       :
241	       s1->st_size >= s2->st_size);
242    };
243    if (s1->st_mode & S_IFDIR) {
244	return(-1);
245    }
246    if (s2->st_mode & S_IFDIR) {
247	return(1);
248    }
249    return(s1->st_size < s2->st_size ?
250	   -1
251	   :
252	   s1->st_size >= s2->st_size);
253
254} /* dir_sizesort() */
255
256int
257dir_datesort(const void *d1, const void *d2)
258/*
259 *	desc:	compare d1 and d2 on date, but put directories always first
260 */
261{
262    DirList	*f1 = ((DirList *) d1),
263		*f2 = ((DirList *) d2);
264    struct stat	*s1 = &(f1->filestatus);
265    struct stat	*s2 = &(f2->filestatus);
266
267
268    /* check for '..' */
269    if (strcmp(((DirList *) d1)->filename, "..") == 0) {
270	return(-1);
271    }
272    if (strcmp(((DirList *) d2)->filename, "..") == 0) {
273	return(1);
274    }
275
276    /* put directories first */
277    if ((s1->st_mode & S_IFDIR) && (s2->st_mode & S_IFDIR)) {
278	return(s1->st_mtime < s2->st_mtime ?
279	       -1
280	       :
281	       s1->st_mtime >= s2->st_mtime);
282    };
283    if (s1->st_mode & S_IFDIR) {
284	return(-1);
285    }
286    if (s2->st_mode & S_IFDIR) {
287	return(1);
288    }
289    return(s1->st_mtime < s2->st_mtime ?
290	   -1
291	   :
292	   s1->st_mtime >= s2->st_mtime);
293
294} /* dir_datesort() */
295
296
297int
298null_strcmp(char *s1, char *s2)
299/*
300 *	desc:	compare strings allowing NULL pointers
301 */
302{
303	if ((s1 == NULL) && (s2 == NULL)) {
304		return(0);
305	}
306	if (s1 == NULL) {
307		return(-1);
308	}
309	if (s2 == NULL) {
310		return(1);
311	}
312	return(strcmp(s1, s2));
313} /* null_strcmp() */
314
315
316int
317dir_extsort(const void *d1, const void *d2)
318/*
319 *	desc:	compare d1 and d2 on extension, but put directories always first
320 *		extension = "the characters after the last dot in the filename"
321 *	pre:	d1 and d2 are pointers to  DirList type records
322 *	post:	see code
323 */
324{
325    DirList	*f1 = ((DirList *) d1),
326		*f2 = ((DirList *) d2);
327    struct stat	*s1 = &(f1->filestatus);
328    struct stat	*s2 = &(f2->filestatus);
329    char 	*ext1, *ext2;
330    int		extf, ret;
331
332
333    /* check for '..' */
334    if (strcmp(((DirList *) d1)->filename, "..") == 0) {
335	return(-1);
336    }
337    if (strcmp(((DirList *) d2)->filename, "..") == 0) {
338	return(1);
339    }
340
341
342    /* find the first extension */
343
344    ext1 = f1->filename + strlen(f1->filename);
345    extf = FALSE;
346    while (!extf && (ext1 > f1->filename)) {
347	extf = (*--ext1 == '.');
348    }
349    if (!extf) {
350	ext1 = NULL;
351    } else {
352	ext1++;
353    }
354    /* ext1 == NULL if there's no "extension" else ext1 points */
355    /* to the first character of the extension string */
356
357    /* find the second extension */
358
359    ext2 = f2->filename + strlen(f2->filename);
360    extf = FALSE;
361    while (!extf && (ext2 > f2->filename)) {
362	extf = (*--ext2 == '.');
363    }
364    if (!extf) {
365	ext2 = NULL;
366    } else {
367	ext2++;
368    }
369    /* idem as for ext1 */
370
371    if ((s1->st_mode & S_IFDIR) && (s2->st_mode & S_IFDIR)) {
372	ret = null_strcmp(ext1, ext2);
373	if (ret == 0) {
374	    return(strcmp(f1->filename, f2->filename));
375	} else {
376	    return(ret);
377	}
378    };
379    if (s1->st_mode & S_IFDIR) {
380	return(-1);
381    }
382    if (s2->st_mode & S_IFDIR) {
383	return(1);
384    }
385    ret = null_strcmp(ext1, ext2);
386    if (ret == 0) {
387	return(strcmp(f1->filename, f2->filename));
388    } else {
389	return(ret);
390    }
391
392} /* dir_extsort() */
393
394
395void
396get_dir(char *dirname, char *fmask, DirList **dir, int *n)
397/*
398 *	desc:	get the files in the current directory
399 *	pre:	<dir> == NULL
400 *	post:	<dir> contains <n> dir-entries
401 */
402{
403	char		cwd[MAXPATHLEN];
404	char		buf[256];
405	struct dirent	**dire;
406	struct stat	status;
407	int		i, j, nb;
408	long		d;
409
410
411	getcwd(cwd, MAXPATHLEN);
412	if (strcmp(cwd, "/") == 0) {	/* we are in the root directory */
413	    if (show_dotfiles()) {
414		*n = scandir(dirname, &dire, dir_select_root, alphasort);
415	    } else {
416		*n = scandir(dirname, &dire, dir_select_root_nd, alphasort);
417	    }
418	} else {
419	    if (show_dotfiles()) {
420		*n = scandir(dirname, &dire, dir_select, alphasort);
421	    } else {
422		*n = scandir(dirname, &dire, dir_select_nd, alphasort);
423	    }
424	}
425
426	/* There is the possibility that we have entered a directory	*/
427	/* which we are not allowed to read, scandir thus returning  	*/
428	/* -1 for *n.	 						*/
429	/* Actually I should also check for lack of memory, but I'll 	*/
430	/* let my application happily crash if this is the case		*/
431	/* Solution:							*/
432	/*	manually insert the parent directory as the only	*/
433	/*	directory entry, and return.				*/
434
435	if (*n == -1) {
436	    *n = 1;
437	    *dir = (DirList *) malloc(sizeof(DirList));
438	    strcpy((*dir)[0].filename, "..");
439	    lstat("..", &status);
440	    (*dir)[0].filestatus = status;
441	    (*dir)[0].link = FALSE;
442	    return;
443	}
444
445	*dir = (DirList *) malloc( *n * sizeof(DirList) );
446	d = 0;
447	i = 0;
448	j = 0;
449	while (j<*n) {
450	    lstat(dire[j]->d_name, &status);
451	    /* check if this file is to be included */
452	    /* always include directories, the rest is subject to fmask */
453	    if (S_ISDIR(status.st_mode)
454		|| fnmatch(fmask, dire[j]->d_name, FNM_NOESCAPE) != FNM_NOMATCH) {
455		strcpy((*dir)[i].filename, dire[j]->d_name);
456		(*dir)[i].filestatus = status;
457		if ((S_IFMT & status.st_mode) == S_IFLNK) {  /* handle links */
458		    (*dir)[i].link = TRUE;
459		    stat(dire[j]->d_name, &status);
460		    nb = readlink(dire[j]->d_name, buf, sizeof(buf) - 1);
461		    if (nb == -1) {
462			printf("get_dir(): Error reading link: %s\n", dire[j]->d_name);
463			exit(-1);
464		    } else {
465			(*dir)[i].linkname = malloc(sizeof(char) * nb + 1);
466			strncpy((*dir)[i].linkname, buf, nb);
467			(*dir)[i].linkname[nb] = 0;
468		    }
469		    (*dir)[i].filestatus = status;
470		} else {
471		    (*dir)[i].link = FALSE;
472		    (*dir)[i].linkname = NULL;
473		}
474		i++;
475	    } else {
476		/* skip this entry */
477	    }
478	    j++;
479	}
480	*n = i;
481
482	/* sort the directory with the directory names on top */
483	qsort((*dir), *n, sizeof(DirList), _sort_func);
484
485	/* Free the allocated memory */
486	for (i=0; i<*n; i++) {
487	    free(dire[i]);
488	}
489	free(dire);
490
491	return;
492}/* get_dir() */
493
494
495void
496FreeDir(DirList *d, int n)
497/*
498 * 	desc:	free the dirlist d
499 *	pre:	d != NULL
500 *	post:	memory allocated to d has been released
501 */
502{
503    int i;
504
505    if (d) {
506	for (i=0; i<n; i++) {
507	    if (d[i].linkname) {
508		free(d[i].linkname);
509	    }
510	}
511	free(d);
512    } else {
513	printf("dir.c:FreeDir(): d == NULL\n");
514	exit(-1);
515    }
516
517    return;
518} /* FreeDir() */
519
520void
521toggle_dotfiles(void)
522/*
523 *	desc: toggle visibility of dot-files
524 */
525{
526    _showdotfiles = !_showdotfiles;
527
528    return;
529} /* toggle_dotfiles() */
530
531int
532show_dotfiles(void)
533/*
534 *	desc: return the value of _showdotfiles
535 */
536{
537    return(_showdotfiles);
538} /* show_dotfiles() */
539
540void
541set_dotfiles(int b)
542/*
543 *	desc: set the value of _showdotfiles
544 */
545{
546    _showdotfiles = b;
547
548    return;
549} /* set_dotfiles() */
550