a66ad17330ab80f6f033f93b2cb462d628510f2c
[oftpd.git] / src / file_list.c
1 /*
2  * $Id$
3
4  * [wk: code reviewed 2003-08-09]
5  */
6
7 #include <config.h>
8 #include <stdio.h>
9 #include <limits.h>
10 #include <string.h>
11 #include <sys/stat.h>
12 #include <unistd.h>
13 #include <glob.h>
14 #include <stdlib.h>
15 #include <stdarg.h>
16
17 #if TIME_WITH_SYS_TIME
18 # include <sys/time.h>
19 # include <time.h>
20 #else
21 # if HAVE_SYS_TIME_H
22 #  include <sys/time.h>
23 # else
24 #  include <time.h>
25 # endif
26 #endif
27
28 #include "file_list.h"
29 #include "daemon_assert.h"
30
31 /* AIX requires this to be the first thing in the file.  */
32 #ifndef __GNUC__
33 # if HAVE_ALLOCA_H
34 #  include <alloca.h>
35 # else
36 #  ifdef _AIX
37 #pragma alloca
38 #  else
39 #   ifndef alloca /* predefined by HP cc +Olibcalls */
40 char *alloca ();
41 #   endif
42 #  endif
43 # endif
44 #endif
45
46 /* GLOB_PERIOD is defined in Linux but not Solaris */
47 #ifndef GLOB_PERIOD
48 #define GLOB_PERIOD 0
49 #endif /* GLOB_PERIOD */
50
51 /* GLOB_ABORTED is defined in Linux but not on FreeBSD */
52 #ifndef GLOB_ABORTED
53 #define GLOB_ABORTED GLOB_ABEND
54 #endif
55
56 static int is_valid_dir(const char *dir);
57 static void fdprintf(int fd, const char *fmt, ...);
58
59 /* if no localtime_r() is available, provide one */
60 #ifndef HAVE_LOCALTIME_R
61 #include <pthread.h>
62
63 struct tm *localtime_r(const time_t *timep, struct tm *timeptr) 
64 {
65     static pthread_mutex_t time_lock = PTHREAD_MUTEX_INITIALIZER;
66
67     pthread_mutex_lock(&time_lock);
68     *timeptr = *(localtime(timep));
69     pthread_mutex_unlock(&time_lock);
70     return timeptr;
71 }
72 #endif /* HAVE_LOCALTIME_R */
73
74 int file_nlst(int out, const char *cur_dir, const char *filespec)
75 {
76     int dir_len;
77     char pattern[PATH_MAX+1];
78     int glob_ret;
79     glob_t glob_buf;
80     int i;
81     char *file_name;
82
83     daemon_assert(out >= 0);
84     daemon_assert(is_valid_dir(cur_dir));
85     daemon_assert(filespec != NULL);
86
87     if (filespec[0] == '/') {
88         cur_dir = "";
89         dir_len = 0;
90     } else {
91         strcpy(pattern, cur_dir);
92         dir_len = strlen(pattern);
93         if ((dir_len + 1) > PATH_MAX) {
94             fdprintf(out, "Error; Directory name too long\r\n");
95             return 0;
96         }
97         if ((cur_dir[0] != '/') || (cur_dir[1] != '\0')) {
98             strcat(pattern, "/");
99             dir_len++;
100         }
101     }
102
103     /* make sure we have enough space */
104     if ((dir_len + 1 + strlen(filespec)) > PATH_MAX) {
105         fdprintf(out, "Error; Path name too long\r\n");
106         return 0;
107     }
108     strcat(pattern, filespec);
109
110     /* do a glob() */
111     memset(&glob_buf, 0, sizeof(glob_buf));
112     glob_ret = glob(pattern, 
113                     GLOB_ERR | GLOB_NOSORT | GLOB_PERIOD, 
114                     NULL, 
115                     &glob_buf);
116     if (glob_ret == GLOB_NOSPACE) {
117         fdprintf(out, "Error; Out of memory\r\n");
118         return 0;
119 #ifdef GLOB_NOMATCH  /* not present in FreeBSD */
120     } else if (glob_ret == GLOB_NOMATCH) {
121         return 1;
122 #endif /* GLOB_NOMATCH */
123     } else if (glob_ret == GLOB_ABORTED) {
124         fdprintf(out, "Error; Read error\r\n");
125         return 0;
126     } else if (glob_ret != 0) {
127         fdprintf(out, "Error; Unknown glob() error %d\r\n", glob_ret);
128         return 0;
129     }
130
131     /* print our results */
132     for (i=0; i<glob_buf.gl_pathc; i++) {
133         file_name = glob_buf.gl_pathv[i];
134         if (memcmp(file_name, pattern, dir_len) == 0) {
135             file_name += dir_len;
136         }
137         fdprintf(out, "%s\r\n", file_name);
138     }
139
140     /* free and return */
141     globfree(&glob_buf);
142     return 1;
143 }
144
145 typedef struct {
146     char *name;
147     char *full_path;
148     struct stat stat;
149 } file_info_t;
150
151 int file_list(int out, const char *cur_dir, const char *filespec)
152 {
153     int dir_len;
154     char pattern[PATH_MAX+1];
155     int glob_ret;
156     glob_t glob_buf;
157     int i;
158     file_info_t *file_info;
159     int num_files;
160     unsigned long total_blocks;
161     char *file_name;
162
163     mode_t mode;
164     time_t now;
165     struct tm tm_now;
166     double age;
167     char date_buf[13];
168     char link[PATH_MAX+1];
169     int link_len;
170     
171     daemon_assert(out >= 0);
172     daemon_assert(is_valid_dir(cur_dir));
173     daemon_assert(filespec != NULL);
174
175     if (filespec[0] == '/') {
176         cur_dir = "";
177         dir_len = 0;
178     } else {
179         dir_len = strlen(pattern);
180         if ((dir_len + 1) > PATH_MAX) {
181             fdprintf(out, "Error; Directory name too long\r\n");
182             return 0;
183         }
184         strcpy(pattern, cur_dir);
185         if ((cur_dir[0] != '/') || (cur_dir[1] != '\0')) {
186             strcat(pattern, "/");
187             dir_len++;
188         }
189     }
190
191     /* make sure we have enough space */
192     if ((dir_len + 1 + strlen(filespec)) > PATH_MAX) {
193         fdprintf(out, "Error; Path name too long\r\n");
194         return 0;
195     }
196     strcat(pattern, filespec);
197
198     /* do a glob() */
199     memset(&glob_buf, 0, sizeof(glob_buf));
200     glob_ret = glob(pattern, GLOB_ERR, NULL, &glob_buf);
201 #ifndef GLOB_NOMATCH /* FreeBSD */
202     if (glob_ret == GLOB_NOCHECK) {
203 #else
204     if (glob_ret == GLOB_NOMATCH) {
205 #endif
206         fdprintf(out, "total 0\r\n");
207         return 1;
208     } else if (glob_ret == GLOB_NOSPACE) {
209         fdprintf(out, "Error; Out of memory\r\n");
210         return 0;
211     } else if (glob_ret == GLOB_ABORTED) {
212         fdprintf(out, "Error; Read error\r\n");
213         return 0;
214     } else if (glob_ret != 0) {
215         fdprintf(out, "Error; Unknown glob() error %d\r\n", glob_ret);
216         return 0;
217     }
218
219     /* make a buffer to store our information */
220 #ifdef HAVE_ALLOCA
221     file_info = (file_info_t *)alloca(sizeof(file_info_t) * glob_buf.gl_pathc);
222 #else
223     file_info = (file_info_t *)malloc(sizeof(file_info_t) * glob_buf.gl_pathc);
224 #endif
225     if (file_info == NULL) {
226         fdprintf(out, "Error; Out of memory\r\n");
227         globfree(&glob_buf);
228         return 0;
229     }
230
231     /* collect information */
232     num_files = 0;
233     total_blocks = 0;
234     for (i=0; i<glob_buf.gl_pathc; i++) {
235         file_name = glob_buf.gl_pathv[i];
236         if (memcmp(file_name, pattern, dir_len) == 0) {
237             file_name += dir_len;
238         }
239         if (lstat(glob_buf.gl_pathv[i], &file_info[num_files].stat) == 0) {
240 #ifdef AC_STRUCT_ST_BLKSIZE
241             total_blocks += file_info[num_files].stat.st_blocks;
242 #endif
243             file_info[num_files].name = file_name;
244             file_info[num_files].full_path = glob_buf.gl_pathv[i];
245             num_files++;
246         }
247     }
248
249     /* okay, we have information, now display it */
250     fdprintf(out, "total %lu\r\n", total_blocks);
251     time(&now);
252     for (i=0; i<num_files; i++) {
253
254         mode = file_info[i].stat.st_mode;
255
256         /* output file type */
257         switch (mode & S_IFMT) {
258             case S_IFSOCK:  fdprintf(out, "s"); break;
259             case S_IFLNK:   fdprintf(out, "l"); break;
260             case S_IFBLK:   fdprintf(out, "b"); break;
261             case S_IFDIR:   fdprintf(out, "d"); break;
262             case S_IFCHR:   fdprintf(out, "c"); break;
263             case S_IFIFO:   fdprintf(out, "p"); break;
264             default:        fdprintf(out, "-"); 
265         }
266
267         /* output permissions */
268         fdprintf(out, (mode & S_IRUSR) ? "r" : "-");
269         fdprintf(out, (mode & S_IWUSR) ? "w" : "-");
270         if (mode & S_ISUID) { 
271             fdprintf(out, (mode & S_IXUSR) ? "s" : "S");
272         } else {
273             fdprintf(out, (mode & S_IXUSR) ? "x" : "-");
274         }
275         fdprintf(out, (mode & S_IRGRP) ? "r" : "-");
276         fdprintf(out, (mode & S_IWGRP) ? "w" : "-");
277         if (mode & S_ISGID) { 
278             fdprintf(out, (mode & S_IXGRP) ? "s" : "S");
279         } else {
280             fdprintf(out, (mode & S_IXGRP) ? "x" : "-");
281         }
282         fdprintf(out, (mode & S_IROTH) ? "r" : "-");
283         fdprintf(out, (mode & S_IWOTH) ? "w" : "-");
284         if (mode & S_ISVTX) {
285             fdprintf(out, (mode & S_IXOTH) ? "t" : "T");
286         } else {
287             fdprintf(out, (mode & S_IXOTH) ? "x" : "-");
288         }
289
290         /* output link & ownership information */
291         fdprintf(out, " %3d %-8d %-8d ", 
292             file_info[i].stat.st_nlink, 
293             file_info[i].stat.st_uid, 
294             file_info[i].stat.st_gid);
295
296         /* output either i-node information or size */
297 #ifdef AC_STRUCT_ST_RDEV
298         if (((mode & S_IFMT) == S_IFBLK) || ((mode & S_IFMT) == S_IFCHR)) {
299             fdprintf(out, "%3d, %3d ", 
300                 (int)((file_info[i].stat.st_rdev >> 8) & 0xff),
301                 (int)(file_info[i].stat.st_rdev & 0xff));
302         } else {
303             fdprintf(out, "%8lu ", 
304                 (unsigned long)file_info[i].stat.st_size);
305         }
306 #else
307         fdprintf(out, "%8lu ", (unsigned long)file_info[i].stat.st_size);
308 #endif
309             
310         /* output date */
311         localtime_r(&file_info[i].stat.st_mtime, &tm_now);
312         age = difftime(now, file_info[i].stat.st_mtime);
313         if ((age > 60 * 60 * 24 * 30 * 6) || (age < -(60 * 60 * 24 * 30 * 6))) {
314             strftime(date_buf, sizeof(date_buf), "%b %e  %Y", &tm_now);
315         } else {
316             strftime(date_buf, sizeof(date_buf), "%b %e %H:%M", &tm_now);
317         }
318         fdprintf(out, "%s ", date_buf);
319
320         /* output filename */
321         fdprintf(out, "%s", file_info[i].name);
322   
323         /* display symbolic link information */
324         if ((mode & S_IFMT) == S_IFLNK) {
325             link_len = readlink(file_info[i].full_path, link, sizeof(link));
326             if (link_len > 0) {
327                 fdprintf(out, " -> ");
328                 link[link_len] = '\0';
329                 fdprintf(out, "%s", link);
330             }
331         }
332
333         /* advance to next line */
334         fdprintf(out, "\r\n");
335     }
336
337     /* free memory & return */
338 #ifndef HAVE_ALLOCA
339     free(file_info);
340 #endif 
341     globfree(&glob_buf);
342     return 1;
343 }
344
345 static int is_valid_dir(const char *dir)
346 {
347     /* directory can not be NULL (of course) */
348     if (dir == NULL) {
349         return 0;
350     }
351  
352     /* directory must be absolute (i.e. start with '/') */
353     if (dir[0] != '/') {
354         return 0;
355     }
356
357     /* length cannot be greater than PATH_MAX */
358     if (strlen(dir) > PATH_MAX) {
359         return 0;
360     }
361
362     /* assume okay */
363     return 1;
364 }
365
366 static void fdprintf(int fd, const char *fmt, ...)
367 {
368     char buf[PATH_MAX+1];
369     int buflen;
370     va_list ap;
371     int amt_written;
372     int write_ret;
373
374     daemon_assert(fd >= 0);
375     daemon_assert(fmt != NULL);
376
377     va_start(ap, fmt);
378     buflen = vsnprintf(buf, sizeof(buf)-1, fmt, ap);
379     buf[PATH_MAX] = 0; /* Make extra sure that the string is terminated. */
380     va_end(ap);
381     if (buflen <= 0) {
382         return;
383     }
384     if (buflen >= sizeof(buf)) {
385         buflen = sizeof(buf)-1;
386     }
387
388     amt_written = 0;
389     while (amt_written < buflen) {
390         write_ret = write(fd, buf+amt_written, buflen-amt_written);
391         if (write_ret <= 0) {
392             return;
393         }
394         amt_written += write_ret;
395     }
396 }