[PATCH 2/8] sysfs: add name formatting support for symlinks

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This patch implements sysfs name formatting.  sysfs_add_link() is
passed @name_fmt instead of @fmt.  In the format string only
"%[:alnum:]" and "%%" are handled specially.  "%0" is substitued with
the name of @target, "%3" with the name of the third ancestor of
@target, "%[aA]" the 10th and "%[zZ]" with 35th.  "%%" is substituted
with "%".

This formatting is mainly to make symlink renaming automatic such that
when the target or one of its ancestors gets renamed the symlink can
be renamed together automatically & atomically.  It also simplifies
sysfs creation a bit by allowing callers to use static format string
instead of formatting symlink name itself.

@name to kobject based sysfs_create_link() is not interpreted as
format string to keep backward compatibility.

Signed-off-by: Tejun Heo <[email protected]>
---
 fs/sysfs/dir.c        |    3 +
 fs/sysfs/kobject.c    |    2 +-
 fs/sysfs/symlink.c    |  245 ++++++++++++++++++++++++++++++++++++++++++++++--
 fs/sysfs/sysfs.h      |    9 ++
 include/linux/sysfs.h |    6 +-
 5 files changed, 250 insertions(+), 15 deletions(-)

diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c
index d50d3ac..b042a2e 100644
--- a/fs/sysfs/dir.c
+++ b/fs/sysfs/dir.c
@@ -295,6 +295,9 @@ void release_sysfs_dirent(struct sysfs_dirent * sd)
 		sysfs_put(sd->s_link.target);
 	if (sd->s_flags & SYSFS_FLAG_NAME_COPIED)
 		kfree(sd->s_name);
+	if (sd->s_flags & SYSFS_FLAG_LINK_NAME_FMT_COPIED)
+		kfree(sd->s_link.name_fmt);
+
 	kfree(sd->s_iattr);
 	sysfs_free_ino(sd->s_ino);
 	kmem_cache_free(sysfs_dir_cachep, sd);
diff --git a/fs/sysfs/kobject.c b/fs/sysfs/kobject.c
index 16e10de..7ea9186 100644
--- a/fs/sysfs/kobject.c
+++ b/fs/sysfs/kobject.c
@@ -425,7 +425,7 @@ int sysfs_create_link(struct kobject *kobj, struct kobject *target,
 	if (!target_sd)
 		return -ENOENT;
 
-	sd = sysfs_add_link(parent_sd, name, SYSFS_COPY_NAME, target_sd);
+	sd = __sysfs_add_link(parent_sd, name, SYSFS_COPY_NAME, target_sd, 0);
 
 	sysfs_put(target_sd);
 
diff --git a/fs/sysfs/symlink.c b/fs/sysfs/symlink.c
index 42ecb69..296fef5 100644
--- a/fs/sysfs/symlink.c
+++ b/fs/sysfs/symlink.c
@@ -7,6 +7,7 @@
 #include <linux/module.h>
 #include <linux/namei.h>
 #include <linux/mutex.h>
+#include <linux/ctype.h>
 
 #include "sysfs.h"
 
@@ -43,16 +44,162 @@ static void fill_object_path(struct sysfs_dirent *sd, char *buffer, int length)
 	}
 }
 
