To all it may concern,

I have modified mkisofs 2.0 to make it more useful
for incremental backups. The patch is attached,
but it is not the final version - if people
are interested in using it, I'd also update the
man page and fix the remaining problems (see below).

I CC: James Pearson as he is listed as author of mkisofs
together with Joerg and I don't know whether he
reads this list or not.

Motivation:

Incremental backups onto CD-R have been possible
with multisession writing for a while. When writing
the same set of directories/files several times,
only modified or new files are added to the disc
while keeping all old ones.

However, accessing older versions of a file is
difficult: the CD filesystems must be able to select
arbitrary session and thus the old file. At least on
the Amiga this works, but I have doubts about Linux
and Windows. Comparing different versions would require
mounting the same disc several times - that's even
harder.

Restoring the directories to exactly the last state
is also difficult, because the disc would have
all of the old files, even those that were deleted
intentionally before the last backup.


Proposed solution:

I have added the command line option
  -root <dir>
that moves all files and directories into
<dir> in the image. This is essentially the
same as using -graft-points and adding <dir>
in front of every pathspec, but is easier to
use.

<dir> may actually be several levels deep. It is
created with the same permissions as other graft points.

The second new option
  -old-root <dir>
preserves the mechanism in cdrecord that identifies
unmodified files and reuses them instead of writing
their content again. It also preserves the existing
directory structure without modifications. This may
cause conflicts unless the new root is different
from all previous ones.


Usage:

The initial image might be created like this:
  mkisofs -root "backup_1" <dir>
Then take further snapshots like this:
  mkisofs -M ... -C ... -old-root "backup_1" -root "backup_2" <dir>

The new options also work nicely with
growisofs; in that case one doesn't have to
deal with -M/C or actually writing the images
at all.

Comparing modified files can be done by just mounting
the disk and using either the "backup_1" or "backup_2"
directories - using the date of the backup instead of 1 and 2
would be more useful, but is left to the reader as an exercise...

Restoring the latest snapshot is the same as simply copying
back "backup_2".


Rational:

I think reducing the number of new options would require
hard-coding the naming conventions for the backup directories,
and I have no idea which convention would be generally
acceptable - my guess is there is none, so it has to
be configurable.


Implementation:

The -root option is implemented using the existing code
for handling graft points. Reusing the files required
modifications in multi.c/merge_previous_session().
It gets passed the two new command line options. If they
are NULL, then the function executes exactly the same code
as before, so the patch should not break anything.

I tried to follow the coding style, but I think I goofed
up by writing "if(!<pointer>)" instead of "if(<pointer> == NULL)"
several times - old habits. I'll fix this, if it should be a problem.


Remaining Problems:

- conflicts due to incorrect usage (i.e. not using different
  roots) is not checked for; it probably leads to "directory
  sorting" errors
- merging the old directory completely must not preserve
  artificial directories like rr_root

The solution for both might be a more elaborate reusal of
the old directory structure, where new entries overwrite
the old ones.


Questions:

Is there a need for this patch?
Joerg and James, are you willing to include it in the official
release of mkisofs? What would you like to see modified?

-- 
Bye, Patrick Ohly
--  
[EMAIL PROTECTED]
[EMAIL PROTECTED] (MakeCD related mails)
http://home.pages.de/~ohly/
http://makecd.core.de/ (MakeCD home page)
*** mkisofs/multi.c	Wed Dec 25 15:15:24 2002
--- mkisofs2/multi.c	Sun Mar 23 21:13:31 2003
***************
*** 1512,1520 ****
   * directory entries, so that we can determine how large each directory is.
   */
  int
