OpenHarmony porting: how to adapt KV storage components of utils subsystem

Abstract: This paper introduces how to adapt the KV storage unit of utils subsystem when transplanting the development board, and introduces the relevant operation mechanism and principle.

This article is shared from Huawei cloud community< OpenHarmony transplantation case and principle - KV storage unit of utils subsystem >, author: zhushy.

Utils subsystem is the public basic library of OpenHarmony, which stores the common basic components of OpenHarmony. These basic components can be used by various business subsystems and upper level applications of OpenHarmony. The capabilities provided by the common basic library on different platforms:

  • LiteOS-M kernel: KV(key value) storage, file operation, timer, Dump system attribute.
  • LiteOS-A kernel: KV(key value) storage, timer, JS API (device query, data storage), Dump system attribute.

This paper introduces how to adapt the kV storage unit of utils subsystem when transplanting the development board, and introduces the relevant operation mechanism and principle. KV storage units are defined in utils\native\lite \. The source code directory is as follows:

utils/native/lite/              # Common base library root directory
├── file                        # File interface implementation
├── hals                        # HAL directory
│   └── file                    # File operation hardware abstraction layer header file
├── include                     # Public basic library external interface file
├── js                          # JS API directory                 
│   └── builtin
│       ├── common
│       ├── deviceinfokit       # Device information Kit
│       ├── filekit             # File Kit
│       └── kvstorekit          # KV storage Kit
├── kal                         # KAL directory
│   └── timer                   # KAL implementation of Timer
├── kv_store	                # KV storage implementation
│   ├── innerkits               # KV storage internal interface
│   └── src	                    # KV storage source file
├── memory
│   └── include                 # Memory pool management interface
├── os_dump                     # Dump system properties
└── timer_task                  # Timer implementation

1. KV storage unit adaptation example

1.1 configure product solution config json

Refer to vendor \ ohemu \ QEMU for the adaptation example of KV storage components of utils subsystem_ csky_ mini_ system_ demo\config. JSON, the code snippet is as follows. (1) KV storage components used to configure subsystems. (2) specify the adaptation directory in the development board directory. Under this adaptation directory, you need to create the directory device\qemu\SmartL_E802\adapter\hals\utils\file \, vendor was introduced in the previous migration case and principle article_ adapter_ Dir directory. For KV storage components, it is compatible with syspara_lite components are similar and adapt to KV_ When the store component is, the key value pairs are written to the file. In the lightweight system, the file operation related interfaces include POSIX interface and HalFiles interface. Part file utils \ native \ Lite \ kv_store\src\BUILD. Enable is declared in GN_ ohos_ utils_ native_ lite_ kv_store_ use_ posix_ KV_ API configuration parameter. The default value is true. When the default value is used or the active setting is true, the POSIX related interface is used; otherwise, the HalFiles related interface is used. If you use HalFiles related interfaces, you need to adapt the UtilsFile components. Refer to the previous migration cases and principle articles.

      {
        "subsystem": "utils",
        "components": [
          { "component": "file", "features":[] },
⑴        { "component": "kv_store", "features":[] }
        ]
      }
    ],
⑵   "vendor_adapter_dir": "//device/qemu/SmartL_E802/adapter",

1.2 running example code after adaptation

After adaptation, write the following code to use the KV storage function. The following is a sample code program fragment to save the key value, obtain the corresponding value through the key name, delete the key value, and so on.

// Store / update data item corresponding to key
const char key1[] = "key_sample";
const char defValue[] = "test case of key value store.";
int ret = UtilsSetValue(key1, defValue);

// Get the corresponding data item according to the key
char value1[32] = {0};
ret = UtilsGetValue(key1, value1, 32);

// Delete data item corresponding to key
UtilsDeleteValue(key1);

2. KV storage unit kvstore_common common code

2.1 structure, function declaration and variable

In the file utils\native\lite\kv_store\src\kvstore_common\kvstore_common.h declares the function of kV storage and defines the structure KvItem. (1) defines the maximum length of the key value, and (2) defines the FEATURE_KV_CACHE macro switch, defined in utils\native\lite\include\utils_config.h. By default, the macro is defined. (3) for the structure defined at, the member contains the key value and the pointer of the predecessor and successor structure.

⑴  #define MAX_KEY_LEN 32
    #define MAX_VALUE_LEN 128

    boolean IsValidChar(const char ch);
    boolean IsValidValue(const char* value, unsigned int len);
    boolean IsValidKey(const char* key);

⑵  #ifdef FEATURE_KV_CACHE
⑶  typedef struct KvItem_ {
        char* key;
        char* value;
        struct KvItem_* next;
        struct KvItem_* prev;
    } KvItem;

    void DeleteKVCache(const char* key);
    void AddKVCache(const char* key, const char* value, boolean isNew);
    int GetValueByCache(const char* key, char* value, unsigned int maxLen);
    int ClearKVCacheInner(void);
    #endif

