aboutsummaryrefslogtreecommitdiff
path: root/libdiskfs/dir-renamed.c
diff options
context:
space:
mode:
Diffstat (limited to 'libdiskfs/dir-renamed.c')
-rw-r--r--libdiskfs/dir-renamed.c229
1 files changed, 229 insertions, 0 deletions
diff --git a/libdiskfs/dir-renamed.c b/libdiskfs/dir-renamed.c
new file mode 100644
index 00000000..3d518eb9
--- /dev/null
+++ b/libdiskfs/dir-renamed.c
@@ -0,0 +1,229 @@
+/*
+ Copyright (C) 1994, 95, 96, 97, 98, 1999 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2, or (at
+ your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+#include "priv.h"
+
+
+/* Check if source directory is in the path of the target directory.
+ We get target locked, source unlocked but with a reference. When
+ we return, nothing is locked, and target has lost its reference.
+ This routine assumes that no renames of directories will happen
+ while it is running; as a result, ufs_rename serializes all renames
+ of directories. */
+static error_t
+checkpath(struct node *source,
+ struct node *target,
+ struct protid *cred)
+{
+ error_t err;
+ struct node *np;
+
+ np = target;
+ for (np = target, err = 0;
+ /* nothing */;
+ /* This special lookup does a diskfs_nput on its first argument
+ when it succeeds. */
+ err = diskfs_lookup (np, "..", LOOKUP | SPEC_DOTDOT, &np, 0, cred))
+ {
+ if (err)
+ {
+ diskfs_nput (np);
+ return err;
+ }
+
+ if (np == source)
+ {
+ diskfs_nput (np);
+ return EINVAL;
+ }
+
+ if (np == diskfs_root_node)
+ {
+ diskfs_nput (np);
+ return 0;
+ }
+ }
+}
+
+/* Rename directory node FNP (whose parent is FDP, and which has name
+ FROMNAME in that directory) to have name TONAME inside directory
+ TDP. None of these nodes are locked, and none should be locked
+ upon return. This routine is serialized, so it doesn't have to be
+ reentrant. Directories will never be renamed except by this
+ routine. FROMCRED and TOCRED are the users responsible for
+ FDP/FNP and TDP respectively. */
+error_t
+diskfs_rename_dir (struct node *fdp, struct node *fnp, const char *fromname,
+ struct node *tdp, const char *toname,
+ struct protid *fromcred, struct protid *tocred)
+{
+ error_t err;
+ struct node *tnp, *tmpnp;
+ void *buf = alloca (diskfs_dirstat_size);
+ struct dirstat *ds;
+ struct dirstat *tmpds;
+
+ mutex_lock (&tdp->lock);
+ diskfs_nref (tdp); /* reference and lock will get consumed by
+ checkpath */
+ err = checkpath (fnp, tdp, tocred);
+
+ if (err)
+ return err;
+
+ /* Now, lock the parent directories. This is legal because tdp is not
+ a child of fnp (guaranteed by checkpath above). */
+ mutex_lock (&fdp->lock);
+ if (fdp != tdp)
+ mutex_lock (&tdp->lock);
+
+ /* 1: Lookup target; if it exists, make sure it's an empty directory. */
+ ds = buf;
+ err = diskfs_lookup (tdp, toname, RENAME, &tnp, ds, tocred);
+ assert (err != EAGAIN); /* <-> assert (TONAME != "..") */
+
+ if (tnp == fnp)
+ {
+ diskfs_drop_dirstat (tdp, ds);
+ diskfs_nput (tnp);
+ mutex_unlock (&tdp->lock);
+ if (fdp != tdp)
+ mutex_unlock (&fdp->lock);
+ return 0;
+ }
+
+ /* Now we can safely lock fnp */
+ mutex_lock (&fnp->lock);
+
+ if (tnp)
+ {
+ if (! S_ISDIR(tnp->dn_stat.st_mode))
+ err = ENOTDIR;
+ else if (!diskfs_dirempty (tnp, tocred))
+ err = ENOTEMPTY;
+ }
+
+ if (err && err != ENOENT)
+ goto out;
+
+ /* 2: Set our .. to point to the new parent */
+ if (fdp != tdp)
+ {
+ if (tdp->dn_stat.st_nlink == diskfs_link_max - 1)
+ {
+ err = EMLINK;
+ return EMLINK;
+ }
+ tdp->dn_stat.st_nlink++;
+ tdp->dn_set_ctime = 1;
+ if (diskfs_synchronous)
+ diskfs_node_update (tdp, 1);
+
+ tmpds = alloca (diskfs_dirstat_size);
+ err = diskfs_lookup (fnp, "..", RENAME | SPEC_DOTDOT,
+ &tmpnp, tmpds, fromcred);
+ assert (err != ENOENT);
+ assert (tmpnp == fdp);
+ if (err)
+ {
+ diskfs_drop_dirstat (fnp, tmpds);
+ goto out;
+ }
+
+ err = diskfs_dirrewrite (fnp, fdp, tdp, "..", tmpds);
+ if (diskfs_synchronous)
+ diskfs_file_update (fnp, 1);
+ if (err)
+ goto out;
+
+ fdp->dn_stat.st_nlink--;
+ fdp->dn_set_ctime = 1;
+ if (diskfs_synchronous)
+ diskfs_node_update (fdp, 1);
+ }
+
+
+ /* 3: Increment the link count on the node being moved and rewrite
+ tdp. */
+ if (fnp->dn_stat.st_nlink == diskfs_link_max - 1)
+ {
+ mutex_unlock (&fnp->lock);
+ diskfs_drop_dirstat (tdp, ds);
+ mutex_unlock (&tdp->lock);
+ if (tnp)
+ diskfs_nput (tnp);
+ return EMLINK;
+ }
+ fnp->dn_stat.st_nlink++;
+ fnp->dn_set_ctime = 1;
+ diskfs_node_update (fnp, 1);
+
+ if (tnp)
+ {
+ err = diskfs_dirrewrite (tdp, tnp, fnp, toname, ds);
+ ds = 0;
+ if (!err)
+ {
+ tnp->dn_stat.st_nlink--;
+ tnp->dn_set_ctime = 1;
+ }
+ diskfs_clear_directory (tnp, tdp, tocred);
+ if (diskfs_synchronous)
+ diskfs_file_update (tnp, 1);
+ }
+ else
+ {
+ err = diskfs_direnter (tdp, toname, fnp, ds, tocred);
+ if (diskfs_synchronous)
+ diskfs_file_update (tdp, 1);
+ }
+
+ if (err)
+ goto out;
+
+ /* 4: Remove the entry in fdp. */
+ ds = buf;
+ mutex_unlock (&fnp->lock);
+ err = diskfs_lookup (fdp, fromname, REMOVE, &tmpnp, ds, fromcred);
+ assert (tmpnp == fnp);
+ diskfs_nrele (tmpnp);
+ if (err)
+ goto out;
+
+ diskfs_dirremove (fdp, fnp, fromname, ds);
+ ds = 0;
+ fnp->dn_stat.st_nlink--;
+ fnp->dn_set_ctime = 1;
+ if (diskfs_synchronous)
+ {
+ diskfs_file_update (fdp, 1);
+ diskfs_node_update (fnp, 1);
+ }
+
+ out:
+ if (tdp)
+ mutex_unlock (&tdp->lock);
+ if (tnp)
+ diskfs_nput (tnp);
+ if (fdp && fdp != tdp)
+ mutex_unlock (&fdp->lock);
+ if (fnp)
+ mutex_unlock (&fnp->lock);
+ if (ds)
+ diskfs_drop_dirstat (tdp, ds);
+ return err;
+}