|  | // SPDX-License-Identifier: GPL-2.0-or-later | 
|  | /* | 
|  | * Copyright (c) 2023-2024 Oracle.  All Rights Reserved. | 
|  | * Author: Darrick J. Wong <djwong@kernel.org> | 
|  | */ | 
|  | #include "libxfs.h" | 
|  | #include "libxfs/xfile.h" | 
|  | #include "libxfs/xfblob.h" | 
|  | #include "repair/strblobs.h" | 
|  |  | 
|  | /* | 
|  | * String Blob Structure | 
|  | * ===================== | 
|  | * | 
|  | * This data structure wraps the storage of strings with explicit length in an | 
|  | * xfblob structure.  It stores a hashtable of string checksums to provide | 
|  | * fast(ish) lookups of existing strings to enable deduplication of the strings | 
|  | * contained within. | 
|  | */ | 
|  | struct strblob_hashent { | 
|  | struct strblob_hashent	*next; | 
|  |  | 
|  | xfblob_cookie		str_cookie; | 
|  | unsigned int		str_len; | 
|  | xfs_dahash_t		str_hash; | 
|  | }; | 
|  |  | 
|  | struct strblobs { | 
|  | struct xfblob		*strings; | 
|  | unsigned int		nr_buckets; | 
|  |  | 
|  | struct strblob_hashent	*buckets[]; | 
|  | }; | 
|  |  | 
|  | static inline size_t strblobs_sizeof(unsigned int nr_buckets) | 
|  | { | 
|  | return sizeof(struct strblobs) + | 
|  | (nr_buckets * sizeof(struct strblobs_hashent *)); | 
|  | } | 
|  |  | 
|  | /* Initialize a string blob structure. */ | 
|  | int | 
|  | strblobs_init( | 
|  | const char		*descr, | 
|  | unsigned int		hash_buckets, | 
|  | struct strblobs		**sblobs) | 
|  | { | 
|  | struct strblobs		*sb; | 
|  | int			error; | 
|  |  | 
|  | sb = calloc(strblobs_sizeof(hash_buckets), 1); | 
|  | if (!sb) | 
|  | return ENOMEM; | 
|  |  | 
|  | error = -xfblob_create(descr, &sb->strings); | 
|  | if (error) | 
|  | goto out_free; | 
|  |  | 
|  | sb->nr_buckets = hash_buckets; | 
|  | *sblobs = sb; | 
|  | return 0; | 
|  |  | 
|  | out_free: | 
|  | free(sb); | 
|  | return error; | 
|  | } | 
|  |  | 
|  | /* Deconstruct a string blob structure. */ | 
|  | void | 
|  | strblobs_destroy( | 
|  | struct strblobs		**sblobs) | 
|  | { | 
|  | struct strblobs		*sb = *sblobs; | 
|  | struct strblob_hashent	*ent, *ent_next; | 
|  | unsigned int		bucket; | 
|  |  | 
|  | for (bucket = 0; bucket < sb->nr_buckets; bucket++) { | 
|  | ent = sb->buckets[bucket]; | 
|  | while (ent != NULL) { | 
|  | ent_next = ent->next; | 
|  | free(ent); | 
|  | ent = ent_next; | 
|  | } | 
|  | } | 
|  |  | 
|  | xfblob_destroy(sb->strings); | 
|  | free(sb); | 
|  | *sblobs = NULL; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Search the string hashtable for a matching entry.  Sets sets the cookie and | 
|  | * returns 0 if one is found; ENOENT if there is no match; or a positive errno. | 
|  | */ | 
|  | static int | 
|  | __strblobs_lookup( | 
|  | struct strblobs		*sblobs, | 
|  | xfblob_cookie		*str_cookie, | 
|  | const unsigned char	*str, | 
|  | unsigned int		str_len, | 
|  | xfs_dahash_t		str_hash) | 
|  | { | 
|  | struct strblob_hashent	*ent; | 
|  | unsigned char		*buf = NULL; | 
|  | unsigned int		bucket; | 
|  | int			error; | 
|  |  | 
|  | bucket = str_hash % sblobs->nr_buckets; | 
|  | ent = sblobs->buckets[bucket]; | 
|  |  | 
|  | for (ent = sblobs->buckets[bucket]; ent != NULL; ent = ent->next) { | 
|  | if (ent->str_len != str_len || ent->str_hash != str_hash) | 
|  | continue; | 
|  |  | 
|  | if (!buf) { | 
|  | buf = malloc(str_len); | 
|  | if (!buf) | 
|  | return ENOMEM; | 
|  | } | 
|  |  | 
|  | error = strblobs_load(sblobs, ent->str_cookie, buf, str_len); | 
|  | if (error) | 
|  | goto out; | 
|  |  | 
|  | if (memcmp(str, buf, str_len)) | 
|  | continue; | 
|  |  | 
|  | *str_cookie = ent->str_cookie; | 
|  | goto out; | 
|  | } | 
|  | error = ENOENT; | 
|  |  | 
|  | out: | 
|  | free(buf); | 
|  | return error; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Search the string hashtable for a matching entry.  Sets sets the cookie and | 
|  | * returns 0 if one is found; ENOENT if there is no match; or a positive errno. | 
|  | */ | 
|  | int | 
|  | strblobs_lookup( | 
|  | struct strblobs		*sblobs, | 
|  | xfblob_cookie		*str_cookie, | 
|  | const unsigned char	*str, | 
|  | unsigned int		str_len, | 
|  | xfs_dahash_t		str_hash) | 
|  | { | 
|  | return __strblobs_lookup(sblobs, str_cookie, str, str_len, str_hash); | 
|  | } | 
|  |  | 
|  | /* Remember a string in the hashtable. */ | 
|  | static int | 
|  | strblobs_hash( | 
|  | struct strblobs		*sblobs, | 
|  | xfblob_cookie		str_cookie, | 
|  | const unsigned char	*str, | 
|  | unsigned int		str_len, | 
|  | xfs_dahash_t		str_hash) | 
|  | { | 
|  | struct strblob_hashent	*ent; | 
|  | unsigned int		bucket; | 
|  |  | 
|  | bucket = str_hash % sblobs->nr_buckets; | 
|  |  | 
|  | ent = malloc(sizeof(struct strblob_hashent)); | 
|  | if (!ent) | 
|  | return ENOMEM; | 
|  |  | 
|  | ent->str_cookie = str_cookie; | 
|  | ent->str_len = str_len; | 
|  | ent->str_hash = str_hash; | 
|  | ent->next = sblobs->buckets[bucket]; | 
|  |  | 
|  | sblobs->buckets[bucket] = ent; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* Store a string and return a cookie for its retrieval. */ | 
|  | int | 
|  | strblobs_store( | 
|  | struct strblobs		*sblobs, | 
|  | xfblob_cookie		*str_cookie, | 
|  | const unsigned char	*str, | 
|  | unsigned int		str_len, | 
|  | xfs_dahash_t		str_hash) | 
|  | { | 
|  | int			error; | 
|  |  | 
|  | error = __strblobs_lookup(sblobs, str_cookie, str, str_len, str_hash); | 
|  | if (error != ENOENT) | 
|  | return error; | 
|  |  | 
|  | error = -xfblob_store(sblobs->strings, str_cookie, str, str_len); | 
|  | if (error) | 
|  | return error; | 
|  |  | 
|  | return strblobs_hash(sblobs, *str_cookie, str, str_len, str_hash); | 
|  | } | 
|  |  | 
|  | /* Retrieve a previously stored string. */ | 
|  | int | 
|  | strblobs_load( | 
|  | struct strblobs		*sblobs, | 
|  | xfblob_cookie		str_cookie, | 
|  | unsigned char		*str, | 
|  | unsigned int		str_len) | 
|  | { | 
|  | return -xfblob_load(sblobs->strings, str_cookie, str, str_len); | 
|  | } |