In the file utils \ native \ Lite \ kV_ store\src\kvstore_ common\kvstore_ common. The internal global variable is defined in C, g_itemHeader,g_itemTail points to the beginning and end of the key value linked list respectively, g_sum records the number of key value pairs.

#ifdef FEATURE_KV_CACHE
static KvItem* g_itemHeader = NULL;
static KvItem* g_itemTail = NULL;
static int g_sum = 0;
#endif

2.2 key value validity judgment function

The functions isvalidkey and isvalidvalue are used to judge whether the key and value are valid respectively. (1) indicates that the key value must be lowercase characters, numeric values, underscores or dot symbols. When using isvalidvalue to judge whether the value is valid, two parameters need to be passed in, one is the pointer of the string value to be judged, and the other is the length len. (2) get the number of strings at, including the last null; Max length not exceeded_ VALUE_ LEN. Then it is further judged that if the length is 0, the length is greater than or equal to the maximum length MAX_VALUE_LEN (because null at the end is required, it cannot be equal to or greater than the length passed in the parameter), it will return FALSE, otherwise it will return TRUE. When IsValidKey is used to determine whether the key is valid, the function IsValidValue is first called to ensure that the length is valid. Then the function IsValidChar is called to determine that every character is valid, and can only be lowercase, numeric or dot symbol.

    boolean IsValidChar(const char ch)
    {
⑴      if (islower(ch) || isdigit(ch) || (ch == '_') || (ch == '.')) {
            return TRUE;
        }
        return FALSE;
    }

    boolean IsValidValue(const char* value, unsigned int len)
    {
        if (value == NULL) {
            return FALSE;
        }
⑵      size_t valueLen = strnlen(value, MAX_VALUE_LEN);
        if ((valueLen == 0) || (valueLen >= MAX_VALUE_LEN) || (valueLen >= len)) {
            return FALSE;
        }
        return TRUE;
    }

    boolean IsValidKey(const char* key)
    {
        if (!IsValidValue(key, MAX_KEY_LEN)) {
            return FALSE;
        }
        size_t keyLen = strnlen(key, MAX_KEY_LEN);
        for (size_t i = 0; i < keyLen; i++) {
            if (!IsValidChar(key[i])) {
                return FALSE;
            }
        }
        return TRUE;
    }

2.3 delete kvcache according to the key value

(1) the function FreeItem at releases the pointer of the structure member variable and the memory occupied by the structure. The function DeleteKVCache is used to delete the value corresponding to the key parameter. (2) start from the first key value in the head of the key value pair, cycle the key value linked list, and compare the key in the parameter with the key to be cycled. If they are not equal, the next linked list node will be cycled. If they are not equal all the time, and the node looped to is NULL, it indicates that the same key does not exist in the linked list, and the direct return does not need to delete. If you execute (3), it indicates that there are matching keys in the key value pairs, and the total number of key value pairs is subtracted by 1. (4) judge various conditions of the quantity after deleting the key value. If the quantity of the key value pair is 0, the head and tail pointers of the key value pair are set to NULL; If you delete the team head element, team tail element and team in element, process them separately. (5) release the memory occupied by the structure to be deleted.

⑴   static void FreeItem(KvItem* item)
    {
        if (item == NULL) {
            return;
        }
        if (item->key != NULL) {
            free(item->key);
        }
        if (item->value != NULL) {
            free(item->value);
        }
        free(item);
    }

    void DeleteKVCache(const char* key)
    {
        if (key == NULL || g_itemHeader == NULL) {
            return;
        }

⑵      KvItem* item = g_itemHeader;
        while (strcmp(key, item->key) != 0) {
            item = item->next;
            if (item == NULL) {
                return;
            }
        }
⑶      g_sum--;
⑷      if (g_sum == 0) {
            g_itemHeader = NULL;
            g_itemTail = NULL;
        } else if (item == g_itemHeader) {
            g_itemHeader = item->next;
            g_itemHeader->prev = NULL;
        } else if (item == g_itemTail) {
            g_itemTail = item->prev;
            g_itemTail->next = NULL;
        } else {
            item->prev->next = item->next;
            item->next->prev = item->prev;
        }
⑸      FreeItem(item);
    }

2.4 add cache AddKVCache

