Changeset: da504dd75c95 for MonetDB URL: https://dev.monetdb.org/hg/MonetDB?cmd=changeset;node=da504dd75c95 Modified Files: tools/merovingian/client/monetdb.c tools/merovingian/daemon/snapshot.c Branch: hot-snapshot Log Message:
Implement snapshot restore ad hoc diffs (285 lines): diff --git a/tools/merovingian/client/monetdb.c b/tools/merovingian/client/monetdb.c --- a/tools/merovingian/client/monetdb.c +++ b/tools/merovingian/client/monetdb.c @@ -1720,7 +1720,7 @@ snapshot_restore_adhoc(char *sourcefile, printf("done\n"); } } else { - fprintf(stderr, "snapshot restore: %s\n", out); + fprintf(stderr, "failed: %s\n", out); exit(1); } } diff --git a/tools/merovingian/daemon/snapshot.c b/tools/merovingian/daemon/snapshot.c --- a/tools/merovingian/daemon/snapshot.c +++ b/tools/merovingian/daemon/snapshot.c @@ -11,15 +11,19 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <sys/stat.h> +#include <sys/types.h> #include <time.h> #include "monetdb_config.h" +#include "stream.h" #include "msabaoth.h" #include "merovingian.h" #include "mapi.h" #include "snapshot.h" static err validate_location(const char *path); +static err unpack_tarstream(stream *tarstream, char *destdir, int skipcomponents); /* Create a snapshot of database dbname to file dest. * TODO: Make it work for databases without monetdb/monetdb root account. @@ -97,6 +101,11 @@ snapshot_restore_from(char *dbname, char err e; (void)dbname; (void)source; + sabdb *stats = NULL; + char *dbfarm = NULL; + char *tmppath = NULL; + char *destpath = NULL; + stream *instream = NULL; /* Do not read random files on the system. */ e = validate_location(source); @@ -104,9 +113,67 @@ snapshot_restore_from(char *dbname, char goto bailout; } - e = newErr("not implemented"); + /* For the time being, do not restore existing databases */ + e = msab_getStatus(&stats, dbname); + if (e != NO_ERR) + goto bailout; + if (stats != NULL) { + e = newErr("database '%s' already exists", dbname); + goto bailout; + } + + /* Figure out the directory to create */ + e = msab_getDBfarm(&dbfarm); + if (e != NO_ERR) + goto bailout; + tmppath = malloc(strlen(dbfarm) + strlen(dbname) + 100); + sprintf(tmppath, "%s/.tmp.%s", dbfarm, dbname); + destpath = malloc(strlen(dbfarm) + strlen(dbname) + 100); + sprintf(destpath, "%s/%s", dbfarm, dbname); + + /* Remove the tmp directory, if it exists. */ + e = deletedir(tmppath); + if (e != NO_ERR) + goto bailout; + + /* Open the file */ + instream = open_rstream(source); + if (instream == NULL) { + e = newErr("Could not open %s", source); + goto bailout; + } + + /* Now create the tmp directory */ + if (mkdir(tmppath, 0755) < 0) { + e = newErr("could not create dir '%s': %s", tmppath, strerror(errno)); + goto bailout; + } + + /* fill it */ + e = unpack_tarstream(instream, tmppath, 1); + if (e != NO_ERR) + goto bailout; + + /* remove whatever's laying around in the destination directory */ + e = deletedir(destpath); + if (e != NO_ERR) + goto bailout; + + /* and finally, move the thing into place */ + if (rename(tmppath, destpath) < 0) { + e = newErr("Couldn't rename '%s' to '%s': %s", + tmppath, destpath, strerror(errno)); + goto bailout; + } bailout: + if (stats != NULL) + msab_freeStatus(&stats); + if (instream != NULL) + close_stream(instream); + free(dbfarm); + free(tmppath); + free(destpath); return e; } @@ -200,12 +267,164 @@ validate_location(const char *dest) goto bailout; } - - bailout: free(resolved_snapdir); free(dest_dup); free(resolved_destination); return e; -} \ No newline at end of file +} + +#define TAR_BLOCK_SIZE (512) + +/* Read a full block from the stream. + * Return 0 on succes, -1 on failure. + * Set *err to NULL if the failure is caused by an end of file + * on a block boundary, non-NULL on read error. + */ +static int +read_tar_block(stream *s, char *block, err *error) +{ + ssize_t nread = mnstr_read(s, block, 1, TAR_BLOCK_SIZE); + if (nread <= 0) { + if (mnstr_errnr(s) != 0) { + /* failure */ + *error = newErr("Read error (%zd): %s", nread, mnstr_error(s)); + } else { + *error = NULL; + } + return -1; + } + if (nread < TAR_BLOCK_SIZE) { + *error = newErr("Incomplete read"); + return -1; + } + *error = NULL; + return 0; +} + +static char * +extract_tar_member_filename(const char *block) +{ + const char *name_field = block; + size_t name_len = strnlen(name_field, 100); + const char *prefix_field = block + 345; + size_t prefix_len = strnlen(prefix_field, 155); + char *buf = malloc(100 + 1 + 155 + 1); // prefix slash name nul + + if (prefix_len == 0) { + memmove(buf, name_field, name_len); + buf[name_len] = '\0'; + } else { + memmove(buf, prefix_field, prefix_len); + buf[prefix_len] = '/'; + memmove(buf + prefix_len + 1, name_field, name_len); + buf[prefix_len + 1 + name_len] = '\0'; + } + + return buf; +} + +static ssize_t +extract_tar_member_size(const char *block) +{ + size_t size; + char buf[13]; + memmove(buf, block + 124, 12); + buf[12] = '\0'; + if (sscanf(buf, "%zo", &size) != 1) + return -1; + return (ssize_t) size; +} + + +/* We're implementing our own untar instead of invoking /usr/bin/tar. Why? + * Wrapping /usr/bin/tar correctly is quite a lot of work. We have to fork, we + * have to get any error messages from tar to the child process and then to the + * parent process, etc. Also, we cannot depend on /usr/bin/tar being able to + * decompress any compression algorithms so to do it right we should decompress + * the data ourselves and pipe it to tar to unpack it. A lot of work and a lot + * of corner cases. Moreover, the snapshot generating tar code is also home + * grown. This is necessary because we snapshot parts of files, not whole files. + */ +static err +unpack_tarstream(stream *tarstream, char *destdir, int skipfirstcomponent) +{ + char block[TAR_BLOCK_SIZE]; + err e = NO_ERR; + char *whole_filename = NULL; + char *destfile = NULL; + FILE *outfile = NULL; + + while (read_tar_block(tarstream, block, &e) >= 0) { + if (e != NO_ERR) + return e; + + // Determine the target filename + free(whole_filename); + whole_filename = extract_tar_member_filename(block); + if (strlen(whole_filename) == 0) { + goto bailout; // done + } + + char *useful_part; + if (skipfirstcomponent) { + useful_part = strchr(whole_filename, '/'); + if (useful_part != NULL) + useful_part += 1; + else + useful_part = whole_filename; + } else { + useful_part = whole_filename; + } + destfile = realloc(destfile, strlen(destdir) + 1 + strlen(useful_part) + 1); + sprintf(destfile, "%s/%s", destdir, useful_part); + + // Create any missing directories + for (char *p = destfile + strlen(destdir) + 1; *p != '\0'; p++) { + if (*p == '/') { + *p = '\0'; + int ret = mkdir(destfile, 0755); + if (ret < 0 && errno != EEXIST) { + e = newErr("Could not create directory %s: %s", destfile, strerror(errno)); + goto bailout; + } + *p = '/'; + } + } + + // Copy the data + outfile = fopen(destfile, "w"); + if (outfile == NULL) { + e = newErr("Could not open %s", destfile); + goto bailout; + } + ssize_t size = extract_tar_member_size(block); + while (size > 0) { + int read_result = read_tar_block(tarstream, block, &e); + if (e != NO_ERR) { + goto bailout; + } + if (read_result < 0) { + e = newErr("unexpected end of tar file"); + goto bailout; + } + size_t nwrite = size > TAR_BLOCK_SIZE ? TAR_BLOCK_SIZE : size; + size_t written = fwrite(block, 1, nwrite, outfile); + if (written < nwrite) { + e = newErr("Error writing %s: %s", destfile, strerror(errno)); + goto bailout; + } + size -= TAR_BLOCK_SIZE; + } + fclose(outfile); + outfile = NULL; + } + +bailout: + if (outfile != NULL) + fclose(outfile); + free(whole_filename); + free(destfile); + return e; +} _______________________________________________ checkin-list mailing list checkin-list@monetdb.org https://www.monetdb.org/mailman/listinfo/checkin-list