ftpd: reuse ls applet for LIST/NLST/STAT generation

function                                             old     new   delta
popen_ls                                               -     211    +211
ftpd_main                                           1760    1826     +66
handle_dir_common                                    199     228     +29
get_remote_transfer_fd                                89     104     +15
replace_char                                          30      34      +4
handle_upload_common                                 263     265      +2
bind_for_passive_mode                                129     121      -8
cmdio_write                                           84      62     -22
escape_text                                          166     136     -30
init_data_sock_params                                 81       -     -81
ftpdataio_dispose_transfer_fd                         87       -     -87
write_dirstats                                       149       -    -149
write_filestats                                      603       -    -603
------------------------------------------------------------------------------
(add/remove: 1/4 grow/shrink: 11/5 up/down: 384/-986)        Total: -602 bytes
   text    data     bss     dec     hex filename
 808804     476    7864  817144   c77f8 busybox_old
 808156     476    7864  816496   c7570 busybox_unstripped
This commit is contained in:
Denis Vlasenko 2009-03-09 15:46:07 +00:00
parent 9b2fbda538
commit c41cba5a57
4 changed files with 156 additions and 246 deletions

View File

@ -44,6 +44,7 @@ lib-$(CONFIG_LENGTH) += length.o
lib-$(CONFIG_LN) += ln.o lib-$(CONFIG_LN) += ln.o
lib-$(CONFIG_LOGNAME) += logname.o lib-$(CONFIG_LOGNAME) += logname.o
lib-$(CONFIG_LS) += ls.o lib-$(CONFIG_LS) += ls.o
lib-$(CONFIG_FTPD) += ls.o
lib-$(CONFIG_MD5SUM) += md5_sha1_sum.o lib-$(CONFIG_MD5SUM) += md5_sha1_sum.o
lib-$(CONFIG_MKDIR) += mkdir.o lib-$(CONFIG_MKDIR) += mkdir.o
lib-$(CONFIG_MKFIFO) += mkfifo.o lib-$(CONFIG_MKFIFO) += mkfifo.o

View File

