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 [EMAIL PROTECTED])
+ *     @new_name: new name of @renamed_sd (NULL if [EMAIL PROTECTED])
+ *
+ *     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 [EMAIL PROTECTED] points to needs to be
+ *     freed later.  0 if success and [EMAIL PROTECTED] 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/

Reply via email to