diff --git a/HISTORY b/HISTORY index 740b14fe..aecffe24 100644 --- a/HISTORY +++ b/HISTORY @@ -1,10 +1,11 @@ SnapRAID HISTORY ================ -1.6 2011/8 +1.6 2011/9 ========== -* In "diff" now recognizes the reuse of inodes. +* In "diff" now it recognizes the reuse of inodes. * Windows hidden files are now backuped like any other files. +* Symbolic links are now saved in *nix. Not supported in Windows. 1.5 2011/7 ========== diff --git a/Makefile.am b/Makefile.am index db437e28..2ab73b14 100644 --- a/Makefile.am +++ b/Makefile.am @@ -92,13 +92,13 @@ check-local: $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -v -c $(srcdir)/test-raid6.conf sync $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -S -c $(srcdir)/test-raid6.conf check echo --- Create some files, sync raid6 and check - $(TESTENV) ./mktest$(EXEEXT) generate 1 3 1000 4000 + $(TESTENV) ./mktest$(EXEEXT) generate 1 3 1500 4000 $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -S -v -c $(srcdir)/test-raid6.conf diff $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -S -v -c $(srcdir)/test-raid6.conf sync $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -S -c $(srcdir)/test-raid5.conf check $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -S -c $(srcdir)/test-raid6.conf check echo --- Create some more files, sync raid5 and check - $(TESTENV) ./mktest$(EXEEXT) generate 2 3 1000 4000 + $(TESTENV) ./mktest$(EXEEXT) generate 2 3 1500 4000 $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -S -v -c $(srcdir)/test-raid5.conf diff $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -S -v -c $(srcdir)/test-raid5.conf sync $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -S -c $(srcdir)/test-raid5.conf check @@ -114,7 +114,7 @@ check-local: rm test/disk1/1* rm test/disk2/2* rm test/disk3/3* - $(TESTENV) ./mktest$(EXEEXT) generate 3 3 100 4000 + $(TESTENV) ./mktest$(EXEEXT) generate 3 3 200 4000 $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -S -v -c $(srcdir)/test-raid6.conf sync $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -S -c $(srcdir)/test-raid6.conf check echo --- Move some files, sync and check @@ -144,7 +144,7 @@ check-local: rm test/disk1/4* rm test/disk2/5* rm test/disk3/6* - $(TESTENV) ./mktest$(EXEEXT) generate 4 3 100 4000 + $(TESTENV) ./mktest$(EXEEXT) generate 4 3 200 4000 $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -S -v -c $(srcdir)/test-raid6.conf -t 100 sync $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -S -c $(srcdir)/test-raid6.conf check 2>> test.log $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -S -v -c $(srcdir)/test-raid6.conf -t 700 sync @@ -154,7 +154,7 @@ check-local: $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -S -v -c $(srcdir)/test-raid6.conf sync $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -S -c $(srcdir)/test-raid6.conf check echo --- Delete some files, fix and check with raid5 - rm test/disk1/1* + rm test/disk1/8* $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -S -R -c $(srcdir)/test-raid5.conf check 2>> test.log $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -S -c $(srcdir)/test-raid5.conf fix 2>> test.log $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -S -c $(srcdir)/test-raid6.conf check diff --git a/TODO b/TODO index 0d690897..ffbdd983 100644 --- a/TODO +++ b/TODO @@ -3,11 +3,11 @@ SnapRAID TODO This is a list of possible TODO items for SnapRAID. The order is not relevant. -* Support to backup also symlinks. It's important to allow a full backup as -many software relay on them to store important information. - * When fixing, before overwriting the present file, make a copy of it just in case that the original file cannot be completely recovered. +We can always open files in read-only mode, if a write is required, we close it, +rename it to with a .bak extension, and rewrite it up to the required size. +The same for symlink if a file with the same name exist or viceversa. * If a file cannot be synched, for example because it was changing, or it the file name cannot be supported, it's better to do not stop the sync process. diff --git a/check.c b/check.c index 74bbc904..51764169 100644 --- a/check.c +++ b/check.c @@ -220,6 +220,7 @@ static int state_check_process(struct snapraid_state* state, int fix, int parity ++countmax; } + /* check all the blocks in files */ countsize = 0; countpos = 0; state_progress_begin(state, blockstart, blockmax, countmax); @@ -518,6 +519,94 @@ static int state_check_process(struct snapraid_state* state, int fix, int parity } } + /* check all the links */ + for(i=0;idiskarr, i); + + /* for each link in the disk */ + node = disk->linklist; + while (node) { + char path[PATH_MAX]; + char linkto[PATH_MAX]; + struct snapraid_link* link; + int failed = 0; + + link = node->data; + node = node->next; /* next node */ + + /* if excluded continue to the next one */ + if (link_flag_has(link, FILE_IS_EXCLUDED)) { + continue; + } + + /* read the symlink */ + pathprint(path, sizeof(path), "%s%s", disk->dir, link->sub); + ret = readlink(path, linkto, sizeof(linkto)); + if (ret < 0) { + failed = 1; + + fprintf(stderr, "Error reading symlink '%s'. %s.\n", path, strerror(errno)); + fprintf(stderr, "symlink_error:%s:%s: Symlink read error\n", disk->name, link->sub); + ++error; + } else if (ret == sizeof(linkto)) { + failed = 1; + + fprintf(stderr, "Error reading symlink '%s'. Symlink too long.\n", path); + fprintf(stderr, "symlink_error:%s:%s: Symlink read error\n", disk->name, link->sub); + ++error; + } else { + linkto[ret] = 0; + + if (strcmp(linkto, link->linkto) != 0) { + failed = 1; + + fprintf(stderr, "symlink_error:%s:%s: Symlink data error '%s' instead of '%s'\n", disk->name, link->sub, linkto, link->linkto); + ++error; + } + } + + if (fix && failed) { + /* create the ancestor directories */ + ret = handle_ancestor(path); + if (ret != 0) { + fprintf(stderr, "WARNING! Without a working data disk, it isn't possible to fix errors on it.\n"); + printf("Stopping\n"); + ++unrecoverable_error; + goto bail; + } + + /* if it exists, it must be deleted before recreating */ + ret = remove(path); + if (ret != 0 && errno != ENOENT) { + fprintf(stderr, "Error removing '%s'. %s.\n", path, strerror(errno)); + fprintf(stderr, "WARNING! Without a working data disk, it isn't possible to fix errors on it.\n"); + printf("Stopping\n"); + ++unrecoverable_error; + goto bail; + } + + /* create it */ + ret = symlink(link->linkto, path); + if (ret != 0) { + fprintf(stderr, "Error writing symlink '%s'. %s.\n", path, strerror(errno)); + if (errno == EACCES) { + fprintf(stderr, "WARNING! Please give write permission to the symlink.\n"); + } else { + /* we do not use DANGER because it could be ENOSPC which is not always correctly reported */ + fprintf(stderr, "WARNING! Without a working data disk, it isn't possible to fix errors on it.\n"); + } + printf("Stopping\n"); + ++unrecoverable_error; + goto bail; + } + + fprintf(stderr, "symlink_fixed:%s:%s: Fixed symlink error\n", disk->name, link->sub); + ++recovered_error; + } + } + } + state_progress_end(state, countpos, countmax, countsize); bail: diff --git a/elem.c b/elem.c index acaba55c..ec278184 100644 --- a/elem.c +++ b/elem.c @@ -262,6 +262,30 @@ int file_inode_compare(const void* void_arg, const void* void_data) return 0; } +struct snapraid_link* link_alloc(const char* sub, const char* linkto) +{ + struct snapraid_link* link; + + link = malloc_nofail(sizeof(struct snapraid_link)); + pathcpy(link->sub, sizeof(link->sub), sub); + pathcpy(link->linkto, sizeof(link->linkto), linkto); + link->flag = 0; + + return link; +} + +void link_free(struct snapraid_link* link) +{ + free(link); +} + +int link_name_compare(const void* void_arg, const void* void_data) +{ + const char* arg = void_arg; + const struct snapraid_link* link = void_data; + return strcmp(arg, link->sub); +} + struct snapraid_disk* disk_alloc(const char* name, const char* dir) { struct snapraid_disk* disk; @@ -276,6 +300,8 @@ struct snapraid_disk* disk_alloc(const char* name, const char* dir) disk->first_free_block = 0; tommy_list_init(&disk->filelist); tommy_hashdyn_init(&disk->inodeset); + tommy_list_init(&disk->linklist); + tommy_hashdyn_init(&disk->linkset); tommy_array_init(&disk->blockarr); return disk; @@ -290,6 +316,13 @@ void disk_free(struct snapraid_disk* disk) file_free(file); } tommy_hashdyn_done(&disk->inodeset); + node = disk->linklist; + while (node) { + struct snapraid_link* link = node->data; + node = node->next; + link_free(link); + } + tommy_hashdyn_done(&disk->linkset); tommy_array_done(&disk->blockarr); free(disk); } diff --git a/elem.h b/elem.h index f606fc45..b4775342 100644 --- a/elem.h +++ b/elem.h @@ -98,6 +98,19 @@ struct snapraid_file { tommy_hashdyn_node nodeset; }; +/** + * Symbolic Link. + */ +struct snapraid_link { + unsigned flag; /**< FILE_IS_* flags. */ + char sub[PATH_MAX]; /**< Sub path of the file. Without the disk dir. The disk is implicit. */ + char linkto[PATH_MAX]; /**< Link to. */ + + /* nodes for data structures */ + tommy_node nodelist; + tommy_hashdyn_node nodeset; +}; + /** * Disk. */ @@ -107,6 +120,8 @@ struct snapraid_disk { block_off_t first_free_block; /**< First free searching block. */ tommy_list filelist; /**< List of all the files. */ tommy_hashdyn inodeset; /**< Hashtable by inode of all the files. */ + tommy_list linklist; /**< List of all the links. */ + tommy_hashdyn linkset; /**< Hashtable by name of all the links. */ tommy_array blockarr; /**< Block array of the disk. */ }; @@ -204,7 +219,7 @@ void file_free(struct snapraid_file* file); const char* file_name(struct snapraid_file* file); /** - * Compares two files by inode. + * Compares a file with an inode. */ int file_inode_compare(const void* void_arg, const void* void_data); @@ -216,6 +231,44 @@ static inline tommy_uint32_t file_inode_hash(uint64_t inode) return (tommy_uint32_t)tommy_inthash_u64(inode); } +static inline int link_flag_has(struct snapraid_link* link, unsigned mask) +{ + return (link->flag & mask) == mask; +} + +static inline void link_flag_set(struct snapraid_link* link, unsigned mask) +{ + link->flag |= mask; +} + +static inline void link_flag_clear(struct snapraid_link* link, unsigned mask) +{ + link->flag &= ~mask; +} + +/** + * Allocates a link. + */ +struct snapraid_link* link_alloc(const char* name, const char* link); + +/** + * Deallocates a link. + */ +void link_free(struct snapraid_link* link); + +/** + * Compare a link with a name. + */ +int link_name_compare(const void* void_arg, const void* void_data); + +/** + * Computes the hash of a link name. + */ +static inline tommy_uint32_t link_name_hash(const char* name) +{ + return tommy_hash_u32(0, name, strlen(name)); +} + /** * Allocates a disk. */ diff --git a/handle.c b/handle.c index 0d1e5f42..79a3372a 100644 --- a/handle.c +++ b/handle.c @@ -195,9 +195,10 @@ int handle_open(struct snapraid_handle* handle, struct snapraid_file* file) return 0; } + pathprint(handle->path, sizeof(handle->path), "%s%s", handle->disk->dir, file->sub); + /* opening in sequential mode in Windows */ /* do not follow links to ensure to open the real file */ - pathprint(handle->path, sizeof(handle->path), "%s%s", handle->disk->dir, file->sub); handle->f = open(handle->path, O_RDONLY | O_BINARY | O_SEQUENTIAL | O_NOFOLLOW); if (handle->f == -1) { diff --git a/mktest.c b/mktest.c index ecc04ed8..4fa1f4ca 100644 --- a/mktest.c +++ b/mktest.c @@ -32,23 +32,29 @@ unsigned rnd(unsigned max) char CHARSET[] = "qwertyuiopasdfghjklzxcvbnm1234567890 .-+"; #define CHARSET_LEN (sizeof(CHARSET)-1) +void rnd_name(char* file) +{ + int l = 1 + rnd(20); + while (l) { + *file++ = CHARSET[rnd(CHARSET_LEN)]; + --l; + } + *file = 0; +} + void generate(int disk, int size) { char path[PATH_MAX]; - FILE* f; char* file; - int count; - int i; - int l; snprintf(path, sizeof(path), "test/disk%d/", disk); - i = strlen(path); + file = path + strlen(path); /* add a directory */ if (rnd(2) == 0) { - path[i++] = '_'; - path[i++] = 'a' + rnd(2); - path[i] = 0; + *file++ = '_'; + *file++ = 'a' + rnd(2); + *file = 0; /* create it */ if (mkdir(path, 0777) != 0) { @@ -58,14 +64,14 @@ void generate(int disk, int size) } } - path[i++] = '/'; + *file++ = '/'; } /* add another directory */ if (rnd(2) == 0) { - path[i++] = '_'; - path[i++] = 'a' + rnd(2); - path[i] = 0; + *file++ = '_'; + *file++ = 'a' + rnd(2); + *file = 0; /* create it */ if (mkdir(path, 0777) != 0) { @@ -75,145 +81,224 @@ void generate(int disk, int size) } } - path[i++] = '/'; - } - - /* add a file */ - l = 1 + rnd(20); - file = path + i; - while (l) { - path[i++] = CHARSET[rnd(CHARSET_LEN)]; - --l; - } - path[i] = 0; - - /* skip some invalid file name, see http://en.wikipedia.org/wiki/Filename */ - if (strcmp(file, ".") == 0 - || strcmp(file, "..") == 0 - || strcmp(file, "prn") == 0 - || strcmp(file, "con") == 0 - || strcmp(file, "nul") == 0 - || strcmp(file, "aux") == 0 - || file[0] == ' ' - || file[strlen(file)-1] == ' ' - || file[strlen(file)-1] == '.' - ) { - return; + *file++ = '/'; } - f = fopen(path, "wb"); - if (!f) { - fprintf(stderr, "Error writing %s\n", path); - exit(EXIT_FAILURE); + while (1) { + /* add a random file */ + rnd_name(file); + + /* skip some invalid file name, see http://en.wikipedia.org/wiki/Filename */ + if (strcmp(file, ".") == 0 + || strcmp(file, "..") == 0 + || strcmp(file, "prn") == 0 + || strcmp(file, "con") == 0 + || strcmp(file, "nul") == 0 + || strcmp(file, "aux") == 0 + || file[0] == ' ' + || file[strlen(file)-1] == ' ' + || file[strlen(file)-1] == '.' + ) { + continue; + } + + break; } - count = size; - while (count) { - fputc(rnd(256), f); - --count; + /* remove the existing file if any */ + if (remove(path) != 0) { + if (errno != ENOENT) { + fprintf(stderr, "Error removing file %s\n", path); + exit(EXIT_FAILURE); + } } - fclose(f); +#ifndef WIN32 /* Windows XP doesn't support symlinks */ + if (rnd(32) == 0) { + /* symlink */ + char linkto[PATH_MAX]; + int ret; + + rnd_name(linkto); + + ret = symlink(linkto, path); + if (ret != 0) { + fprintf(stderr, "Error writing symlink %s\n", path); + exit(EXIT_FAILURE); + } + } else +#endif + { + /* file */ + FILE* f; + int count; + + f = fopen(path, "wb"); + if (!f) { + fprintf(stderr, "Error writing file %s\n", path); + exit(EXIT_FAILURE); + } + + count = size; + while (count) { + fputc(rnd(256), f); + --count; + } + + fclose(f); + } } void damage(const char* path, int size) { - FILE* f; struct stat st; - off_t start; - int count; if (!size) return; - f = fopen(path, "r+b"); - if (!f) { - fprintf(stderr, "Error writing %s\n", path); - exit(EXIT_FAILURE); - } - - if (fstat(fileno(f), &st) != 0) { + if (lstat(path, &st) != 0) { + if (errno == ENOENT) + return; /* it may be already deleted */ fprintf(stderr, "Error accessing %s\n", path); exit(EXIT_FAILURE); } - /* start at random position inside the file */ - if (st.st_size) - start = rnd(st.st_size); - else - start = 0; - if (fseek(f, start, SEEK_SET) != 0) { - fprintf(stderr, "Error seeking %s\n", path); - exit(EXIT_FAILURE); - } + if (S_ISLNK(st.st_mode)) { + /* symlink */ + int ret; + + ret = remove(path); + if (ret != 0) { + fprintf(stderr, "Error removing %s\n", path); + exit(EXIT_FAILURE); + } + } else { + /* file */ + FILE* f; + off_t start; + int count; - /* write garbage, in case also over the end */ - count = rnd(size); - while (count) { - fputc(rnd(256), f); - --count; - } + f = fopen(path, "r+b"); + if (!f) { + fprintf(stderr, "Error writing %s\n", path); + exit(EXIT_FAILURE); + } - fclose(f); + /* start at random position inside the file */ + if (st.st_size) + start = rnd(st.st_size); + else + start = 0; + if (fseek(f, start, SEEK_SET) != 0) { + fprintf(stderr, "Error seeking %s\n", path); + exit(EXIT_FAILURE); + } + + /* write garbage, in case also over the end */ + count = rnd(size); + while (count) { + fputc(rnd(256), f); + --count; + } + + fclose(f); + } } void append(const char* path, int size) { - FILE* f; - int count; - - if (!size) - return; - - f = fopen(path, "ab"); - if (!f) { - fprintf(stderr, "Error appending %s\n", path); - exit(EXIT_FAILURE); - } - - /* write garbage, in case also over the end */ - count = rnd(size); - while (count) { - fputc(rnd(256), f); - --count; - } - - fclose(f); -} - -void truncat(const char* path, int size) -{ - FILE* f; struct stat st; - off_t start; - int count; if (!size) return; - f = fopen(path, "r+b"); - if (!f) { - fprintf(stderr, "Error writing %s\n", path); - exit(EXIT_FAILURE); - } - - if (fstat(fileno(f), &st) != 0) { + if (lstat(path, &st) != 0) { + if (errno == ENOENT) + return; /* it may be already deleted */ fprintf(stderr, "Error accessing %s\n", path); exit(EXIT_FAILURE); } - /* truncate at random position inside the file */ - count = rnd(size); - if (count > st.st_size) - count = st.st_size; - start = st.st_size - count; - - if (ftruncate(fileno(f), start) != 0) { - fprintf(stderr, "Error truncating %s\n", path); + if (S_ISLNK(st.st_mode)) { + /* symlink */ + int ret; + + ret = remove(path); + if (ret != 0) { + fprintf(stderr, "Error removing %s\n", path); + exit(EXIT_FAILURE); + } + } else { + /* file */ + + FILE* f; + int count; + + f = fopen(path, "ab"); + if (!f) { + fprintf(stderr, "Error appending %s\n", path); + exit(EXIT_FAILURE); + } + + /* write garbage, in case also over the end */ + count = rnd(size); + while (count) { + fputc(rnd(256), f); + --count; + } + + fclose(f); + } +} + +void truncat(const char* path, int size) +{ + struct stat st; + + if (!size) + return; + + if (lstat(path, &st) != 0) { + if (errno == ENOENT) + return; /* it may be already deleted */ + fprintf(stderr, "Error accessing %s\n", path); exit(EXIT_FAILURE); } - fclose(f); + if (S_ISLNK(st.st_mode)) { + /* symlink */ + int ret; + + ret = remove(path); + if (ret != 0) { + fprintf(stderr, "Error removing %s\n", path); + exit(EXIT_FAILURE); + } + } else { + /* file */ + FILE* f; + off_t start; + int count; + + f = fopen(path, "r+b"); + if (!f) { + fprintf(stderr, "Error writing %s\n", path); + exit(EXIT_FAILURE); + } + + /* truncate at random position inside the file */ + count = rnd(size); + if (count > st.st_size) + count = st.st_size; + start = st.st_size - count; + + if (ftruncate(fileno(f), start) != 0) { + fprintf(stderr, "Error truncating %s\n", path); + exit(EXIT_FAILURE); + } + + fclose(f); + } } diff --git a/scan.c b/scan.c index 69f03b27..b15df040 100644 --- a/scan.c +++ b/scan.c @@ -31,7 +31,8 @@ struct snapraid_scan { unsigned count_remove; unsigned count_insert; - tommy_list insert_list; /**< Files to insert. */ + tommy_list file_insert_list; /**< Files to insert. */ + tommy_list link_insert_list; /**< Links to insert. */ }; /** @@ -228,7 +229,97 @@ static void scan_file(struct snapraid_scan* scan, struct snapraid_state* state, file_flag_set(file, FILE_IS_PRESENT); /* insert it in the delayed insert list */ - tommy_list_insert_tail(&scan->insert_list, &file->nodelist, file); + tommy_list_insert_tail(&scan->file_insert_list, &file->nodelist, file); +} + +/** + * Removes the specified link from the data set. + */ +static void scan_link_remove(struct snapraid_state* state, struct snapraid_disk* disk, struct snapraid_link* link) +{ + /* state changed */ + state->need_write = 1; + + /* remove the file from the link containers */ + tommy_hashdyn_remove_existing(&disk->linkset, &link->nodeset); + tommy_list_remove_existing(&disk->linklist, &link->nodelist); + + /* deallocate */ + link_free(link); +} + +/** + * Inserts the specified link in the data set. + */ +static void scan_link_insert(struct snapraid_state* state, struct snapraid_disk* disk, struct snapraid_link* link) +{ + /* state changed */ + state->need_write = 1; + + /* insert the link in the link containers */ + tommy_hashdyn_insert(&disk->linkset, &link->nodeset, link, link_name_hash(link->sub)); + tommy_list_insert_tail(&disk->linklist, &link->nodelist, link); +} + +/** + * Processes a symbolic link. + */ +static void scan_link(struct snapraid_scan* scan, struct snapraid_state* state, int output, struct snapraid_disk* disk, const char* sub, const char* linkto) +{ + struct snapraid_link* link; + + /* check if the link already exists */ + link = tommy_hashdyn_search(&disk->linkset, link_name_compare, sub, link_name_hash(sub)); + if (link) { + /* check if multiple files have the same inode */ + if (link_flag_has(link, FILE_IS_PRESENT)) { + fprintf(stderr, "Internal inconsistency for symlink '%s%s'\n", disk->dir, sub); + exit(EXIT_FAILURE); + } + + /* mark as present */ + link_flag_set(link, FILE_IS_PRESENT); + + /* check if the link is not changed */ + if (strcmp(link->linkto, linkto) == 0) { + /* it's equal */ + ++scan->count_equal; + + if (state->gui) { + fprintf(stderr, "scan:equal:%s:%s\n", disk->name, link->sub); + fflush(stderr); + } + + /* nothing more to do */ + return; + } else { + /* it's an update */ + if (state->gui) { + fprintf(stderr, "scan:update:%s:%s\n", disk->name, link->sub); + fflush(stderr); + } + if (output) { + printf("Update '%s%s'\n", disk->dir, link->sub); + } + + ++scan->count_change; + + /* update it */ + pathcpy(link->linkto, sizeof(link->linkto), linkto); + + /* nothing more to do */ + return; + } + } + + /* insert it */ + link = link_alloc(sub, linkto); + + /* mark it as present */ + link_flag_set(link, FILE_IS_PRESENT); + + /* insert it in the delayed insert list */ + tommy_list_insert_tail(&scan->link_insert_list, &link->nodelist, link); } /** @@ -307,6 +398,30 @@ static void scan_dir(struct snapraid_scan* scan, struct snapraid_state* state, i printf("Excluding file '%s'\n", path_next); } } + } else if (S_ISLNK(st.st_mode)) { + if (filter_path(&state->filterlist, sub_next, 0) == 0) { + char subnew[PATH_MAX]; + int ret; + + ret = readlink(path_next, subnew, sizeof(subnew)); + if (ret >= PATH_MAX) { + fprintf(stderr, "Error in readlink file '%s'. Symlink too long.\n", path_next); + exit(EXIT_FAILURE); + } + if (ret < 0) { + fprintf(stderr, "Error in readlink file '%s'. %s.\n", path_next, strerror(errno)); + exit(EXIT_FAILURE); + } + + /* readlink doesn't put the final 0 */ + subnew[ret] = 0; + + scan_link(scan, state, output, disk, sub_next, subnew); + } else { + if (state->verbose) { + printf("Excluding file '%s'\n", path_next); + } + } } else if (S_ISDIR(st.st_mode)) { if (filter_path(&state->filterlist, sub_next, 1) == 0) { pathslash(path_next, sizeof(path_next)); @@ -347,7 +462,8 @@ void state_scan(struct snapraid_state* state, int output) scan[i].count_change = 0; scan[i].count_remove = 0; scan[i].count_insert = 0; - tommy_list_init(&scan[i].insert_list); + tommy_list_init(&scan[i].file_insert_list); + tommy_list_init(&scan[i].link_insert_list); } for(i=0;ilinklist; + while (node) { + struct snapraid_link* link = node->data; + + /* next node */ + node = node->next; + + /* remove if not present */ + if (!link_flag_has(link, FILE_IS_PRESENT)) { + ++scan[i].count_remove; + + if (state->gui) { + fprintf(stderr, "scan:remove:%s:%s\n", disk->name, link->sub); + fflush(stderr); + } + if (output) { + printf("Remove '%s%s'\n", disk->dir, link->sub); + } + + scan_link_remove(state, disk, link); + } + } + /* insert all the new files, we insert them only after the deletion */ - node = scan[i].insert_list; + node = scan[i].file_insert_list; while (node) { struct snapraid_file* file = node->data; @@ -405,6 +545,29 @@ void state_scan(struct snapraid_state* state, int output) scan_file_insert(state, disk, file); } + /* insert all the new links */ + node = scan[i].link_insert_list; + while (node) { + struct snapraid_link* link = node->data; + + /* next node */ + node = node->next; + + /* create the new link */ + ++scan->count_insert; + + if (state->gui) { + fprintf(stderr, "scan:add:%s:%s\n", disk->name, link->sub); + fflush(stderr); + } + if (output) { + printf("Add '%s%s'\n", disk->dir, link->sub); + } + + /* insert it */ + scan_link_insert(state, disk, link); + } + /* if all the previous file were removed */ if (scan[i].count_equal == 0 && scan[i].count_moved == 0 && scan[i].count_remove != 0) { if (!state->force_empty) { diff --git a/snapraid.1 b/snapraid.1 index c6a857e5..ac7727f8 100644 --- a/snapraid.1 +++ b/snapraid.1 @@ -101,8 +101,8 @@ It\(cqs able to recover damages only from up to two disks. With a Backup you are able to recover from a complete failure of the whole disk array. .IP \(bu -Only the file data is saved. Permissions, time, extended attributes, -hard\(hylinks, symbolic\(hylinks are not saved. +Only file data and symlinks are saved. Permissions, time, extended attributes, +hard\(hylinks are not saved. .PD .SH GETTING STARTED To use SnapRAID you need first select one disk of your disk array diff --git a/snapraid.d b/snapraid.d index 1e49710d..0e854815 100644 --- a/snapraid.d +++ b/snapraid.d @@ -67,8 +67,8 @@ Limitations * It's able to recover damages only from up to two disks. With a Backup you are able to recover from a complete failure of the whole disk array. - * Only the file data is saved. Permissions, time, extended attributes, - hard-links, symbolic-links are not saved. + * Only file data and symlinks are saved. Permissions, time, extended attributes, + hard-links are not saved. Getting Started To use SnapRAID you need first select one disk of your disk array diff --git a/snapraid.txt b/snapraid.txt index 57644139..0e0dd05c 100644 --- a/snapraid.txt +++ b/snapraid.txt @@ -77,8 +77,8 @@ Other limitations are: * It's able to recover damages only from up to two disks. With a Backup you are able to recover from a complete failure of the whole disk array. -* Only the file data is saved. Permissions, time, extended attributes, - hard-links, symbolic-links are not saved. +* Only file data and symlinks are saved. Permissions, time, extended attributes, + hard-links are not saved. 4 GETTING STARTED diff --git a/state.c b/state.c index 404fb771..45dab9ef 100644 --- a/state.c +++ b/state.c @@ -287,10 +287,12 @@ void state_read(struct snapraid_state* state) unsigned line; unsigned count_file; unsigned count_block; + unsigned count_link; tommy_node* node; count_file = 0; count_block = 0; + count_link = 0; /* iterate over all the available content files and load the first one present */ f = 0; @@ -435,7 +437,6 @@ void state_read(struct snapraid_state* state) ++count_block; } else if (strcmp(tag, "file") == 0) { /* file */ - char buffer[PATH_MAX]; char sub[PATH_MAX]; uint64_t v_size; uint64_t v_mtime; @@ -535,6 +536,86 @@ void state_read(struct snapraid_state* state) /* stat */ ++count_file; + } else if (strcmp(tag, "symlink") == 0) { + /* symlink */ + char sub[PATH_MAX]; + char linkto[PATH_MAX]; + char tokento[32]; + unsigned i; + struct snapraid_link* link; + + ret = sgettok(f, buffer, sizeof(buffer)); + if (ret < 0) { + fprintf(stderr, "Invalid 'symlink' specification in '%s' at line %u\n", path, line); + exit(EXIT_FAILURE); + } + + c = sgetc(f); + if (c != ' ') { + fprintf(stderr, "Invalid 'symlink' specification in '%s' at line %u\n", path, line); + exit(EXIT_FAILURE); + } + + ret = sgetline(f, sub, sizeof(sub)); + if (ret < 0) { + fprintf(stderr, "Invalid 'symlink' specification in '%s' at line %u\n", path, line); + exit(EXIT_FAILURE); + } + + c = sgeteol(f); + if (c != '\n') { + fprintf(stderr, "Invalid 'symlink' specification in '%s' at line %u\n", path, line); + exit(EXIT_FAILURE); + } + ++line; + + ret = sgettok(f, tokento, sizeof(tokento)); + if (ret < 0) { + fprintf(stderr, "Invalid 'symlink' specification in '%s' at line %u\n", path, line); + exit(EXIT_FAILURE); + } + if (strcmp(tokento, "to") != 0) { + fprintf(stderr, "Invalid 'symlink' specification in '%s' at line %u\n", path, line); + exit(EXIT_FAILURE); + } + + c = sgetc(f); + if (c != ' ') { + fprintf(stderr, "Invalid 'symlink' specification in '%s' at line %u\n", path, line); + exit(EXIT_FAILURE); + } + + ret = sgetline(f, linkto, sizeof(linkto)); + if (ret < 0) { + fprintf(stderr, "Invalid 'symlink' specification in '%s' at line %u\n", path, line); + exit(EXIT_FAILURE); + } + + if (!*sub || !*linkto) { + fprintf(stderr, "Invalid 'symlink' specification in '%s' at line %u\n", path, line); + exit(EXIT_FAILURE); + } + + /* find the disk */ + for(i=0;idiskarr);++i) { + disk = tommy_array_get(&state->diskarr, i); + if (strcmp(disk->name, buffer) == 0) + break; + } + if (i == tommy_array_size(&state->diskarr)) { + fprintf(stderr, "Disk named '%s' not found in '%s' at line %u\n", buffer, path, line); + exit(EXIT_FAILURE); + } + + /* allocate the link */ + link = link_alloc(sub, linkto); + + /* insert the link in the link containers */ + tommy_hashdyn_insert(&disk->linkset, &link->nodeset, link, link_name_hash(link->sub)); + tommy_list_insert_tail(&disk->linklist, &link->nodelist, link); + + /* stat */ + ++count_link; } else if (strcmp(tag, "checksum") == 0) { ret = sgettok(f, buffer, sizeof(buffer)); if (ret < 0) { @@ -605,20 +686,23 @@ void state_read(struct snapraid_state* state) if (state->verbose) { printf("\tfile %u\n", count_file); printf("\tblock %u\n", count_block); + printf("\tsymlink %u\n", count_link); } } -static void state_write_one(struct snapraid_state* state, const char* path, unsigned* out_count_file, unsigned* out_count_block) +static void state_write_one(struct snapraid_state* state, const char* path, unsigned* out_count_file, unsigned* out_count_block, unsigned* out_count_link) { FILE* f; char tmp[PATH_MAX]; unsigned count_file; unsigned count_block; + unsigned count_link; unsigned i; int ret; count_file = 0; count_block = 0; + count_link = 0; printf("Saving state to %s...\n", path); @@ -697,6 +781,19 @@ static void state_write_one(struct snapraid_state* state, const char* path, unsi ++count_file; } + + /* for each link */ + for(j=disk->linklist;j!=0;j=j->next) { + struct snapraid_link* link = j->data; + + ret = fprintf(f,"symlink %s %s\nto %s\n", disk->name, link->sub, link->linkto); + if (ret < 0) { + fprintf(stderr, "Error writing the content file '%s' in fprintf(). %s.\n", tmp, strerror(errno)); + exit(EXIT_FAILURE); + } + + ++count_link; + } } /* Use the sequence fflush() -> fsync() -> fclose() -> rename() to ensure */ @@ -728,27 +825,32 @@ static void state_write_one(struct snapraid_state* state, const char* path, unsi *out_count_file = count_file; if (out_count_block) *out_count_block = count_block; + if (out_count_link) + *out_count_link = count_link; } void state_write(struct snapraid_state* state) { unsigned count_file; unsigned count_block; + unsigned count_link; tommy_node* node; count_file = 0; count_block = 0; + count_link = 0; node = tommy_list_head(&state->contentlist); while (node) { struct snapraid_content* content = node->data; - state_write_one(state, content->content, &count_file, &count_block); + state_write_one(state, content->content, &count_file, &count_block, &count_link); node = node->next; } if (state->verbose) { printf("\tfile %u\n", count_file); printf("\tblock %u\n", count_block); + printf("\tsymlink %u\n", count_link); } } @@ -790,6 +892,19 @@ void state_filter(struct snapraid_state* state, tommy_list* filterlist) printf("Processing file '%s'\n", file->sub); } } + + /* for each link */ + for(j=tommy_list_head(&disk->linklist);j!=0;j=j->next) { + struct snapraid_link* link = j->data; + + if (filter_path(filterlist, link->sub, 0) != 0) { + link_flag_set(link, FILE_IS_EXCLUDED); + } + + if (state->verbose && !link_flag_has(link, FILE_IS_EXCLUDED)) { + printf("Processing symlink '%s'\n", link->sub); + } + } } }