+size_t sysfs_format_link_name(const char *fmt, struct sysfs_dirent *target,
+			      char *buf, struct sysfs_dirent *renamed_sd,
+			      struct sysfs_dirent *new_parent,
+			      const char *new_name)
+{
+	char *dst = buf;
+	int cnt = 0;
+
+	while (*fmt != '\0') {
+		char ch = *fmt++, next = *fmt;
+		struct sysfs_dirent *tsd;
+		const char *tname;
+		int level;
+		size_t len;
+
+		if (ch != '%' || !isalnum(next)) {
+			if (dst)
+				*dst++ = ch;
+			cnt++;
+			/* %% is % */
+			if (ch== '%' && next == '%')
+				fmt++;
+			continue;
+		}
+
+		/* Seen %[:alnum:].  %0 is the target's name.  %z is
+		 * the name of the 35th ancestor of the target.
+		 */
+		fmt++;
+		if (isdigit(next))
+			level = next - '0';
+		else
+			level = tolower(next) - 'a' + 10;
+
+		/* Work up level times and use its name.  It's a bit
+		 * complicated due to the renamed node handling.
+		 */
+		tsd = target;
+		while (level && tsd->s_parent) {
+			if (tsd == renamed_sd)
+				tsd = new_parent;
+			else
+				tsd = tsd->s_parent;
+			level--;
+		}
+		WARN_ON(level);
+
+		tname = tsd->s_name;
+		if (tsd == renamed_sd)
+			tname = new_name;
+
+		/* got the name, copy it */
+		len = strlen(tname);
+		if (dst) {
+			memcpy(dst, tname, len);
+			dst += len;
+		}
+		cnt += len;
+	}
+
+	if (dst)
+		*dst = '\0';
+	return cnt;
+}
+
 /**
- *	sysfs_add_link - add a new sysfs symlink
+ *	sysfs_link_name - format the name of symlink
+ *	@fmt: format string
+ *	@target: target sysfs_dirent the symlink points to
+ *	@link_name: out parameter for the formatted string
+ *	@renamed_sd: renamed sysfs_dirent (can be NULL)
+ *	@new_parent: new parent of @renamed_sd (NULL if !@renamed_sd)
+ *	@new_name: new name of @renamed_sd (NULL if !@renamed_sd)
+ *
+ *	Format the name of the symlink according to @fmt and given
+ *	parameters.
+ *
+ *	%[:alnum:] has special meaning in the format string.  %0 is
+ *	substituted with the target's name.  %1 is the parent, %2 the
+ *	parent's parent, %9 the 9th ancestor, %a or %A the 10th
+ *	ancestor and so on upto %z or %Z which is substitued with the
+ *	name of the 35th ancestor.  %% is substituted with %. All
+ *	other sequences are copied verbatim.
+ *
+ *	@renamed_sd is used to override the specified sd's parent and
+ *	name.  If a sd which matches @renamed_sd is in the path
+ *	between sysfs_root and @sd, @new_parent is used instead of
+ *	sd->s_parent and @new_name is used instead of sd->s_name.
+ *	This is used to format new symlink name while preparing to
+ *	rename the target or one of the ancestors of it.
+ *
+ *	LOCKING:
+ *	mutex_lock(sysfs_op_mutex)
+ *
+ *	RETURNS:
+ *	1 if success and the buffer *@link_name points to needs to be
+ *	freed later.  0 if success and *@link_name points to @fmt
+ *	directly.  -errno on failure.
+ */
+int sysfs_link_name(const char *fmt, struct sysfs_dirent *target,
+		    const char **link_name, struct sysfs_dirent *renamed_sd,
+		    struct sysfs_dirent *new_parent, const char *new_name)
+{
+	size_t len;
+	char *buf;
+
+	/* nothing to format? */
+	if (!strchr(fmt, '%')) {
+		*link_name = fmt;
+		return 0;
+	}
+
+	/* determine length and allocate space for the formatted string */
+	len = sysfs_format_link_name(fmt, target, NULL,
+				     renamed_sd, new_parent, new_name);
+	buf = kmalloc(len + 1, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	/* format it */
+	sysfs_format_link_name(fmt, target, buf,
+			       renamed_sd, new_parent, new_name);
+	*link_name = buf;
+	return 1;
+}
+
+/**
+ *	__sysfs_add_link - add a new sysfs symlink
  *	@parent: sysfs_dirent to add symlink under
- *	@name: name of the symlink
+ *	@name_fmt: format string for the name of the symlink
  *	@mode: SYSFS_* flags for the new symlink
  *	@target: target of the symlink
+ *	@format: whether format symlink name or not
  *
  *	Add a new symlink which points to @target under @parent with
  *	the specified parameters.
  *
+ *	If @format is not zero, @name_fmt is interpreted as format
+ *	string and the symlink name will be formatted accordingly.
+ *	Also, the symlink will be chained into the links list of the
+ *	@target and will be renamed automatically when the @target is
+ *	renamed or moved.
+ *
+ *	If @format is zero, @name_fmt is taken as verbatim name of the
+ *	symlink and won't be renamed automatically no matter what
+ *	happens to the @target.
+ *
+ *	SYSFS_COPY_NAME always specifies that the string @name_fmt
+ *	points to should be copied whether it's interpreted as format
+ *	string or not.
+ *
+ *	This is an internal function to be used to implement
+ *	sysfs_add_link() and sysfs_create_link().  @format is
+ *	necessary to support the original sysfs_create_link()
+ *	semantics where the symlink name is specified verbatim.
+ *
  *	LOCKING:
  *	Kernel thread context (may sleep).
  *
@@ -60,27 +207,103 @@ static void fill_object_path(struct sysfs_dirent *sd, char *buffer, int length)
  *	Pointer to the new sysfs_dirent on success, ERR_PTR() value on
  *	error.
  */
-struct sysfs_dirent *sysfs_add_link(struct sysfs_dirent *parent,
-				    const char *name, mode_t mode,
-				    struct sysfs_dirent *target)
+struct sysfs_dirent *__sysfs_add_link(struct sysfs_dirent *parent,
+				      const char *name_fmt, mode_t mode,
+				      struct sysfs_dirent *target, int format)
 {
-	struct sysfs_dirent *sd;
+	struct sysfs_dirent *sd = NULL;
+	unsigned int flags = 0;
+	const char *name;
+	struct sysfs_addrm_cxt acxt;
+	int rc;
+
+	/* acquire locks early for link name formatting */
+	sysfs_addrm_start(&acxt);
 
 	/* Only symlink to directories are allowed.  This is an
-	 * artificial limitation.  If ever needed, allowing symlinks
-	 * to point to other types of sysfs nodes isn't difficult.
+	 * artificial limitation mainly to reduce the size of
+	 * sysfs_dirent by putting the links head into s_dir union
+	 * member.  If ever needed, allowing symlinks to point to
+	 * other types of sysfs nodes isn't difficult.
 	 */
+	rc = -EINVAL;
 	if (sysfs_type(target) != SYSFS_DIR)
-		return ERR_PTR(-EINVAL);
+		goto err;
+
+	if (format) {
+		/* SYSFS_COPY_NAME means 'copy the format string' */
+		rc = -ENOMEM;
+		if (mode & SYSFS_COPY_NAME) {
+			name_fmt = kstrdup(name_fmt, GFP_KERNEL);
+			if (!name_fmt)
+				goto err;
+			mode &= ~SYSFS_COPY_NAME;
+			flags |= SYSFS_FLAG_LINK_NAME_FMT_COPIED;
+		}
+
+		/* format name */
+		rc = sysfs_link_name(name_fmt, target, &name, NULL, NULL, NULL);
+		if (rc < 0)
+			goto err;
+		/* set NAME_COPIED if name has been allocated and formatted */
+		if (rc)
+			flags |= SYSFS_FLAG_NAME_COPIED;
+	} else
+		name = name_fmt;
 
 	/* allocate & initialize */
+	rc = -ENOMEM;
 	sd = sysfs_new_dirent(name, mode | S_IRWXUGO, SYSFS_LINK);
 	if (!sd)
-		return ERR_PTR(-ENOMEM);
+		goto err;
 
+	sd->s_flags |= flags;
+	sd->s_link.name_fmt = name_fmt;
 	sd->s_link.target = sysfs_get(target);
 
-	return sysfs_insert_one(parent, sd);
+	/* add the new node */
+	rc = sysfs_add_one(&acxt, parent, sd);
+	if (rc) {
+		sysfs_put(sd);	/* target is put when sd is released */
+		goto err;
+	}
+
+	sysfs_addrm_finish(&acxt);
+	return sd;
+
+ err:
+	sysfs_addrm_finish(&acxt);
+	if (flags & SYSFS_FLAG_LINK_NAME_FMT_COPIED)
+		kfree(name_fmt);
+	if (flags & SYSFS_FLAG_NAME_COPIED)
+		kfree(name);
+	return ERR_PTR(rc);
+}
+
+/**
+ *	sysfs_add_link - add a new sysfs symlink
+ *	@parent: sysfs_dirent to add symlink under
+ *	@name_fmt: format string for the name of the symlink
+ *	@mode: SYSFS_* flags for the new symlink
+ *	@target: target of the symlink
+ *
+ *	Add a new symlink which points to @target under @parent with
+ *	the specified parameters.  @name_fmt is always interpreted as
+ *	format string for symlink name.  See __sysfs_add_link() for
+ *	details.
+ *
+ *	LOCKING:
+ *	Kernel thread context (may sleep).
+ *
+ *	RETURNS:
+ *	Pointer to the new sysfs_dirent on success, ERR_PTR() value on
+ *	error.
+ */
+struct sysfs_dirent *sysfs_add_link(struct sysfs_dirent *parent,
+				    const char *name, mode_t mode,
+				    struct sysfs_dirent *target)
+{
+	return __sysfs_add_link(parent, name, mode, target, 1);
 }
 EXPORT_SYMBOL_GPL(sysfs_add_link);
 
diff --git a/fs/sysfs/sysfs.h b/fs/sysfs/sysfs.h
index 732b292..9167032 100644
--- a/fs/sysfs/sysfs.h
+++ b/fs/sysfs/sysfs.h
@@ -9,6 +9,7 @@ struct sysfs_elem_dir {
 
 struct sysfs_elem_link {
 	struct sysfs_dirent	*target;
+	const char		*name_fmt;
 };
 
 struct sysfs_elem_file {
@@ -62,6 +63,7 @@ struct sysfs_dirent {
 #define SYSFS_FLAG_MASK			~SYSFS_TYPE_MASK
 #define SYSFS_FLAG_REMOVED		0x0200
 #define SYSFS_FLAG_NAME_COPIED		0x0400
+#define SYSFS_FLAG_LINK_NAME_FMT_COPIED	0x0800
 
 static inline unsigned int sysfs_type(struct sysfs_dirent *sd)
 {
@@ -152,4 +154,11 @@ extern const struct file_operations sysfs_bin_file_operations;
 /*
  * symlink.c
  */
+struct sysfs_dirent *__sysfs_add_link(struct sysfs_dirent *parent,
+				      const char *name, mode_t mode,
+				      struct sysfs_dirent *target, int format);
+int sysfs_link_name(const char *fmt, struct sysfs_dirent *target,
+		    const char **link_name, struct sysfs_dirent *renamed_sd,
+		    struct sysfs_dirent *new_parent, const char *new_name);
+
 extern const struct inode_operations sysfs_link_inode_operations;
diff --git a/include/linux/sysfs.h b/include/linux/sysfs.h
index f0279a7..5afe3bd 100644
--- a/include/linux/sysfs.h
+++ b/include/linux/sysfs.h
@@ -29,7 +29,7 @@ struct vm_area_struct;
  * specific flags.
  */
 #define SYSFS_DIR_MODE		(S_IRWXU | S_IRUGO | S_IXUGO)
-#define SYSFS_COPY_NAME		010000	/* copy passed @name */
+#define SYSFS_COPY_NAME		010000	/* copy passed @name[_fmt] */
 
 /* collection of all flags for verification */
 #define SYSFS_MODE_FLAGS	SYSFS_COPY_NAME
@@ -61,7 +61,7 @@ struct sysfs_dirent *sysfs_add_bin(struct sysfs_dirent *parent,
 			const char *name, mode_t mode, size_t size,
 			const struct sysfs_bin_ops *bops, void *data);
 struct sysfs_dirent *sysfs_add_link(struct sysfs_dirent *parent,
-			const char *name, mode_t mode,
+			const char *name_fmt, mode_t mode,
 			struct sysfs_dirent *target);
 
 struct sysfs_dirent *sysfs_find_child(struct sysfs_dirent *parent,
@@ -100,7 +100,7 @@ static inline struct sysfs_dirent *sysfs_add_bin(struct sysfs_dirent *parent,
 }
 
 static inline struct sysfs_dirent *sysfs_add_link(struct sysfs_dirent *parent,
-			const char *name, mode_t mode,
+			const char *name_fmt, mode_t mode,
 			struct sysfs_dirent *target)
 {
 	return NULL;
-- 
1.5.0.3


-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

[Index of Archives]     [Kernel Newbies]     [Netfilter]     [Bugtraq]     [Photo]     [Stuff]     [Gimp]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Video 4 Linux]     [Linux for the blind]     [Linux Resources]
  Powered by Linux