! merge_previous_session(this_dir, mrootp)
  	struct directory *this_dir;
  	struct iso_directory_record *mrootp;
  {
  	struct directory_entry **orig_contents = NULL;
  	struct directory_entry *odpnt = NULL;
--- 1512,1522 ----
   * directory entries, so that we can determine how large each directory is.
   */
  int
! merge_previous_session(this_dir, mrootp, reloc_root, reloc_old_root)
  	struct directory *this_dir;
  	struct iso_directory_record *mrootp;
+ 	char *reloc_root;
+ 	char *reloc_old_root;
  {
  	struct directory_entry **orig_contents = NULL;
  	struct directory_entry *odpnt = NULL;
***************
*** 1526,1540 ****
--- 1528,1659 ----
  			lstatbuf;
  	int		retcode;
  
+ 	/* skip leading slash */
+ 	while (reloc_old_root && reloc_old_root[0] == PATH_SEPARATOR) {
+ 		reloc_old_root++;
+ 	}
+ 	while (reloc_root && reloc_root[0] == PATH_SEPARATOR) {
+ 		reloc_root++;
+ 	}
+ 	
+ #if 0
+    /*
+ 	 * If the old root is relocated, then the old and new
+ 	 * directory must be different. Otherwise we would have
+ 	 * to handle conflicts between old and new files/dirs.
+ 	 * This check is very crude, because it does not compare
+ 	 * the names as they appear in the image.
+ 	 */
+ 	if (reloc_old_root) {
+ 		if (!reloc_root || !strcmp(reloc_old_root, reloc_root)) {
+ 		}
+ 	}
+ #endif
+ 
  	/*
  	 * Parse the same directory in the image that we are merging for
  	 * multisession stuff.
  	 */
  	orig_contents = read_merging_directory(mrootp, &n_orig);
  	if (orig_contents == NULL) {
+ 		if (reloc_old_root) {
+ #ifdef	USE_LIBSCHILY
+ 			comerrno(EX_BAD, "Reading old session failed, cannot execute -old-root.\n");
+ #else
+ 			fprintf(stderr, "Reading old session failed, cannot execute -old-root.\n");
+ #endif
+ 			return (-1);
+ 		}
  		return (0);
  	}
  
+ 	if (reloc_old_root && reloc_old_root[0]) {
+ 		struct directory_entry **new_orig_contents = orig_contents;
+ 		int		new_n_orig = n_orig;
+ 
+ 		/* decend until we reach the original root */
+ 		while (reloc_old_root[0]) {
+ 			int i;
+ 			char *next;
+ 			int last;
+ 
+ 			for (next = reloc_old_root; *next && *next != PATH_SEPARATOR; next++);
+ 			if (*next) {
+ 				last = 0;
+ 				*next = 0;
+ 				next++;
+ 			} else {
+ 				last = 1;
+ 			}
+ 			while (*next == PATH_SEPARATOR) {
+ 				next++;
+ 			}
+ 
+ 			for (i = 0; i < new_n_orig; i++) {
+ 				struct iso_directory_record subroot;
+ 
+ 				if (new_orig_contents[i]->name != NULL
+ 					 && strcmp(new_orig_contents[i]->name, reloc_old_root) != 0) {
+ 					/* Not the same name continue */
+ 					continue;
+ 				}
+ 				/*
+ 				 * enter directory, free old one only if not the top level,
+ 				 * which is still needed
+ 				 */
+ 				subroot = new_orig_contents[i]->isorec;
+ 				if (new_orig_contents != orig_contents) {
+ 					free_mdinfo(new_orig_contents, new_n_orig);
+ 				}				
+ 				new_orig_contents = read_merging_directory(&subroot, &new_n_orig);
+ 
+ 				if (!new_orig_contents) {
+ #ifdef	USE_LIBSCHILY
+ 					comerrno(EX_BAD, "Reading directory %s in old session failed, cannot execute -old-root.\n", reloc_old_root );
+ #else
+ 					fprintf(stderr, "Reading directory %s in old session failed, cannot execute -old-root.\n", reloc_old_root );
+ #endif
+ 					return (-1);
+ 				}
+ 				
+ 				i = -1;
+ 				break;
+ 			}
+ 
+ 			if (i == new_n_orig) {
+ 				#ifdef	USE_LIBSCHILY
+ 				comerrno(EX_BAD, "-old-root (sub)directory %s not found in old session.\n", reloc_old_root );
+ #else
+ 				fprintf(stderr, "-old-root (sub)directory %s not found in old session.\n", reloc_old_root );
+ #endif
+ 				return (-1);
+ 			}
+ 
+ 			/* restore string, proceed to next sub directory */
+ 			if (!last) {
+ 				reloc_old_root[strlen(reloc_old_root)] = PATH_SEPARATOR;
+ 			}
+ 			reloc_old_root = next;
+ 		}
+ 
+ 		/* preserve the old session completely */
+ 		merge_remaining_entries(this_dir, orig_contents, n_orig);
+ 
+ 		/* use new directory */
+ 		free_mdinfo(orig_contents, n_orig);
+ 		orig_contents = new_orig_contents;
+ 		n_orig = new_n_orig;		
+ 		
+ 		if (reloc_root && reloc_root[0]) {
+ 			/* also decend into new root before searching for files */
+ 			this_dir = find_or_create_directory(this_dir, reloc_root, NULL, TRUE);
+ 			if (!this_dir) {
+ 				return (-1);
+ 			}
+ 		}
+ 	}
+ 
+ 
  	/*
  	 * Now we scan the directory itself, and look at what is inside of it.
  	 */
***************
*** 1595,1601 ****
  					s_entry->whole_name,
  					s_entry, 1);
  				dflag = merge_previous_session(child,
! 					&odpnt->isorec);
  				if (dflag == -1) {
  					return (-1);
  				}
--- 1714,1721 ----
  					s_entry->whole_name,
  					s_entry, 1);
  				dflag = merge_previous_session(child,
! 					&odpnt->isorec,
! 					reloc_old_root, NULL );
  				if (dflag == -1) {
  					return (-1);
  				}
