Hard-links, via sys_link()
authorBarret Rhoden <brho@cs.berkeley.edu>
Thu, 19 Aug 2010 02:23:54 +0000 (19:23 -0700)
committerKevin Klues <klueska@cs.berkeley.edu>
Thu, 3 Nov 2011 00:35:52 +0000 (17:35 -0700)
kern/include/vfs.h
kern/src/kfs.c
kern/src/syscall.c
kern/src/vfs.c
tests/file_test.c

index e9e054e..b088bab 100644 (file)
@@ -504,6 +504,7 @@ ssize_t generic_dir_read(struct file *file, char *u_buf, size_t count,
                          off_t *offset);
 struct file *do_file_open(char *path, int flags, int mode);
 int do_symlink(char *path, const char *symname, int mode);
+int do_link(char *old_path, char *new_path);
 int do_file_access(char *path, int mode);
 struct file *dentry_open(struct dentry *dentry, int flags);
 void file_release(struct kref *kref);
index 6c782b8..53babd8 100644 (file)
@@ -346,11 +346,16 @@ struct dentry *kfs_lookup(struct inode *dir, struct dentry *dentry,
 }
 
 /* Hard link to old_dentry in directory dir with a name specified by new_dentry.
- * TODO: should this also make the dentry linkage, or just discard everything?*/
+ * At the very least, set the new_dentry's FS-specific fields. */
 int kfs_link(struct dentry *old_dentry, struct inode *dir,
              struct dentry *new_dentry)
 {
-       return -1;
+       assert(new_dentry->d_op = &kfs_d_op);
+       kref_get(&new_dentry->d_kref, 1);               /* pin the dentry, KFS-style */
+       /* KFS-style directory-tracking-of-kids */
+       TAILQ_INSERT_TAIL(&((struct kfs_i_info*)dir->i_fs_info)->children,
+                         new_dentry, d_subdirs_link);
+       return 0;
 }
 
 /* Removes the link from the dentry in the directory */
index 0eadc4f..d3999bb 100644 (file)
@@ -1018,23 +1018,21 @@ static intreg_t sys_lseek(struct proc *p, int fd, off_t offset, int whence)
        return ret;
 }
 
