* src/oftpd.c (reopen_syslog_hack): Removed.
[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     if ( !strncmp (pattern, "/dev", 4)
110          && (!pattern[4] || pattern[4] == '/'))
111       return 1; /* ignore /dev */
112
113     /* do a glob() */
114     memset(&glob_buf, 0, sizeof(glob_buf));
115     glob_ret = glob(pattern, 
116                     GLOB_ERR | GLOB_NOSORT | GLOB_PERIOD, 
117                     NULL, 
118                     &glob_buf);
119     if (glob_ret == GLOB_NOSPACE) {
120         fdprintf(out, "Error; Out of memory\r\n");
121         return 0;
122 #ifdef GLOB_NOMATCH  /* not present in FreeBSD */
123     } else if (glob_ret == GLOB_NOMATCH) {
124         return 1;
125 #endif /* GLOB_NOMATCH */
126     } else if (glob_ret == GLOB_ABORTED) {
127         fdprintf(out, "Error; Read error\r\n");
128         return 0;
129     } else if (glob_ret != 0) {
130         fdprintf(out, "Error; Unknown glob() error %d\r\n", glob_ret);
131         return 0;
132     }
133
134     /* print our results */
135     for (i=0; i<glob_buf.gl_pathc; i++) {
136         file_name = glob_buf.gl_pathv[i];
137         if ( !strncmp (file_name, "/dev", 4)
138              && (!file_name[4] || file_name[4] == '/'))
139           continue; /* ignore /dev */
140         if (memcmp(file_name, pattern, dir_len) == 0) {
141           file_name += dir_len;
142         }
143         fdprintf(out, "%s\r\n", file_name);
144     }
145
146     /* free and return */
147     globfree(&glob_buf);
148     return 1;
149 }
150
151 typedef struct {
152     char *name;
153     char *full_path;
154     struct stat stat;
155 } file_info_t;
156
157 int file_list(int out, const char *cur_dir, const char *filespec)
158 {
159     int dir_len;
160     char pattern[PATH_MAX+1];
161     int glob_ret;
162     glob_t glob_buf;
163     int i;
164     file_info_t *file_info;
165     int num_files;
166     unsigned long total_blocks;
167     char *file_name;
168
169     mode_t mode;
170     time_t now;
171     struct tm tm_now;
172     double age;
173     char date_buf[13];
174     char link[PATH_MAX+1];
175     int link_len;
176     
177     daemon_assert(out >= 0);
178     daemon_assert(is_valid_dir(cur_dir));
179     daemon_assert(filespec != NULL);
180
181     if (filespec[0] == '/') {
182         cur_dir = "";
183         dir_len = 0;
184     } else {
185         dir_len = strlen(pattern);
186         if ((dir_len + 1) > PATH_MAX) {
187             fdprintf(out, "Error; Directory name too long\r\n");
188             return 0;
189         }
190         strcpy(pattern, cur_dir);
191         if ((cur_dir[0] != '/') || (cur_dir[1] != '\0')) {
192             strcat(pattern, "/");
193             dir_len++;
194         }
195     }
196
197     /* make sure we have enough space */
198     if ((dir_len + 1 + strlen(filespec)) > PATH_MAX) {
199         fdprintf(out, "Error; Path name too long\r\n");
200         return 0;
201     }
202     strcat(pattern, filespec);
203     if ( !strncmp (pattern, "/dev", 4)
204          && (!pattern[4] || pattern[4] == '/'))
205       return 1; /* ignore /dev */
206
207
208     /* do a glob() */
209     memset(&glob_buf, 0, sizeof(glob_buf));
210     glob_ret = glob(pattern, GLOB_ERR, NULL, &glob_buf);
211 #ifndef GLOB_NOMATCH /* FreeBSD */
212     if (glob_ret == GLOB_NOCHECK) {
213 #else
214     if (glob_ret == GLOB_NOMATCH) {
215 #endif
216         fdprintf(out, "total 0\r\n");
217         return 1;
218     } else if (glob_ret == GLOB_NOSPACE) {
219         fdprintf(out, "Error; Out of memory\r\n");
220         return 0;
221     } else if (glob_ret == GLOB_ABORTED) {
222         fdprintf(out, "Error; Read error\r\n");
223         return 0;
224     } else if (glob_ret != 0) {
225         fdprintf(out, "Error; Unknown glob() error %d\r\n", glob_ret);
226         return 0;
227     }
228
229     /* make a buffer to store our information */
230 #ifdef HAVE_ALLOCA
231     file_info = (file_info_t *)alloca(sizeof(file_info_t) * glob_buf.gl_pathc);
232 #else
233     file_info = (file_info_t *)malloc(sizeof(file_info_t) * glob_buf.gl_pathc);
234 #endif
235     if (file_info == NULL) {
236         fdprintf(out, "Error; Out of memory\r\n");
237         globfree(&glob_buf);
238         return 0;
239     }
240
241     /* collect information */
242     num_files = 0;
243     total_blocks = 0;
244     for (i=0; i<glob_buf.gl_pathc; i++) {
245         file_name = glob_buf.gl_pathv[i];
246         if ( !strncmp (file_name, "/dev", 4)
247              && (!file_name[4] || file_name[4] == '/'))
248           continue;
249         if (memcmp(file_name, pattern, dir_len) == 0) {
250             file_name += dir_len;
251         }
252         if (lstat(glob_buf.gl_pathv[i], &file_info[num_files].stat) == 0) {
253 #ifdef AC_STRUCT_ST_BLKSIZE
254             total_blocks += file_info[num_files].stat.st_blocks;
255 #endif
256             file_info[num_files].name = file_name;
257             file_info[num_files].full_path = glob_buf.gl_pathv[i];
258             num_files++;
259         }
260     }
261
262     /* okay, we have information, now display it */
263     fdprintf(out, "total %lu\r\n", total_blocks);
264     time(&now);
265     for (i=0; i<num_files; i++) {
266
267         mode = file_info[i].stat.st_mode;
268
269         /* output file type */
270         switch (mode & S_IFMT) {
271             case S_IFSOCK:  fdprintf(out, "s"); break;
272             case S_IFLNK:   fdprintf(out, "l"); break;
273             case S_IFBLK:   fdprintf(out, "b"); break;
274             case S_IFDIR:   fdprintf(out, "d"); break;
275             case S_IFCHR:   fdprintf(out, "c"); break;
276             case S_IFIFO:   fdprintf(out, "p"); break;
277             default:        fdprintf(out, "-"); 
278         }
279
280         /* output permissions */
281         fdprintf(out, (mode & S_IRUSR) ? "r" : "-");
282         fdprintf(out, (mode & S_IWUSR) ? "w" : "-");
283         if (mode & S_ISUID) { 
284             fdprintf(out, (mode & S_IXUSR) ? "s" : "S");
285         } else {
286             fdprintf(out, (mode & S_IXUSR) ? "x" : "-");
287         }
288         fdprintf(out, (mode & S_IRGRP) ? "r" : "-");
289         fdprintf(out, (mode & S_IWGRP) ? "w" : "-");
290         if (mode & S_ISGID) { 
291             fdprintf(out, (mode & S_IXGRP) ? "s" : "S");
292         } else {
293             fdprintf(out, (mode & S_IXGRP) ? "x" : "-");
294         }
295         fdprintf(out, (mode & S_IROTH) ? "r" : "-");
296         fdprintf(out, (mode & S_IWOTH) ? "w" : "-");
297         if (mode & S_ISVTX) {
298             fdprintf(out, (mode & S_IXOTH) ? "t" : "T");
299         } else {
300             fdprintf(out, (mode & S_IXOTH) ? "x" : "-");
301         }
302
303         /* output link & ownership information */
304         fdprintf(out, " %3d %-8d %-8d ", 
305             file_info[i].stat.st_nlink, 
306             file_info[i].stat.st_uid, 
307             file_info[i].stat.st_gid);
308
309         /* output either i-node information or size */
310 #ifdef AC_STRUCT_ST_RDEV
311         if (((mode & S_IFMT) == S_IFBLK) || ((mode & S_IFMT) == S_IFCHR)) {
312             fdprintf(out, "%3d, %3d ", 
313                 (int)((file_info[i].stat.st_rdev >> 8) & 0xff),
314                 (int)(file_info[i].stat.st_rdev & 0xff));
315         } else {
316             fdprintf(out, "%8lu ", 
317                 (unsigned long)file_info[i].stat.st_size);
318         }
319 #else
320         fdprintf(out, "%8lu ", (unsigned long)file_info[i].stat.st_size);
321 #endif
322             
323         /* output date */
324         localtime_r(&file_info[i].stat.st_mtime, &tm_now);
325         age = difftime(now, file_info[i].stat.st_mtime);
326         if ((age > 60 * 60 * 24 * 30 * 6) || (age < -(60 * 60 * 24 * 30 * 6))) {
327             strftime(date_buf, sizeof(date_buf), "%b %e  %Y", &tm_now);
328         } else {
329             strftime(date_buf, sizeof(date_buf), "%b %e %H:%M", &tm_now);
330         }
331         fdprintf(out, "%s ", date_buf);
332
333         /* output filename */
334         fdprintf(out, "%s", file_info[i].name);
335   
336         /* display symbolic link information */
337         if ((mode & S_IFMT) == S_IFLNK) {
338             link_len = readlink(file_info[i].full_path, link, sizeof(link));
339             if (link_len > 0) {
340                 fdprintf(out, " -> ");
341                 link[link_len] = '\0';
342                 fdprintf(out, "%s", link);
343             }
344         }
345
346         /* advance to next line */
347         fdprintf(out, "\r\n");
348     }
349
350     /* free memory & return */
351 #ifndef HAVE_ALLOCA
352     free(file_info);
353 #endif 
354     globfree(&glob_buf);
355     return 1;
356 }
357
358 static int is_valid_dir(const char *dir)
359 {
360     /* directory can not be NULL (of course) */
361     if (dir == NULL) {
362         return 0;
363     }
364  
365     /* directory must be absolute (i.e. start with '/') */
366     if (dir[0] != '/') {
367         return 0;
368     }
369
370     /* length cannot be greater than PATH_MAX */
371     if (strlen(dir) > PATH_MAX) {
372         return 0;
373     }
374
375     /* assume okay */
376     return 1;
377 }
378
379 static void fdprintf(int fd, const char *fmt, ...)
380 {
381     char buf[PATH_MAX+1];
382     int buflen;
383     va_list ap;
384     int amt_written;
385     int write_ret;
386
387     daemon_assert(fd >= 0);
388     daemon_assert(fmt != NULL);
389
390     va_start(ap, fmt);
391     buflen = vsnprintf(buf, sizeof(buf)-1, fmt, ap);
392     buf[PATH_MAX] = 0; /* Make extra sure that the string is terminated. */
393     va_end(ap);
394     if (buflen <= 0) {
395         return;
396     }
397     if (buflen >= sizeof(buf)) {
398         buflen = sizeof(buf)-1;
399     }
400
401     amt_written = 0;
402     while (amt_written < buflen) {
403         write_ret = write(fd, buf+amt_written, buflen-amt_written);
404         if (write_ret <= 0) {
405             return;
406         }
407         amt_written += write_ret;
408     }
409 }