/*
* $Id: file_list.c,v 1.15 2004/03/25 20:46:02 shane Exp $
*/
#include <config.h>
#include <stdio.h>
#include <limits.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <glob.h>
#include <stdlib.h>
#include <stdarg.h>
#include <ctype.h>
#if TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# if HAVE_SYS_TIME_H
# include <sys/time.h>
# else
# include <time.h>
# endif
#endif
#include "file_list.h"
#include "daemon_assert.h"
/* AIX requires this to be the first thing in the file. */
#ifndef __GNUC__
# if HAVE_ALLOCA_H
# include <alloca.h>
# else
# ifdef _AIX
#pragma alloca
# else
# ifndef alloca /* predefined by HP cc +Olibcalls */
char *alloca ();
# endif
# endif
# endif
#endif
/* GLOB_PERIOD is defined in Linux but not Solaris */
#ifndef GLOB_PERIOD
#define GLOB_PERIOD 0
#endif /* GLOB_PERIOD */
/* GLOB_ABORTED is defined in Linux but not on FreeBSD */
#ifndef GLOB_ABORTED
#ifdef GLOB_ABEND
#define GLOB_ABORTED GLOB_ABEND
#endif
#endif
static int is_valid_dir(const char *dir);
static void fdprintf(int fd, const char *fmt, ...);
static const char *skip_ls_options(const char *filespec);
/* if no localtime_r() is available, provide one */
#ifndef HAVE_LOCALTIME_R
#include <pthread.h>
struct tm *localtime_r(const time_t *timep, struct tm *timeptr)
{
static pthread_mutex_t time_lock = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&time_lock);
*timeptr = *(localtime(timep));
pthread_mutex_unlock(&time_lock);
return timeptr;
}
#endif /* HAVE_LOCALTIME_R */
int file_nlst(int out, const char *cur_dir, const char *filespec)
{
int dir_len;
char pattern[PATH_MAX+1];
int glob_ret;
glob_t glob_buf;
int i;
char *file_name;
daemon_assert(out >= 0);
daemon_assert(is_valid_dir(cur_dir));
daemon_assert(filespec != NULL);
if (filespec[0] == '/') {
cur_dir = "";
dir_len = 0;
} else {
strcpy(pattern, cur_dir);
if ((cur_dir[0] != '/') || (cur_dir[1] != '\0')) {
strcat(pattern, "/");
}
dir_len = strlen(pattern);
}
/* make sure we have enough space */
if ((dir_len + 1 + strlen(filespec)) > PATH_MAX) {
fdprintf(out, "Error; Path name too long\r\n");
return 0;
}
strcat(pattern, filespec);
/* do a glob() */
memset(&glob_buf, 0, sizeof(glob_buf));
glob_ret = glob(pattern,
GLOB_ERR | GLOB_NOSORT | GLOB_PERIOD,
NULL,
&glob_buf);
if (glob_ret == GLOB_NOSPACE) {
fdprintf(out, "Error; Out of memory\r\n");
return 0;
#ifdef GLOB_NOMATCH /* not present in FreeBSD */
} else if (glob_ret == GLOB_NOMATCH) {
return 1;
#endif /* GLOB_NOMATCH */
#ifdef GLOB_ABORTED /* not present in older gcc */
} else if (glob_ret == GLOB_ABORTED) {
fdprintf(out, "Error; Read error\r\n");
return 0;
#endif /* GLOB_ABORTED */
} else if (glob_ret != 0) {
fdprintf(out, "Error; Unknown glob() error %d\r\n", glob_ret);
return 0;
}
/* print our results */
for (i=0; i<glob_buf.gl_pathc; i++) {
file_name = glob_buf.gl_pathv[i];
if (memcmp(file_name, pattern, dir_len) == 0) {
file_name += dir_len;
}
fdprintf(out, "%s\r\n", file_name);
}
/* free and return */
globfree(&glob_buf);
return 1;
}
typedef struct {
char *name;
char *full_path;
struct stat stat;
} file_info_t;
int file_list(int out, const char *cur_dir, const char *filespec)
{
int dir_len;
char pattern[PATH_MAX+1];
int glob_ret;
glob_t glob_buf;
int i;
file_info_t *file_info;
int num_files;
unsigned long total_blocks;
char *file_name;
mode_t mode;
time_t now;
struct tm tm_now;
double age;
char date_buf[13];
char link[PATH_MAX+1];
int link_len;
daemon_assert(out >= 0);
daemon_assert(is_valid_dir(cur_dir));
daemon_assert(filespec != NULL);
filespec = skip_ls_options(filespec);
if (filespec[0] == '/') {
cur_dir = "";
dir_len = 0;
} else {
strcpy(pattern, cur_dir);
if ((cur_dir[0] != '/') || (cur_dir[1] != '\0')) {
strcat(pattern, "/");
}
dir_len = strlen(pattern);
}
/* make sure we have enough space */
if ((dir_len + 1 + strlen(filespec)) > PATH_MAX) {
fdprintf(out, "Error; Path name too long\r\n");
return 0;
}
strcat(pattern, filespec);
/* do a glob() */
memset(&glob_buf, 0, sizeof(glob_buf));
glob_ret = glob(pattern, GLOB_ERR, NULL, &glob_buf);
#ifndef GLOB_NOMATCH /* FreeBSD */
if (glob_ret == GLOB_NOCHECK) {
#else
if (glob_ret == GLOB_NOMATCH) {
#endif
fdprintf(out, "total 0\r\n");
return 1;
} else if (glob_ret == GLOB_NOSPACE) {
fdprintf(out, "Error; Out of memory\r\n");
return 0;
#ifdef GLOB_ABORTED /* not present in older gcc */
} else if (glob_ret == GLOB_ABORTED) {
fdprintf(out, "Error; Read error\r\n");
return 0;
#endif /* GLOB_ABORTED */
} else if (glob_ret != 0) {
fdprintf(out, "Error; Unknown glob() error %d\r\n", glob_ret);
return 0;
}
/* make a buffer to store our information */
#ifdef HAVE_ALLOCA
file_info = (file_info_t *)alloca(sizeof(file_info_t) * glob_buf.gl_pathc);
#else
file_info = (file_info_t *)malloc(sizeof(file_info_t) * glob_buf.gl_pathc);
#endif
if (file_info == NULL) {
fdprintf(out, "Error; Out of memory\r\n");
globfree(&glob_buf);
return 0;
}
/* collect information */
num_files = 0;
total_blocks = 0;
for (i=0; i<glob_buf.gl_pathc; i++) {
file_name = glob_buf.gl_pathv[i];
if (memcmp(file_name, pattern, dir_len) == 0) {
file_name += dir_len;
}
if (lstat(glob_buf.gl_pathv[i], &file_info[num_files].stat) == 0) {
#ifdef AC_STRUCT_ST_BLKSIZE
total_blocks += file_info[num_files].stat.st_blocks;
#endif
file_info[num_files].name = file_name;
file_info[num_files].full_path = glob_buf.gl_pathv[i];
num_files++;
}
}
/* okay, we have information, now display it */
fdprintf(out, "total %lu\r\n", total_blocks);
time(&now);
for (i=0; i<num_files; i++) {
mode = file_info[i].stat.st_mode;
/* output file type */
switch (mode & S_IFMT) {
case S_IFSOCK: fdprintf(out, "s"); break;
case S_IFLNK: fdprintf(out, "l"); break;
case S_IFBLK: fdprintf(out, "b"); break;
case S_IFDIR: fdprintf(out, "d"); break;
case S_IFCHR: fdprintf(out, "c"); break;
case S_IFIFO: fdprintf(out, "p"); break;
default: fdprintf(out, "-");
}
/* output permissions */
fdprintf(out, (mode & S_IRUSR) ? "r" : "-");
fdprintf(out, (mode & S_IWUSR) ? "w" : "-");
if (mode & S_ISUID) {
fdprintf(out, (mode & S_IXUSR) ? "s" : "S");
} else {
fdprintf(out, (mode & S_IXUSR) ? "x" : "-");
}
fdprintf(out, (mode & S_IRGRP) ? "r" : "-");
fdprintf(out, (mode & S_IWGRP) ? "w" : "-");
if (mode & S_ISGID) {
fdprintf(out, (mode & S_IXGRP) ? "s" : "S");
} else {
fdprintf(out, (mode & S_IXGRP) ? "x" : "-");
}
fdprintf(out, (mode & S_IROTH) ? "r" : "-");
fdprintf(out, (mode & S_IWOTH) ? "w" : "-");
if (mode & S_ISVTX) {
fdprintf(out, (mode & S_IXOTH) ? "t" : "T");
} else {
fdprintf(out, (mode & S_IXOTH) ? "x" : "-");
}
/* output link & ownership information */
fdprintf(out, " %3d %-8d %-8d ",
file_info[i].stat.st_nlink,
file_info[i].stat.st_uid,
file_info[i].stat.st_gid);
/* output either i-node information or size */
#ifdef AC_STRUCT_ST_RDEV
if (((mode & S_IFMT) == S_IFBLK) || ((mode & S_IFMT) == S_IFCHR)) {
fdprintf(out, "%3d, %3d ",
(int)((file_info[i].stat.st_rdev >> 8) & 0xff),
(int)(file_info[i].stat.st_rdev & 0xff));
} else {
fdprintf(out, "%8lu ",
(unsigned long)file_info[i].stat.st_size);
}
#else
fdprintf(out, "%8lu ", (unsigned long)file_info[i].stat.st_size);
#endif
/* output date */
localtime_r(&file_info[i].stat.st_mtime, &tm_now);
age = difftime(now, file_info[i].stat.st_mtime);
if ((age > 60 * 60 * 24 * 30 * 6) || (age < -(60 * 60 * 24 * 30 * 6))) {
strftime(date_buf, sizeof(date_buf), "%b %e %Y", &tm_now);
} else {
strftime(date_buf, sizeof(date_buf), "%b %e %H:%M", &tm_now);
}
fdprintf(out, "%s ", date_buf);
/* output filename */
fdprintf(out, "%s", file_info[i].name);
/* display symbolic link information */
if ((mode & S_IFMT) == S_IFLNK) {
link_len = readlink(file_info[i].full_path, link, sizeof(link));
if (link_len > 0) {
fdprintf(out, " -> ");
link[link_len] = '\0';
fdprintf(out, "%s", link);
}
}
/* advance to next line */
fdprintf(out, "\r\n");
}
/* free memory & return */
#ifndef HAVE_ALLOCA
free(file_info);
#endif
globfree(&glob_buf);
return 1;
}
static int is_valid_dir(const char *dir)
{
/* directory can not be NULL (of course) */
if (dir == NULL) {
return 0;
}
/* directory must be absolute (i.e. start with '/') */
if (dir[0] != '/') {
return 0;
}
/* length cannot be greater than PATH_MAX */
if (strlen(dir) > PATH_MAX) {
return 0;
}
/* assume okay */
return 1;
}
static void fdprintf(int fd, const char *fmt, ...)
{
char buf[PATH_MAX+1];
int buflen;
va_list ap;
int amt_written;
int write_ret;
daemon_assert(fd >= 0);
daemon_assert(fmt != NULL);
va_start(ap, fmt);
buflen = vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
if (buflen <= 0) {
return;
}
if (buflen >= sizeof(buf)) {
buflen = sizeof(buf)-1;
}
amt_written = 0;
while (amt_written < buflen) {
write_ret = write(fd, buf+amt_written, buflen-amt_written);
if (write_ret <= 0) {
return;
}
amt_written += write_ret;
}
}
/*
hack workaround clients like Midnight Commander that send:
LIST -al /dirname
*/
const char *
skip_ls_options(const char *filespec)
{
daemon_assert(filespec != NULL);
for (;;) {
/* end when we've passed all options */
if (*filespec != '-') {
break;
}
filespec++;
/* if we find "--", eat it and any following whitespace then return */
if ((filespec[0] == '-') && (filespec[1] == ' ')) {
filespec += 2;
while (isspace(*filespec)) {
filespec++;
}
break;
}
/* otherwise, skip this option */
while ((*filespec != '\0') && !isspace(*filespec)) {
filespec++;
}
/* and skip any whitespace */
while (isspace(*filespec)) {
filespec++;
}
}
daemon_assert(filespec != NULL);
return filespec;
}
syntax highlighted by Code2HTML, v. 0.9.1