The function AddKVCache adds a pair of key values to the cache. There are three parameters, the first two are keys and values; When the third parameter boolean isNew is true, it will first try to delete the old key value pairs and only keep the latest key value data. If false, there may be two key value pairs with the same key value, but the values are different. After the necessary parameter non null verification, execute (1) to obtain the character length of the key and value. (2) whether to delete the old key value pair data. (3) place is the key value to apply for the memory area of the structure, and the memory area is empty. (4) apply for the memory area for the key and value respectively. When applying, add one more character length to save null and empty characters. (5) copy the key value data passed in by the parameter to the memory area corresponding to the key value pair structure. (6) handle the case that there is no key value data in the cache. When the key value information is cached, the newly added key value is placed in the head of the linked list. (8) when the number of caches is greater than the maximum number of caches, they are deleted from the tail in turn.

void AddKVCache(const char* key, const char* value, boolean isNew)
{
    if (key == NULL || value == NULL) {
        return;
    }

⑴  size_t keyLen = strnlen(key, MAX_KEY_LEN);
    size_t valueLen = strnlen(value, MAX_VALUE_LEN);
    if ((keyLen >= MAX_KEY_LEN) || (valueLen >= MAX_VALUE_LEN)) {
        return;
    }
⑵  if (isNew) {
        DeleteKVCache(key);
    }
⑶  KvItem* item = (KvItem *)malloc(sizeof(KvItem));
    if (item == NULL) {
        return;
    }
    (void)memset_s(item, sizeof(KvItem), 0, sizeof(KvItem));
⑷  item->key = (char *)malloc(keyLen + 1);
    item->value = (char *)malloc(valueLen + 1);
    if ((item->key == NULL) || (item->value == NULL)) {
        FreeItem(item);
        return;
    }
⑸  if ((strcpy_s(item->key, keyLen + 1, key) != EOK) ||
        (strcpy_s(item->value, valueLen + 1, value) != EOK)) {
        FreeItem(item);
        return;
    }
    item->prev = NULL;
    item->next = NULL;
⑹  if (g_itemHeader == NULL) {
        g_itemHeader = item;
        g_itemTail = item;
        g_sum++;
        return;
    }
⑺  item->next = g_itemHeader;
    g_itemHeader->prev = item;
    g_itemHeader = item;
    g_sum++;
⑻  while (g_sum > MAX_CACHE_SIZE) {
        KvItem* needDel = g_itemTail;
        g_itemTail = g_itemTail->prev;
        FreeItem(needDel);
        g_itemTail->next = NULL;
        g_sum--;
    }
}

2.5 get value GetValueByCache from cache

The function GetValueByCache is used to read a value from the cache. There are three parameters in total. The first two are keys and values, const char* ke is keys, and input parameters; char* value is the output parameter, which is used to save the returned value; The third parameter, unsigned int maxLen, is used to limit the maximum length of the obtained value. The return value of this function represents the successful EC acquisition_ Success or failure EC_FAILURE. After the necessary parameter non null verification is completed, execute ⑴ circular key value pair linked list to obtain the key value structure of the corresponding key. If not, EC is returned_ FAILURE; Otherwise, execute (2) to obtain the length of the value. When this length exceeds the maximum length of the value, EC is returned_ FAILURE. (3) if the length of the obtained value exceeds the length passed in by the parameter, it will not be truncated, but an error will be returned. Copy the value from item - > value to the output parameter. If it fails, an error will be returned.

int GetValueByCache(const char* key, char* value, unsigned int maxLen)
{
    if (key == NULL || value == NULL || g_itemHeader == NULL) {
        return EC_FAILURE;
    }

    KvItem* item = g_itemHeader;
⑴  while (strcmp(key, item->key) != 0) {
        item = item->next;
        if (item == NULL) {
            return EC_FAILURE;
        }
    }
⑵  size_t valueLen = strnlen(item->value, MAX_VALUE_LEN);
    if (valueLen >= MAX_VALUE_LEN) {
        return EC_FAILURE;
    }
⑶  if ((valueLen >= maxLen) || (strcpy_s(value, maxLen, item->value) != EOK)) {
        return EC_FAILURE;
    }
    return EC_SUCCESS;
}

2.6 clear cache ClearKVCacheInner

Clearing the cache function ClearKVCacheInner will clear all the cached key value pairs and return the return value of successful or failed clearing. (1) if the key value pair chain header node is empty, success is returned. (2) each key value pair element in the linked list is deleted one by one. For each deletion, execute (3) to reduce the number of key value pairs in the basic cache by 1.

int ClearKVCacheInner(void)
{
⑴  if (g_itemHeader == NULL) {
        return EC_SUCCESS;
    }
    KvItem* item = g_itemHeader;
⑵  while (item != NULL) {
        KvItem* temp = item;
        item = item->next;
        FreeItem(temp);
⑶      g_sum--;
    }
    g_itemHeader = NULL;
    g_itemTail = NULL;

    return (g_sum != 0) ? EC_FAILURE : EC_SUCCESS;
}

3. External interface of KV storage parts

