summaryrefslogtreecommitdiffstats
path: root/sys/fs/devfs
diff options
context:
space:
mode:
authorjh <jh@FreeBSD.org>2010-09-27 17:47:09 +0000
committerjh <jh@FreeBSD.org>2010-09-27 17:47:09 +0000
commitb6e78e30f23dbed7263727af46739f94ef85fd86 (patch)
treeaac5e5193fff6dc7e4ad901a2ea6b4bab1a82a25 /sys/fs/devfs
parent0901947e8d8500bb8120febd32587bc1eeabca98 (diff)
downloadFreeBSD-src-b6e78e30f23dbed7263727af46739f94ef85fd86.zip
FreeBSD-src-b6e78e30f23dbed7263727af46739f94ef85fd86.tar.gz
Add reference counting for devfs paths containing user created symbolic
links. The reference counting is needed to be able to determine if a specific devfs path exists. For true device file paths we can traverse the cdevp_list but a separate directory list is needed for user created symbolic links. Add a new directory entry flag DE_USER to mark entries which should unreference their parent directory on deletion. A new function to traverse cdevp_list and the directory list will be introduced in a separate commit. Idea from: kib Reviewed by: kib
Diffstat (limited to 'sys/fs/devfs')
-rw-r--r--sys/fs/devfs/devfs.h5
-rw-r--r--sys/fs/devfs/devfs_devs.c13
-rw-r--r--sys/fs/devfs/devfs_dir.c175
-rw-r--r--sys/fs/devfs/devfs_int.h6
-rw-r--r--sys/fs/devfs/devfs_vnops.c2
5 files changed, 199 insertions, 2 deletions
diff --git a/sys/fs/devfs/devfs.h b/sys/fs/devfs/devfs.h
index 433fa0d..4972eaa 100644
--- a/sys/fs/devfs/devfs.h
+++ b/sys/fs/devfs/devfs.h
@@ -124,6 +124,8 @@ MALLOC_DECLARE(M_DEVFS);
struct componentname;
+TAILQ_HEAD(devfs_dlist_head, devfs_dirent);
+
struct devfs_dirent {
struct cdev_priv *de_cdp;
int de_inode;
@@ -133,10 +135,11 @@ struct devfs_dirent {
#define DE_DOTDOT 0x04
#define DE_DOOMED 0x08
#define DE_COVERED 0x10
+#define DE_USER 0x20
int de_holdcnt;
struct dirent *de_dirent;
TAILQ_ENTRY(devfs_dirent) de_list;
- TAILQ_HEAD(, devfs_dirent) de_dlist;
+ struct devfs_dlist_head de_dlist;
struct devfs_dirent *de_dir;
int de_links;
mode_t de_mode;
diff --git a/sys/fs/devfs/devfs_devs.c b/sys/fs/devfs/devfs_devs.c
index 60c296b..99b33ec 100644
--- a/sys/fs/devfs/devfs_devs.c
+++ b/sys/fs/devfs/devfs_devs.c
@@ -338,6 +338,10 @@ devfs_delete(struct devfs_mount *dm, struct devfs_dirent *de, int flags)
dd = devfs_parent_dirent(de);
if (dd != NULL)
DEVFS_DE_HOLD(dd);
+ if (de->de_flags & DE_USER) {
+ KASSERT(dd != NULL, ("devfs_delete: NULL dd"));
+ devfs_dir_unref_de(dm, dd);
+ }
} else
dd = NULL;
@@ -396,10 +400,17 @@ devfs_purge(struct devfs_mount *dm, struct devfs_dirent *dd)
DEVFS_DE_HOLD(dd);
for (;;) {
- de = TAILQ_FIRST(&dd->de_dlist);
+ /*
+ * Use TAILQ_LAST() to remove "." and ".." last.
+ * We might need ".." to resolve a path in
+ * devfs_dir_unref_de().
+ */
+ de = TAILQ_LAST(&dd->de_dlist, devfs_dlist_head);
if (de == NULL)
break;
TAILQ_REMOVE(&dd->de_dlist, de, de_list);
+ if (de->de_flags & DE_USER)
+ devfs_dir_unref_de(dm, dd);
if (de->de_flags & (DE_DOT | DE_DOTDOT))
devfs_delete(dm, de, DEVFS_DEL_NORECURSE);
else if (de->de_dirent->d_type == DT_DIR)
diff --git a/sys/fs/devfs/devfs_dir.c b/sys/fs/devfs/devfs_dir.c
new file mode 100644
index 0000000..c497e55
--- /dev/null
+++ b/sys/fs/devfs/devfs_dir.c
@@ -0,0 +1,175 @@
+/*-
+ * Copyright (c) 2010 Jaakko Heinonen <jh@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/param.h>
+#include <sys/conf.h>
+#include <sys/types.h>
+#include <sys/kernel.h>
+#include <sys/libkern.h>
+#include <sys/lock.h>
+#include <sys/malloc.h>
+#include <sys/mutex.h>
+#include <sys/queue.h>
+#include <sys/systm.h>
+#include <sys/sx.h>
+
+#include <fs/devfs/devfs.h>
+#include <fs/devfs/devfs_int.h>
+
+struct dirlistent {
+ char *dir;
+ int refcnt;
+ LIST_ENTRY(dirlistent) link;
+};
+
+static LIST_HEAD(, dirlistent) devfs_dirlist =
+ LIST_HEAD_INITIALIZER(devfs_dirlist);
+
+static MALLOC_DEFINE(M_DEVFS4, "DEVFS4", "DEVFS directory list");
+
+static struct mtx dirlist_mtx;
+MTX_SYSINIT(dirlist_mtx, &dirlist_mtx, "devfs dirlist lock", MTX_DEF);
+
+/* Returns 1 if the path is in the directory list. */
+int
+devfs_dir_find(const char *path)
+{
+ struct dirlistent *dle;
+
+ mtx_lock(&dirlist_mtx);
+ LIST_FOREACH(dle, &devfs_dirlist, link) {
+ if (devfs_pathpath(dle->dir, path) != 0) {
+ mtx_unlock(&dirlist_mtx);
+ return (1);
+ }
+ }
+ mtx_unlock(&dirlist_mtx);
+
+ return (0);
+}
+
+static struct dirlistent *
+devfs_dir_findent_locked(const char *dir)
+{
+ struct dirlistent *dle;
+
+ mtx_assert(&dirlist_mtx, MA_OWNED);
+
+ LIST_FOREACH(dle, &devfs_dirlist, link) {
+ if (strcmp(dir, dle->dir) == 0)
+ return (dle);
+ }
+
+ return (NULL);
+}
+
+static void
+devfs_dir_ref(const char *dir)
+{
+ struct dirlistent *dle, *dle_new;
+
+ if (*dir == '\0')
+ return;
+
+ dle_new = malloc(sizeof(*dle), M_DEVFS4, M_WAITOK);
+ dle_new->dir = strdup(dir, M_DEVFS4);
+ dle_new->refcnt = 1;
+
+ mtx_lock(&dirlist_mtx);
+ dle = devfs_dir_findent_locked(dir);
+ if (dle != NULL) {
+ dle->refcnt++;
+ mtx_unlock(&dirlist_mtx);
+ free(dle_new->dir, M_DEVFS4);
+ free(dle_new, M_DEVFS4);
+ return;
+ }
+ LIST_INSERT_HEAD(&devfs_dirlist, dle_new, link);
+ mtx_unlock(&dirlist_mtx);
+}
+
+void
+devfs_dir_ref_de(struct devfs_mount *dm, struct devfs_dirent *de)
+{
+ char dirname[SPECNAMELEN + 1], *namep;
+
+ namep = devfs_fqpn(dirname, dm, de, NULL);
+ KASSERT(namep != NULL, ("devfs_ref_dir_de: NULL namep"));
+
+ devfs_dir_ref(namep);
+}
+
+static void
+devfs_dir_unref(const char *dir)
+{
+ struct dirlistent *dle;
+
+ if (*dir == '\0')
+ return;
+
+ mtx_lock(&dirlist_mtx);
+ dle = devfs_dir_findent_locked(dir);
+ KASSERT(dle != NULL, ("devfs_dir_unref: dir %s not referenced", dir));
+ dle->refcnt--;
+ KASSERT(dle->refcnt >= 0, ("devfs_dir_unref: negative refcnt"));
+ if (dle->refcnt == 0) {
+ LIST_REMOVE(dle, link);
+ mtx_unlock(&dirlist_mtx);
+ free(dle->dir, M_DEVFS4);
+ free(dle, M_DEVFS4);
+ } else
+ mtx_unlock(&dirlist_mtx);
+}
+
+void
+devfs_dir_unref_de(struct devfs_mount *dm, struct devfs_dirent *de)
+{
+ char dirname[SPECNAMELEN + 1], *namep;
+
+ namep = devfs_fqpn(dirname, dm, de, NULL);
+ KASSERT(namep != NULL, ("devfs_unref_dir_de: NULL namep"));
+
+ devfs_dir_unref(namep);
+}
+
+/* Returns 1 if the path p1 contains the path p2. */
+int
+devfs_pathpath(const char *p1, const char *p2)
+{
+
+ for (;;p1++, p2++) {
+ if (*p1 != *p2) {
+ if (*p1 == '/' && *p2 == '\0')
+ return (1);
+ else
+ return (0);
+ } else if (*p1 == '\0')
+ return (1);
+ }
+ /* NOTREACHED */
+}
diff --git a/sys/fs/devfs/devfs_int.h b/sys/fs/devfs/devfs_int.h
index f5612e1..cd58284 100644
--- a/sys/fs/devfs/devfs_int.h
+++ b/sys/fs/devfs/devfs_int.h
@@ -38,6 +38,7 @@
#ifdef _KERNEL
struct devfs_dirent;
+struct devfs_mount;
struct cdev_privdata {
struct file *cdpd_fp;
@@ -76,6 +77,11 @@ void devfs_create(struct cdev *dev);
void devfs_destroy(struct cdev *dev);
void devfs_destroy_cdevpriv(struct cdev_privdata *p);
+int devfs_dir_find(const char *);
+void devfs_dir_ref_de(struct devfs_mount *, struct devfs_dirent *);
+void devfs_dir_unref_de(struct devfs_mount *, struct devfs_dirent *);
+int devfs_pathpath(const char *, const char *);
+
extern struct unrhdr *devfs_inos;
extern struct mtx devmtx;
extern struct mtx devfs_de_interlock;
diff --git a/sys/fs/devfs/devfs_vnops.c b/sys/fs/devfs/devfs_vnops.c
index bbf0680..5f1063d 100644
--- a/sys/fs/devfs/devfs_vnops.c
+++ b/sys/fs/devfs/devfs_vnops.c
@@ -1564,6 +1564,7 @@ devfs_symlink(struct vop_symlink_args *ap)
dd = ap->a_dvp->v_data;
de = devfs_newdirent(ap->a_cnp->cn_nameptr, ap->a_cnp->cn_namelen);
+ de->de_flags = DE_USER;
de->de_uid = 0;
de->de_gid = 0;
de->de_mode = 0755;
@@ -1587,6 +1588,7 @@ devfs_symlink(struct vop_symlink_args *ap)
de_dotdot = TAILQ_FIRST(&dd->de_dlist); /* "." */
de_dotdot = TAILQ_NEXT(de_dotdot, de_list); /* ".." */
TAILQ_INSERT_AFTER(&dd->de_dlist, de_dotdot, de, de_list);
+ devfs_dir_ref_de(dmp, dd);
return (devfs_allocv(de, ap->a_dvp->v_mount, LK_EXCLUSIVE, ap->a_vpp));
}
OpenPOWER on IntegriCloud