-intreg_t sys_link(struct proc *p, const char *_old, size_t old_l,
-                  const char *_new, size_t new_l)
+intreg_t sys_link(struct proc *p, char *old_path, size_t old_l,
+                  char *new_path, size_t new_l)
 {
-       char* oldpath = user_strdup_errno(p,_old,PGSIZE);
-       if(oldpath == NULL)
+       int ret;
+       char *t_oldpath = user_strdup_errno(p, old_path, old_l);
+       if (t_oldpath == NULL)
                return -1;
-
-       char* newpath = user_strdup_errno(p,_new,PGSIZE);
-       if(newpath == NULL)
-       {
-               user_memdup_free(p,oldpath);
+       char *t_newpath = user_strdup_errno(p, new_path, new_l);
+       if (t_newpath == NULL) {
+               user_memdup_free(p, t_oldpath);
                return -1;
        }
-
-       int ret = ufe(link,PADDR(oldpath),PADDR(newpath),0,0);
-       user_memdup_free(p,oldpath);
-       user_memdup_free(p,newpath);
+       ret = do_link(t_oldpath, t_newpath);
+       user_memdup_free(p, t_oldpath);
+       user_memdup_free(p, t_newpath);
        return ret;
 }
 
index 20cf1ac..3ac619c 100644 (file)
@@ -513,6 +513,8 @@ void init_sb(struct super_block *sb, struct vfsmount *vmnt,
  * set still: d_op (if no parent), d_fs_info (opt), d_inode, connect the inode
  * to the dentry (and up the d_kref again), maybe dcache_put().  The inode
  * stitching is done in get_inode() or lookup (depending on the FS).
+ * The setting of the d_op might be problematic when dealing with mounts.  Just
+ * overwrite it.
  *
  * If the name is longer than the inline name, it will kmalloc a buffer, so
  * don't worry about the storage for *name after calling this. */
@@ -524,6 +526,8 @@ struct dentry *get_dentry(struct super_block *sb, struct dentry *parent,
        struct dentry *dentry = kmem_cache_alloc(dentry_kcache, 0);
        char *l_name = 0;
 
+       if (!dentry)
+               return 0;
        //memset(dentry, 0, sizeof(struct dentry));
        kref_init(&dentry->d_kref, dentry_release, 1);  /* this ref is returned */
        spinlock_init(&dentry->d_lock);
@@ -1023,6 +1027,11 @@ int do_symlink(char *path, const char *symname, int mode)
        }
        /* Doesn't already exist, let's try to make it: */
        sym_d = get_dentry(nd->dentry->d_sb, nd->dentry, nd->last.name);
+       if (!sym_d) {
+               set_errno(ENOMEM);
+               path_release(nd);
+               return -1;
+       }
        parent_i = nd->dentry->d_inode;
        /* TODO: mode should be & ~umask. */
        if (create_symlink(parent_i, sym_d, symname, mode)) {
@@ -1036,6 +1045,74 @@ int do_symlink(char *path, const char *symname, int mode)
        return 0;
 }
 
+/* Makes a hard link for the file behind old_path to new_path */
+int do_link(char *old_path, char *new_path)
+{
+       struct dentry *link_d, *old_d;
+       struct inode *inode, *parent_dir;
+       struct nameidata nd_r = {0}, *nd = &nd_r;
+       int error;
+       int retval = -1;
+
+       nd->intent = LOOKUP_CREATE;
+       /* get the absolute parent of the new_path */
+       error = path_lookup(new_path, LOOKUP_PARENT | LOOKUP_FOLLOW, nd);
+       if (error) {
+               set_errno(-error);
+               goto out_path_only;
+       }
+       parent_dir = nd->dentry->d_inode;
+       /* see if the new target is already there, handle accordingly */
+       link_d = do_lookup(nd->dentry, nd->last.name); 
+       if (link_d) {
+               set_errno(EEXIST);
+               goto out_link_d;
+       }
+       /* Doesn't already exist, let's try to make it.  Still need to stitch it to
+        * an inode and set its FS-specific stuff after this.*/
+       link_d = get_dentry(nd->dentry->d_sb, nd->dentry, nd->last.name);
+       if (!link_d) {
+               set_errno(ENOMEM);
+               goto out_path_only;
+       }
+       /* Now let's get the old_path target */
+       old_d = lookup_dentry(old_path, LOOKUP_FOLLOW);
+       if (!old_d)                                     /* errno set by lookup_dentry */
+               goto out_link_d;
+       /* For now, can only link to files */
+       if (old_d->d_inode->i_type != FS_I_FILE) {
+               set_errno(EPERM);
+               goto out_both_ds;
+       }
+       /* Must be on the same FS */
+       if (old_d->d_sb != link_d->d_sb) {
+               set_errno(EXDEV);
+               goto out_both_ds;
+       }
+       /* Do whatever FS specific stuff there is first (which is also a chance to
+        * bail out). */
+       error = parent_dir->i_op->link(old_d, parent_dir, link_d);
+       if (error) {
+               set_errno(-error);
+               goto out_both_ds;
+       }
+       /* Finally stitch it up */
+       inode = old_d->d_inode;
+       kref_get(&inode->i_kref, 1);
+       link_d->d_inode = inode;
+       inode->i_nlink++;
+       TAILQ_INSERT_TAIL(&inode->i_dentry, link_d, d_alias);   /* weak ref */
+       dcache_put(link_d);
+       retval = 0;                             /* Note the fall through to the exit paths */
+out_both_ds:
+       kref_put(&old_d->d_kref);
+out_link_d:
+       kref_put(&link_d->d_kref);
+out_path_only:
+       path_release(nd);
+       return retval;
+}
+
 /* Checks to see if path can be accessed via mode.  Doesn't do much now.  This
  * is an example of decent error propagation from the lower levels via int
  * retvals. */
index 8f591e8..d05494a 100644 (file)
@@ -93,6 +93,12 @@ int main()
                printf("End of the directory\n");
        else
                printf("Dirent name: %s\n", result->d_name);
+       
+       /* Hardlink tests */
+       printf("Linking to /bin/hello at /dir1/hardhello\n");
+       retval = link("/bin/hello", "/dir1/hardhello");
+       if (retval < 0)
+               printf("WARNING! Link failed!\n");
 
        breakpoint();
 }