In the file utils\native\lite\include\kv_store.h defines the external interface of KV storage components, as follows. It supports reading key values from key value pair cache, setting key values, deleting key values, clearing cache, etc.

int UtilsGetValue(const char* key, char* value, unsigned int len);

int UtilsSetValue(const char* key, const char* value);

int UtilsDeleteValue(const char* key);

#ifdef FEATURE_KV_CACHE
int ClearKVCache(void);
#endif

In the file utils\native\lite\kv_store\innerkits\kvstore_env.h defines the following interfaces. When using POSIX interface, you need to use the interface first and set the data file path. This interface is not required when using the UtilsFile interface.

int UtilsSetEnv(const char* path);

4. POSIX interface code corresponding to the part

Analyze the code of POSIX interface corresponding to KV storage unit. We know that the external interface includes setting the key value UtilsSetValue, obtaining the key value UtilsGetValue, deleting the key value UtilsDeleteValue and clearing the cache ClearKVCache. Let's look at the internal interface first.

4.1 internal interface

4.1.1 GetResolvedPath resolution path

The function GetResolvedPath is used to parse the file path and assemble the file path storing the value value value according to the key name key. Four parameters are required. The first parameter char* dataPath is the file path saved by the key value pair. Before using the KV feature, it is set to the global variable by the utilsseenv function g_dataPath; The second parameter is the key char* key; The third parameter char* resolvedPath is the resolved path, which is the output parameter; The fourth parameter unsigned int len is the path length. Look at the code. Part (1) applies for memory for the parsed path, and part (2) assembles the file path of the key value pair in the format of "XXX/kvstore/key". (3) convert the relative path to the absolute path. If the resolution is successful, the file path will be resolved to the output parameter resolvedPath. (4) if there is an error in executing realpath function and the specified file does not exist, it will execute (5) copy keyPath to the output function resolvedPath.

static int GetResolvedPath(const char* dataPath, const char* key, char* resolvedPath, unsigned int len)
{
⑴  char* keyPath = (char *)malloc(MAX_KEY_PATH + 1);
    if (keyPath == NULL) {
        return EC_FAILURE;
    }
⑵  if (sprintf_s(keyPath, MAX_KEY_PATH + 1, "%s/%s/%s", dataPath, KVSTORE_PATH, key) < 0) {
        free(keyPath);
        return EC_FAILURE;
    }
⑶  if (realpath(keyPath, resolvedPath) != NULL) {
        free(keyPath);
        return EC_SUCCESS;
    }
⑷  if (errno == ENOENT) {
⑸      if (strncpy_s(resolvedPath, len, keyPath, strlen(keyPath)) == EOK) {
            free(keyPath);
            return EC_SUCCESS;
        }
    }
    free(keyPath);
    return EC_FAILURE;
}

4.1.2 GetValueByFile reads the key value from the file

The function GetValueByFile reads the value corresponding to the key from the file. Four parameters are required. The first parameter is the directory path where the key value file is stored; The second parameter is the key; The third is the output parameter, which stores the value of the obtained key; The fourth parameter is the length of the output parameter. The return value of this function is EC_FAILURE or the length of the successfully obtained value. (1) obtain the file path of the corresponding key name key, and (2) read the status information of the file. Because the file content is the value corresponding to the key, part (3) indicates that if the size of the value is greater than or equal to the parameter len, the error code will be returned. Equal to or not. You need 1 character length to store null characters for the end. (4) open the file at and then read the file, and the content will be stored in the output parameter value. (5) set the null character at the end of the string at.

static int GetValueByFile(const char* dataPath, const char* key, char* value, unsigned int len)
{
    char* keyPath = (char *)malloc(PATH_MAX + 1);
    if (keyPath == NULL) {
        return EC_FAILURE;
    }
⑴  if (GetResolvedPath(dataPath, key, keyPath, PATH_MAX + 1) != EC_SUCCESS) {
        free(keyPath);
        return EC_FAILURE;
    }
    struct stat info = {0};
⑵  if (stat(keyPath, &info) != F_OK) {
        free(keyPath);
        return EC_FAILURE;
    }
⑶  if (info.st_size >= len) {
        free(keyPath);
        return EC_FAILURE;
    }
⑷  int fd = open(keyPath, O_RDONLY, S_IRUSR);
    free(keyPath);
    keyPath = NULL;
    if (fd < 0) {
        return EC_FAILURE;
    }
    int ret = read(fd, value, info.st_size);
    close(fd);
    fd = -1;
    if (ret < 0) {
        return EC_FAILURE;
    }
⑸  value[info.st_size] = '\0';
    return info.st_size;
}

4.1.3 SetValueToFile\DeleteValueFromFile save \ delete key value

