@@ -13,11 +13,17 @@
#include <linux/lsm_hooks.h>
#include <linux/digest_cache.h>
+/* Digest cache bits in flags. */
+#define INIT_IN_PROGRESS 0 /* Digest cache being initialized. */
+#define INIT_STARTED 1 /* Digest cache init started. */
+#define INVALID 2 /* Digest cache marked as invalid. */
+
/**
* struct digest_cache - Digest cache
* @ref_count: Number of references to the digest cache
* @path_str: Path of the digest list the digest cache was created from
* @flags: Control flags
+ * @digest_list_path: Path structure of the digest list
*
* This structure represents a cache of digests extracted from a digest list.
*/
@@ -25,6 +31,7 @@ struct digest_cache {
atomic_t ref_count;
char *path_str;
unsigned long flags;
+ struct path digest_list_path;
};
/**
@@ -84,6 +91,8 @@ digest_cache_unref(struct digest_cache *digest_cache)
struct digest_cache *digest_cache_create(struct dentry *dentry,
struct path *digest_list_path,
char *path_str, char *filename);
+struct digest_cache *digest_cache_init(struct dentry *dentry,
+ struct digest_cache *digest_cache);
int __init digest_cache_do_init(const struct lsm_id *lsm_id,
loff_t inode_offset);
@@ -154,6 +154,14 @@ struct digest_cache *digest_cache_create(struct dentry *dentry,
/* Increment ref. count for reference returned to the caller. */
digest_cache = digest_cache_ref(dig_sec->dig_owner);
+
+ /* Make other digest cache requestors wait until creation complete. */
+ set_bit(INIT_IN_PROGRESS, &digest_cache->flags);
+
+ /* Get the digest list path for initialization. */
+ digest_cache->digest_list_path.dentry = digest_list_path->dentry;
+ digest_cache->digest_list_path.mnt = digest_list_path->mnt;
+ path_get(&digest_cache->digest_list_path);
mutex_unlock(&dig_sec->dig_owner_mutex);
out:
if (digest_list_path == &file_path)
@@ -226,6 +234,45 @@ static struct digest_cache *digest_cache_new(struct dentry *dentry)
return digest_cache;
}
+/**
+ * digest_cache_init - Initialize a digest cache
+ * @dentry: Dentry of the inode for which the digest cache will be used
+ * @digest_cache: Digest cache to initialize
+ *
+ * This function checks if the INIT_STARTED digest cache flag is set. If it is,
+ * it waits until the caller that saw INIT_STARTED unset completes the
+ * initialization.
+ *
+ * Otherwise, it sets INIT_STARTED (atomically), performs the initialization,
+ * clears the INIT_IN_PROGRESS digest cache flag, and wakes up the other
+ * callers.
+ *
+ * Return: A valid and initialized digest cache.
+ */
+struct digest_cache *digest_cache_init(struct dentry *dentry,
+ struct digest_cache *digest_cache)
+{
+ /* Wait for digest cache initialization. */
+ if (test_and_set_bit(INIT_STARTED, &digest_cache->flags)) {
+ wait_on_bit(&digest_cache->flags, INIT_IN_PROGRESS,
+ TASK_UNINTERRUPTIBLE);
+ goto out;
+ }
+
+ path_put(&digest_cache->digest_list_path);
+ /* Notify initialization complete. */
+ clear_and_wake_up_bit(INIT_IN_PROGRESS, &digest_cache->flags);
+out:
+ if (test_bit(INVALID, &digest_cache->flags)) {
+ pr_debug("Digest cache %s is invalid, don't return it\n",
+ digest_cache->path_str);
+ digest_cache_put(digest_cache);
+ digest_cache = NULL;
+ }
+
+ return digest_cache;
+}
+
/**
* digest_cache_get - Get a digest cache for a given inode
* @dentry: Dentry of the inode for which the digest cache will be used
@@ -268,6 +315,10 @@ struct digest_cache *digest_cache_get(struct dentry *dentry)
mutex_unlock(&dig_sec->dig_user_mutex);
+ if (digest_cache)
+ /* This must be always executed, or path ref. is not released.*/
+ digest_cache = digest_cache_init(dentry, digest_cache);
+
return digest_cache;
}
EXPORT_SYMBOL_GPL(digest_cache_get);