/*$Id: spegla.c,v 1.117 2000/05/27 13:38:14 jens Exp $*/ /* * Copyright (c) 1997, 1998, 1999, 2000 * Jens A. Nilsson, jnilsson@ludd.luth.se. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifdef MEMDEBUG #include #endif #include #ifdef MTYPES #include "missing/defs.h" #endif #ifdef Solaris #include #endif #ifdef LINUX #include #endif #include #include #include #include #if defined(SunOS) || defined(Solaris) || defined(OSF1) || defined(ULTRIX) # include #else # include #endif #if defined(LINUX) # include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include "strlcpy.h" #include "spegla.h" #include "parserow.h" #include "jftp.h" #include "spf_util.h" #include "tgetopt.h" #include "e_err.h" #include #ifdef NOPROTOS char *strdup(char *); #ifdef ULTRIX int statfs(char *, struct fs_data *); #else int statfs(const char *, struct statfs *); #endif #endif void set_mirror_uid(char *mirroruser); #define MAX_LINE 1024 #define GO_CONFIGFILE 0 #define GO_SECTION 1 #define GO_LOCALDIR 2 #define GO_REMOTEDIR 3 #define GO_USERNAME 4 #define GO_PASSWORD 5 #define GO_HOST 6 #define GO_VERSION 7 #define GO_WARNOVERRIDES 8 #define GO_SKIP 9 #define GO_SHOWCONF 10 #define GO_LOGFILE 11 #define GO_LOCKFILE 12 #define GO_RETRIES 13 #define GO_RETRYTIME 14 #define GO_TIMEOUT 15 #define GO_PORT 16 #define GO_PASSIVE 17 #define GO_MAXDELETE 18 #define GO_FASTSYNC 19 #define GO_LOGLEVEL 20 #define GO_TEMPDIR 21 #define GO_TREATASDIR 22 #define GO_TREATASDIRDEST 23 #define GO_MINFREE 24 #define GO_DODELETE 25 #define GO_MIRRORUSER 26 #define GO_HELP 27 #define GO_URL 28 #define GO_FAMILY 29 static struct get_opt go[] = { { GO_CONFIGFILE, "configfile", 'f', REQ_ARG }, { GO_SECTION, "section", 's', REQ_ARG }, { GO_LOCALDIR, "localdir", 'l', REQ_ARG }, { GO_REMOTEDIR, "remotedir", 'r', REQ_ARG }, { GO_USERNAME, "username", 'u', REQ_ARG }, { GO_PASSWORD, "password", 'p', REQ_ARG }, { GO_HOST, "host", 'h', REQ_ARG }, { GO_VERSION, "version", 'v', OPT_ARG }, { GO_WARNOVERRIDES, "warnoverrides", 'w', REQ_ARG }, { GO_SKIP, "skip", '?', REQ_ARG }, { GO_SHOWCONF, "showconf", '?', REQ_ARG }, { GO_LOGFILE, "logfile", '?', REQ_ARG }, { GO_LOCKFILE, "lockfile", '?', REQ_ARG }, { GO_RETRIES, "retries", '?', REQ_ARG }, { GO_RETRYTIME, "retrytime", '?', REQ_ARG }, { GO_TIMEOUT, "timeout", '?', REQ_ARG }, { GO_PORT, "port", '?', REQ_ARG }, { GO_PASSIVE, "passive", '?', REQ_ARG }, { GO_MAXDELETE, "maxdelete", '?', REQ_ARG }, #ifdef NOTYET { GO_FASTSYNC, "fastsync", '?', REQ_ARG }, #endif { GO_LOGLEVEL, "loglevel", '?', REQ_ARG }, { GO_TEMPDIR, "tempdir", '?', REQ_ARG }, { GO_TREATASDIR, "treatasdir", '?', REQ_ARG }, { GO_TREATASDIRDEST, "treatasdirdest", '?', REQ_ARG }, { GO_MINFREE, "minfree", '?', REQ_ARG }, { GO_DODELETE, "dodelete", '?', REQ_ARG }, { GO_MIRRORUSER, "mirroruser", '?', REQ_ARG }, { GO_HELP, "help", '?', NO_ARG }, { GO_URL, "url", '?', REQ_ARG }, { GO_FAMILY, "family", 'a', REQ_ARG }, { -1,NULL,0,NO_ARG} }; extern char cur_dir[PATH_MAX]; int sp_do_delete; int sp_deletes; int sp_dirs_traversed; int sp_errors; int sp_max_delete; unsigned int sp_retrytime; static FILE *fp_log_file; static char *lock_file = NULL; static char *start_wd = NULL; static struct ftp_con *connection = NULL; static time_t start_time; static int nowarn = 0; extern struct cl_sps_que *skip; extern struct cl_spt_que *treatasdir; static void remove_lock(void) { if (seteuid(getuid()) < 0) e_warn("seteuid(%d)", getuid()); if (start_wd != NULL) { if (chdir(start_wd) < 0) { e_warn("chdir(%s)", start_wd); free(start_wd); return; } free(start_wd); } if (lock_file != NULL) { if (unlink(lock_file) < 0) e_warn("unlink(%s)", lock_file); } } static void clean_up_vars(void) { cl_sps_free(skip, sps_unalloc); cl_spt_free(treatasdir, spt_unalloc); if (connection != NULL) ftp_unalloc(connection); } static void print_stats(void) { time_t tval; (void) time(&tval); e_warnx("Spegla finished at %s", ctime(&tval)); e_warnx("dirs traversed : %d", sp_dirs_traversed); e_warnx("files deleted : %d", sp_deletes); if (connection != NULL) { #ifdef NO_QUADS e_warnx("bytes recieved : %lu", connection->ftp_recd); e_warnx("bytes sent : %lu", connection->ftp_sent); #else e_warnx("bytes recieved : %llu", #if defined (NetBSD) && ARCH == Alpha && !defined(lint) (long long unsigned int) /* XXX - impossible to make it in another */ /* way without a warning */ #endif connection->ftp_recd); e_warnx("bytes sent : %llu", #if defined (NetBSD) && ARCH == Alpha && !defined(lint) (long long unsigned int) /* XXX - impossible to make it in another */ /* way without a warning */ #endif connection->ftp_sent); #endif if (tval - start_time != 0) e_warnx("average download rate : %.2f K/sec", (float)((float)(connection->ftp_recd / 1024.0) / (float)(tval - start_time)) ); e_warnx("number of relogins : %d", connection->ftp_relogins); e_warnx("files downloaded : %d", connection->ftp_downloads); e_warnx("number of timeouts : %d", connection->ftp_timeouts); } } /* ARGSUSED0 */ static void signal_handler(int n) { n = n; /* silent gcc */ exit(2); /* for profiling */ } static void setup_signals(void) { struct sigaction act; sigemptyset(&act.sa_mask); act.sa_flags = 0; act.sa_handler = signal_handler; (void) sigaction(SIGQUIT, &act, NULL); (void) sigaction(SIGTERM, &act, NULL); (void) sigaction(SIGINT, &act, NULL); /* don't want to quit if a connection is lost */ act.sa_handler = SIG_IGN; (void) sigaction(SIGPIPE, &act, NULL); } static struct heap_spf_list * build_remote_dir(FILE * fp) { char buf[PATH_MAX]; size_t len; struct sp_file *spf; struct heap_spf_list *headp; if ((fp == NULL) || (fgets(buf, (int)sizeof(buf), fp) == NULL)) return NULL; if ((strncmp(buf + 1, "otal", (size_t)4) == 0) && (fgets(buf, (int)sizeof(buf), fp) == NULL)) return NULL; headp = heap_spf_init(spf_push_comp); do { /* remove trailing '\r' and '\n' */ len = strlen(buf); if (buf[len - 1] == '\n') buf[len - 1] = '\0'; len = strlen(buf); if (buf[len - 1] == '\r') buf[len - 1] = '\0'; /* short row */ if (strlen(buf) == 0) continue; if ((spf = parse_row(buf)) == NULL) { e_warnx("build_remote_dir: parse error: \"%s\"", buf); continue; } /* don't want . or .. */ if (strcmp("..", spf->spf_name) == 0 || strcmp(".", spf->spf_name) == 0) { spf_unalloc(spf); continue; } /* treat_as_dir before skip */ if (spf_is_a_treatasdir(spf)) { spf->spf_type = DIRECTORY; spf->spf_opt |= SPFO_ISLINK; } if (spf_is_a_skip(spf)) { spf_unalloc(spf); continue; } (void) heap_spf_push(headp, spf); } while (fgets(buf, (int)sizeof(buf), fp) != NULL); return headp; } static struct heap_spf_list * build_local_dir(void) { DIR *dirp; struct heap_spf_list *headp; struct sp_file *spf; struct dirent *dp; if ((dirp = opendir(cur_dir)) == NULL) return NULL; headp = heap_spf_init(spf_push_comp); while ((dp = readdir(dirp)) != NULL) { /* don't want . or .. */ if (strcmp("..", dp->d_name) == 0 || strcmp(".", dp->d_name) == 0) continue; /* create our spf */ if (spf_stat_init(dp->d_name, &spf) < 0) continue; /* treat_as_dir before skip */ if (spf_is_a_treatasdir(spf)) { spf->spf_type = DIRECTORY; spf->spf_opt |= SPFO_ISLINK; } if (spf_is_a_skip(spf)) { spf_unalloc(spf); continue; } (void) heap_spf_push(headp, spf); } (void) closedir(dirp); return headp; } static void get_ftp_file(struct ftp_con * c, struct sp_file *spf) { char remote_file[PATH_MAX], local_file[PATH_MAX]; struct stat sb; size_t size = 0; int res, rmed_orig = 0, erased = 0; if (spf_full_name2(spf, remote_file, sizeof(remote_file)) == NULL) return; if (spf_use_tmp_name(spf) == NULL) return; if (spf_full_name2(spf, local_file, sizeof(local_file)) == NULL) return; restart: if (size == 0) e_warnx("get %s", remote_file); else e_warnx("continues at byte %ld in %s", (unsigned long)size, remote_file); res = ftp_get(c, local_file, remote_file, size); if (res < 0) { if (c->ftp_resp != JFTP_BROKEN || c->ftp_resp != JFTP_TIMEOUT) e_warnx("get %s: failed", remote_file); switch (c->ftp_resp) { case JFTP_BROKEN: case JFTP_TIMEOUT: do { if (c->ftp_retries-- < 0) { (void)spf_rm(spf); (void)spf_restore_name(spf); e_errx(1, "I've retried enough now, quit"); } e_warnx("%s on %s, win quit in %d retries", c->ftp_resp == JFTP_BROKEN ? "broken connection":"timeout", remote_file, c->ftp_retries); if (!erased) { if (stat(local_file, &sb) < 0) size = 0; else size = (size_t)sb.st_size; if (size == 0) { (void)spf_rm(spf); erased = 1; } } e_warnx("sleeping for %d seconds...", sp_retrytime); (void) sleep(sp_retrytime); e_warnx("trying to login again"); res = ftp_relogin(c); if (res < 0) e_warnx("relogin failed code %d, will quit in %d retries", c->ftp_resp, c->ftp_retries); } while (res < 0); goto restart; case JFTP_ERR: (void)spf_rm(spf); (void)spf_restore_name(spf); return; case JFTP_WRITEERR: if (!rmed_orig) { int fd; e_warnx("file system full, " "trucates and removes original file (%s) and retries", remote_file); /* truncate and remove the original file * to free some space. * * XXX - remote_file is the name of the original file */ if ((fd = open(remote_file, O_TRUNC)) > -1) close(fd); unlink(remote_file); rmed_orig = 1; goto restart; } e_errx(1, "File system full!"); /* FALLTHROUGH */ /* not really e_errx exits */ default: e_errx(1, "ERROR unexpected error %d fel %s", c->ftp_resp, spf_full_name(spf)); } } (void)spf_chmod(spf, 0); (void)spf_new_time(spf); (void)spf_restore_name(spf); } static void sp_get_file(struct ftp_con *c, struct sp_file *spf, struct fifo_spf_list *fifo) { switch (spf->spf_type) { case DIRECTORY: if ((spf = spf_clone(spf)) == NULL) e_err(1, "spf_clone"); (void) fifo_spf_push(fifo, spf_clone(spf)); return; case PLAINFILE: get_ftp_file(c, spf); return; case SYMLINK: (void) spf_symlink(spf); return; default: e_warnx("%s unknown file", spf_full_name(spf)); } } #if 0 void dump_spf_heap(struct heap_spf_list *h) { struct heap_spf_list *hc; struct sp_file *spf; int i; if ((hc = heap_spf_clone(h)) == NULL) return; printf("*** heap_data dump ***\n"); for (i = 0; i < hc->heap_keys; i++) fprintf(stderr, "name = '%s'\n", hc->heap_data[i]->spf_name); printf("*** picked out in order ***\n"); while (heap_spf_pop(hc, &spf) == 0) fprintf(stderr, "name = '%s'\n", spf->spf_name); heap_spf_free(hc, NULL); } void dump_spf_fifo(struct fifo_spf_list *f) { struct fifo_spf_list *fc; struct sp_file *spf; if ((fc = fifo_spf_clone(f)) == NULL) return; printf("*** fifo_data dump ***\n"); while (fifo_spf_pop(fc, &spf) == 0) fprintf(stderr, "name = '%s'\n", spf->spf_name); fifo_spf_free(fc, NULL); } #endif static void sp_sync_dir(struct ftp_con *c, struct heap_spf_list *l, struct heap_spf_list *r) { char *temp_file; int cmp, res; FILE *fp; struct fifo_spf_list *dirs; struct sp_file *spf, *spf_r, *spf_l; if (l == NULL) { e_warnx("sp_sync_dir: Reading of local dir: failed"); return; } if (r == NULL) { e_warnx("sp_sync_dir: Reading of remote dir: failed"); return; } sp_dirs_traversed++; dirs = fifo_spf_init(); spf = NULL; while (heap_spf_peek(l, NULL) == 0 || heap_spf_peek(r, NULL) == 0) { /****** There are no more files locally ******/ if (heap_spf_peek(l, NULL) < 0) { (void) heap_spf_pop(r, &spf); sp_get_file(c, spf, dirs); spf_unalloc(spf); continue; } /****** There are no more files on the server ******/ if (heap_spf_peek(r, NULL) < 0) { (void) heap_spf_pop(l, &spf); (void) spf_rm(spf); spf_unalloc(spf); continue; } (void) heap_spf_peek(r, &spf_r); (void) heap_spf_peek(l, &spf_l); cmp = spf_name_cmp(spf_l, spf_r); /****** A file is missing locally ******/ if (cmp > 0) { (void) heap_spf_pop(r, &spf); assert(spf != NULL); sp_get_file(c, spf, dirs); spf_unalloc(spf); continue; } /****** Exceeding local file******/ if (cmp < 0) { (void) heap_spf_pop(l, &spf); assert(spf != NULL); (void) spf_rm(spf); spf_unalloc(spf); continue; } /****** Differing file types ******/ if (spf_r->spf_type != spf_l->spf_type) { /* Remove local file */ (void) heap_spf_pop(l, &spf); (void) spf_rm(spf); spf_unalloc(spf); /* Download remote file */ (void) heap_spf_pop(r, &spf); sp_get_file(c, spf, dirs); (void) spf_unalloc(spf); continue; } /****** The files are of the same type now ******/ switch (spf_r->spf_type) { case DIRECTORY: /* Put directory in directory queue for later analyze */ spf_r->spf_opt |= SPFO_DONT_CREATE; sp_get_file(c, spf_r, dirs); break; case PLAINFILE: if ((spf_time_cmp(spf_r, spf_l) != 0) || (spf_r->spf_size != spf_l->spf_size)) { #if 0 /* should not be needed any longer */ (void) spf_rm(spf_l); #endif sp_get_file(c, spf_r, dirs); break; } if (spf_r->spf_mode != spf_l->spf_mode) { spf_r->spf_opt |= SPFO_LOG_CHMOD; (void) spf_chmod(spf_r, 0); } break; case SYMLINK: if (strcmp(spf_r->spf_symlink, spf_l->spf_symlink) != 0) { if (spf_rm(spf_r) >= 0) sp_get_file(c, spf_r, dirs); } break; default: e_warnx("sp_sync_dir: %s unknown file", spf_full_name(spf)); continue; } /* Pop file of queue, can't do any more for this file */ (void) heap_spf_pop(l, &spf); spf_unalloc(spf); (void) heap_spf_pop(r, &spf); spf_unalloc(spf); } heap_spf_free(l, spf_unalloc); heap_spf_free(r, spf_unalloc); while (fifo_spf_pop(dirs, &spf) == 0) { if (spf_is_a_skip(spf)) { e_warnx("does this happen? skipping directory %s", spf_full_name(spf)); spf_unalloc(spf); continue; } if (spf_mkdir(spf) < 0) { /* If the directory already existed, no problem */ if (errno != EEXIST) continue; } /* make sure we can descend, will restore file mode later */ if (spf_chmod(spf, S_IRWXU) < 0) continue; (void) cur_dir_cd(spf->spf_name); /** one step down in the hierarchy **/ l = build_local_dir(); do { e_warnx("list %s", cur_dir); temp_file = ftp_dir(c, cur_dir); if (temp_file == NULL) { switch (c->ftp_resp) { case JFTP_BROKEN: case JFTP_TIMEOUT: do { if (c->ftp_retries-- < 0) e_errx(1, "retried to much, bye %s", cur_dir); if (c->ftp_resp == JFTP_TIMEOUT) { e_warnx("timeout on %s, retries = %d", cur_dir, c->ftp_retries); } else if (c->ftp_resp == JFTP_BROKEN) { e_warnx("broken connection on %s, " "retries = %d", cur_dir, c->ftp_retries); } e_warnx("sleeping for %d seconds...", sp_retrytime); (void) sleep(sp_retrytime); e_warnx("trying to login again"); res = ftp_relogin(c); if (res < 0) e_warnx("relogin failed, code %d", c->ftp_resp); } while (res < 0); break; case JFTP_ERR: break; default: e_errx(1, "ERROR: unexpected error dir = %s", cur_dir); } } if (temp_file == NULL && c->ftp_resp == JFTP_ERR) break; } while (temp_file == NULL); if (temp_file != NULL) { fp = fopen(temp_file, "r"); r = build_remote_dir(fp); (void) fclose(fp); } else r = NULL; (void) unlink(temp_file); free(temp_file); if (l == NULL) e_warnx("reading of local dir '%s': failed", cur_dir); if (r == NULL) e_warnx("reading of remote dir '%s': failed", cur_dir); if (r != NULL && l != NULL) sp_sync_dir(c, l, r); /** one step up **/ (void) cur_dir_cdup(); (void) spf_new_time(spf); (void) spf_chmod(spf, 0); spf_unalloc(spf); } fifo_spf_free(dirs, spf_unalloc); } static void check_minfree(int minfree, char *path) { #ifdef ULTRIX struct fs_data f; #else #ifdef Solaris struct statvfs f; #else struct statfs f; #endif #endif if (minfree == 0) return; #ifdef Solaris if (statvfs(path, &f) < 0) e_err(1, "statvfs: %s", path); #else if (statfs(path, &f) < 0) e_err(1, "statfs: %s", path); #endif #ifdef ULTRIX if ((f.fd_req.bfreen * 1024) < minfree) #else if (((long long)f.f_bsize * f.f_bavail) < minfree) #endif e_errx(1, "%s: Not enough space left", path); } void set_mirror_uid(char *mirroruser) { struct passwd *pw; if (mirroruser == NULL) return; if ((pw = getpwnam(mirroruser)) == NULL) e_err(1, "getpwnam(%s)", mirroruser); if (seteuid(pw->pw_uid) < 0) e_err(1, "seteuid(%d)", pw->pw_uid); } static void set_param_str(int option, const char *arg, char **var) { if (*var != NULL) { if (!nowarn) e_warnx("overriding %s's valule '%s' with '%s'", go[option].go_name, *var, arg); free(*var); } *var = strdup(arg); } static void set_param_yes_no(int option, const char *arg, int **var) { int tmp; tmp = 0; /* Silent gcc */ if (strlen(arg) == 0 || strcmp(arg, "yes") == 0) tmp = 1; else if (strcmp(arg, "no") == 0) tmp = 0; else e_errx(1, "%s's argument '%s' invalid", go[option].go_name, arg); if (*var != NULL) { if (!nowarn) e_warnx("overriding %s's value '%s' with '%s'", go[option].go_name, **var ? "yes" : "no", arg); } else { *var = malloc(sizeof(tmp)); if (*var == NULL) e_err(1, "malloc"); } **var = tmp; } static void set_param_num(int option, const char *arg, int **var, int min_val, int max_val) { int tmp; char *endptr; tmp = (int) strtol(arg, &endptr, 10); if (*endptr != '\0' || tmp > max_val || tmp < min_val) e_errx(1, "%s's argument '%s' invalid", go[option].go_name, arg); if (*var != NULL) { if (!nowarn) e_warnx("overriding %s's value '%d' with '%s'", go[option].go_name, **var, arg); } else { *var = malloc(sizeof(tmp)); if (*var == NULL) e_err(1, "malloc"); } **var = tmp; } /* * Parses an url of the format: ftp://[user[:pass]@]host[:port]/path */ static void set_param_url(int option, const char *arg, char **user, char **passwd, char **host, int **port, char **path) { char *url, *p, *puser, *ppass, *phost, *pport, *ppath; puser = NULL; ppass = NULL; phost = NULL; pport = NULL; ppath = NULL; if ((url = strdup(arg)) == NULL) err(1, "strdup"); if (strncasecmp(url, "ftp://", 6) != 0) errx(1, "supports only ftp URL's"); url += 6; if (*url == '\0') errx(1, "short url: %s", arg); /* Parse eventual user:pass field */ if ((p = strchr(url, '@')) != NULL) { puser = url; url = p + 1; /* advance url to host */ *p = '\0'; /* NULL terminate user[:pass] field */ if ((p = strchr(puser, ':')) != NULL) { ppass = p + 1; *p = '\0'; /* NULL terminate user field */ set_param_str(GO_PASSWORD, ppass, passwd); } if (*url == '\0') errx(1, "short url: %s", arg); set_param_str(GO_USERNAME, puser, user); } /* Parse host + eventual port field */ if ((p = strchr(url, '/')) == NULL) errx(1, "short url: %s", arg); phost = url; url = p + 1; *p = '\0'; /* NULL terminate host[:port] field */ if ((p = strchr(phost, ':')) != NULL) { pport = p + 1; *p = '\0'; /* NULL terminate host field */ set_param_num(GO_PORT, pport, port, 0, INT_MAX); } set_param_str(GO_HOST, phost, host); /* Parse path field */ if (*url != '\0') { ppath = url - 1; *ppath = '/'; set_param_str(GO_REMOTEDIR, ppath, path); } } static void set_param_family(int option, const char *arg, int **var) { int family; char *f; family = PF_UNSPEC; /* Silent gcc */ if (strlen(arg) == 0 || strcmp(arg, "unspec") == 0) family = PF_UNSPEC; else if (strcmp(arg, "inet") == 0) family = PF_INET; #ifdef INET6 else if (strcmp(arg, "inet6") == 0) family = PF_INET6; #endif else e_errx(1, "%s's argument '%s' invalid", go[option].go_name, arg); if (*var != NULL) { if (!nowarn) { switch (**var) { case PF_UNSPEC: f = "unspec"; break; case PF_INET: f = "inet"; break; #ifdef INET6 case PF_INET6: f = "inet6"; break; #endif default: f = "unknown"; break; } e_warnx("overriding %s's value '%s' with '%s'", go[option].go_name, f, arg); } } else { *var = malloc(sizeof(family)); if (*var == NULL) e_err(1, "malloc"); } **var = family; } /* ARGSUSED */ static void add_param_sps(int option, const char *arg, struct cl_sps_que **q) { struct sp_skip *sps; option = 0; /* quiet gcc */ if (*q == NULL && ((*q = cl_sps_init()) == NULL)) e_err(1, "cl_sps_init"); if ((sps = sps_init(arg)) == NULL) e_err(1, "sps_init"); if (sps_error(sps)) e_errx(1, "sps_init: %s", sps_strerror(sps)); (void) cl_sps_push(*q, sps); } /* ARGSUSED */ static void add_param_spt(int option, const char *arg, struct cl_spt_que **q) { struct sp_treatasdir *spt; if (*q == NULL && ((*q = cl_spt_init()) == NULL)) e_err(1, "cl_spt_init"); if ((spt = spt_init(arg)) == NULL) e_err(1, "spt_init"); if (spt_error(spt)) e_errx(1, "spt_init: %s", spt_strerror(spt)); spt->spt_deref = (go[option].go_opt_num == GO_TREATASDIRDEST); (void)cl_spt_push(*q, spt); } static void require(const char *name, char *str, int show) { if (str == NULL) e_errx(1, "%s required", name); if (show) e_warnx("%s = '%s'", name, str); } static void default_str(const char *name, char **val, const char *str, int show) { if (*val == NULL) { *val = strdup(str); if (*val == NULL) e_err(1, "strdup"); } if (show) e_warnx("%s = '%s'", name, *val); } static void expand_with_str(const char *name, char **val, const char *str, int show) { char *buf, *p; int i, j; p = *val; if (p == NULL) return; if (strchr(p, '%') == NULL) goto ret; if ((buf = malloc((size_t)MAX_LINE)) == NULL) e_err(1, "malloc"); for (i = j = 0; i < MAX_LINE && p[j] != '\0'; i++, j++) { if (p[j] == '%') { switch (p[j + 1]) { case '%': j++; buf[i] = '%'; continue; case 's': j++; /* if buf gets full we will break the for-loop anyway */ i += (unsigned)strlcpy(buf + i, str, (size_t)(MAX_LINE - i)) - 1; continue; default: break; } } buf[i] = p[j]; } buf[i] = '\0'; *val = buf; ret: if (show) e_warnx("%s = '%s'", name, *val); } static void default_num(const char *name, int **val, int num, int show) { if (*val == NULL) { *val = malloc(sizeof(int)); if (*val == NULL) e_err(1, "malloc"); **val = num; } if (show) e_warnx("%s = %d", name, **val); } static void default_family(const char *name, int **val, int num, int show) { char *f; if (*val == NULL) { *val = malloc(sizeof(int)); if (*val == NULL) e_err(1, "malloc"); **val = num; } if (show) { switch (**val) { case PF_UNSPEC: f = "unspec"; break; case PF_INET: f = "inet"; break; #ifdef INET6 case PF_INET6: f = "inet6"; break; #endif default: f = "unknown"; break; } e_warnx("%s = %s", name, f); } } static void show_skip_map_fun(struct sp_skip *sps) { e_warnx("skip = '%s'", sps->sps_name); } static void show_treatasdir_map_fun(struct sp_treatasdir *spt) { e_warnx("treatasdir%s = '%s'", spt->spt_deref ? "dest" : "", spt->spt_name); } static void usage(void) { e_errx(1, "usage: spegla -f \n" "lots of other options are also supported\n" "please consult man page\n"); } int main(int argc, char * const *argv) { FILE *fp; char *temp_file, anonpass[MAX_LINE]; const char *arg, *p; int option; int fd_lock; int read_from_file; struct stat sb; struct ftp_con *c; struct heap_spf_list *l, *r; struct get_opt_state *gos_argv, *gos_file; struct { char *host; char *localdir; char *remotedir; char *username; char *password; char *logfile; char *lockfile; char *configfile; char *tempdir; char *version; char *section; char *mirroruser; int *timeout; int *retries; int *port; int *family; int *passive; int *retrytime; int *showconf; int *fastsync; int *maxdelete; int *loglevel; int *warnoverrides; int *minfree; int *dodelete; struct cl_sps_que *skip; struct cl_spt_que *treatasdir; } param; (void) memset(¶m, 0, sizeof(param)); param.tempdir = getenv("TEMPDIR"); /* If malloc fails exit */ container_err_exit = 1; read_from_file = 0; setup_signals(); if (argc < 2) usage(); sp_deletes = sp_dirs_traversed = sp_errors = 0; gos_argv = tgetopt_argv_init(argc, argv, go); if (gos_argv == NULL) e_err(1, "tgetopt_argv_init"); gos_file = NULL; fp = NULL; for ( ;; ) { /* * if there is a file to read from, read from it * otherwise read from command line arguments */ if (gos_file != NULL) { if (tgetopt(gos_file, &option, &arg) < 0) { if (tgetopt_errno(gos_file) == GOS_END_OF_OPTIONS) { tgetopt_free(gos_file); gos_file = NULL; (void) fclose(fp); fp = NULL; continue; } e_errx(1, tgetopt_strerror(gos_file)); } } else if (tgetopt(gos_argv, &option, &arg) < 0) { if (tgetopt_errno(gos_argv) == GOS_END_OF_OPTIONS) break; e_errx(1, tgetopt_strerror(gos_argv)); tgetopt_free(gos_argv); } switch(go[option].go_opt_num) { case GO_FAMILY: set_param_family(option, arg, ¶m.family); break; case GO_LOCALDIR: set_param_str(option, arg, ¶m.localdir); break; case GO_REMOTEDIR: set_param_str(option, arg, ¶m.remotedir); break; case GO_USERNAME: set_param_str(option, arg, ¶m.username); break; case GO_PASSWORD: set_param_str(option, arg, ¶m.password); break; case GO_HOST: set_param_str(option, arg, ¶m.host); break; case GO_RETRIES: set_param_num(option, arg, ¶m.retries, 0, INT_MAX); break; case GO_RETRYTIME: set_param_num(option, arg, ¶m.retrytime, 0, INT_MAX); break; case GO_TIMEOUT: set_param_num(option, arg, ¶m.timeout, 0, INT_MAX); break; case GO_PORT: set_param_num(option, arg, ¶m.port, 0, INT_MAX); break; case GO_MINFREE: set_param_num(option, arg, ¶m.minfree, 0, INT_MAX); break; case GO_PASSIVE: set_param_yes_no(option, arg, ¶m.passive); break; case GO_SKIP: add_param_sps(option, arg, ¶m.skip); break; case GO_TREATASDIR: add_param_spt(option, arg, ¶m.treatasdir); break; case GO_TREATASDIRDEST: add_param_spt(option, arg, ¶m.treatasdir); break; case GO_LOGFILE: set_param_str(option, arg, ¶m.logfile); break; case GO_LOCKFILE: set_param_str(option, arg, ¶m.lockfile); break; case GO_MIRRORUSER: set_param_str(option, arg, ¶m.mirroruser); break; case GO_MAXDELETE: set_param_num(option, arg, ¶m.maxdelete, 0, INT_MAX); break; case GO_LOGLEVEL: set_param_num(option, arg, ¶m.loglevel, 0, INT_MAX); break; case GO_SHOWCONF: set_param_yes_no(option, arg, ¶m.showconf); break; case GO_FASTSYNC: set_param_yes_no(option, arg, ¶m.fastsync); break; case GO_DODELETE: set_param_yes_no(option, arg, ¶m.dodelete); break; case GO_WARNOVERRIDES: set_param_yes_no(option, arg, ¶m.warnoverrides); nowarn = !*param.warnoverrides; break; case GO_TEMPDIR: set_param_str(option, arg, ¶m.tempdir); break; case GO_HELP: usage(); break; case GO_URL: set_param_url(option, arg, ¶m.username, ¶m.password, ¶m.host, ¶m.port, ¶m.remotedir); default_str("localdir", ¶m.localdir, ".", 0); break; case GO_VERSION: if (arg == NULL) e_errx(0, "Spegla version" SPEGLA_VERSION); set_param_str(option, arg, ¶m.version); if (strcmp(param.version, SPEGLA_VERSION) != 0 && strcmp(param.version, "1.1") != 0) e_errx(1, "expected version " SPEGLA_VERSION ", got %s", param.version); break; case GO_SECTION: /* don't allow sections to be choosen in config file */ if (fp != NULL) e_errx(1, "can't select a new section in config file"); set_param_str(option, arg, ¶m.section); break; case GO_CONFIGFILE: /* * Switch to config file imediatly and continue * with the other command line options later, * don't allow config files specified in a config file. */ read_from_file = 1; if (fp != NULL) e_errx(1, "already reading from a config file"); if (lstat(arg, &sb) < 0) e_err(1, "stat"); if (!S_ISREG(sb.st_mode)) e_errx(1, "%s is not a regular file", arg); if ((fp = fopen(arg, "r")) == NULL) e_err(1, "fopen(%s, \"r\")", arg); if ((gos_file = tgetopt_file_init(fp, param.section, go)) == NULL) e_err(1, "tgetopt_file_init"); break; default: e_errx(1, "unknown option number %d", option); } } e_set_progname(NULL); if ((p = getenv("FTPANONPASS")) != NULL) { strlcpy(anonpass, p, sizeof(anonpass)); } else { if ((p = getenv("USER")) == NULL) p = "default"; snprintf(anonpass, sizeof(anonpass), "%s@", p); } /* check configed variables and show them if asked for */ default_num("showconf", ¶m.showconf, 1, 0); /* Only require the version option if a file has been read from. */ if (read_from_file) require("version", param.version, *param.showconf); require("host", param.host, *param.showconf); require("localdir", param.localdir, *param.showconf); require("remotedir",param.remotedir,*param.showconf); default_str("username",¶m.username, "anonymous",*param.showconf); default_str("password", ¶m.password, anonpass, *param.showconf); default_str("section", ¶m.section, "", *param.showconf); default_str("version", ¶m.version, "not defined",*param.showconf); default_str("tempdir", ¶m.tempdir, "/tmp", *param.showconf); default_num("timeout", ¶m.timeout, 150,*param.showconf); default_num("retries", ¶m.retries, 20, *param.showconf); default_num("port", ¶m.port, 21, *param.showconf); default_family("family", ¶m.family, PF_UNSPEC, *param.showconf); default_num("passive", ¶m.passive, 1, *param.showconf); default_num("retrytime",¶m.retrytime, 150,*param.showconf); default_num("fastsync", ¶m.fastsync, 0, *param.showconf); default_num("dodelete", ¶m.dodelete, 0, *param.showconf); default_num("maxdelete",¶m.maxdelete, 150,*param.showconf); default_num("showconf", ¶m.showconf, 1, *param.showconf); default_num("loglevel", ¶m.loglevel, 0, *param.showconf); default_num("warnoverrides",¶m.warnoverrides,1,*param.showconf); default_num("minfree", ¶m.minfree, 0, *param.showconf); if (*param.showconf) { e_warnx("logfile = '%s'", param.logfile == NULL ? "stdout" : param.logfile); if (param.skip) cl_sps_map(param.skip, show_skip_map_fun, NULL); if (param.treatasdir) cl_spt_map(param.treatasdir, show_treatasdir_map_fun, NULL); } expand_with_str("logfile", ¶m.logfile, param.section, *param.showconf); expand_with_str("lockfile", ¶m.lockfile, param.section, *param.showconf); skip = param.skip; treatasdir = param.treatasdir; sp_max_delete = *param.maxdelete; sp_retrytime = *param.retrytime; sp_do_delete = *param.dodelete; /**** End of configuration analysis ****/ check_minfree(*param.minfree, param.localdir); if (param.logfile == NULL || strcmp(param.logfile, "stdout") == 0) fp_log_file = stdout; else if (strcmp(param.logfile, "stderr") == 0) fp_log_file = stderr; else { if ((fp_log_file = fopen(param.logfile, "a")) == NULL) e_err(1, "can't open %s for writing", param.logfile); } (void)atexit(clean_up_vars); if (param.lockfile != NULL) { if ((fd_lock = open(param.lockfile, O_CREAT | O_EXCL, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) < 0 ) { e_err(1, "can't create lock file %s", param.lockfile); } if (atexit(remove_lock) < 0) e_warn("warning: atexit(3) failed lock file, %s, " "will not be removed on exit", param.lockfile); lock_file = param.lockfile; (void) close(fd_lock); } if (atexit(print_stats) < 0) e_warn("warning: atexit(3) failed, no stats will be printed on exit"); set_mirror_uid(param.mirroruser); if ((start_wd = malloc((size_t)PATH_MAX)) == NULL) e_err(1, "malloc"); (void) getcwd(start_wd, (size_t)PATH_MAX); (void) strcpy(cur_dir, "./"); if (chdir(param.localdir)) e_err(1, "spegla: can't cd to %s", param.localdir); (void) time(&start_time); e_set_file(fp_log_file); e_warnx("Spegla started at %s", ctime(&start_time)); e_warnx("Logging in to %s", param.host); do { c = ftp_login(param.host, *param.port, *param.family, param.username, param.password, fp_log_file, *param.loglevel); if (c == NULL) { if ((*param.retries)-- < 0) e_errx(1, "I've retried enough now, quit"); e_warnx("Login failed"); e_warnx("sleeping for %d seconds...", sp_retrytime); (void) sleep(sp_retrytime); e_warnx("trying to login again"); } } while (c == NULL); connection = c; ftp_set_passive(c, *param.passive); ftp_set_tempdir(c, param.tempdir); if (ftp_req(c, "TYPE I") < 0) e_err(1, "couldn't set type to bin, quit"); ftp_set_timeout_val(c, *param.timeout); c->ftp_retries = *param.retries; e_warnx("cd %s", param.remotedir); if (ftp_cd(c, param.remotedir) < 0) e_errx(1, "cd '%s': failed, abort", param.remotedir); e_warnx(". is now '%s'", param.remotedir); e_warnx("list ."); temp_file = ftp_dir(c, "."); if (temp_file == NULL) e_errx(1, "list of .: failed, abort"); #if 0 #define SP_FREE(var) if (param.var) free(param.var) SP_FREE(host); SP_FREE(localdir); SP_FREE(remotedir); SP_FREE(username); SP_FREE(password); SP_FREE(logfile); SP_FREE(lockfile); SP_FREE(configfile); SP_FREE(tempdir); SP_FREE(version); SP_FREE(section); SP_FREE(timeout); SP_FREE(retries); SP_FREE(port); SP_FREE(family); SP_FREE(passive); SP_FREE(retrytime); SP_FREE(showconf); SP_FREE(fastsync); SP_FREE(maxdelete); SP_FREE(loglevel); SP_FREE(warnoverrides); SP_FREE(minfree); #endif fp = fopen(temp_file, "r"); r = build_remote_dir(fp); (void) fclose(fp); (void) unlink(temp_file); free(temp_file); l = build_local_dir(); sp_sync_dir(c, l, r); (void) chdir(start_wd); (void) ftp_bye(c); exit(0); /* NOTREACHED */ }