***************
*** 1605,1615 ****
  		}
  	}
  
! 	/*
! 	 * Whatever is left over, are things which are no longer in the tree on
! 	 * disk. We need to also merge these into the tree.
! 	 */
! 	merge_remaining_entries(this_dir, orig_contents, n_orig);
  	free_mdinfo(orig_contents, n_orig);
  	return (1);
  }
--- 1725,1737 ----
  		}
  	}
  
! 	if (!reloc_old_root) {
! 		/*
! 		 * Whatever is left over, are things which are no longer in the tree on
! 		 * disk. We need to also merge these into the tree.
! 		 */
! 		merge_remaining_entries(this_dir, orig_contents, n_orig);
! 	}
  	free_mdinfo(orig_contents, n_orig);
  	return (1);
  }
*** mkisofs/mkisofs.c	Sat Dec  7 20:59:41 2002
--- mkisofs2/mkisofs.c	Sun Mar 23 17:56:04 2003
***************
*** 401,406 ****
--- 401,409 ----
  #define OPTION_HFS_BLESS		2040
  #define OPTION_HFS_PARMS		2041
  
+ #define OPTION_RELOC_ROOT     2042
+ #define OPTION_RELOC_OLD_ROOT 2043
+ 
  #endif	/* APPLE_HYB */
  
  static int	save_pname = 0;
***************
*** 510,515 ****
--- 513,522 ----
  	0, NULL, "Do not pad output to a multiple of 32k", ONE_DASH},
  	{{"prev-session", required_argument, NULL, 'M'},
  	'M', "FILE", "Set path to previous session to merge", ONE_DASH},
