418 lines
13 KiB
C
418 lines
13 KiB
C
|
/**
|
||
|
* @file NCDUdevCache.c
|
||
|
* @author Ambroz Bizjak <ambrop7@gmail.com>
|
||
|
*
|
||
|
* @section LICENSE
|
||
|
*
|
||
|
* Redistribution and use in source and binary forms, with or without
|
||
|
* modification, are permitted provided that the following conditions are met:
|
||
|
* 1. Redistributions of source code must retain the above copyright
|
||
|
* notice, this list of conditions and the following disclaimer.
|
||
|
* 2. Redistributions in binary form must reproduce the above copyright
|
||
|
* notice, this list of conditions and the following disclaimer in the
|
||
|
* documentation and/or other materials provided with the distribution.
|
||
|
* 3. Neither the name of the author nor the
|
||
|
* names of its contributors may be used to endorse or promote products
|
||
|
* derived from this software without specific prior written permission.
|
||
|
*
|
||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||
|
* DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||
|
*/
|
||
|
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
|
||
|
#include <misc/offset.h>
|
||
|
#include <misc/string_begins_with.h>
|
||
|
#include <misc/concat_strings.h>
|
||
|
#include <misc/compare.h>
|
||
|
#include <base/BLog.h>
|
||
|
|
||
|
#include <udevmonitor/NCDUdevCache.h>
|
||
|
|
||
|
#include <generated/blog_channel_NCDUdevCache.h>
|
||
|
|
||
|
static int string_comparator (void *unused, const char **str1, const char **str2)
|
||
|
{
|
||
|
int c = strcmp(*str1, *str2);
|
||
|
return B_COMPARE(c, 0);
|
||
|
}
|
||
|
|
||
|
static void free_device (NCDUdevCache *o, struct NCDUdevCache_device *device)
|
||
|
{
|
||
|
if (device->is_cleaned) {
|
||
|
// remove from cleaned devices list
|
||
|
LinkedList1_Remove(&o->cleaned_devices_list, &device->cleaned_devices_list_node);
|
||
|
} else {
|
||
|
// remove from devices tree
|
||
|
BAVL_Remove(&o->devices_tree, &device->devices_tree_node);
|
||
|
}
|
||
|
|
||
|
// free map
|
||
|
BStringMap_Free(&device->map);
|
||
|
|
||
|
// free structure
|
||
|
free(device);
|
||
|
}
|
||
|
|
||
|
static struct NCDUdevCache_device * lookup_device (NCDUdevCache *o, const char *devpath)
|
||
|
{
|
||
|
BAVLNode *tree_node = BAVL_LookupExact(&o->devices_tree, &devpath);
|
||
|
if (!tree_node) {
|
||
|
return NULL;
|
||
|
}
|
||
|
struct NCDUdevCache_device *device = UPPER_OBJECT(tree_node, struct NCDUdevCache_device, devices_tree_node);
|
||
|
ASSERT(!device->is_cleaned)
|
||
|
|
||
|
return device;
|
||
|
}
|
||
|
|
||
|
static void rename_devices (NCDUdevCache *o, const char *prefix, const char *new_prefix)
|
||
|
{
|
||
|
ASSERT(strlen(prefix) > 0)
|
||
|
|
||
|
size_t prefix_len = strlen(prefix);
|
||
|
|
||
|
// lookup prefix
|
||
|
BAVLNode *tree_node = BAVL_Lookup(&o->devices_tree, &prefix);
|
||
|
if (!tree_node) {
|
||
|
return;
|
||
|
}
|
||
|
struct NCDUdevCache_device *device = UPPER_OBJECT(tree_node, struct NCDUdevCache_device, devices_tree_node);
|
||
|
ASSERT(!device->is_cleaned)
|
||
|
|
||
|
// if the result does not begin with prefix, we might gave gotten the device before all
|
||
|
// devices beginning with prefix, so skip it
|
||
|
if (!string_begins_with(device->devpath, prefix)) {
|
||
|
tree_node = BAVL_GetNext(&o->devices_tree, tree_node);
|
||
|
}
|
||
|
|
||
|
while (tree_node) {
|
||
|
// get next node (must be here because we rename this device)
|
||
|
BAVLNode *next_tree_node = BAVL_GetNext(&o->devices_tree, tree_node);
|
||
|
|
||
|
// get device
|
||
|
device = UPPER_OBJECT(tree_node, struct NCDUdevCache_device, devices_tree_node);
|
||
|
ASSERT(!device->is_cleaned)
|
||
|
|
||
|
// if it doesn't begin with prefix, we're done
|
||
|
if (!string_begins_with(device->devpath, prefix)) {
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// build new devpath
|
||
|
char *new_devpath = concat_strings(2, new_prefix, device->devpath + prefix_len);
|
||
|
if (!new_devpath) {
|
||
|
BLog(BLOG_ERROR, "concat_strings failed");
|
||
|
goto fail_loop0;
|
||
|
}
|
||
|
|
||
|
// make sure the new name does not exist
|
||
|
if (BAVL_LookupExact(&o->devices_tree, &new_devpath)) {
|
||
|
BLog(BLOG_ERROR, "rename destination already exists");
|
||
|
goto fail_loop1;
|
||
|
}
|
||
|
|
||
|
BLog(BLOG_DEBUG, "rename %s -> %s", device->devpath, new_devpath);
|
||
|
|
||
|
// remove from tree
|
||
|
BAVL_Remove(&o->devices_tree, &device->devices_tree_node);
|
||
|
|
||
|
// update devpath in map
|
||
|
if (!BStringMap_Set(&device->map, "DEVPATH", new_devpath)) {
|
||
|
BLog(BLOG_ERROR, "BStringMap_Set failed");
|
||
|
ASSERT_EXECUTE(BAVL_Insert(&o->devices_tree, &device->devices_tree_node, NULL))
|
||
|
goto fail_loop1;
|
||
|
}
|
||
|
|
||
|
// update devpath pointer
|
||
|
device->devpath = BStringMap_Get(&device->map, "DEVPATH");
|
||
|
ASSERT(device->devpath)
|
||
|
|
||
|
// insert to tree
|
||
|
ASSERT_EXECUTE(BAVL_Insert(&o->devices_tree, &device->devices_tree_node, NULL))
|
||
|
|
||
|
fail_loop1:
|
||
|
free(new_devpath);
|
||
|
fail_loop0:
|
||
|
tree_node = next_tree_node;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static int add_device (NCDUdevCache *o, BStringMap map)
|
||
|
{
|
||
|
ASSERT(BStringMap_Get(&map, "DEVPATH"))
|
||
|
|
||
|
// alloc structure
|
||
|
struct NCDUdevCache_device *device = malloc(sizeof(*device));
|
||
|
if (!device) {
|
||
|
BLog(BLOG_ERROR, "malloc failed");
|
||
|
goto fail0;
|
||
|
}
|
||
|
|
||
|
// init map
|
||
|
device->map = map;
|
||
|
|
||
|
// set device path
|
||
|
device->devpath = BStringMap_Get(&device->map, "DEVPATH");
|
||
|
|
||
|
// insert to devices tree
|
||
|
BAVLNode *ex_node;
|
||
|
if (!BAVL_Insert(&o->devices_tree, &device->devices_tree_node, &ex_node)) {
|
||
|
BLog(BLOG_DEBUG, "update %s", device->devpath);
|
||
|
|
||
|
// get existing device
|
||
|
struct NCDUdevCache_device *ex_device = UPPER_OBJECT(ex_node, struct NCDUdevCache_device, devices_tree_node);
|
||
|
ASSERT(!ex_device->is_cleaned)
|
||
|
|
||
|
// remove exiting device
|
||
|
free_device(o, ex_device);
|
||
|
|
||
|
// insert
|
||
|
ASSERT_EXECUTE(BAVL_Insert(&o->devices_tree, &device->devices_tree_node, NULL))
|
||
|
} else {
|
||
|
BLog(BLOG_DEBUG, "add %s", device->devpath);
|
||
|
}
|
||
|
|
||
|
// set not cleaned
|
||
|
device->is_cleaned = 0;
|
||
|
|
||
|
// set refreshed
|
||
|
device->is_refreshed = 1;
|
||
|
|
||
|
return 1;
|
||
|
|
||
|
fail0:
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
void NCDUdevCache_Init (NCDUdevCache *o)
|
||
|
{
|
||
|
// init devices tree
|
||
|
BAVL_Init(&o->devices_tree, OFFSET_DIFF(struct NCDUdevCache_device, devpath, devices_tree_node), (BAVL_comparator)string_comparator, NULL);
|
||
|
|
||
|
// init cleaned devices list
|
||
|
LinkedList1_Init(&o->cleaned_devices_list);
|
||
|
|
||
|
DebugObject_Init(&o->d_obj);
|
||
|
}
|
||
|
|
||
|
void NCDUdevCache_Free (NCDUdevCache *o)
|
||
|
{
|
||
|
DebugObject_Free(&o->d_obj);
|
||
|
|
||
|
// free cleaned devices
|
||
|
LinkedList1Node *list_node;
|
||
|
while (list_node = LinkedList1_GetFirst(&o->cleaned_devices_list)) {
|
||
|
struct NCDUdevCache_device *device = UPPER_OBJECT(list_node, struct NCDUdevCache_device, cleaned_devices_list_node);
|
||
|
ASSERT(device->is_cleaned)
|
||
|
free_device(o, device);
|
||
|
}
|
||
|
|
||
|
// free devices
|
||
|
BAVLNode *tree_node;
|
||
|
while (tree_node = BAVL_GetFirst(&o->devices_tree)) {
|
||
|
struct NCDUdevCache_device *device = UPPER_OBJECT(tree_node, struct NCDUdevCache_device, devices_tree_node);
|
||
|
ASSERT(!device->is_cleaned)
|
||
|
free_device(o, device);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const BStringMap * NCDUdevCache_Query (NCDUdevCache *o, const char *devpath)
|
||
|
{
|
||
|
DebugObject_Access(&o->d_obj);
|
||
|
|
||
|
// lookup device
|
||
|
struct NCDUdevCache_device *device = lookup_device(o, devpath);
|
||
|
if (!device) {
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
// return map
|
||
|
return &device->map;
|
||
|
}
|
||
|
|
||
|
int NCDUdevCache_Event (NCDUdevCache *o, BStringMap map)
|
||
|
{
|
||
|
DebugObject_Access(&o->d_obj);
|
||
|
|
||
|
// get device path
|
||
|
const char *devpath = BStringMap_Get(&map, "DEVPATH");
|
||
|
if (!devpath) {
|
||
|
BLog(BLOG_ERROR, "missing DEVPATH");
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
// get action
|
||
|
const char *action = BStringMap_Get(&map, "ACTION");
|
||
|
|
||
|
// if this is a remove event, remove device if we have it
|
||
|
if (action && !strcmp(action, "remove")) {
|
||
|
// remove existing device
|
||
|
struct NCDUdevCache_device *device = lookup_device(o, devpath);
|
||
|
if (device) {
|
||
|
BLog(BLOG_DEBUG, "remove %s", devpath);
|
||
|
free_device(o, device);
|
||
|
} else {
|
||
|
BLog(BLOG_DEBUG, "remove unknown %s", devpath);
|
||
|
}
|
||
|
|
||
|
// eat map
|
||
|
BStringMap_Free(&map);
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
// if this is a move event, remove old device and contaned devices
|
||
|
if (action && !strcmp(action, "move")) {
|
||
|
const char *devpath_old = BStringMap_Get(&map, "DEVPATH_OLD");
|
||
|
if (!devpath_old) {
|
||
|
goto fail_rename0;
|
||
|
}
|
||
|
|
||
|
// remove old device
|
||
|
struct NCDUdevCache_device *old_device = lookup_device(o, devpath_old);
|
||
|
if (old_device) {
|
||
|
BLog(BLOG_DEBUG, "remove moved %s", old_device->devpath);
|
||
|
free_device(o, old_device);
|
||
|
}
|
||
|
|
||
|
// construct prefix "<devpath_old>/" and new prefix "<devpath>/"
|
||
|
char *prefix = concat_strings(2, devpath_old, "/");
|
||
|
if (!prefix) {
|
||
|
BLog(BLOG_ERROR, "concat_strings failed");
|
||
|
goto fail_rename0;;
|
||
|
}
|
||
|
char *new_prefix = concat_strings(2, devpath, "/");
|
||
|
if (!new_prefix) {
|
||
|
BLog(BLOG_ERROR, "concat_strings failed");
|
||
|
goto fail_rename1;
|
||
|
}
|
||
|
|
||
|
// rename devices with paths starting with prefix
|
||
|
rename_devices(o, prefix, new_prefix);
|
||
|
|
||
|
free(new_prefix);
|
||
|
fail_rename1:
|
||
|
free(prefix);
|
||
|
fail_rename0:;
|
||
|
}
|
||
|
|
||
|
// add device
|
||
|
if (!add_device(o, map)) {
|
||
|
BLog(BLOG_DEBUG, "failed to add device %s", devpath);
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
return 1;
|
||
|
|
||
|
fail:
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
void NCDUdevCache_StartClean (NCDUdevCache *o)
|
||
|
{
|
||
|
DebugObject_Access(&o->d_obj);
|
||
|
|
||
|
// mark all devices not refreshed
|
||
|
BAVLNode *tree_node = BAVL_GetFirst(&o->devices_tree);
|
||
|
while (tree_node) {
|
||
|
struct NCDUdevCache_device *device = UPPER_OBJECT(tree_node, struct NCDUdevCache_device, devices_tree_node);
|
||
|
ASSERT(!device->is_cleaned)
|
||
|
|
||
|
// set device not refreshed
|
||
|
device->is_refreshed = 0;
|
||
|
|
||
|
tree_node = BAVL_GetNext(&o->devices_tree, tree_node);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void NCDUdevCache_FinishClean (NCDUdevCache *o)
|
||
|
{
|
||
|
DebugObject_Access(&o->d_obj);
|
||
|
|
||
|
// move all devices not marked refreshed to the cleaned devices list
|
||
|
BAVLNode *tree_node = BAVL_GetFirst(&o->devices_tree);
|
||
|
while (tree_node) {
|
||
|
BAVLNode *next_tree_node = BAVL_GetNext(&o->devices_tree, tree_node);
|
||
|
|
||
|
struct NCDUdevCache_device *device = UPPER_OBJECT(tree_node, struct NCDUdevCache_device, devices_tree_node);
|
||
|
ASSERT(!device->is_cleaned)
|
||
|
|
||
|
if (!device->is_refreshed) {
|
||
|
BLog(BLOG_DEBUG, "clean %s", device->devpath);
|
||
|
|
||
|
// remove from devices tree
|
||
|
BAVL_Remove(&o->devices_tree, &device->devices_tree_node);
|
||
|
|
||
|
// insert to cleaned devices list
|
||
|
LinkedList1_Append(&o->cleaned_devices_list, &device->cleaned_devices_list_node);
|
||
|
|
||
|
// set device cleaned
|
||
|
device->is_cleaned = 1;
|
||
|
}
|
||
|
|
||
|
tree_node = next_tree_node;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int NCDUdevCache_GetCleanedDevice (NCDUdevCache *o, BStringMap *out_map)
|
||
|
{
|
||
|
DebugObject_Access(&o->d_obj);
|
||
|
|
||
|
// get cleaned device
|
||
|
LinkedList1Node *list_node = LinkedList1_GetFirst(&o->cleaned_devices_list);
|
||
|
if (!list_node) {
|
||
|
return 0;
|
||
|
}
|
||
|
struct NCDUdevCache_device *device = UPPER_OBJECT(list_node, struct NCDUdevCache_device, cleaned_devices_list_node);
|
||
|
ASSERT(device->is_cleaned)
|
||
|
|
||
|
// remove from cleaned devices list
|
||
|
LinkedList1_Remove(&o->cleaned_devices_list, &device->cleaned_devices_list_node);
|
||
|
|
||
|
// give away map
|
||
|
*out_map = device->map;
|
||
|
|
||
|
// free structure
|
||
|
free(device);
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
const char * NCDUdevCache_First (NCDUdevCache *o)
|
||
|
{
|
||
|
DebugObject_Access(&o->d_obj);
|
||
|
|
||
|
BAVLNode *tree_node = BAVL_GetFirst(&o->devices_tree);
|
||
|
if (!tree_node) {
|
||
|
return NULL;
|
||
|
}
|
||
|
struct NCDUdevCache_device *device = UPPER_OBJECT(tree_node, struct NCDUdevCache_device, devices_tree_node);
|
||
|
ASSERT(!device->is_cleaned)
|
||
|
|
||
|
return device->devpath;
|
||
|
}
|
||
|
|
||
|
const char * NCDUdevCache_Next (NCDUdevCache *o, const char *key)
|
||
|
{
|
||
|
ASSERT(BAVL_LookupExact(&o->devices_tree, &key))
|
||
|
|
||
|
BAVLNode *tree_node = BAVL_GetNext(&o->devices_tree, BAVL_LookupExact(&o->devices_tree, &key));
|
||
|
if (!tree_node) {
|
||
|
return NULL;
|
||
|
}
|
||
|
struct NCDUdevCache_device *device = UPPER_OBJECT(tree_node, struct NCDUdevCache_device, devices_tree_node);
|
||
|
ASSERT(!device->is_cleaned)
|
||
|
|
||
|
return device->devpath;
|
||
|
}
|