#include #include #include #include #include #include #include #include #include "nbase.h" #ifndef WIN32 #include #include #include #include #include "config.h" #else #include #include "win_config.h" #endif /* See the file tools/examples/minimal_client.c in the Subversion source directory for an example of using the svn_client API. */ #if HAVE_SUBVERSION_1_SVN_CLIENT_H #include #include #include #include #include #else #include #include #include #include #include #endif /* From svn_auth.c. */ svn_error_t * nmap_update_svn_cmdline_setup_auth_baton(svn_auth_baton_t **ab, svn_boolean_t non_interactive, const char *auth_username, const char *auth_password, const char *config_dir, svn_boolean_t no_auth_cache, svn_config_t *cfg, svn_cancel_func_t cancel_func, void *cancel_baton, apr_pool_t *pool); #include "default_channel.h" #ifdef WIN32 #define PATHSEP "\\" #else #define PATHSEP "/" #endif static const char *DEFAULT_SVN_REPO = "https://svn.nmap.org/updates"; static const char *DEFAULT_CHANNELS[] = { DEFAULT_CHANNEL }; /* Internal error handling. */ #define NELEMS(a) (sizeof(a) / sizeof(*a)) #define internal_error(msg) \ do {\ fprintf(stderr, "%s:%d: internal error: %s.\n", __FILE__, __LINE__, msg); \ abort(); \ } while (0) #define internal_assert(expr) \ do { \ if (!(expr)) \ internal_error("assertion failed: " #expr); \ } while (0) static char *safe_strdup(const char *s) { char *t; size_t len; len = strlen(s); t = safe_malloc(len + 1); memcpy(t, s, len); t[len] = '\0'; return t; } static int streq(const char *a, const char *b) { return strcmp(a, b) == 0; } static char *string_make(const char *begin, const char *end) { char *s; s = safe_malloc(end - begin + 1); memcpy(s, begin, end - begin); s[end - begin] = '\0'; return s; } static char *strbuf_append(char **buf, size_t *size, size_t *offset, const char *s, size_t n) { internal_assert(*offset <= *size); /* Double the buffer size if necessary. */ if (n >= *size - *offset) { *size = (*size + n) * 2; *buf = safe_realloc(*buf, *size + 1); } memcpy(*buf + *offset, s, n); *offset += n; (*buf)[*offset] = '\0'; return *buf; } /* Append a '\0'-terminated string as with strbuf_append. */ static char *strbuf_append_str(char **buf, size_t *size, size_t *offset, const char *s) { return strbuf_append(buf, size, offset, s, strlen(s)); } static char *strbuf_append_char(char **buf, size_t *size, size_t *offset, char c) { return strbuf_append(buf, size, offset, &c, 1); } static char *strbuf_trim(char **buf, size_t *size, size_t *offset) { if (*offset < *size) { *size = *offset; *buf = safe_realloc(*buf, *size + 1); } internal_assert((*buf)[*size] == '\0'); return *buf; } static char *string_unescape(const char *escaped) { char *buf; size_t size, offset; const char *p; buf = NULL; size = 0; offset = 0; p = escaped; while (*p != '\0') { char hex[3], *tail; unsigned long byte; /* We support backslash escapes for '\\' and '"', and \xXX hexadecimal only. */ if (*p == '\\') { p++; switch (*p) { case '\\': case '"': strbuf_append_char(&buf, &size, &offset, *p); p++; break; case 'x': p++; if (!(isxdigit(*p) && isxdigit(*(p + 1)))) goto bail; memcpy(hex, p, 2); hex[2] = '\0'; errno = 0; byte = strtoul(hex, &tail, 16); if (errno != 0 || byte > 255 || *tail != '\0') goto bail; strbuf_append_char(&buf, &size, &offset, (char) byte); p += 2; break; default: goto bail; break; } } else { strbuf_append_char(&buf, &size, &offset, *p); p++; } } return strbuf_trim(&buf, &size, &offset); bail: if (buf != NULL) free(buf); return NULL; } /* Return a newly allocated string that is the concatenation of all the va_list args, separated by join: str1 JOIN str2 JOIN str3 ... The final argument must be NULL. */ static char *strs_vjoin(const char *join, const char *first, va_list ap) { char *buf; size_t size, offset; const char *p; internal_assert(first != NULL); buf = NULL; size = 0; offset = 0; strbuf_append_str(&buf, &size, &offset, first); while ((p = va_arg(ap, const char *)) != NULL) { strbuf_append_str(&buf, &size, &offset, join); strbuf_append_str(&buf, &size, &offset, p); } strbuf_trim(&buf, &size, &offset); return buf; } static char *strs_cat(const char *first, ...) { va_list ap; char *result; va_start(ap, first); result = strs_vjoin("", first, ap); va_end(ap); return result; } static char *path_join(const char *first, ...) { va_list ap; char *result; va_start(ap, first); result = strs_vjoin(PATHSEP, first, ap); va_end(ap); return result; } #ifdef WIN32 static char *get_user_dir(const char *subdir) { char appdata[MAX_PATH]; if (SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, appdata) != S_OK) return NULL; return path_join(appdata, "nmap", subdir, NULL); } #else static char *get_user_dir(const char *subdir) { static struct passwd *pw; errno = 0; pw = getpwuid(getuid()); if (pw == NULL) return NULL; return path_join(pw->pw_dir, ".nmap", subdir, NULL); } #endif static char *get_install_dir(void) { return get_user_dir("updates"); } static char *get_staging_dir(void) { return get_user_dir("updates-staging"); } static char *get_conf_filename(void) { return get_user_dir("nmap-update.conf"); } /* Configuration file parsing. */ enum token_type { TOKEN_ERROR, TOKEN_EOL, TOKEN_EOF, TOKEN_WORD, TOKEN_EQUALS, TOKEN_STRING, }; struct config_parser { FILE *fp; unsigned long lineno; }; struct config_entry { char *key; char *value; }; static void config_entry_free(struct config_entry *entry) { free(entry->key); free(entry->value); } static int config_parser_open(const char *filename, struct config_parser *cp) { cp->fp = fopen(filename, "r"); if (cp->fp == NULL) return -1; cp->lineno = 1; return 0; } static int config_parser_close(struct config_parser *cp) { int ret; ret = fclose(cp->fp); if (ret == EOF) return -1; return 0; } static int is_word_char(int c) { return c != EOF && !isspace(c) && c != '"' && c != '#'; } static char *read_quoted_string(struct config_parser *cp) { char *buf, *unescaped; size_t size, offset; int c; buf = NULL; size = 0; offset = 0; for (;;) { errno = 0; c = fgetc(cp->fp); if (c == EOF) /* EOF in the middle of a string is always an error. */ return NULL; if (c == '\n') return NULL; if (c == '"') break; if (c == '\\') { strbuf_append_char(&buf, &size, &offset, c); errno = 0; c = fgetc(cp->fp); if (c == EOF) return NULL; } strbuf_append_char(&buf, &size, &offset, c); } unescaped = string_unescape(buf); free(buf); return unescaped; } static enum token_type config_parser_read_token(struct config_parser *cp, char **token) { size_t size, offset; unsigned long prev_lineno; int c; *token = NULL; size = 0; offset = 0; /* Skip comments and blank space. */ prev_lineno = cp->lineno; do { errno = 0; while (isspace(c = fgetc(cp->fp))) { if (c == '\n') cp->lineno++; } if (c == EOF) { if (errno != 0) goto bail; *token = NULL; return TOKEN_EOF; } if (c == '#') { while ((c = fgetc(cp->fp)) != EOF && c != '\n') ; if (c == EOF) { if (errno != 0) goto bail; *token = NULL; return TOKEN_EOF; } else if (c == '\n') { cp->lineno++; } } } while (isspace(c) || c == '#'); /* Collapse multiple consecutive line endings. */ if (cp->lineno != prev_lineno) { ungetc(c, cp->fp); *token = NULL; return TOKEN_EOL; } if (c == '=') { strbuf_append_char(token, &size, &offset, c); return TOKEN_EQUALS; } else if (is_word_char(c)) { while (is_word_char(c)) { strbuf_append_char(token, &size, &offset, c); errno = 0; c = fgetc(cp->fp); if (c == EOF && errno != 0) goto bail; } return TOKEN_WORD; } else if (c == '"') { char *qs; qs = read_quoted_string(cp); if (qs == NULL) goto bail; *token = safe_strdup(qs); return TOKEN_STRING; } else { goto bail; } bail: if (*token != NULL) free(*token); *token = NULL; return TOKEN_ERROR; } static int config_parser_next(struct config_parser *cp, struct config_entry *entry) { char *token; enum token_type type; while ((type = config_parser_read_token(cp, &token)) == TOKEN_EOL) ; if (type == TOKEN_EOF) { free(token); return 0; } if (type != TOKEN_WORD) { free(token); return -1; } entry->key = token; type = config_parser_read_token(cp, &token); if (type != TOKEN_EQUALS) { free(token); return -1; } free(token); type = config_parser_read_token(cp, &token); if (!(type == TOKEN_WORD || type == TOKEN_STRING)) { free(token); return -1; } entry->value = token; return 1; } /* Global state. */ static char *program_name; static struct { int verbose; const char *install_dir; const char *staging_dir; const char *conf_filename; const char **channels; unsigned int num_channels; char *svn_repo; char *username; char *password; } options; struct metadata { int is_expired; time_t expiry_date; }; static void metadata_init(struct metadata *metadata) { metadata->is_expired = 0; metadata->expiry_date = 0; } static void init_options(void) { options.verbose = 0; options.install_dir = get_install_dir(); if (options.install_dir == NULL) { fprintf(stderr, "Could not find an install directory: %s.\n", strerror(errno)); exit(1); } options.staging_dir = get_staging_dir(); if (options.staging_dir == NULL) { fprintf(stderr, "Could not find a staging directory: %s.\n", strerror(errno)); exit(1); } options.conf_filename = get_conf_filename(); if (options.conf_filename == NULL) { fprintf(stderr, "Could not find the configuration file: %s.\n", strerror(errno)); exit(1); } options.channels = DEFAULT_CHANNELS; options.num_channels = NELEMS(DEFAULT_CHANNELS); options.svn_repo = NULL; options.username = NULL; options.password = NULL; } static int read_config_file(const char *conf_filename) { struct config_parser cp; struct config_entry entry; int ret; if (options.verbose) printf("Trying to open configuration file %s.\n", conf_filename); errno = 0; if (config_parser_open(conf_filename, &cp) == -1) { if (options.verbose) printf("Failed to open %s: %s.\n", conf_filename, strerror(errno)); return -1; } while ((ret = config_parser_next(&cp, &entry)) > 0) { if (streq(entry.key, "username")) { if (options.username != NULL) { fprintf(stderr, "Warning: %s:%lu: duplicate \"%s\".\n", conf_filename, cp.lineno, entry.key); free(options.username); } options.username = safe_strdup(entry.value); } else if (streq(entry.key, "password")) { if (options.password != NULL) { fprintf(stderr, "Warning: %s:%lu: duplicate \"%s\".\n", conf_filename, cp.lineno, entry.key); free(options.password); } options.password = safe_strdup(entry.value); } else if (streq(entry.key, "repo")) { if (options.svn_repo != NULL) { fprintf(stderr, "Warning: %s:%lu: duplicate \"%s\".\n", conf_filename, cp.lineno, entry.key); free(options.svn_repo); } options.svn_repo = safe_strdup(entry.value); } else { fprintf(stderr, "Warning: %s:%lu: unknown key \"%s\".\n", conf_filename, cp.lineno, entry.key); } config_entry_free(&entry); } if (ret == -1) { fprintf(stderr, "Parse error on line %lu of %s.\n", cp.lineno, conf_filename); exit(1); } errno = 0; if (config_parser_close(&cp) == -1) { if (options.verbose) printf("Failed to close %s: %s.\n", conf_filename, strerror(errno)); return -1; } return 0; } static int parse_date(const char *s, time_t *t) { struct tm tm = {0}; if (sscanf(s, "%d-%d-%d", &tm.tm_year, &tm.tm_mon, &tm.tm_mday) != 3) return -1; tm.tm_year -= 1900; tm.tm_mon -= 1; *t = mktime(&tm); if (*t == -1) return -1; return 0; } static int date_is_after(time_t t, time_t now) { return difftime(t, now) > 0; } static int read_metadata_file(const char *metadata_filename, struct metadata *metadata) { struct config_parser cp; struct config_entry entry; int ret; errno = 0; if (config_parser_open(metadata_filename, &cp) == -1) { /* A missing file is not an error for metadata. */ return 0; } while ((ret = config_parser_next(&cp, &entry)) > 0) { if (streq(entry.key, "expired")) { if (parse_date(entry.value, &metadata->expiry_date) == -1) { fprintf(stderr, "Warning: %s:%lu: can't parse date \"%s\".\n", metadata_filename, cp.lineno, entry.value); } else { if (date_is_after(time(NULL), metadata->expiry_date)) metadata->is_expired = 1; } } else { fprintf(stderr, "Warning: %s:%lu: unknown key \"%s\".\n", metadata_filename, cp.lineno, entry.key); } config_entry_free(&entry); } if (ret == -1) { fprintf(stderr, "Parse error on line %lu of %s.\n", cp.lineno, metadata_filename); config_parser_close(&cp); return -1; } errno = 0; if (config_parser_close(&cp) == -1) { if (options.verbose) printf("Failed to close %s: %s.\n", metadata_filename, strerror(errno)); return -1; } return 0; } static void usage(FILE *fp) { char *install_dir; internal_assert(program_name != NULL); install_dir = get_install_dir(); fprintf(fp, "\ Usage: %s [-d INSTALL_DIR] [CHANNEL...]\n\ Updates system-independent Nmap files. By default the new files are installed to\n\ %s. Each CHANNEL is a version number like \"" DEFAULT_CHANNEL "\".\n\ \n\ -d DIR install files to DIR (default %s).\n\ -h, --help show this help.\n\ -r, --repo REPO use REPO as SVN repository and path (default %s).\n\ -v, --verbose be more verbose.\n\ --username USERNAME use this username.\n\ --password PASSWORD use this password.\n\ ", program_name, install_dir, install_dir, DEFAULT_SVN_REPO); free(install_dir); } static void usage_error(void) { usage(stderr); exit(1); } static const char *try_channels(const char *channels[], unsigned int num_channels); static int stage_and_install(const char *channel); static int stage_channel(const char *channel, const char *staging_dir); static int install(const char *staging_dir, const char *install_dir); static int channel_is_expired(const char *channel, time_t *expiry_date); static void summarize_options(void) { unsigned int i; printf("Installing to directory: %s.\n", options.install_dir); printf("Using staging directory: %s.\n", options.staging_dir); printf("Using channels:"); for (i = 0; i < options.num_channels; i++) printf(" %s", options.channels[i]); printf(".\n"); } const struct option LONG_OPTIONS[] = { { "help", no_argument, NULL, 'h' }, { "repo", required_argument, NULL, 'r' }, { "verbose", required_argument, NULL, 'v' }, { "username", required_argument, NULL, '?' }, { "password", required_argument, NULL, '?' }, }; int main(int argc, char *argv[]) { int opt, longoptidx; const char *successful_channel; const char *username, *password, *svn_repo; time_t expiry_date; internal_assert(argc > 0); program_name = argv[0]; init_options(); if (svn_cmdline_init(program_name, stderr) != 0) internal_error("svn_cmdline_init"); username = NULL; password = NULL; svn_repo = NULL; while ((opt = getopt_long(argc, argv, "d:hr:v", LONG_OPTIONS, &longoptidx)) != -1) { if (opt == 'd') { options.install_dir = optarg; } else if (opt == 'h') { usage(stdout); exit(0); } else if (opt == 'r') { svn_repo = optarg; } else if (opt == 'v') { options.verbose = 1; } else if (opt == '?' && streq(LONG_OPTIONS[longoptidx].name, "username")) { username = optarg; } else if (opt == '?' && streq(LONG_OPTIONS[longoptidx].name, "password")) { password = optarg; } else { usage_error(); } } /* User-specified channels. */ if (optind < argc) { options.channels = (const char **) argv + optind; options.num_channels = argc - optind; } internal_assert(options.channels != NULL); internal_assert(options.num_channels > 0); if (options.verbose) summarize_options(); read_config_file(options.conf_filename); /* Default options. */ if (options.svn_repo == NULL) options.svn_repo = safe_strdup(DEFAULT_SVN_REPO); /* Possibly override configuration file. */ if (username != NULL) { free(options.username); options.username = safe_strdup(username); } if (password != NULL) { free(options.password); options.password = safe_strdup(password); } if (svn_repo != NULL) { free(options.svn_repo); options.svn_repo = safe_strdup(svn_repo); } successful_channel = try_channels(options.channels, options.num_channels); if (successful_channel != NULL && channel_is_expired(successful_channel, &expiry_date)) { fprintf(stderr, "\ \n\ UPDATE CHANNEL %s HAS EXPIRED:\n\ \n\ The channel %s has expired and won't receive any more\n\ updates. Visit https://nmap.org for a newer Nmap release with \n\ supported updates.\n\ ", successful_channel, successful_channel); } if (successful_channel == NULL && options.username == NULL) { fprintf(stderr, "\ \n\ Could not stage any channels and don't have authentication credentials.\n\ \n\ Edit the file %s and enter your username and password. For example:\n\ username = user\n\ password = secret\n\ ", options.conf_filename); } if (successful_channel != NULL) return 0; else return 1; } static const char *try_channels(const char *channels[], unsigned int num_channels) { unsigned int i; for (i = 0; i < num_channels; i++) { if (stage_and_install(channels[i]) == 0) return channels[i]; } return NULL; } static void fatal_err_svn(svn_error_t *err) { svn_handle_error2(err, stderr, TRUE, "nmap-update: "); } static svn_error_t *checkout_svn(const char *url, const char *path) { svn_error_t *err; apr_pool_t *pool; svn_opt_revision_t peg_revision, revision; svn_client_ctx_t *ctx; svn_revnum_t revnum; svn_config_t *cfg; peg_revision.kind = svn_opt_revision_unspecified; revision.kind = svn_opt_revision_head; pool = svn_pool_create(NULL); err = svn_client_create_context(&ctx, pool); if (err != NULL) fatal_err_svn(err); /* The creation of this directory is needed to cache credentials. */ err = svn_config_ensure(NULL, pool); if (err != NULL) fatal_err_svn(err); err = svn_config_get_config(&ctx->config, NULL, pool); if (err != NULL) fatal_err_svn(err); cfg = apr_hash_get(ctx->config, SVN_CONFIG_CATEGORY_CONFIG, APR_HASH_KEY_STRING); svn_config_set_bool(cfg, SVN_CONFIG_SECTION_GLOBAL, SVN_CONFIG_OPTION_SSL_TRUST_DEFAULT_CA, TRUE); nmap_update_svn_cmdline_setup_auth_baton(&ctx->auth_baton, FALSE, /* non_interactive */ options.username, /* username */ options.password, /* password */ NULL, /* config_dir */ FALSE, /* no_auth_cache */ cfg, /* cfg */ NULL, /* cancel_func */ NULL, /* cancel_baton */ pool); err = svn_client_checkout2(&revnum, url, path, &peg_revision, &revision, TRUE, /* recurse */ TRUE, /* ignore_externals */ ctx, pool); svn_pool_destroy(pool); if (err != NULL) return err; printf("Checked out r%lu\n", (unsigned long) revnum); return SVN_NO_ERROR; } static int stage_and_install(const char *channel) { char *staging_dir, *install_dir; int rc; internal_assert(options.staging_dir != NULL); staging_dir = path_join(options.staging_dir, channel, NULL); rc = stage_channel(channel, staging_dir); if (rc == -1) { free(staging_dir); return -1; } install_dir = path_join(options.install_dir, channel, NULL); rc = install(staging_dir, install_dir); free(staging_dir); free(install_dir); return rc; } static int stage_channel(const char *channel, const char *staging_dir) { char *svn_url; svn_error_t *err; int rc; rc = 0; svn_url = strs_cat(options.svn_repo, "/", channel, NULL); if (options.verbose) printf("Checking out %s to %s.\n", svn_url, staging_dir); printf("\ \n\ The Nmap Updater is currently only available to a small set of users\n\ for testing purposes. We hope to expand it in the future.\n\ \n\ "); err = checkout_svn(svn_url, staging_dir); if (err != NULL) { svn_handle_error2(err, stderr, FALSE, "nmap-update: "); fprintf(stderr, "Error checking out %s.\n", svn_url); rc = -1; } free(svn_url); return rc; } static int channel_is_expired(const char *channel, time_t *expiry_date) { char *metadata_filename; struct metadata metadata; int rc; metadata_init(&metadata); metadata_filename = path_join(options.staging_dir, channel, "metadata.conf", NULL); rc = read_metadata_file(metadata_filename, &metadata); if (rc == -1) { fprintf(stderr, "Can't read metadata file %s.\n", metadata_filename); free(metadata_filename); exit(1); } free(metadata_filename); *expiry_date = metadata.expiry_date; return metadata.is_expired; } static int copy_tree(const char *from_dirname, const char *to_dirname); static int rename_file(const char *from_filename, const char *to_filename); static int install(const char *staging_dir, const char *install_dir) { if (options.verbose) printf("Installing from %s to %s.\n", staging_dir, install_dir); return copy_tree(staging_dir, install_dir); } static int copy_file(const char *from_filename, const char *to_filename) { char buf[BUFSIZ]; char *tmp_filename; FILE *from_fd, *tmp_fd; int rc, from_rc, tmp_rc; size_t nr, nw; tmp_filename = NULL; from_fd = NULL; tmp_fd = NULL; errno = 0; from_fd = fopen(from_filename, "rb"); if (from_fd == NULL) { fprintf(stderr, "Can't open %s: %s.\n", from_filename, strerror(errno)); goto bail; } tmp_filename = strs_cat(to_filename, "-tmp", NULL); errno = 0; tmp_fd = fopen(tmp_filename, "wb"); if (tmp_fd == NULL) { fprintf(stderr, "Can't open %s: %s.\n", tmp_filename, strerror(errno)); goto bail; } errno = 0; while ((nr = fread(buf, 1, sizeof(buf), from_fd)) != 0) { errno = 0; nw = fwrite(buf, 1, nr, tmp_fd); if (nw != nr || errno != 0) { printf("%lu %lu\n", nw, nr); fprintf(stderr, "Error writing to %s: %s.\n", tmp_filename, strerror(errno)); goto bail; } } if (errno != 0) { fprintf(stderr, "Error reading from %s: %s.\n", from_filename, strerror(errno)); goto bail; } from_rc = fclose(from_fd); from_fd = NULL; if (from_rc == -1) { fprintf(stderr, "Can't close %s: %s.\n", from_filename, strerror(errno)); goto bail; } tmp_rc = fclose(tmp_fd); tmp_fd = NULL; if (tmp_rc == -1) { fprintf(stderr, "Can't close %s: %s.\n", to_filename, strerror(errno)); goto bail; } rc = rename_file(tmp_filename, to_filename); if (rc == -1) { fprintf(stderr, "Can't rename %s to %s: %s.\n", tmp_filename, to_filename, strerror(errno)); goto bail; } free(tmp_filename); tmp_filename = NULL; return 0; bail: if (from_fd != NULL) fclose(from_fd); if (tmp_fd != NULL) fclose(tmp_fd); if (tmp_filename != NULL) free(tmp_filename); return -1; } static int is_pathsep(int c) { #ifdef WIN32 return c == '/' || c == '\\'; #else return c == '/'; #endif } static char *parent_dir(const char *path) { const char *p; p = path + strlen(path) - 1; while (p > path && is_pathsep(*p)) p--; while (p > path && !is_pathsep(*p)) p--; while (p > path && is_pathsep(*p)) p--; if (p == path) return safe_strdup("/"); return string_make(path, p + 1); } #ifdef WIN32 static int rename_file(const char *from_filename, const char *to_filename) { int rc; /* Windows rename doesn't remove the destination if it exists. */ errno = 0; rc = _unlink(to_filename); if (rc == -1 && errno != ENOENT) return -1; return rename(from_filename, to_filename); } static int makedir(const char *dirname) { return CreateDirectory(dirname, NULL) != 0 ? 0 : -1; } static int makedirs(const char *dirname) { char *parent; int rc; rc = makedir(dirname); if (rc == 0 || GetLastError() == ERROR_ALREADY_EXISTS) return 0; if (GetLastError() != ERROR_PATH_NOT_FOUND) return -1; parent = parent_dir(dirname); rc = makedirs(parent); free(parent); if (rc == -1) return -1; rc = makedir(dirname); if (rc == -1) return -1; return rc; } static int copy_tree(const char *from_dirname, const char *to_dirname) { WIN32_FIND_DATA ffd; HANDLE find_handle; DWORD dwError; char *from_pattern; int rc; rc = makedirs(to_dirname); if (rc == -1) { fprintf(stderr, "Can't create the directory %s: %s.\n", to_dirname, strerror(errno)); return -1; } from_pattern = path_join(from_dirname, "*", NULL); find_handle = FindFirstFile(from_pattern, &ffd); free(from_pattern); if (find_handle == INVALID_HANDLE_VALUE) { fprintf(stderr, "Can't open the directory %s.\n", from_dirname); return -1; } do { char *from_path, *to_path; int error; from_path = path_join(from_dirname, ffd.cFileName, NULL); to_path = path_join(to_dirname, ffd.cFileName, NULL); error = 0; if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { if (streq(ffd.cFileName, ".") || streq(ffd.cFileName, "..")) continue; if (streq(ffd.cFileName, ".svn")) continue; rc = makedirs(to_path); if (rc == 0) { rc = copy_tree(from_path, to_path); if (rc == -1) error = 1; } else { error = 1; } } else { rc = copy_file(from_path, to_path); if (rc == -1) error = 1; } free(from_path); free(to_path); if (error) goto bail; } while (FindNextFile(find_handle, &ffd) != 0); dwError = GetLastError(); if (dwError != ERROR_NO_MORE_FILES) { fprintf(stderr, "Error in FindFirstFile/FindNextFile.\n"); goto bail; } FindClose(find_handle); return 0; bail: FindClose(find_handle); return -1; } #else static int rename_file(const char *from_filename, const char *to_filename) { return rename(from_filename, to_filename); } static int makedir(const char *dirname) { return mkdir(dirname, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); } static int makedirs(const char *dirname) { char *parent; int rc; rc = makedir(dirname); if (rc == 0 || errno == EEXIST) return 0; if (errno != ENOENT) return -1; parent = parent_dir(dirname); rc = makedirs(parent); free(parent); if (rc == -1) return -1; rc = makedir(dirname); if (rc == -1) return -1; return rc; } static int copy_tree(const char *from_dirname, const char *to_dirname) { DIR *dir; const struct dirent *ent; int rc; rc = makedirs(to_dirname); if (rc == -1) { fprintf(stderr, "Can't create the directory %s: %s.\n", to_dirname, strerror(errno)); return -1; } dir = opendir(from_dirname); if (dir == NULL) { fprintf(stderr, "Can't open the directory %s: %s.\n", from_dirname, strerror(errno)); return -1; } errno = 0; while ((ent = readdir(dir)) != NULL) { char *from_path, *to_path; int error; from_path = path_join(from_dirname, ent->d_name, NULL); to_path = path_join(to_dirname, ent->d_name, NULL); error = 0; if (ent->d_type == DT_DIR) { if (streq(ent->d_name, ".") || streq(ent->d_name, "..")) continue; if (streq(ent->d_name, ".svn")) continue; rc = makedirs(to_path); if (rc == 0) { rc = copy_tree(from_path, to_path); if (rc == -1) error = 1; } else { error = 1; } } else if (ent->d_type == DT_REG) { rc = copy_file(from_path, to_path); if (rc == -1) error = 1; } else { fprintf(stderr, "Warning: unknown file type %u of %s.\n", ent->d_type, ent->d_name); } free(from_path); free(to_path); if (error) goto bail; } if (errno != 0) { fprintf(stderr, "Error in readdir: %s.\n", strerror(errno)); goto bail; } rc = closedir(dir); if (rc == -1) { fprintf(stderr, "Can't close the directory %s: %s.\n", from_dirname, strerror(errno)); return -1; } return 0; bail: closedir(dir); return -1; } #endif