@ -864,7 +864,6 @@ static const char ls_color_opt[] ALIGN1 =
#endif #endif
int ls_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int ls_main(int argc UNUSED_PARAM, char **argv) int ls_main(int argc UNUSED_PARAM, char **argv)
{ {
struct dnode **dnd; struct dnode **dnd;

View File

@ -917,6 +917,8 @@ int test_main(int argc, char **argv) USE_TEST(MAIN_EXTERNALLY_VISIBLE);
int kill_main(int argc, char **argv) USE_KILL(MAIN_EXTERNALLY_VISIBLE); int kill_main(int argc, char **argv) USE_KILL(MAIN_EXTERNALLY_VISIBLE);
/* Similar, but used by chgrp, not shell */ /* Similar, but used by chgrp, not shell */
int chown_main(int argc, char **argv) USE_CHOWN(MAIN_EXTERNALLY_VISIBLE); int chown_main(int argc, char **argv) USE_CHOWN(MAIN_EXTERNALLY_VISIBLE);
/* Used by ftpd */
int ls_main(int argc, char **argv) USE_LS(MAIN_EXTERNALLY_VISIBLE);
/* Don't need USE_xxx() guard for these */ /* Don't need USE_xxx() guard for these */
int gunzip_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; int gunzip_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int bunzip2_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; int bunzip2_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;

View File

@ -63,9 +63,24 @@
#define STR1(s) #s #define STR1(s) #s
#define STR(s) STR1(s) #define STR(s) STR1(s)
/* Convert a constant to 3-digit string, packed into uint32_t */
enum { enum {
OPT_v = (1 << 0), /* Shift for Nth decimal digit */
OPT_w = (1 << 1), SHIFT0 = 16 * BB_LITTLE_ENDIAN + 8 * BB_BIG_ENDIAN,
SHIFT1 = 8 * BB_LITTLE_ENDIAN + 16 * BB_BIG_ENDIAN,
SHIFT2 = 0 * BB_LITTLE_ENDIAN + 24 * BB_BIG_ENDIAN,
};
#define STRNUM32(s) (uint32_t)(0 \
| (('0' + ((s) / 1 % 10)) << SHIFT0) \
| (('0' + ((s) / 10 % 10)) << SHIFT1) \
| (('0' + ((s) / 100 % 10)) << SHIFT2) \
)
enum {
OPT_l = (1 << 0),
OPT_1 = (1 << 1),
OPT_v = (1 << 2),
OPT_w = (1 << 3),
#define mk_const4(a,b,c,d) (((a * 0x100 + b) * 0x100 + c) * 0x100 + d) #define mk_const4(a,b,c,d) (((a * 0x100 + b) * 0x100 + c) * 0x100 + d)
#define mk_const3(a,b,c) ((a * 0x100 + b) * 0x100 + c) #define mk_const3(a,b,c) ((a * 0x100 + b) * 0x100 + c)
@ -106,78 +121,84 @@ struct globals {
len_and_sockaddr *local_addr; len_and_sockaddr *local_addr;
len_and_sockaddr *port_addr; len_and_sockaddr *port_addr;
int pasv_listen_fd; int pasv_listen_fd;
int data_fd; int proc_self_fd;
off_t restart_pos; off_t restart_pos;
char *ftp_cmd; char *ftp_cmd;
char *ftp_arg; char *ftp_arg;
#if ENABLE_FEATURE_FTP_WRITE #if ENABLE_FEATURE_FTP_WRITE
char *rnfr_filename; char *rnfr_filename;
#endif #endif
smallint opts;
}; };
#define G (*(struct globals*)&bb_common_bufsiz1) #define G (*(struct globals*)&bb_common_bufsiz1)
#define INIT_G() do { } while (0) #define INIT_G() do { } while (0)
static char * static char *
escape_text(const char *prepend, const char *str, char from, const char *to, char append) escape_text(const char *prepend, const char *str, unsigned escapee)
{ {
size_t retlen, remainlen, chunklen, tolen; unsigned retlen, remainlen, chunklen;
const char *remain;
char *ret, *found; char *ret, *found;
char append;
append = (char)escapee;
escapee >>= 8;
remain = str;
remainlen = strlen(str); remainlen = strlen(str);
tolen = strlen(to);
/* Simply alloc strlen(str)*strlen(to). "to" is max 2 so it's ok */
retlen = strlen(prepend); retlen = strlen(prepend);
ret = xmalloc(retlen + remainlen * tolen + 1 + 1); ret = xmalloc(retlen + remainlen * 2 + 1 + 1);
strcpy(ret, prepend); strcpy(ret, prepend);
for (;;) { for (;;) {
found = strchrnul(remain, from); found = strchrnul(str, escapee);
chunklen = found - remain; chunklen = found - str + 1;
/* Copy chunk which doesn't contain 'from' to ret */ /* Copy chunk up to and including escapee (or NUL) to ret */
memcpy(&ret[retlen], remain, chunklen); memcpy(ret + retlen, str, chunklen);
retlen += chunklen; retlen += chunklen;
if (*found != '\0') { if (*found == '\0') {
/* Now copy 'to' instead of 'from' */ /* It wasn't escapee, it was NUL! */
memcpy(&ret[retlen], to, tolen); ret[retlen - 1] = append; /* replace NUL */
retlen += tolen; ret[retlen] = '\0'; /* add NUL */
remain = found + 1;
} else {
ret[retlen] = append;
ret[retlen+1] = '\0';
break; break;
} }
ret[retlen++] = escapee; /* duplicate escapee */
str = found + 1;
} }
return ret; return ret;
} }
static void /* Returns strlen as a bonus */
static unsigned
replace_char(char *str, char from, char to) replace_char(char *str, char from, char to)
{ {
while ((str = strchr(str, from)) != NULL) char *p = str;
*str++ = to; while (*p) {
if (*p == from)
*p = to;
p++;
}
return p - str;
} }
static void static void
cmdio_write(unsigned status, const char *str) cmdio_write(uint32_t status_str, const char *str)
{ {
union {
char buf[4];
uint32_t v32;
} u;
char *response; char *response;
int len; int len;
u.v32 = status_str;
/* FTP allegedly uses telnet protocol for command link. /* FTP allegedly uses telnet protocol for command link.
* In telnet, 0xff is an escape char, and needs to be escaped: */ * In telnet, 0xff is an escape char, and needs to be escaped: */
response = escape_text(utoa(status), str, '\xff', "\xff\xff", '\r'); response = escape_text(u.buf, str, (0xff << 8) + '\r');
/* ?! does FTP send embedded LFs as NULs? wow */ /* ?! does FTP send embedded LFs as NULs? wow */
len = strlen(response); len = replace_char(response, '\n', '\0');
replace_char(response, '\n', '\0');
response[len++] = '\n'; /* tack on trailing '\n' */ response[len++] = '\n'; /* tack on trailing '\n' */
xwrite(STDOUT_FILENO, response, len); xwrite(STDOUT_FILENO, response, len);
@ -215,9 +236,9 @@ handle_pwd(void)
cwd = xstrdup(""); cwd = xstrdup("");
/* We have to promote each " to "" */ /* We have to promote each " to "" */
response = escape_text(" \"", cwd, '\"', "\"\"", '\"'); response = escape_text(" \"", cwd, ('"' << 8) + '"');
free(cwd); free(cwd);
cmdio_write(FTP_PWDOK, response); cmdio_write(STRNUM32(FTP_PWDOK), response);
free(response); free(response);
} }
@ -261,21 +282,6 @@ handle_help(void)
/* Download commands */ /* Download commands */
static void
init_data_sock_params(int sock_fd)
{
struct linger linger;
G.data_fd = sock_fd;
memset(&linger, 0, sizeof(linger));
linger.l_onoff = 1;
linger.l_linger = 32767;
setsockopt(sock_fd, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
setsockopt(sock_fd, SOL_SOCKET, SO_LINGER, &linger, sizeof(linger));
}
static int static int
ftpdataio_get_pasv_fd(void) ftpdataio_get_pasv_fd(void)
{ {
@ -288,39 +294,10 @@ ftpdataio_get_pasv_fd(void)
return remote_fd; return remote_fd;
} }
init_data_sock_params(remote_fd); setsockopt(remote_fd, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
return remote_fd; return remote_fd;
} }
static int
ftpdataio_get_port_fd(void)
{
int remote_fd;
/* Do we want to die or print error to client? */
remote_fd = xconnect_stream(G.port_addr);
init_data_sock_params(remote_fd);
return remote_fd;
}
static void
ftpdataio_dispose_transfer_fd(void)
{
/* This close() blocks because we set SO_LINGER */
if (G.data_fd > STDOUT_FILENO) {
if (close(G.data_fd) < 0) {
/* Do it again without blocking. */
struct linger linger;
memset(&linger, 0, sizeof(linger));
setsockopt(G.data_fd, SOL_SOCKET, SO_LINGER, &linger, sizeof(linger));
close(G.data_fd);
}
}
G.data_fd = -1;
}
static inline int static inline int
port_active(void) port_active(void)
{ {
@ -341,12 +318,12 @@ get_remote_transfer_fd(const char *p_status_msg)
if (pasv_active()) if (pasv_active())
remote_fd = ftpdataio_get_pasv_fd(); remote_fd = ftpdataio_get_pasv_fd();
else else
remote_fd = ftpdataio_get_port_fd(); remote_fd = xconnect_stream(G.port_addr);
if (remote_fd < 0) if (remote_fd < 0)
return remote_fd; return remote_fd;
cmdio_write(FTP_DATACONN, p_status_msg); cmdio_write(STRNUM32(FTP_DATACONN), p_status_msg);
return remote_fd; return remote_fd;
} }
@ -374,21 +351,21 @@ port_pasv_cleanup(void)
static unsigned static unsigned
bind_for_passive_mode(void) bind_for_passive_mode(void)
{ {
int fd;
unsigned port; unsigned port;
port_pasv_cleanup(); port_pasv_cleanup();
G.pasv_listen_fd = xsocket(G.local_addr->u.sa.sa_family, SOCK_STREAM, 0); G.pasv_listen_fd = fd = xsocket(G.local_addr->u.sa.sa_family, SOCK_STREAM, 0);
setsockopt_reuseaddr(G.pasv_listen_fd); setsockopt_reuseaddr(fd);
set_nport(G.local_addr, 0); set_nport(G.local_addr, 0);
xbind(G.pasv_listen_fd, &G.local_addr->u.sa, G.local_addr->len); xbind(fd, &G.local_addr->u.sa, G.local_addr->len);
xlisten(G.pasv_listen_fd, 1); xlisten(fd, 1);
getsockname(G.pasv_listen_fd, &G.local_addr->u.sa, &G.local_addr->len); getsockname(fd, &G.local_addr->u.sa, &G.local_addr->len);
port = get_nport(&G.local_addr->u.sa); port = get_nport(&G.local_addr->u.sa);
port = ntohs(port); port = ntohs(port);
return port; return port;
} }
@ -487,7 +464,7 @@ handle_retr(void)
G.restart_pos = 0; G.restart_pos = 0;
if (!data_transfer_checks_ok()) if (!data_transfer_checks_ok())
return; return; /* data_transfer_checks_ok emitted error response */
/* O_NONBLOCK is useful if file happens to be a device node */ /* O_NONBLOCK is useful if file happens to be a device node */
opened_file = G.ftp_arg ? open(G.ftp_arg, O_RDONLY | O_NONBLOCK) : -1; opened_file = G.ftp_arg ? open(G.ftp_arg, O_RDONLY | O_NONBLOCK) : -1;
@ -520,7 +497,7 @@ handle_retr(void)
goto port_pasv_cleanup_out; goto port_pasv_cleanup_out;
trans_ret = bb_copyfd_eof(opened_file, remote_fd); trans_ret = bb_copyfd_eof(opened_file, remote_fd);
ftpdataio_dispose_transfer_fd(); close(remote_fd);
if (trans_ret < 0) if (trans_ret < 0)
cmdio_write_error(FTP_BADSENDFILE); cmdio_write_error(FTP_BADSENDFILE);
else else
@ -535,185 +512,105 @@ handle_retr(void)
/* List commands */ /* List commands */
static char * static int
statbuf_getperms(const struct stat *statbuf) popen_ls(const char *opt)
{ {
char *perms; char *cwd;
enum { r = 'r', w = 'w', x = 'x', s = 's', S = 'S' }; const char *argv[5] = { "ftpd", opt, NULL, G.ftp_arg, NULL };
struct fd_pair outfd;
pid_t pid;
perms = xmalloc(11); cwd = xrealloc_getcwd_or_warn(NULL);
memset(perms, '-', 10);
perms[0] = '?'; xpiped_pair(outfd);
switch (statbuf->st_mode & S_IFMT) {
case S_IFREG: perms[0] = '-'; break;
case S_IFDIR: perms[0] = 'd'; break;
case S_IFLNK: perms[0] = 'l'; break;
case S_IFIFO: perms[0] = 'p'; break;
case S_IFSOCK: perms[0] = s; break;
case S_IFCHR: perms[0] = 'c'; break;
case S_IFBLK: perms[0] = 'b'; break;
}
if (statbuf->st_mode & S_IRUSR) perms[1] = r; fflush(NULL);
if (statbuf->st_mode & S_IWUSR) perms[2] = w; pid = vfork();
if (statbuf->st_mode & S_IXUSR) perms[3] = x;
if (statbuf->st_mode & S_IRGRP) perms[4] = r;
if (statbuf->st_mode & S_IWGRP) perms[5] = w;
if (statbuf->st_mode & S_IXGRP) perms[6] = x;
if (statbuf->st_mode & S_IROTH) perms[7] = r;
if (statbuf->st_mode & S_IWOTH) perms[8] = w;
if (statbuf->st_mode & S_IXOTH) perms[9] = x;
if (statbuf->st_mode & S_ISUID) perms[3] = (perms[3] == x) ? s : S;
if (statbuf->st_mode & S_ISGID) perms[6] = (perms[6] == x) ? s : S;
if (statbuf->st_mode & S_ISVTX) perms[9] = (perms[9] == x) ? 't' : 'T';
perms[10] = '\0'; switch (pid) {
case -1: /* failure */
return perms; bb_perror_msg_and_die("vfork");
} case 0: /* child */
/* NB: close _first_, then move fds! */
static void close(outfd.rd);
write_filestats(int fd, const char *filename, xmove_fd(outfd.wr, STDOUT_FILENO);
const struct stat *statbuf) close(STDIN_FILENO);
{ /* xopen("/dev/null", O_RDONLY); - chroot may lack it! */
off_t size; if (fchdir(G.proc_self_fd) == 0) {
char *stats, *lnkname = NULL, *perms; close(G.proc_self_fd);
const char *name; argv[2] = cwd;
char timestr[32]; /* ftpd ls helper chdirs to argv[2],
struct tm *tm; * preventing peer from seeing /proc/self
*/
name = bb_get_last_path_component_nostrip(filename); execv("exe", (char**) argv);
if (statbuf != NULL) {
size = statbuf->st_size;
if (S_ISLNK(statbuf->st_mode))
/* Damn symlink... */
lnkname = xmalloc_readlink(filename);
tm = gmtime(&statbuf->st_mtime);
if (strftime(timestr, sizeof(timestr), "%b %d %H:%M", tm) == 0)
bb_error_msg_and_die("strftime");
timestr[sizeof(timestr) - 1] = '\0';
perms = statbuf_getperms(statbuf);
stats = xasprintf("%s %u\tftp ftp %"OFF_FMT"u\t%s %s",
perms, (int) statbuf->st_nlink,
size, timestr, name);
free(perms);
} else
stats = xstrdup(name);
xwrite_str(fd, stats);
free(stats);
if (lnkname != NULL) {
xwrite_str(fd, " -> ");
xwrite_str(fd, lnkname);
free(lnkname);
}
xwrite_str(fd, "\r\n");
}
static void
write_dirstats(int fd, const char *dname, int details)
{
DIR *dir;
struct dirent *dirent;
struct stat statbuf;
char *filename;
dir = xopendir(dname);
for (;;) {
dirent = readdir(dir);
if (dirent == NULL)
break;
/* Ignore . and .. */
if (dirent->d_name[0] == '.') {
if (dirent->d_name[1] == '\0'
|| (dirent->d_name[1] == '.' && dirent->d_name[2] == '\0')
) {
continue;
}
} }
_exit(127);
if (details) {
filename = xasprintf("%s/%s", dname, dirent->d_name);
if (lstat(filename, &statbuf) != 0) {
free(filename);
break;
}
} else
filename = xstrdup(dirent->d_name);
write_filestats(fd, filename, details ? &statbuf : NULL);
free(filename);
} }
/* parent */
closedir(dir); close(outfd.wr);
free(cwd);
return outfd.rd;
} }
static void static void
handle_dir_common(int full_details, int stat_cmd) handle_dir_common(int opts)
{ {
int fd; FILE *ls_fp;
struct stat statbuf; char *line;
int ls_fd;
if (!stat_cmd && !data_transfer_checks_ok()) if (!(opts & 1) && !data_transfer_checks_ok())
return; return; /* data_transfer_checks_ok emitted error response */
if (stat_cmd) { ls_fd = popen_ls((opts & 2) ? "-l" : "-1");
fd = STDIN_FILENO; ls_fp = fdopen(ls_fd, "r");
if (!ls_fp) /* never happens. paranoia */
bb_perror_msg_and_die("fdopen");
if (opts & 1) {
/* STAT <filename> */
cmdio_write_raw(STR(FTP_STATFILE_OK)"-Status follows:\r\n"); cmdio_write_raw(STR(FTP_STATFILE_OK)"-Status follows:\r\n");
} else { while (1) {
fd = get_remote_transfer_fd(" Here comes the directory listing"); line = xmalloc_fgetline(ls_fp);
if (fd < 0) if (!line)
goto bail; break;
} cmdio_write(0, line); /* hack: 0 results in no status at all */
free(line);
if (G.ftp_arg) {
if (lstat(G.ftp_arg, &statbuf) != 0) {
/* Dir doesn't exist => return ok to client */
goto bail;
} }
if (S_ISREG(statbuf.st_mode) || S_ISLNK(statbuf.st_mode)) cmdio_write_ok(FTP_STATFILE_OK);
write_filestats(fd, G.ftp_arg, &statbuf); } else {
else if (S_ISDIR(statbuf.st_mode)) /* LIST/NLST [<filename>] */
write_dirstats(fd, G.ftp_arg, full_details); int remote_fd = get_remote_transfer_fd(" Here comes the directory listing");
} else if (remote_fd >= 0) {
write_dirstats(fd, ".", full_details); while (1) {
line = xmalloc_fgetline(ls_fp);
bail: if (!line)
/* Well, if we can't open directory/file it doesn't matter */ break;
if (!stat_cmd) { xwrite_str(remote_fd, line);
ftpdataio_dispose_transfer_fd(); xwrite(remote_fd, "\r\n", 2);
free(line);
}
}
close(remote_fd);
port_pasv_cleanup(); port_pasv_cleanup();
cmdio_write_ok(FTP_TRANSFEROK); cmdio_write_ok(FTP_TRANSFEROK);
} else }
cmdio_write_ok(FTP_STATFILE_OK); fclose(ls_fp); /* closes ls_fd too */
} }
static void static void
handle_list(void) handle_list(void)
{ {
handle_dir_common(1, 0); handle_dir_common(2);
} }
static void static void
handle_nlst(void) handle_nlst(void)
{ {
handle_dir_common(0, 0); handle_dir_common(0);
} }
static void static void
handle_stat_file(void) handle_stat_file(void)
{ {
handle_dir_common(1, 1); handle_dir_common(3);
} }
static void static void
@ -840,7 +737,7 @@ handle_upload_common(int is_append, int is_unique)
goto bail; goto bail;
trans_ret = bb_copyfd_eof(remote_fd, local_file_fd); trans_ret = bb_copyfd_eof(remote_fd, local_file_fd);
ftpdataio_dispose_transfer_fd(); close(remote_fd);
if (trans_ret < 0) if (trans_ret < 0)
cmdio_write_error(FTP_BADSENDFILE); cmdio_write_error(FTP_BADSENDFILE);
@ -905,8 +802,19 @@ cmdio_get_cmd_and_arg(void)
} }
int ftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; int ftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int ftpd_main(int argc UNUSED_PARAM, char **argv) int ftpd_main(int argc, char **argv)
{ {
smallint opts;
opts = getopt32(argv, "l1v" USE_FEATURE_FTP_WRITE("w"));
if (opts & (OPT_l|OPT_1)) {
/* Our secret backdoor to ls */
xchdir(argv[2]);
argv[2] = (char*)"--";
return ls_main(argc, argv);
}
INIT_G(); INIT_G();
G.local_addr = get_sock_lsa(STDIN_FILENO); G.local_addr = get_sock_lsa(STDIN_FILENO);
@ -920,13 +828,13 @@ int ftpd_main(int argc UNUSED_PARAM, char **argv)
* failure */ * failure */
} }
G.opts = getopt32(argv, "v" USE_FEATURE_FTP_WRITE("w"));
openlog(applet_name, LOG_PID, LOG_DAEMON); openlog(applet_name, LOG_PID, LOG_DAEMON);
logmode |= LOGMODE_SYSLOG; logmode |= LOGMODE_SYSLOG;
if (!(G.opts & OPT_v)) if (!(opts & OPT_v))
logmode = LOGMODE_SYSLOG; logmode = LOGMODE_SYSLOG;
G.proc_self_fd = xopen("/proc/self", O_RDONLY | O_DIRECTORY);
if (argv[optind]) { if (argv[optind]) {
xchdir(argv[optind]); xchdir(argv[optind]);
chroot("."); chroot(".");
@ -1064,7 +972,7 @@ int ftpd_main(int argc UNUSED_PARAM, char **argv)
else if (cmdval == const_REST) else if (cmdval == const_REST)
handle_rest(); handle_rest();
#if ENABLE_FEATURE_FTP_WRITE #if ENABLE_FEATURE_FTP_WRITE
else if (G.opts & OPT_w) { else if (opts & OPT_w) {
if (cmdval == const_STOR) if (cmdval == const_STOR)
handle_stor(); handle_stor();
else if (cmdval == const_MKD) else if (cmdval == const_MKD)