diff -Naur orig/fs/ext2/ext2.h vvfs/fs/ext2/ext2.h --- orig/fs/ext2/ext2.h 2005-11-11 17:06:16.000000000 +0100 +++ vvfs/fs/ext2/ext2.h 2005-11-17 16:06:41.000000000 +0100 @@ -1,3 +1,5 @@ +/* VERSION SUPPORT */ + #include #include @@ -41,6 +43,10 @@ __u32 i_prealloc_block; __u32 i_prealloc_count; __u32 i_dir_start_lookup; + /* VERSION SUPPORT - BEGIN */ + __u16 i_maximum_versions; + /* VERSION SUPPORT - BEGIN */ + #ifdef CONFIG_EXT2_FS_XATTR /* * Extended attributes can be read independently of the main file diff -Naur orig/fs/ext2/inode.c vvfs/fs/ext2/inode.c --- orig/fs/ext2/inode.c 2005-11-11 17:06:16.000000000 +0100 +++ vvfs/fs/ext2/inode.c 2005-11-17 16:06:41.000000000 +0100 @@ -1,3 +1,5 @@ +/* VERSION SUPPORT */ + /* * linux/fs/ext2/inode.c * @@ -75,10 +77,10 @@ EXT2_I(inode)->i_dtime = get_seconds(); mark_inode_dirty(inode); ext2_update_inode(inode, inode_needs_sync(inode)); - inode->i_size = 0; if (inode->i_blocks) ext2_truncate (inode); + ext2_free_inode (inode); return; @@ -1049,6 +1051,8 @@ struct ext2_inode * raw_inode = ext2_get_inode(inode->i_sb, ino, &bh); int n; + //struct ext2_super_block *es = EXT2_SB(inode->i_sb)->s_es; + #ifdef CONFIG_EXT2_FS_POSIX_ACL ei->i_acl = EXT2_ACL_NOT_CACHED; ei->i_default_acl = EXT2_ACL_NOT_CACHED; @@ -1100,6 +1104,9 @@ ei->i_prealloc_count = 0; ei->i_block_group = (ino - 1) / EXT2_INODES_PER_GROUP(inode->i_sb); ei->i_dir_start_lookup = 0; + /* VERSION SUPPORT - BEGIN */ + ei->i_maximum_versions = raw_inode->i_max_versions; + /* VERSION SUPPORT - END */ /* * NOTE! The in-memory inode i_data array is in little-endian order @@ -1204,6 +1211,10 @@ raw_inode->i_frag = ei->i_frag_no; raw_inode->i_fsize = ei->i_frag_size; raw_inode->i_file_acl = cpu_to_le32(ei->i_file_acl); + /* VERSION SUPPORT - BEGIN */ + raw_inode->i_max_versions = ei->i_maximum_versions; + /* VERSION SUPPORT - END */ + if (!S_ISREG(inode->i_mode)) raw_inode->i_dir_acl = cpu_to_le32(ei->i_dir_acl); else { @@ -1240,6 +1251,7 @@ } } else for (n = 0; n < EXT2_N_BLOCKS; n++) raw_inode->i_block[n] = ei->i_data[n]; + mark_buffer_dirty(bh); if (do_sync) { sync_dirty_buffer(bh); diff -Naur orig/fs/ext2/namei.c vvfs/fs/ext2/namei.c --- orig/fs/ext2/namei.c 2005-11-11 17:06:16.000000000 +0100 +++ vvfs/fs/ext2/namei.c 2005-11-17 16:06:41.000000000 +0100 @@ -1,3 +1,5 @@ +/* VERSION SUPPORT */ + /* * linux/fs/ext2/namei.c * @@ -34,6 +36,52 @@ #include "ext2.h" #include "xattr.h" #include "acl.h" +#include +#include +#include + +/* VERSION SUPPORT - BEGIN */ +extern struct ext2_inode *ext2_get_inode( + struct super_block *, ino_t, struct buffer_head **); + +/* + * Read the number of versions allowed for this directory from the extended + * inode struct entry 'i_maximum_version' + */ +static int ext2_get_max_versions(struct inode * inode) +{ + struct ext2_inode_info * ei = EXT2_I(inode); + + /* Only a directory inode holdes the disired information */ + if (!S_ISDIR(inode->i_mode)) + return -ENOTDIR; + + return ei->i_maximum_versions; +} + +/* + * Write the number of versions allowed for this directory to the extended + * inode struct entry 'i_maximum_versions'. + */ +static int ext2_set_max_versions(struct inode * inode, int max_versions) +{ + struct ext2_inode_info * ei = EXT2_I(inode); + + /* Setting this attribute is only allowed for directories. */ + if (!S_ISDIR(inode->i_mode)) + return -ENOTDIR; + + ei->i_maximum_versions = max_versions; + + /* The inode was modified so update the inode change time. */ + inode->i_ctime = CURRENT_TIME_SEC; + + /* Mark the inode as dirty to make sure the changes will be written */ + mark_inode_dirty(inode); + + return 0; +} +/* VERSION SUPPORT - END */ /* * Couple of helper functions - make the code slightly cleaner. @@ -404,6 +452,10 @@ #endif .setattr = ext2_setattr, .permission = ext2_permission, + /* VERSION SUPPORT - BEGIN */ + .getmaxversions = ext2_get_max_versions, + .setmaxversions = ext2_set_max_versions, + /* VERSION SUPPORT - END */ }; struct inode_operations ext2_special_inode_operations = { diff -Naur orig/fs/file.c vvfs/fs/file.c --- orig/fs/file.c 2005-11-11 17:06:16.000000000 +0100 +++ vvfs/fs/file.c 2005-11-17 16:06:40.000000000 +0100 @@ -1,3 +1,5 @@ +/* VERSION SUPPORT */ + /* * linux/fs/file.c * @@ -13,7 +15,76 @@ #include #include #include +#include + + +/* + * This function copies data from one file pointer to another. Since + * generic_file_sendfile was restricted to copy data from a mm_file to a + * socket there is no other convinience function available. + */ +int version_copy(struct file * src_file, struct file * dst_file) +{ + unsigned long page = 0; + char * buffer; + mm_segment_t orgfs; + int read = 0; + int written = 0; + int retn = 0; + struct inode * src_inode; + struct inode * dst_inode; + + /* Save the current address space */ + orgfs = get_fs(); + + /* + * Since generic_file_read is build to return the bytes read to a user + * space pointer, we have to change this because the buffer lies in kernel + * space. + */ + set_fs(KERNEL_DS); + + /* Get one free memory page */ + page = __get_free_page(GFP_KERNEL); + + buffer = (char *) page; + + while ((read = vfs_read(src_file, buffer, PAGE_SIZE, + &src_file->f_pos)) > 0) { + written = vfs_write(dst_file, buffer, read, &dst_file->f_pos); + retn = -EFAULT; + if (!written) + goto __finally; + + retn = written; + if (written < 0) + goto __finally; + } + + src_inode = src_file->f_dentry->d_inode; + dst_inode = dst_file->f_dentry->d_inode; + + dst_inode->i_mode = src_inode->i_mode; + dst_inode->i_ctime = src_inode->i_ctime; + dst_inode->i_mtime = CURRENT_TIME; + dst_inode->i_atime = CURRENT_TIME; + + src_inode->i_atime = CURRENT_TIME; + + mark_inode_dirty(src_inode); + mark_inode_dirty(dst_inode); + + retn = 0; +__finally: + free_page(page); + + /* Restore the original memory space */ + set_fs(orgfs); + + return retn; +} +/* VERSION SUPPORT - END */ /* * Allocate an fd array, using kmalloc or vmalloc. diff -Naur orig/fs/ioctl.c vvfs/fs/ioctl.c --- orig/fs/ioctl.c 2005-11-11 17:06:16.000000000 +0100 +++ vvfs/fs/ioctl.c 2005-11-17 16:06:40.000000000 +0100 @@ -1,3 +1,5 @@ +/* VERSION SUPPORT */ + /* * linux/fs/ioctl.c * @@ -88,6 +90,10 @@ { unsigned int flag; int on, error = 0; + /* VERSION SUPPORT - BEGIN */ + struct inode * inode = filp->f_dentry->d_inode; + int max_versions = 0; + /* VERSION SUPPORT - END */ switch (cmd) { case FIOCLEX: @@ -146,6 +152,29 @@ else error = -ENOTTY; break; + /* VERSION SUPPORT - BEGIN */ + case FIGETMAXVERSIONS: + // Read the number of versions allowed for the current dir. + if (!inode->i_op->getmaxversions) + return -EINVAL; + // Only directories have this attribute attached. + if (!S_ISDIR(inode->i_mode)) + return -ENOTDIR; + return put_user(inode->i_op->getmaxversions(inode), (int __user *) arg); + case FISETMAXVERSIONS: + // Set the number of versions allowed for the current dir. + if (!inode->i_op->setmaxversions) + return -EINVAL; + // Only directories have this attribute attached. + if (!S_ISDIR(inode->i_mode)) + return -ENOTDIR; + /* Check for write permissions */ + if ((error = generic_permission(inode, MAY_WRITE, NULL))) + return error; + if (get_user(max_versions, (int __user *) arg )) + return -EFAULT; + return inode->i_op->setmaxversions(inode, max_versions); + /* VERSION SUPPORT - END */ default: if (S_ISREG(filp->f_dentry->d_inode->i_mode)) error = file_ioctl(filp, cmd, arg); diff -Naur orig/fs/namei.c vvfs/fs/namei.c --- orig/fs/namei.c 2005-11-11 17:06:16.000000000 +0100 +++ vvfs/fs/namei.c 2005-11-17 21:22:52.000000000 +0100 @@ -1,3 +1,5 @@ +/* VERSION SUPPORT */ + /* * linux/fs/namei.c * @@ -104,6 +106,320 @@ * any extra contention... */ +/* VERSION SUPPORT - BEGIN */ +void version_separate( + const char *filename, /* complete name */ + char *name, /* file name without separator and version */ + int *version_out) /* version */ +{ + char sep = VERSION_SEPARATOR; + char *sep_pos = NULL; + int ver_nr = 0; + char *endptr = NULL; + int tmp_version = 0; + int * version = version_out == 0 ? &tmp_version : version_out; + + *version = -1; + + /* Pointer the last occurance of the separator */ + sep_pos = strrchr(filename, sep); + + /* If no separator is found copy the whole name and return */ + if (!sep_pos) { + strcpy(name, filename); + return; + } + + /* Move one character behind the separator */ + ++sep_pos; + + /* Try to convert the digets behind the separator into a number */ + ver_nr = simple_strtol(sep_pos, &endptr, 10); + + /* If nothing follows the separator or the return value is different + * from NULL copy the whole name and return -1 */ + if (!strlen(sep_pos) || *endptr != '\0') { + strcpy(name, filename); + return; + } + + *version = ver_nr; + strncpy(name, filename, strlen(filename) - strlen(sep_pos) - 1); + return; +} + +void version_file_separate( + const char *path, /* dir and file name */ + char *dir, /* dir name without a trailing slash */ + char *file) /* filename */ +{ + char *sep_pos = NULL; + + memset( dir, 0, VERSION_NAME_LEN ); + memset( file, 0, VERSION_NAME_LEN ); + + /* Pointer the last occurance of the separator */ + sep_pos = strrchr( path, '/' ); + + /* If no separator is found copy the whole path to the filename and set + * the directory to '.' */ + if( !sep_pos ) + { + dir[0] = '.'; + dir[1] = '/'; + strcpy( file, path ); + return; + } + + /* Move one character behind the separator */ + ++sep_pos; + + /* If nothing follows the separator no filename was specified */ + if( strlen( sep_pos ) == 0 ) + { + return; + } + + strncpy( dir, path, strlen( path ) - strlen( sep_pos ) ); + strncpy( file, sep_pos, strlen( sep_pos ) ); + return; +} + +static int version_on_file_found(void * buf, const char * filename, + int length, loff_t offset, ino_t inode, unsigned int type) +{ + int version = -1; + char tmp_filename[VERSION_NAME_LEN]; + char tmp_name[VERSION_NAME_LEN]; + struct name_and_version * nav = buf; + + memset(tmp_filename, 0, VERSION_NAME_LEN); + memcpy(tmp_filename, filename, length); + + /* + * Split the filename into the name and version parts. + */ + memset(tmp_name, 0, VERSION_NAME_LEN); + version_separate(tmp_filename, tmp_name, &version); + if (version < 0) + return 0; + + /* + * If the name part of the file name is equal to the basename we are + * looking for, then the version numbers are going to be compared. + * If a version bigger or smaller than the current extrem values is + * found, this version will be stored. Additionally the counter of overall + * versions is incremented. + */ + if (!strcmp(nav->name, tmp_name)) { + ++nav->version_count; + if (version > nav->max_version) { + nav->pre_max_version = nav->max_version; + nav->max_version = version; + } + else if (version > nav->pre_max_version) { + nav->pre_max_version = version; + } + if (version < nav->min_version) + nav->min_version = version; + } + + return 0; +} + +static int version_find(struct name_and_version * nav) +{ + struct file * dir; + char path[VERSION_NAME_LEN]; + char filename[VERSION_NAME_LEN]; + + version_file_separate(nav->name, path, filename); + + /* + * Find the newest version of the specified filename. If no version number + * is found at all the newest_version is set to -1. + */ + if ((dir = filp_open(path, O_RDONLY, 0)) <= 0) + /* TODO welcher return Wert */ + return -1; + + nav->name = filename; + nav->max_version = -1; + nav->pre_max_version = -1; + nav->min_version = VERSION_MAX; + nav->version_count = 0; + + if (!dir->f_op || !dir->f_op->readdir) + /* TODO welcher return Wert */ + return -1; + + dir->f_op->readdir(dir, nav, version_on_file_found); + filp_close(dir, NULL); + + return 0; +} + +int version_concatenate(char * buf, const char * name, int version) +{ + char version_string[6]; + char sep[2] = {VERSION_SEPARATOR, '\0'}; + + /* Convert version number into a string */ + snprintf(version_string, sizeof(version_string), "%d", version); + memset(buf, 0, VERSION_NAME_LEN); + + /* Concatenate filename, separator and version_string */ + strcpy(buf, name); + strcat(buf, sep); + strcat(buf, version_string); + + return 0; +} + +int version_latestfile(char * filename, const char * basename, + struct name_and_version * extern_nav) +{ + int error = -ENAMETOOLONG; + struct name_and_version tmp_nav; + struct name_and_version * nav = extern_nav == 0 ? &tmp_nav : extern_nav; + + /* For the versioning extensions we need one additional charactor for the + * separator and five characters for the version number. */ + if (strlen(basename) > VERSION_NAME_LEN - 7) + return error; + + /* Search the current directory for all files matching the basename and + * determine the latest version */ + nav->name = (char *)basename; + if ((error = version_find(nav)) < 0) + return error; + + /* Increment the latest version by one since version numbers have to be + * increased on each write access. */ + ++nav->max_version; + + error = EVERSIONMAX; + if (nav->max_version > VERSION_MAX) + return error; + + /* Build the new filename */ + return version_concatenate(filename, basename, nav->max_version); +} + +/* The function is a copy of 'sys_unlink' except there is no version code in + * here */ +int version_do_unlink(const char * name) +{ + int error = 0; + struct dentry *dentry; + struct nameidata nd; + struct inode *inode = NULL; + + error = path_lookup(name, LOOKUP_PARENT, &nd); + if (error) + goto exit; + error = -EISDIR; + if (nd.last_type != LAST_NORM) + goto exit1; + //down(&nd.dentry->d_inode->i_sem); + dentry = lookup_hash(&nd.last, nd.dentry); + error = PTR_ERR(dentry); + + if (!IS_ERR(dentry)) { + /* Why not before? Because we want correct error value */ + if (nd.last.name[nd.last.len]) + goto slashes; + + inode = dentry->d_inode; + if (inode) + atomic_inc(&inode->i_count); + + error = vfs_unlink(nd.dentry->d_inode, dentry); + exit2: + dput(dentry); + } + + //up(&nd.dentry->d_inode->i_sem); + if (inode) + iput(inode); /* truncate the inode here */ +exit1: + path_release(&nd); +exit: + return error; + +slashes: + error = !dentry->d_inode ? -ENOENT : + S_ISDIR(dentry->d_inode->i_mode) ? -EISDIR : -ENOTDIR; + goto exit2; +} + +/* Deletes the oldest version if the maximum number of version for one file + * was exceeded */ +int version_delete(struct inode * dir, char * basename, + struct name_and_version * nav) +{ + char filename[VERSION_NAME_LEN]; + + if (!dir->i_op || !dir->i_op->getmaxversions) + return -EVERSIONMAX; + + /* If there exists more versions then specified by the directory inode, + * the oldest version must be deleted. */ + if (nav->version_count >= dir->i_op->getmaxversions(dir)) { + /* Compose the filename of the basename and the oldest version. */ + version_concatenate(filename, basename, nav->min_version); + version_do_unlink(filename); + } + + return 0; +} + +/* Determines the targetname the link is pointing to if the following + * conditions are fulfilled: + * - dentry is a link + * - link count == 1 (direct target) + * - link dir and target dir are identical + * - link name == basename of target */ +int version_followlink(struct dentry * den_link, char * targetname) +{ + struct inode * inode = den_link->d_inode; + struct nameidata nd; + char basename[VERSION_NAME_LEN]; + char tmpname[VERSION_NAME_LEN]; + int error = 0; + + if (!S_ISLNK(inode->i_mode)) + return -ENOTLNK; + nd.depth = 0; + if ((error = inode->i_op->follow_link(den_link, &nd))) + return error; + /* The regular file must be a direct link target */ + if (nd.depth != 0) + return -ENOTLNK; + memset(tmpname, 0, VERSION_NAME_LEN); + strcpy(tmpname, nd.saved_names[0]); + +/* if ((error = path_lookup(tmpname, LOOKUP_PARENT, &nd ))) + goto exit; + error = -ENOTLNK; + if (den_link->d_parent != nd.dentry) + goto exit; */ + + memset(basename, 0, VERSION_NAME_LEN); + version_separate(tmpname, basename, 0); + error = -ENOTLNK; + if (strcmp(den_link->d_name.name, basename)) + goto exit; + if (targetname) + strcpy(targetname, tmpname); + + error = 0; +exit: + /* path_release(&nd); */ + return error; +} +/* VERSION SUPPORT - END */ + /* In order to reduce some races, while at the same time doing additional * checking and hopefully speeding things up, we copy filenames to the * kernel data space before using them.. @@ -1513,6 +1829,12 @@ if (path.dentry->d_inode && S_ISDIR(path.dentry->d_inode->i_mode)) goto exit; ok: + /* VERSION SUPPORT - BEGIN */ + /* If Version support is enabled for the current directory, instead of + * truncating a file we will not copy the content in version_dentry_open */ + if (VERSION_SUPPORT_ENABLED(nd->dentry->d_parent->d_inode)) + flag &= ~O_TRUNC; + /* VERSION SUPPORT - END */ error = may_open(nd, acc_mode, flag); if (error) goto exit; @@ -1864,6 +2186,132 @@ return error; } +/* VERSION SUPPORT - BEGIN */ +/* This function is only called by sys_unlink and distinguishes the following + * cases: + * 1. 'sys_unlink' was called on a link which points to a file with a version + * number. + * -> change the dentry to this file and delete the link + * -> check whether the latest version is going to be deleted, if this is + * the case determine the previous version and build a filename for + * later link creation + * 2. 'sys_unlink' was called on a file with a version number + * -> check whether the latest version is going to be deleted, if this is + * the case determine the previous version and build a filename for + * later link creation + * 3. 'sys_unlink' was called on a regular file without a version number or a + * ordenary link + * -> do nothing at all + */ +static int version_unlink(struct dentry ** dentry, char * basename, + char * versionname, int * link) +{ + struct dentry * file_dentry = *dentry; + struct nameidata nd; + int error = 0; + int version; + char targetname[VERSION_NAME_LEN]; + char tmpname[VERSION_NAME_LEN]; + struct name_and_version nav; + int is_link = 0; + + if (!file_dentry->d_inode) + return 0; + + memset(targetname, 0, VERSION_NAME_LEN); + memset(tmpname, 0, VERSION_NAME_LEN); + memset(versionname, 0, VERSION_NAME_LEN); + strcpy(targetname, basename); + + /* The file which is going to be deleted is a version link */ + if (version_followlink(file_dentry, tmpname) == 0) { + is_link = 1; + strcat(targetname, tmpname); + + /* Change the dentry to the version file so that the code in + * 'sys_unlink' can delete it */ + if ((error = path_lookup(targetname, LOOKUP_PARENT, &nd))) + return error; + + dput(*dentry); + *dentry = lookup_hash(&nd.last, nd.dentry); + path_release(&nd); + error = PTR_ERR(*dentry); + if (IS_ERR(*dentry)) + return error; + + /* Delete the version link */ + memset(tmpname, 0, VERSION_NAME_LEN); + strcat(tmpname, basename); + strcat(tmpname, file_dentry->d_name.name); + if ((error = version_do_unlink(tmpname))) + return error; + } else { + /* Save the original file name */ + strcat(targetname, file_dentry->d_name.name); + } + + memset(basename, 0, VERSION_NAME_LEN); + version_separate(targetname, basename, &version); + + /* If the file has no version number attached stop here */ + if (version < 0) + return 0; + + /* Search the current directory for all files matching the basename and + * determine the latest version */ + nav.name = basename; + if ((error = version_find(&nav))) + return error; + version = (version == nav.max_version) ? + nav.pre_max_version : nav.max_version; + + /* If there are no further versions, no new link has to be created */ + if (version < 0) { + /* Special case: 'sys_unlink' was called directly on the lastest + * version and no further versions exists + * -> the version link pointing to this file has to be deleted + * as well */ + + /* TODO: Check wheter the link exists. If this is the case return the + * error code of version_do_unlink + if (!is_link && error = version_do_unlink(basename))) + return error; */ + if (!is_link) + version_do_unlink(basename); + return 0; + } + + /* Build a file name which can be used as a link target later on */ + if ((error = version_concatenate(versionname, file_dentry->d_name.name, + version))) + return error; + + *link = 1; + return 0; +} + +int version_link(const char * oldname, const char * newname) +{ + + int retn = 0; + mm_segment_t orgfs; + + retn = version_do_unlink(newname); + + /* Save the current address space */ + orgfs = get_fs(); + set_fs(KERNEL_DS); + + retn = sys_symlink(oldname, newname); + + /* Restore the original memory space */ + set_fs(orgfs); + + return retn; +} +/* VERSION SUPPORT - END */ + /* * Make sure that the actual truncation of the file will occur outside its * directory's i_sem. Truncate can take a long time if there is a lot of @@ -1877,6 +2325,11 @@ struct dentry *dentry; struct nameidata nd; struct inode *inode = NULL; + /* VERSION SUPPORT - BEGIN */ + char basename[VERSION_NAME_LEN]; + char versionname[VERSION_NAME_LEN]; + int link = 0; + /* VERSION SUPPORT - END */ name = getname(pathname); if(IS_ERR(name)) @@ -1892,6 +2345,14 @@ dentry = lookup_hash(&nd.last, nd.dentry); error = PTR_ERR(dentry); if (!IS_ERR(dentry)) { + /* VERSION SUPPORT - BEGIN */ + if (VERSION_SUPPORT_ENABLED(nd.dentry->d_inode)) { + version_file_separate(name, basename, versionname); + if ((error = version_unlink(&dentry, basename, versionname, + &link)) < 0) + goto exit2; + } + /* VERSION SUPPORT - END */ /* Why not before? Because we want correct error value */ if (nd.last.name[nd.last.len]) goto slashes; @@ -1905,6 +2366,13 @@ up(&nd.dentry->d_inode->i_sem); if (inode) iput(inode); /* truncate the inode here */ + /* VERSION SUPPORT - BEGIN */ + /* TODO: This code must be moved into the locked area if the + * implementation of version_link makes no use of the big sys_calls which + * leads to a deadlock */ + if (link) + error = version_link(versionname, basename); + /* VERSION SUPPORT - END */ exit1: path_release(&nd); exit: @@ -2207,6 +2675,85 @@ return error; } +/* VERSION SUPPORT - BEGIN */ +static int version_src_rename(const char * old_name, const char * new_name, + const struct nameidata * new_nd) +{ + struct inode * new_inode = new_nd->dentry->d_inode; + struct file * old_filp; + struct file * new_filp; + int flags = O_WRONLY | O_CREAT; + int namei_flags; + struct nameidata tmp_nd; + int error = 0; + + old_filp = filp_open(old_name, O_RDONLY, 0); + error = PTR_ERR(old_filp); + if (IS_ERR(old_filp)) + goto exit0; + + if (VERSION_SUPPORT_ENABLED(new_inode)) { + namei_flags = flags; + if ((namei_flags + 1) & O_ACCMODE) + namei_flags++; + if ((error = open_namei(new_name, namei_flags, 0, &tmp_nd)) < 0) + goto exit1; + new_filp = version_dentry_open(&tmp_nd, new_name, flags, + namei_flags, namei_flags, 0); + } else { + new_filp = filp_open(new_name, O_WRONLY | O_CREAT, 0); + } + + error = PTR_ERR(new_filp); + if (IS_ERR(new_filp)) { + path_release(&tmp_nd); + goto exit1; + } + + if ((error = version_copy(old_filp, new_filp)) < 0) + goto exit2; + +exit2: + filp_close(new_filp, NULL); +exit1: + filp_close(old_filp, NULL); +exit0: + return error; +} + +static int version_dst_rename(struct inode * srcdir_inode, + struct dentry * src_dentry, const char * name) +{ + struct nameidata dst_nd; + struct dentry * dst_dentry; + int error = 0; + char dst_name[VERSION_NAME_LEN]; + char basename[VERSION_NAME_LEN]; + + version_separate(name, basename, 0); + if ((error = version_latestfile(dst_name, basename, 0))) + return error; + if ((error = path_lookup(dst_name, LOOKUP_PARENT, &dst_nd))) + return error; + + dst_dentry = lookup_hash(&dst_nd.last, dst_nd.dentry); + error = PTR_ERR(dst_dentry); + if (IS_ERR(dst_dentry)) + goto exit; + + if ((error = vfs_rename(srcdir_inode, src_dentry, + dst_nd.dentry->d_inode, dst_dentry))) + goto exit1; + error = version_link(dst_dentry->d_name.name, basename); + +exit1: + dput(dst_dentry); +exit: + path_release(&dst_nd); + return error; +} +/* VERSION SUPPORT - END */ + static inline int do_rename(const char * oldname, const char * newname) { int error = 0; @@ -2214,6 +2761,9 @@ struct dentry * old_dentry, *new_dentry; struct dentry * trap; struct nameidata oldnd, newnd; + /* VERSION SUPPORT - BEGIN */ + int do_unlock = 1; + /* VERSION SUPPORT - END */ error = path_lookup(oldname, LOOKUP_PARENT, &oldnd); if (error) @@ -2267,6 +2817,29 @@ if (new_dentry == trap) goto exit5; + /* VERSION SUPPORT - BEGIN */ + if (VERSION_SUPPORT_ENABLED(oldnd.dentry->d_inode) || + VERSION_SUPPORT_ENABLED(newnd.dentry->d_inode)) + { + /* The source must be either a regular file or a version link */ + if (S_ISREG(old_dentry->d_inode->i_mode) || + !version_followlink(old_dentry, 0)) + { + /* This is a dirty hack and has to be changed when the version + * code should become smp save. */ + unlock_rename(new_dir, old_dir); + do_unlock = 0; + + if (VERSION_SUPPORT_ENABLED(oldnd.dentry->d_inode)) + error = version_src_rename(oldname, newname, &newnd); + else + error = version_dst_rename(oldnd.dentry->d_inode, + old_dentry, newname); + goto exit5; + } + } + /* VERSION SUPPORT - END */ + error = vfs_rename(old_dir->d_inode, old_dentry, new_dir->d_inode, new_dentry); exit5: @@ -2274,11 +2847,15 @@ exit4: dput(old_dentry); exit3: + /* VERSION SUPPORT - BEGIN */ + if (do_unlock) + /* VERSION SUPPORT - END */ unlock_rename(new_dir, old_dir); exit2: path_release(&newnd); exit1: path_release(&oldnd); + exit: return error; } diff -Naur orig/fs/open.c vvfs/fs/open.c --- orig/fs/open.c 2005-11-11 17:06:16.000000000 +0100 +++ vvfs/fs/open.c 2005-11-17 21:22:40.000000000 +0100 @@ -1,3 +1,5 @@ +/* VERSION SUPPORT */ + /* * linux/fs/open.c * @@ -736,6 +738,99 @@ return error; } +/* VERSION SUPPORT - BEGIN */ +struct file *version_dentry_open(struct nameidata * nd, + const char *old_filename, int flags, + int namei_flags, int mode, int copy) +{ + int error = 0; + struct nameidata new_nd; + struct dentry * dentry = nd->dentry; + struct inode * inode = dentry->d_inode; + struct inode * current_dir_inode = dentry->d_parent->d_inode; + int filesize = inode->i_size; + char basename[VERSION_NAME_LEN]; /* complete path */ + char dir[VERSION_NAME_LEN]; + char file[VERSION_NAME_LEN]; + char new_filename[VERSION_NAME_LEN]; + int current_version = 0; + struct name_and_version nav; + struct file * filp = 0; + struct file * tmp_filp = 0; + + /* New versions of files are only created if the file is opened with write + * access. */ + if ((flags & O_ACCMODE) == O_RDONLY) + goto out_no_versioning; + + /* Versioning is only allowed for regular files. If the current inode is + * not a file we can stop here. */ + if (!S_ISREG(inode->i_mode)) + goto out_no_versioning; + + /* Versioning support for hidden files will be disabled since many + * programms use this files for temporary data. */ + if (dentry->d_name.name[0] == '.') + goto out_no_versioning; + + /* Separates the filename from the version number */ + memset(basename, 0, VERSION_NAME_LEN); + /* 051117 + version_separate(dentry->d_name.name, basename, ¤t_version);*/ + version_separate(old_filename, basename, ¤t_version); + + /* Versioning is sensless for empty files, which already exists */ + if (filesize == 0 && (!(flags & O_TRUNC)) && current_version >= 0) + goto out_no_versioning; + + if ((error = version_latestfile(new_filename, basename, &nav))) + goto error; + + /* Open the new filename */ + if ((error = open_namei(new_filename, namei_flags | O_CREAT, + mode, &new_nd)) < 0) + goto error; + filp = dentry_open(new_nd.dentry, new_nd.mnt, flags); + if (filp <= 0) + return filp; + + /* If there is no content there is no reason create a new version */ + if (filesize == 0 || copy == 0) { + path_release(nd); + goto out_versioning; + } + + /* Copy the content of the original adressed file to the latest version */ + tmp_filp = dentry_open(nd->dentry, nd->mnt, O_RDONLY); + if (tmp_filp <= 0) + return filp; + error = version_copy(tmp_filp, filp); + filp_close(tmp_filp, NULL); + + if (error < 0) + goto error; + +out_versioning: + version_file_separate(new_filename, dir, file); + /* Create a symlink from the basename to the latest version */ + if ((error = version_link(file, basename)) < 0) + goto error; + /* Delete the oldest version if the maximum version number was exceeded */ + if ((error = version_delete(current_dir_inode, basename, &nav)) < 0) + goto error; + return filp; + +out_no_versioning: + return dentry_open(nd->dentry, nd->mnt, flags); + +error: + if (filp > 0) + filp_close(filp, NULL); + return ERR_PTR(error); +} +/* VERSION SUPPORT - END */ + + /* * Note that while the flag value (low two bits) for sys_open means: * 00 - read-only @@ -754,6 +849,9 @@ { int namei_flags, error; struct nameidata nd; + /* VERSION SUPPORT - BEGIN */ + int copy = 1; + /* VERSION SUPPORT - END */ namei_flags = flags; if ((namei_flags+1) & O_ACCMODE) @@ -762,6 +860,16 @@ namei_flags |= 2; error = open_namei(filename, namei_flags, mode, &nd); + /* VERSION SUPPORT - BEGIN */ + if (!error && VERSION_SUPPORT_ENABLED(nd.dentry->d_parent->d_inode)) { + /* If version support is enabled for the directory a file will never + * be truncated. If a clean file was requestet the copying of data + * between the versions will be switched of instead. */ + copy = flags & O_TRUNC ? 0 : 1; + flags &= ~O_TRUNC; + return version_dentry_open(&nd, filename, flags, namei_flags, mode, copy); + } + /* VERSION SUPPORT - END */ if (!error) return dentry_open(nd.dentry, nd.mnt, flags); diff -Naur orig/include/asm-generic/errno.h vvfs/include/asm-generic/errno.h --- orig/include/asm-generic/errno.h 2005-11-11 17:06:16.000000000 +0100 +++ vvfs/include/asm-generic/errno.h 2005-11-17 16:06:40.000000000 +0100 @@ -1,3 +1,5 @@ +/* VERSION SUPPORT */ + #ifndef _ASM_GENERIC_ERRNO_H #define _ASM_GENERIC_ERRNO_H @@ -106,4 +108,9 @@ #define EOWNERDEAD 130 /* Owner died */ #define ENOTRECOVERABLE 131 /* State not recoverable */ +/* VERSION SUPPORT - BEGIN */ +#define EVERSIONMAX 150 /* Maximum number of versions exceeded */ +#define ENOTLNK 151 /* The file is not a link */ +/* VERSION SUPPORT - END */ + #endif diff -Naur orig/include/linux/ext2_fs.h vvfs/include/linux/ext2_fs.h --- orig/include/linux/ext2_fs.h 2005-11-11 17:06:16.000000000 +0100 +++ vvfs/include/linux/ext2_fs.h 2005-11-17 16:06:40.000000000 +0100 @@ -1,3 +1,5 @@ +/* VERSION SUPPORT */ + /* * linux/include/linux/ext2_fs.h * @@ -132,7 +134,7 @@ /* * Structure of a blocks group descriptor - */ + */# struct ext2_group_desc { __le32 bg_block_bitmap; /* Blocks bitmap block */ @@ -243,7 +245,11 @@ __u16 i_pad1; __le16 l_i_uid_high; /* these 2 fields */ __le16 l_i_gid_high; /* were reserved2[0] */ - __u32 l_i_reserved2; + /* VERSION SUPPORT - BEGIN */ + __u16 l_i_max_versions; + __u16 l_i_reserved2; + /* __u32 l_i_reserved2; */ + /* VERSION SUPPORT - END */ } linux2; struct { __u8 h_i_frag; /* Fragment number */ @@ -272,7 +278,10 @@ #define i_gid_low i_gid #define i_uid_high osd2.linux2.l_i_uid_high #define i_gid_high osd2.linux2.l_i_gid_high -#define i_reserved2 osd2.linux2.l_i_reserved2 +/* VERSION SUPPORT - BEGIN */ +#define i_max_versions osd2.linux2.l_i_max_versions +/*#define i_reserved2 osd2.linux2.l_i_reserved2*/ +/* VERSION SUPPORT - END */ #endif #ifdef __hurd__ diff -Naur orig/include/linux/fs.h vvfs/include/linux/fs.h --- orig/include/linux/fs.h 2005-11-11 17:06:16.000000000 +0100 +++ vvfs/include/linux/fs.h 2005-11-17 16:06:40.000000000 +0100 @@ -1,3 +1,4 @@ +/* VERSION SUPPORT */ #ifndef _LINUX_FS_H #define _LINUX_FS_H @@ -198,6 +199,14 @@ #define BMAP_IOCTL 1 /* obsolete - kept for compatibility */ #define FIBMAP _IO(0x00,1) /* bmap access */ #define FIGETBSZ _IO(0x00,2) /* get the block size used for bmap */ +/* VERSION SUPPORT - BEGIN */ +#define FIGETMAXVERSIONS _IO(0x01,1) /* get number of versions */ +#define FISETMAXVERSIONS _IO(0x01,2) /* set number of versions */ + +#define VERSION_SEPARATOR '#' +#define VERSION_MAX 65000 +#define VERSION_NAME_LEN 255 +/* VERSION SUPPORT - END */ #ifdef __KERNEL__ @@ -916,6 +925,20 @@ #define HAVE_COMPAT_IOCTL 1 #define HAVE_UNLOCKED_IOCTL 1 +/* VERSION SUPPORT - BEGIN */ +#define VERSION_SUPPORT(i) (i && i->i_op && i->i_op->getmaxversions) +#define VERSION_SUPPORT_ENABLED(i) \ + (VERSION_SUPPORT(i) && i->i_op->getmaxversions(i)) + +struct name_and_version { + char * name; /* Reference name to search for. */ + int max_version; /* Largest version found */ + int pre_max_version; /* The second largest version found */ + int min_version; /* Smallest version found */ + int version_count; /* Number of different versions found */ +}; +/* VERSION SUPPORT - END */ + /* * NOTE: * read, write, poll, fsync, readv, writev, unlocked_ioctl and compat_ioctl @@ -973,6 +996,10 @@ ssize_t (*getxattr) (struct dentry *, const char *, void *, size_t); ssize_t (*listxattr) (struct dentry *, char *, size_t); int (*removexattr) (struct dentry *, const char *); + /* VERSION SUPPORT - START */ + int (*getmaxversions) (struct inode *); + int (*setmaxversions) (struct inode *, int); + /* VERSION SUPPORT - END */ }; struct seq_file; @@ -1264,6 +1291,23 @@ extern struct file * dentry_open(struct dentry *, struct vfsmount *, int); extern int filp_close(struct file *, fl_owner_t id); extern char * getname(const char __user *); +/* VERSION SUPPORT - BEGIN */ +extern struct file *version_dentry_open(struct nameidata * nd, + const char *, int flags, int namei_flags, int mode, int copy); + +/* fs/namei.c */ + +extern void version_separate(const char *, char *, int *); +extern void version_file_separate(const char *, char *, char *); +extern int version_delete(struct inode *, char *, struct name_and_version *); +extern int version_concatenate(char *, const char *, int); +extern int version_link(const char *, const char *); +extern int version_latestfile(char *, const char *, struct name_and_version *); + +/* fs/file.c */ + +extern int version_copy(struct file *, struct file *); +/* VERSION SUPPORT - END */ /* fs/dcache.c */ extern void __init vfs_caches_init_early(void);