Initial support for symlinks. Only in *nix.

This commit is contained in:
Andrea Mazzoleni 2011-08-26 14:18:38 +02:00
parent 4e7957ff73
commit 399f181a57
13 changed files with 679 additions and 139 deletions

View file

@ -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
==========

View file

@ -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

6
TODO
View file

@ -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.

89
check.c
View file

@ -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;i<diskmax;++i) {
tommy_node* node;
struct snapraid_disk* disk = tommy_array_get(&state->diskarr, 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:

33
elem.c
View file

@ -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);
}

55
elem.h
View file

@ -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.
*/

View file

@ -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) {

313
mktest.c
View file

@ -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);
}
}

171
scan.c
View file

@ -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;i<diskmax;++i) {
@ -382,8 +498,32 @@ void state_scan(struct snapraid_state* state, int output)
}
}
/* check for removed links */
node = disk->linklist;
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) {

View file

@ -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

View file

@ -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

View file

@ -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

121
state.c
View file

@ -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;i<tommy_array_size(&state->diskarr);++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);
}
}
}
}