+ 	{{"root", required_argument, NULL, OPTION_RELOC_ROOT},
+ 	'\0', "DIR", "Set root directory for all new files and directories", ONE_DASH},
+ 	{{"old-root", required_argument, NULL, OPTION_RELOC_OLD_ROOT},
+ 	'\0', "DIR", "Set root directory in previous session that is searched for files", ONE_DASH},
  	{{"omit-version-number", no_argument, NULL, 'N'},
  	'N', NULL, "Omit version number from ISO9660 filename (violates ISO9660)", ONE_DASH},
  	{{"new-dir-mode", required_argument, NULL, OPTION_NEW_DIR_MODE},
***************
*** 1126,1131 ****
--- 1133,1140 ----
  #endif
  	struct stat	statbuf;
  	char		*merge_image = NULL;
+ 	char     *reloc_root = NULL;
+ 	char     *reloc_old_root = NULL;
  	struct iso_directory_record *mrootp = NULL;
  	struct output_fragment *opnt;
  	int		longind;
***************
*** 1469,1474 ****
--- 1478,1489 ----
  		case 'M':
  			merge_image = optarg;
  			break;
+ 		case OPTION_RELOC_ROOT:
+ 			reloc_root = optarg;
+ 			break;
+ 		case OPTION_RELOC_OLD_ROOT:
+ 			reloc_old_root = optarg;
+ 			break;
  		case 'N':
  			omit_version_number++;
  			warn_violate++;
***************
*** 2543,2548 ****
--- 2558,2569 ----
  
  	memset(&de, 0, sizeof(de));
  
+ 	/*
+ 	 * PO:
+ 	 * Isn't root NULL at this time anyway?
+ 	 * I think it is created by the first call to
+ 	 * find_or_create_directory() below.
+ 	 */
  	de.filedir = root;	/* We need this to bootstrap */
  
  	if (cdrecord_data != NULL && merge_image == NULL) {
***************
*** 2652,2664 ****
  
  		short_name = NULL;
  
! 		if (node != NULL) {
  			char		*pnt;
  			char		*xpnt;
  
! 			*node = '\0';
! 			escstrcpy(graft_point, arg);
! 			*node = '=';
  
  			/*
  			 * Remove unwanted "./" & "/" sequences from start...
--- 2673,2700 ----
  
  		short_name = NULL;
  
! 		if (node != NULL || reloc_root) {
  			char		*pnt;
  			char		*xpnt;
+ 			size_t    len;
+ 
+ 			/* insert -root prefix */
+ 			if (reloc_root != NULL) {
+ 				strcpy(graft_point, reloc_root);
+ 				len = strlen(graft_point);
+ 				if (graft_point[len] != '/' ) {
+ 					graft_point[len] = '/';
+ 					len++;
+ 				}
+ 			} else {
+ 				len = 0;
+ 			}
  
! 			if (node) {
! 				*node = '\0';
! 				escstrcpy(graft_point + len, arg);
! 				*node = '=';
! 			}
  
  			/*
  			 * Remove unwanted "./" & "/" sequences from start...
***************
*** 2673,2679 ****
  				strcpy(graft_point, xpnt);
  			} while (xpnt > graft_point);
  
! 			node = escstrcpy(nodename, ++node);
  
  			graft_dir = root;
  			xpnt = graft_point;
--- 2709,2719 ----
  				strcpy(graft_point, xpnt);
  			} while (xpnt > graft_point);
  
! 			if (node) {
! 				node = escstrcpy(nodename, ++node);
! 			} else {
! 				node = arg;
! 			}
  
  			graft_dir = root;
  			xpnt = graft_point;
***************
*** 2834,2840 ****
  	 * side, since we may need to create some additional directories.
  	 */
  	if (merge_image != NULL) {
! 		if (merge_previous_session(root, mrootp) < 0) {
  #ifdef	USE_LIBSCHILY
  			comerrno(EX_BAD, "Cannot merge previous session.\n");
  #else
--- 2874,2881 ----
  	 * side, since we may need to create some additional directories.
  	 */
  	if (merge_image != NULL) {
! 		if (merge_previous_session(root, mrootp,
! 											reloc_root, reloc_old_root) < 0) {
  #ifdef	USE_LIBSCHILY
  			comerrno(EX_BAD, "Cannot merge previous session.\n");
  #else
*** mkisofs/mkisofs.h	Sat Dec  7 20:59:42 2002
--- mkisofs2/mkisofs.h	Sun Mar 23 17:57:05 2003
***************
*** 494,500 ****
  extern void merge_remaining_entries __PR((struct directory *,
  		                struct directory_entry **, int));
  extern int merge_previous_session __PR((struct directory *,
! 		                struct iso_directory_record *));
  extern int get_session_start __PR((int *));
  
  /* joliet.c */
--- 494,501 ----
  extern void merge_remaining_entries __PR((struct directory *,
  		                struct directory_entry **, int));
  extern int merge_previous_session __PR((struct directory *,
! 		                struct iso_directory_record *,
! 							 char *, char *));
  extern int get_session_start __PR((int *));
  
  /* joliet.c */

Reply via email to