Here's a patch that makes rsync try to find an existing file in a group of hard-linked files so that it doesn't create the first one in the group from scratch if a later file could be used instead.
Details: I decided to avoid having the code do an extra scan down the list when we encounter the lead file in the list. This is because it would be bad to have to do the same scan in the receiver that the generator just performed, especially since there's no guarantee that it will get identical results (if a file pops up at the wrong moment). My solution just keeps moving the master file in the group down the list, causing it to be processed in turn as we go through the normal flist scan. This ensures that we use the right basis file in the receiver, and keeps the code simple. The only complicating factor was that the hard-link post-processing pass was being done by the receiver, while the generator is the one that keeps track of the updated master. To deal with this, I moved the hard-link post-processing loop and the final touch-up of the directory permissions into the final work that the generator does after it gets the "end of phase 2" indicator from the receiver. Some simple changes to the hard-link data structures was needed. I got rid of the "head" pointer, replacing it with an index into the hlink_list array. This lets us update this "first item" pointer to point to the current master. I then made the single-linked list of hard-linked items circular, and added a flag to mark the last item in the original list (so we know when to give up our search and just ask for the file to be created). ..wayne..
--- backup.c 20 Jan 2004 05:24:07 -0000 1.19 +++ backup.c 25 Jan 2004 09:54:32 -0000 @@ -25,6 +25,7 @@ extern int backup_suffix_len; extern int backup_dir_len; extern char *backup_suffix; extern char *backup_dir; +extern struct file_struct **hlink_list; extern int am_root; extern int preserve_devices; @@ -263,7 +264,7 @@ static int keep_backup(char *fname) } #endif if (!kept && preserve_hard_links && file->link_u.links - && file->F_HEAD != file) { + && hlink_list[file->F_INDEX] != file) { if (verbose > 1) rprintf(FINFO, "%s is a hard link\n", f_name(file)); } --- generator.c 22 Jan 2004 04:38:18 -0000 1.72 +++ generator.c 25 Jan 2004 09:56:10 -0000 @@ -43,6 +43,7 @@ extern int protocol_version; extern int always_checksum; extern char *compare_dest; extern int link_dest; +extern struct file_struct **hlink_list; /* choose whether to skip a particular file */ @@ -397,7 +398,8 @@ void recv_generator(char *fname, struct } #endif - if (preserve_hard_links && file->link_u.links && file->F_HEAD != file) { + if (preserve_hard_links && file->link_u.links + && hlink_list[file->F_INDEX] != file) { if (verbose > 1) { rprintf(FINFO, "recv_generator: \"%s\" is a hard link\n", f_name(file)); @@ -438,6 +440,11 @@ void recv_generator(char *fname, struct } if (statret == -1) { + if (preserve_hard_links && file->link_u.links + && !(file->flags & FLAG_HLINK_LAST)) { + hlink_list[file->F_INDEX] = file->F_NEXT; + return; + } if (errno == ENOENT) { write_int(f_out,i); if (!dry_run) write_sum_head(f_out, NULL); @@ -455,6 +462,11 @@ void recv_generator(char *fname, struct } /* now pretend the file didn't exist */ + if (preserve_hard_links && file->link_u.links + && !(file->flags & FLAG_HLINK_LAST)) { + hlink_list[file->F_INDEX] = file->F_NEXT; + return; + } write_int(f_out,i); if (!dry_run) write_sum_head(f_out, NULL); return; @@ -496,6 +508,11 @@ void recv_generator(char *fname, struct rprintf(FERROR, "failed to open %s, continuing: %s\n", full_fname(fnamecmp), strerror(errno)); /* pretend the file didn't exist */ + if (preserve_hard_links && file->link_u.links + && !(file->flags & FLAG_HLINK_LAST)) { + hlink_list[file->F_INDEX] = file->F_NEXT; + return; + } write_int(f_out,i); write_sum_head(f_out, NULL); return; --- hlink.c 22 Jan 2004 04:38:18 -0000 1.30 +++ hlink.c 25 Jan 2004 09:22:23 -0000 @@ -38,8 +38,8 @@ static int hlink_compare(struct file_str return file_compare(file1, file2); } -static struct file_struct **hlink_list; -static int hlink_count; +struct file_struct **hlink_list; +int hlink_count; #define LINKED(p1,p2) ((p1)->F_DEV == (p2)->F_DEV \ && (p1)->F_INODE == (p2)->F_INODE) @@ -56,13 +56,14 @@ static void link_idev_data(void) head = hlink_list[start]; while (from < hlink_count-1 && LINKED(hlink_list[from], hlink_list[from+1])) { - hlink_list[from]->F_HEAD = head; + hlink_list[from]->F_INDEX = to; hlink_list[from]->F_NEXT = hlink_list[from+1]; from++; } if (from > start) { - hlink_list[from]->F_HEAD = head; - hlink_list[from]->F_NEXT = NULL; + hlink_list[from]->F_INDEX = to; + hlink_list[from]->F_NEXT = head; + hlink_list[from]->flags |= FLAG_HLINK_LAST; hlink_list[to++] = head; } else { free((char*)head->link_u.idev); @@ -136,7 +137,7 @@ static void hard_link_one(char *hlink1, void do_hard_links(void) { #if SUPPORT_HARD_LINKS - struct file_struct *file; + struct file_struct *file, *first; char hlink1[MAXPATHLEN]; char *hlink2; STRUCT_STAT st1, st2; @@ -146,10 +147,10 @@ void do_hard_links(void) return; for (i = 0; i < hlink_count; i++) { - file = hlink_list[i]; - if (link_stat(f_name_to(file, hlink1), &st1) != 0) + first = file = hlink_list[i]; + if (link_stat(f_name_to(first, hlink1), &st1) != 0) continue; - while ((file = file->F_NEXT) != NULL) { + while ((file = file->F_NEXT) != first) { hlink2 = f_name(file); if (link_stat(hlink2, &st2) == 0) { if (st2.st_dev == st1.st_dev --- main.c 20 Jan 2004 17:46:31 -0000 1.183 +++ main.c 25 Jan 2004 09:19:45 -0000 @@ -409,6 +409,7 @@ static void do_server_sender(int f_in, i static int do_recv(int f_in,int f_out,struct file_list *flist,char *local_name) { + int i; int pid; int status=0; int error_pipe[2]; @@ -463,6 +464,19 @@ static int do_recv(int f_in,int f_out,st generate_files(f_out, flist, local_name); get_redo_num(); /* Read final -1, and any prior messages. */ + + if (preserve_hard_links) + do_hard_links(); + + /* now we need to fix any directory permissions that were + * modified during the transfer */ + for (i = 0; i < flist->count; i++) { + struct file_struct *file = flist->files[i]; + if (!file->basename || !S_ISDIR(file->mode)) continue; + recv_generator(local_name ? local_name : f_name(file), + file, i, -1); + } + io_flush(FULL_FLUSH); if (protocol_version >= 24) { /* send a final goodbye message */ --- receiver.c 24 Jan 2004 22:12:58 -0000 1.67 +++ receiver.c 25 Jan 2004 09:23:56 -0000 @@ -479,18 +479,6 @@ int recv_files(int f_in,struct file_list && flist->count > 0) delete_files(flist); - if (preserve_hard_links) - do_hard_links(); - - /* now we need to fix any directory permissions that were - * modified during the transfer */ - for (i = 0; i < flist->count; i++) { - file = flist->files[i]; - if (!file->basename || !S_ISDIR(file->mode)) continue; - recv_generator(local_name ? local_name : f_name_to(file, fbuf), - file, i, -1); - } - if (verbose > 2) rprintf(FINFO,"recv_files finished\n"); --- rsync.h 22 Jan 2004 18:37:26 -0000 1.174 +++ rsync.h 25 Jan 2004 09:09:07 -0000 @@ -58,6 +58,11 @@ /* What flags above are relevant after the transfer of the flist? */ #define LIVE_FLAGS FLAG_DELETE +/* These flist flags can be set after the flist is transferred. */ + +/*#define FLAG_DELETE (1<<0) -- from the above list */ +#define FLAG_HLINK_LAST (1<<1) + /* update this if you make incompatible changes */ #define PROTOCOL_VERSION 28 @@ -384,7 +389,7 @@ enum msgcode { #define GID_NONE (gid_t) -1 struct hlink { - struct file_struct *head; + int index; struct file_struct *next; }; @@ -396,7 +401,7 @@ struct idev { #define F_DEV link_u.idev->dev #define F_INODE link_u.idev->inode -#define F_HEAD link_u.links->head +#define F_INDEX link_u.links->index #define F_NEXT link_u.links->next struct file_struct {
-- To unsubscribe or change options: http://lists.samba.org/mailman/listinfo/rsync Before posting, read: http://www.catb.org/~esr/faqs/smart-questions.html