Files
HomeKitADK/PAL/POSIX/HAPPlatformKeyValueStore.c
2019-12-18 05:20:05 -08:00

439 lines
14 KiB
C

// Copyright (c) 2015-2019 The HomeKit ADK Contributors
//
// Licensed under the Apache License, Version 2.0 (the “License”);
// you may not use this file except in compliance with the License.
// See [CONTRIBUTORS.md] for the list of HomeKit ADK project authors.
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include "HAPPlatform+Init.h"
#include "HAPPlatformKeyValueStore+Init.h"
#include "HAPPlatformFileManager.h"
static const HAPLogObject logObject = { .subsystem = kHAPPlatform_LogSubsystem, .category = "KeyValueStore" };
/**
* Enumerates directory @p dir, calling @p body on each directory entry.
*
* @param dir Path to directory to scan.
* @param body Function to call on each directory entry.
* @param ctx Context that is passed to @p body.
*
* @return 0 If successful.
* @return -1 Otherwise. @c errno is set accordingly.
*
* @remarks Errors:
* - [EINVAL] A required parameter is NULL.
* - [EINVAL] The implementation does not support an association of the variable @c _PC_NAME_MAX
* with the specified file.
* - [EACCES] Search permission is denied for the component of the path prefix of @p dir
* or read permission is denied for @p dir.
* - [ELOOP] A loop exists in symbolic links encountered during resolution of the @p dir argument.
* - [ELOOP] More than {SYMLOOP_MAX} symbolic links were encountered during resolution of the @p dir argument.
* - [EMFILE] All file descriptors available to the process are currently open.
* - [ENAMETOOLONG] The length of a pathname exceeds {PATH_MAX}, or pathname resolution of a symbolic link
* produced an intermediate result with a length that exceeds {PATH_MAX}.
* - [ENFILE] Too many files are currently open in the system.
* - [ENOMEM] Insufficient storage space is available.
* - [ENOTDIR] A component of @p dir names an existing file that is neither a directory
* nor a symbolic link to a directory.
* - [EOVERFLOW] One of the values in the dirent structure of a directory entry cannot be represented correctly.
* - Errors thrown by @p body are propagated as well.
*/
HAP_RESULT_USE_CHECK
static int
enumdir(const char* dir,
int (*body)(void* ctx, const char* dir, const struct dirent* ent, bool* cont),
void* ctx) {
HAPPrecondition(dir);
HAPPrecondition(body);
// Open directory.
DIR* d = opendir(dir);
if (!d) {
int _errno = errno;
if (errno == ENOENT) {
errno = 0;
return 0;
}
HAPLogError(&logObject, "opendir %s failed: %d.", dir, _errno);
errno = _errno;
return -1;
}
// Get fd.
int fd = dirfd(d);
if (fd < 0) {
int _errno = errno;
HAPAssert(fd == -1);
HAPLogError(&logObject, "dirfd %s failed: %d.", dir, _errno);
HAPPlatformFileManagerCloseDirFreeSafe(d);
errno = _errno;
return -1;
}
// Get dirent buffer.
errno = 0;
long maxNameBytes = fpathconf(fd, _PC_NAME_MAX);
if (maxNameBytes < 0) {
int _errno = errno;
HAPAssert(maxNameBytes == -1);
if (errno) {
HAPLogError(&logObject, "fpathconf %s failed: %d.", dir, _errno);
} else {
HAPLogError(&logObject, "fpathconf %s returned indefinite {NAME_MAX}.", dir);
}
HAPPlatformFileManagerCloseDirFreeSafe(d);
errno = _errno;
return -1;
}
size_t maxDirentBytes = HAP_OFFSETOF(struct dirent, d_name) + (size_t) maxNameBytes + 1;
if (sizeof(struct dirent) > maxDirentBytes) {
maxDirentBytes = sizeof(struct dirent);
}
struct dirent* direntBuffer = malloc(maxDirentBytes);
if (!direntBuffer) {
int _errno = errno;
HAPLogError(&logObject, "malloc %lu failed: %d.", (unsigned long) maxDirentBytes, _errno);
HAPPlatformFileManagerCloseDirFreeSafe(d);
errno = _errno;
return -1;
}
// Iterate through directory.
for (bool cont = true; cont;) {
// Get next file.
struct dirent* entry;
// readdir_r is deprecated in newer glibc.
// https://lwn.net/Articles/696474/
HAP_DIAGNOSTIC_PUSH
HAP_DIAGNOSTIC_IGNORED_CLANG("-Wdeprecated-declarations")
HAP_DIAGNOSTIC_IGNORED_GCC("-Wdeprecated-declarations")
int e = readdir_r(d, direntBuffer, &entry);
HAP_DIAGNOSTIC_POP
if (e) {
int _errno = errno;
HAPAssert(e == -1);
HAPLogError(&logObject, "readdir_r %s failed: %d.", dir, _errno);
HAPPlatformFreeSafe(direntBuffer);
HAPPlatformFileManagerCloseDirFreeSafe(d);
errno = _errno;
return -1;
}
if (!entry) {
break;
}
// Invoke callback.
errno = 0;
e = body(ctx, dir, entry, &cont);
if (e) {
int _errno = errno;
HAPAssert(e == -1);
HAPPlatformFreeSafe(direntBuffer);
HAPPlatformFileManagerCloseDirFreeSafe(d);
errno = _errno;
return -1;
}
}
HAPPlatformFreeSafe(direntBuffer);
HAPPlatformFileManagerCloseDirFreeSafe(d);
errno = 0;
return 0;
}
void HAPPlatformKeyValueStoreCreate(
HAPPlatformKeyValueStoreRef keyValueStore,
const HAPPlatformKeyValueStoreOptions* options) {
HAPPrecondition(keyValueStore);
HAPPrecondition(options);
HAPPrecondition(options->rootDirectory);
HAPLogDebug(&logObject, "Storage configuration: keyValueStore = %lu", (unsigned long) sizeof *keyValueStore);
keyValueStore->rootDirectory = options->rootDirectory;
}
/**
* Gets the file path under which data for a specified key is stored.
*
* @param keyValueStore Key-value store.
* @param domain Domain.
* @param key Key.
* @param[out] filePath File path for the domain / key. NULL-terminated.
* @param maxFilePathLength Maximum length that the filePath buffer may hold.
*
* @return kHAPError_None If successful.
* @return kHAPError_OutOfResources If @p cap not large enough.
*/
HAP_RESULT_USE_CHECK
static HAPError GetFilePath(
HAPPlatformKeyValueStoreRef keyValueStore,
HAPPlatformKeyValueStoreDomain domain,
HAPPlatformKeyValueStoreKey key,
char* filePath,
size_t maxFilePathLength) {
HAPPrecondition(keyValueStore);
HAPPrecondition(keyValueStore->rootDirectory);
HAPPrecondition(filePath);
HAPError err;
HAPAssert(sizeof domain == sizeof(uint8_t));
HAPAssert(sizeof key == sizeof(uint8_t));
err = HAPStringWithFormat(filePath, maxFilePathLength, "%s/%02X.%02X", keyValueStore->rootDirectory, domain, key);
if (err) {
HAPAssert(err == kHAPError_OutOfResources);
HAPLogError(
&logObject,
"Not enough resources to get path: %s/%02X.%02X",
keyValueStore->rootDirectory,
domain,
key);
return kHAPError_OutOfResources;
}
return kHAPError_None;
}
HAP_RESULT_USE_CHECK
HAPError HAPPlatformKeyValueStoreGet(
HAPPlatformKeyValueStoreRef keyValueStore,
HAPPlatformKeyValueStoreDomain domain,
HAPPlatformKeyValueStoreKey key,
void* _Nullable bytes,
size_t maxBytes,
size_t* _Nullable numBytes,
bool* found) {
HAPPrecondition(keyValueStore);
HAPPrecondition(keyValueStore->rootDirectory);
HAPPrecondition(!maxBytes || bytes);
HAPPrecondition((bytes == NULL) == (numBytes == NULL));
HAPPrecondition(found);
HAPError err;
// Get file name.
char filePath[PATH_MAX];
err = GetFilePath(keyValueStore, domain, key, filePath, sizeof filePath);
if (err) {
HAPAssert(err == kHAPError_OutOfResources);
return kHAPError_Unknown;
}
return HAPPlatformFileManagerReadFile(filePath, bytes, maxBytes, numBytes, found);
}
HAP_RESULT_USE_CHECK
HAPError HAPPlatformKeyValueStoreSet(
HAPPlatformKeyValueStoreRef keyValueStore,
HAPPlatformKeyValueStoreDomain domain,
HAPPlatformKeyValueStoreKey key,
const void* bytes,
size_t numBytes) {
HAPPrecondition(keyValueStore);
HAPPrecondition(keyValueStore->rootDirectory);
HAPPrecondition(bytes);
HAPError err;
char filePath[PATH_MAX];
// Get file name.
err = GetFilePath(keyValueStore, domain, key, filePath, sizeof filePath);
if (err) {
HAPAssert(err == kHAPError_OutOfResources);
return kHAPError_Unknown;
}
// Write the KVS file.
err = HAPPlatformFileManagerWriteFile(filePath, bytes, numBytes);
if (err) {
HAPAssert(err == kHAPError_Unknown);
return err;
}
return kHAPError_None;
}
HAP_RESULT_USE_CHECK
HAPError HAPPlatformKeyValueStoreRemove(
HAPPlatformKeyValueStoreRef keyValueStore,
HAPPlatformKeyValueStoreDomain domain,
HAPPlatformKeyValueStoreKey key) {
HAPPrecondition(keyValueStore);
HAPPrecondition(keyValueStore->rootDirectory);
HAPError err;
char filePath[PATH_MAX];
// Get file name.
err = GetFilePath(keyValueStore, domain, key, filePath, sizeof filePath);
if (err) {
HAPAssert(err == kHAPError_OutOfResources);
return kHAPError_Unknown;
}
// Remove file.
err = HAPPlatformFileManagerRemoveFile(filePath);
if (err) {
HAPAssert(err == kHAPError_Unknown);
return err;
}
return kHAPError_None;
}
/**
* enumdir context.
*/
typedef struct {
/**
* Key-value store.
*/
HAPPlatformKeyValueStoreRef keyValueStore;
/**
* Domain to enumerate.
*/
HAPPlatformKeyValueStoreDomain domain;
/**
* Client callback.
*/
HAPPlatformKeyValueStoreEnumerateCallback body;
/**
* Client context.
*/
void* _Nullable context;
} EnumdirCallbackContext;
HAP_RESULT_USE_CHECK
static int EnumdirCallback(void* ctx, const char* dir, const struct dirent* ent, bool* cont) {
EnumdirCallbackContext* arguments = ctx;
HAPPrecondition(arguments);
HAPPrecondition(arguments->keyValueStore);
HAPPrecondition(arguments->body);
HAPPrecondition(dir);
HAPPrecondition(ent);
HAPPrecondition(cont);
HAPError err;
// Parse file name.
HAPAssert(ent->d_name);
if (HAPStringAreEqual(ent->d_name, ".")) {
return 0;
}
if (HAPStringAreEqual(ent->d_name, "..")) {
return 0;
}
unsigned int domain;
unsigned int key;
int end;
int n = sscanf(ent->d_name, "%2X.%2X%n", &domain, &key, &end);
if (n != 2 || (size_t) end != HAPStringGetNumBytes(ent->d_name)) {
HAPLog(&logObject, "Skipping unexpected file in key-value store directory: %s", ent->d_name);
return 0;
}
HAPAssert(sizeof(HAPPlatformKeyValueStoreDomain) == sizeof(uint8_t));
if (domain > UINT8_MAX) {
HAPLog(&logObject, "Skipping file with too large domain in key-value store directory: %s", ent->d_name);
return 0;
}
HAPAssert(sizeof(HAPPlatformKeyValueStoreKey) == sizeof(uint8_t));
if (key > UINT8_MAX) {
HAPLog(&logObject, "Skipping file with too large key in key-value store directory: %s", ent->d_name);
return 0;
}
// Check domain.
if ((HAPPlatformKeyValueStoreDomain) domain != arguments->domain) {
return 0;
}
// Invoke callback.
err = arguments->body(
arguments->context,
arguments->keyValueStore,
(HAPPlatformKeyValueStoreDomain) domain,
(HAPPlatformKeyValueStoreKey) key,
cont);
if (err) {
HAPAssert(err == kHAPError_Unknown);
return -1;
}
return 0;
}
HAP_RESULT_USE_CHECK
HAPError HAPPlatformKeyValueStoreEnumerate(
HAPPlatformKeyValueStoreRef keyValueStore,
HAPPlatformKeyValueStoreDomain domain,
HAPPlatformKeyValueStoreEnumerateCallback callback,
void* _Nullable context) {
HAPPrecondition(keyValueStore);
HAPPrecondition(keyValueStore->rootDirectory);
HAPPrecondition(callback);
int e =
enumdir(keyValueStore->rootDirectory,
EnumdirCallback,
&(EnumdirCallbackContext) {
.keyValueStore = keyValueStore,
.domain = domain,
.body = callback,
.context = context,
});
if (e) {
HAPAssert(e == -1);
return kHAPError_Unknown;
}
return kHAPError_None;
}
HAP_RESULT_USE_CHECK
static HAPError PurgeDomainEnumerateCallback(
void* _Nullable context HAP_UNUSED,
HAPPlatformKeyValueStoreRef keyValueStore,
HAPPlatformKeyValueStoreDomain domain,
HAPPlatformKeyValueStoreKey key,
bool* shouldContinue) {
HAPPrecondition(keyValueStore);
HAPPrecondition(keyValueStore->rootDirectory);
HAPPrecondition(shouldContinue);
HAPError err;
err = HAPPlatformKeyValueStoreRemove(keyValueStore, domain, key);
if (err) {
HAPAssert(err == kHAPError_Unknown);
return err;
}
return kHAPError_None;
}
HAP_RESULT_USE_CHECK
HAPError HAPPlatformKeyValueStorePurgeDomain(
HAPPlatformKeyValueStoreRef keyValueStore,
HAPPlatformKeyValueStoreDomain domain) {
HAPPrecondition(keyValueStore);
HAPPrecondition(keyValueStore->rootDirectory);
HAPError err;
err = HAPPlatformKeyValueStoreEnumerate(keyValueStore, domain, PurgeDomainEnumerateCallback, NULL);
if (err) {
HAPAssert(err == kHAPError_Unknown);
return err;
}
return kHAPError_None;
}