The function SetValueToFile is the same as saving the key value into the file, and the function DeleteValueFromFile is used to delete the key value. (1) obtain the file path of the stored value according to the key name, keyPath, open the file at (2), and then write the value corresponding to the key name. In the function DeleteValueFromFile, assemble the path at (3), and then delete the file.

static int SetValueToFile(const char* dataPath, const char* key, const char* value)
{
    char* keyPath = (char *)malloc(PATH_MAX + 1);
    if (keyPath == NULL) {
        return EC_FAILURE;
    }
⑴  if (GetResolvedPath(dataPath, key, keyPath, PATH_MAX + 1) != EC_SUCCESS) {
        free(keyPath);
        return EC_FAILURE;
    }
⑵  int fd = open(keyPath, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
    free(keyPath);
    keyPath = NULL;
    if (fd < 0) {
        return EC_FAILURE;
    }
    int ret = write(fd, value, strlen(value));
    close(fd);
    fd = -1;
    return (ret < 0) ? EC_FAILURE : EC_SUCCESS;
}

static int DeleteValueFromFile(const char* dataPath, const char* key)
{
    char* keyPath = (char *)malloc(MAX_KEY_PATH + 1);
    if (keyPath == NULL) {
        return EC_FAILURE;
    }
⑶  if (sprintf_s(keyPath, MAX_KEY_PATH + 1, "%s/%s/%s", dataPath, KVSTORE_PATH, key) < 0) {
        free(keyPath);
        return EC_FAILURE;
    }
    int ret = unlink(keyPath);
    free(keyPath);
    return ret;
}

4.1.4 InitKv creates kvstore directory

The function InitKv ensures that when the key value is saved, the kvstore directory is created to store the key value file. Assemble kvstore directory at (1) and use f at (2)_ The OK parameter determines whether the directory exists. If so, it returns EC_SUCCESS. Otherwise, execute (3) to create the kvstore directory.

static int InitKv(const char* dataPath)
{
    if (dataPath == NULL) {
        return EC_FAILURE;
    }
    char* kvPath = (char *)malloc(MAX_KEY_PATH + 1);
    if (kvPath == NULL) {
        return EC_FAILURE;
    }
⑴  if (sprintf_s(kvPath, MAX_KEY_PATH + 1, "%s/%s", dataPath, KVSTORE_PATH) < 0) {
        free(kvPath);
        return EC_FAILURE;
    }
⑵  if (access(kvPath, F_OK) == F_OK) {
        free(kvPath);
        return EC_SUCCESS;
    }
⑶  if (mkdir(kvPath, S_IRUSR | S_IWUSR | S_IXUSR) != F_OK) {
        free(kvPath);
        return EC_FAILURE;
    }
    free(kvPath);
    return EC_SUCCESS;
}

4.1.5 GetCurrentItem gets the current number of key value pairs

The GetCurrentItem function gets the current number of key value pairs. First, assemble the directory path "XXX/kvstore", then execute (1) to open the directory, and then read the directory item. (2) cycle each directory item to judge the number of key value pairs. ⑶ assemble the file path of each key in the kvstore directory, and then obtain the status information of each file. (4) if the file is a regular ordinary file, the key value pair will add 1 to the quantity. Then read the next directory entry under the kvstore directory and cycle in turn.

static int GetCurrentItem(const char* dataPath)
{
    char kvPath[MAX_KEY_PATH + 1] = {0};
    if (sprintf_s(kvPath, MAX_KEY_PATH + 1, "%s/%s", dataPath, KVSTORE_PATH) < 0) {
        return EC_FAILURE;
    }
⑴  DIR* fileDir = opendir(kvPath);
    if (fileDir == NULL) {
        return EC_FAILURE;
    }
    struct dirent* dir = readdir(fileDir);
    int sum = 0;
⑵  while (dir != NULL) {
        char fullPath[MAX_KEY_PATH + 1] = {0};
        struct stat info = {0};
⑶      if (sprintf_s(fullPath, MAX_KEY_PATH + 1, "%s/%s", kvPath, dir->d_name) < 0) {
            closedir(fileDir);
            return EC_FAILURE;
        }
        if (stat(fullPath, &info) != 0) {
            closedir(fileDir);
            return EC_FAILURE;
        }
⑷      if (S_ISREG(info.st_mode)) {
            sum++;
        }
        dir = readdir(fileDir);
    }
    closedir(fileDir);
    return sum;
}

4.1.6 NewItem determines whether a new key value pair

The function NewItem can be used to determine whether to create a new key value pair. (1) obtain the file path corresponding to the key name at (2), judge whether the file exists at (2), and return FALSE if it exists; Returns TRUE if no key value pair exists.

static boolean NewItem(const char* dataPath, const char* key)
{
    char* keyPath = (char *)malloc(MAX_KEY_PATH + 1);
    if (keyPath == NULL) {
        return FALSE;
    }
⑴  if (sprintf_s(keyPath, MAX_KEY_PATH + 1, "%s/%s/%s", dataPath, KVSTORE_PATH, key) < 0) {
        free(keyPath);
        return FALSE;
    }
⑵  if (access(keyPath, F_OK) == F_OK) {
        free(keyPath);
        return FALSE;
    }
    free(keyPath);
    return TRUE;
}

4.2 reading the key value UtilsGetValue

The function UtilsSetValue is used to read the value corresponding to the key name. The first parameter is the key name of the input parameter, the second parameter is the value corresponding to the key name of the output parameter, and the third parameter is the string length of the value. (1) obtain the path where the key value pair is located, and pay attention to the use of mutex. If key value caching is supported, execute (2) to try to read from the cache. When the cache cannot be read, continue to execute (3) read from the file. If the reading is successful, execute (4) and add it to the cache. Note that the third parameter is FALSE. When reading, the read key value pair will be placed at the head of the cached key value pair linked list, but the previous key value pair data will not be deleted.

int UtilsGetValue(const char* key, char* value, unsigned int len)
{
    if (!IsValidKey(key) || (value == NULL) || (len > MAX_GET_VALUE_LEN)) {
        return EC_INVALID;
    }
    pthread_mutex_lock(&g_kvGlobalMutex);
⑴  const char* dataPath = g_dataPath;
    if (dataPath == NULL) {
        pthread_mutex_unlock(&g_kvGlobalMutex);
        return EC_FAILURE;
    }
#ifdef FEATURE_KV_CACHE
⑵  if (GetValueByCache(key, value, len) == EC_SUCCESS) {
        pthread_mutex_unlock(&g_kvGlobalMutex);
        return EC_SUCCESS;
    }
#endif
⑶  int ret = GetValueByFile(dataPath, key, value, len);
    if (ret < 0) {
        pthread_mutex_unlock(&g_kvGlobalMutex);
        return EC_FAILURE;
    }
#ifdef FEATURE_KV_CACHE
⑷  AddKVCache(key, value, FALSE);
#endif
    pthread_mutex_unlock(&g_kvGlobalMutex);
    return ret;
}

4.3 setting the key value UtilsGetValue

The function UtilsSetValue is used to save a pair of key values. At (1), ensure that the kvstore directory exists. If it does not exist, create it. (2) it is used to obtain the number of key value pairs under the kvstore directory. g_getKvSum is FALSE by default and only needs to be obtained once. The key value logarithm is saved in the global variable g_kvSum. (3) judge whether there is a new key value pair at. If the number of key value pairs exceeds the maximum number allowed by the cache and the new cache needs to be set, return EC_FAILURE. (4) save the key value pair to the file at the location. If caching is supported, it also needs to be stored in the cache. Note that the third-party parameter of AddKVCache stored in the cache is TRUE, and the key value pair corresponding to the same key name will be deleted first. (5) if it is a new key value pair, the number of key value pairs needs to be increased by 1.

int UtilsSetValue(const char* key, const char* value)
{
    if (!IsValidKey(key) || !IsValidValue(value, MAX_VALUE_LEN)) {
        return EC_INVALID;
    }
    pthread_mutex_lock(&g_kvGlobalMutex);
    const char* dataPath = g_dataPath;
⑴  int ret = InitKv(dataPath);
    if (ret != EC_SUCCESS) {
        g_getKvSum = FALSE;
        pthread_mutex_unlock(&g_kvGlobalMutex);
        return EC_FAILURE;
    }
⑵  if (!g_getKvSum) {
        g_kvSum = GetCurrentItem(dataPath);
        if (g_kvSum < 0) {
            pthread_mutex_unlock(&g_kvGlobalMutex);
            return EC_FAILURE;
        }
        g_getKvSum = TRUE;
    }
⑶  boolean newItem = NewItem(dataPath, key);
    if ((g_kvSum >= MAX_KV_SUM) && newItem) {
        pthread_mutex_unlock(&g_kvGlobalMutex);
        return EC_FAILURE;
    }
⑷  ret = SetValueToFile(dataPath, key, value);
    if (ret == EC_SUCCESS) {
#ifdef FEATURE_KV_CACHE
        AddKVCache(key, value, TRUE);
#endif
        if (newItem) {
⑸          g_kvSum++;
        }
    }
    pthread_mutex_unlock(&g_kvGlobalMutex);
    return ret;
}

4.4 delete key value UtilsDeleteValue

The function UtilsDeleteValue is used to delete a pair of key values. (1) if the key value cache is supported at, first try to delete the key value pair from the cache. (2) delete the key value from the file. If the deletion exceeds, the logarithm of the key value will be reduced by 1.

int UtilsDeleteValue(const char* key)
{
    if (!IsValidKey(key)) {
        return EC_INVALID;
    }
    pthread_mutex_lock(&g_kvGlobalMutex);
    const char* dataPath = g_dataPath;
    if (dataPath == NULL) {
        pthread_mutex_unlock(&g_kvGlobalMutex);
        return EC_FAILURE;
    }
#ifdef FEATURE_KV_CACHE
⑴  DeleteKVCache(key);
#endif
⑵  int ret = DeleteValueFromFile(dataPath, key);
    if (ret == EC_SUCCESS) {
        g_kvSum--;
    }
    pthread_mutex_unlock(&g_kvGlobalMutex);
    return ret;
}

4.5 clear the key value cache ClearKVCache and set the cache path UtilsSetEnv

The function ClearKVCache is used to clear the cache and directly call the interface ClearKVCache inner to complete. The function UtilsSetEnv is used to set the saving path of key value pairs, which is maintained in the global variable g_dataPath.

#ifdef FEATURE_KV_CACHE
int ClearKVCache(void)
{
    pthread_mutex_lock(&g_kvGlobalMutex);
    int ret = ClearKVCacheInner();
    pthread_mutex_unlock(&g_kvGlobalMutex);
    return ret;
}
#endif

int UtilsSetEnv(const char* path)
{
    if (path == NULL) {
        return EC_FAILURE;
    }
    pthread_mutex_lock(&g_kvGlobalMutex);
    int ret = strcpy_s(g_dataPath, MAX_KEY_PATH + 1, path);
    pthread_mutex_unlock(&g_kvGlobalMutex);
    return (ret != EOK) ? EC_FAILURE : EC_SUCCESS;
}

5. Code of UtilsFile interface part corresponding to KV storage unit

Analyze the code of the UtilsFile interface part corresponding to the KV storage part. We know that the external interface includes setting the key value UtilsSetValue, obtaining the key value UtilsGetValue, deleting the key value UtilsDeleteValue and clearing the cache ClearKVCache. Let's take a look at the internal interfaces. All these interfaces call the UtilsFile interface instead of the file interface of POSIX.

5.1 internal interface

5.1.1 GetValueByFile and SetValueToFile read and write key values from files

The function GetValueByFile is used to read the key value from the file, and obtain the size of the key value file corresponding to the key name at (1). If the file is greater than or equal to the length len specified in the parameter, EC is returned_ FAILURE. Not even equal to. You need to put an empty character at the end. (2) open the file at, then read the file, and put the read content into the variable value. (3) add a null null character at the end of the position, and then return the length of the obtained character. The function SetValueToFile is used to save the key value to the file. At (4), call the UtilsFile interface to open it, and then write it to the file.

static int GetValueByFile(const char* key, char* value, unsigned int len)
{
    unsigned int valueLen = 0;
⑴  if (UtilsFileStat(key, &valueLen) != EC_SUCCESS) {
        return EC_FAILURE;
    }
    if (valueLen >= len) {
        return EC_FAILURE;
    }
⑵  int fd = UtilsFileOpen(key, O_RDONLY_FS, 0);
    if (fd < 0) {
        return EC_FAILURE;
    }
    int ret = UtilsFileRead(fd, value, valueLen);
    UtilsFileClose(fd);
    fd = -1;
    if (ret < 0) {
        return EC_FAILURE;
    }
⑶  value[valueLen] = '\0';
    return valueLen;
}

static int SetValueToFile(const char* key, const char* value)
{
⑷  int fd = UtilsFileOpen(key, O_RDWR_FS | O_CREAT_FS | O_TRUNC_FS, 0);
    if (fd < 0) {
        return EC_FAILURE;
    }
    int ret = UtilsFileWrite(fd, value, strlen(value));
    UtilsFileClose(fd);
    fd = -1;
    return (ret < 0) ? EC_FAILURE : EC_SUCCESS;
}

5.1.2 GetCurrentItem and SetCurrentItem get the number of set key value pairs

The function GetCurrentItem is used to obtain the number of key value pairs. It can be seen from (1) that the key value pairs are saved in the file kV_ FILE_ In sum. The number of key value pairs read from the file will be put into the string at ⑵. The length of the string is 4, so the number of key value pairs can be K-level. Then execute UtilsFileRead to read the contents of the file, and then convert it to a value through the atoi function. The function SetCurrentItem is used to update the number of key value pairs and save them to a file. (4) convert the shaped parameters into strings at and then open the file KV_FILE_SUM and write.

#define KV_SUM_FILE        "KV_FILE_SUM"
#define KV_SUM_INDEX       4
......
static int GetCurrentItem(void)
{
⑴  int fd = UtilsFileOpen(KV_SUM_FILE, O_RDWR_FS, 0);
    if (fd < 0) {
        return 0;
    }
⑵  char value[KV_SUM_INDEX] = {0};
    int ret = UtilsFileRead(fd, value, KV_SUM_INDEX);
    UtilsFileClose(fd);
    fd = -1;
⑶  return (ret < 0) ? 0 : atoi(value);
}

static int SetCurrentItem(const int num)
{
    char value[KV_SUM_INDEX] = {0};
⑷  if (sprintf_s(value, KV_SUM_INDEX, "%d", num) < 0) {
        return EC_FAILURE;
    }
    int fd = UtilsFileOpen(KV_SUM_FILE, O_RDWR_FS | O_CREAT_FS, 0);
    if (fd < 0) {
        return EC_FAILURE;
    }
    int ret = UtilsFileWrite(fd, value, KV_SUM_INDEX);
    UtilsFileClose(fd);
    fd = -1;
    return (ret < 0) ? EC_FAILURE : EC_SUCCESS;
}

5.1.3 NewItem determines whether a new key value pair

The function NewItem is used to judge whether a given key name is a new key value pair and whether the same key name already exists. Call the function UtilsFileOpen. If it can be opened, the file already exists, otherwise it does not exist.

static boolean NewItem(const char* key)
{
    int fd = UtilsFileOpen(key, O_RDONLY_FS, 0);
    if (fd < 0) {
        return TRUE;
    }
    UtilsFileClose(fd);
    return FALSE;
}

5.2 get the key value UtilsGetValue

The function UtilsGetValue is used to read the key value from the file, pass in the key name key, save the read value in the parameter value, and len sets the length of the read value. If key value pair caching is supported, execute (1) to try to read from the cache, otherwise execute (2) to read from the file. After successful reading, execute (3) to add the read key value to the cache.

int UtilsGetValue(const char* key, char* value, unsigned int len)
{
    if (!IsValidKey(key) || (value == NULL) || (len > MAX_GET_VALUE_LEN)) {
        return EC_INVALID;
    }
#ifdef FEATURE_KV_CACHE
⑴  if (GetValueByCache(key, value, len) == EC_SUCCESS) {
        return EC_SUCCESS;
    }
#endif
⑵  int ret = GetValueByFile(key, value, len);
    if (ret < 0) {
        return EC_FAILURE;
    }
#ifdef FEATURE_KV_CACHE
⑶  AddKVCache(key, value, FALSE);
#endif
    return ret;
}

5.3 setting the key value UtilsGetValue

The function UtilsGetValue is used to set the key value pair to the file. (1) obtain the number of existing key value pairs, and (2) judge whether the key value to be set already exists. (3) if the number of key value pairs has been greater than or equal to the maximum allowable value, and if a new key value pair is added, then EC_FAILURE. (4) save the key value pairs to the file. If caching is supported, add it to the cache. (5) update the number of key value pairs at.

int UtilsSetValue(const char* key, const char* value)
{
    if (!IsValidKey(key) || !IsValidValue(value, MAX_VALUE_LEN)) {
        return EC_INVALID;
    }
⑴  int currentNum = GetCurrentItem();
⑵  boolean newItem = NewItem(key);
⑶  if ((currentNum >= MAX_KV_SUM) && newItem) {
        return EC_FAILURE;
    }
⑷  int ret = SetValueToFile(key, value);
    if (ret == EC_SUCCESS) {
#ifdef FEATURE_KV_CACHE
        AddKVCache(key, value, TRUE);
#endif
        if (newItem) {
            currentNum++;
        }
    }

⑸  return SetCurrentItem(currentNum);
}

5.4 delete key value UtilsDeleteValue, etc

The function UtilsDeleteValue is used to delete the key value file. If caching is supported, it will be deleted from the cache first. Execute (1) delete the file, and update the number of key value pairs at (2). The ClearKVCache function is used to clear the cache. When using the UtilsFile interface, the UtilsSetEnv function is not required.

int UtilsDeleteValue(const char* key)
{
    if (!IsValidKey(key)) {
        return EC_INVALID;
    }
#ifdef FEATURE_KV_CACHE
    DeleteKVCache(key);
#endif
⑴  int ret = UtilsFileDelete(key);
    if (ret == EC_SUCCESS) {
⑵      ret = SetCurrentItem(GetCurrentItem() - 1);
    }
    return ret;
}

#ifdef FEATURE_KV_CACHE
int ClearKVCache(void)
{
    return ClearKVCacheInner();
}
#endif

int UtilsSetEnv(const char* path)
{
    return EC_SUCCESS;
}

Reference site

Refer to the following sites or recommend readers to read the following sites for more information.

 

Click focus to learn about Huawei cloud's new technologies for the first time~

Added by fractalvibes on Fri, 25 Feb 2022 06:03:48 +0200