xaizek / vifm (License: GPLv2+) (since 2018-12-07)
Vifm is a file manager with curses interface, which provides Vi[m]-like environment for managing objects within file systems, extended with some useful ideas from mutt.
<root> / src / utils / fsdata.c (26208f5c9ab1342ac377d59d758553003dbb2f41) (9,294B) (mode 100644) [raw]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418
/* vifm
 * Copyright (C) 2011 xaizek.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
 */

/* The implementation is a tree (with links to leftmost child and right
 * sibling), which is traversed according to slash separated path.  Siblings are
 * sorted by name. */

#include "fsdata.h"
#include "private/fsdata.h"

#include <stddef.h> /* NULL size_t */
#include <stdlib.h> /* free() malloc() */
#include <string.h> /* memcpy() */

#include "../compat/fs_limits.h"
#include "../compat/os.h"
#include "str.h"

/* Special value for get_or_create_node()'s data_size argument to prevent it
 * from creating a node. */
#define NO_CREATE (size_t)-1

/* Tree node type. */
typedef struct node_t
{
	char *name;           /* Name of this node. */
	size_t name_len;      /* Length of the name. */
	int valid;            /* Whether data in this node is meaningful. */
	struct node_t *next;  /* Next sibling on this level. */
	struct node_t *child; /* Leftmost child of this node. */
	char data[];          /* Data associated with the node follows. */
}
node_t;

/* Tree head that holds its settings. */
struct fsdata_t
{
	node_t *root;             /* Root node data. */
	int prefix;               /* Whether we use last seen value on searches. */
	int resolve_paths;        /* Whether input paths should be resolved. */
	fsd_cleanup_func cleanup; /* Node data cleanup function. */
};

static void do_nothing(void *data);
static void nodes_free(node_t *node, fsd_cleanup_func cleanup);
static node_t * get_or_create_node(node_t *root, const char path[],
		size_t data_size, node_t **last, node_t **link);
static node_t * make_node(const char name[], size_t name_len, size_t data_size);
static int map_parents(node_t *root, const char path[],
		fsdata_visit_func visitor, void *arg);
static int resolve_path(const fsdata_t *fsd, const char path[],
		char real_path[]);
static int traverse_node(node_t *node, const node_t *parent,
		fsdata_traverser_func traverser, void *arg);

fsdata_t *
fsdata_create(int prefix, int resolve_paths)
{
	fsdata_t *const fsd = malloc(sizeof(*fsd));
	if(fsd == NULL)
	{
		return NULL;
	}

	fsd->root = NULL;
	fsd->prefix = prefix;
	fsd->resolve_paths = resolve_paths;
	fsd->cleanup = &do_nothing;
	return fsd;
}

/* Node cleanup stub, that does nothing. */
static void
do_nothing(void *data)
{
	(void)data;
}

void
fsdata_set_cleanup(fsdata_t *fsd, fsd_cleanup_func cleanup)
{
	fsd->cleanup = cleanup;
}

void
fsdata_free(fsdata_t *fsd)
{
	if(fsd != NULL)
	{
		nodes_free(fsd->root, fsd->cleanup);
		free(fsd);
	}
}

/* Recursively frees all the nodes and everything associated with them. */
static void
nodes_free(node_t *node, fsd_cleanup_func cleanup)
{
	if(node == NULL)
	{
		return;
	}

	if(node->valid)
	{
		cleanup(&node->data);
	}

	nodes_free(node->child, cleanup);
	nodes_free(node->next, cleanup);

	free(node->name);
	free(node);
}

int
fsdata_set(fsdata_t *fsd, const char path[], const void *data, size_t len)
{
	node_t *node;
	char real_path[PATH_MAX + 1];

	if(resolve_path(fsd, path, real_path) != 0)
	{
		return -1;
	}

	/* Create root node lazily, when we know data size. */
	if(fsd->root == NULL)
	{
		fsd->root = make_node("/", 1U, len);
		if(fsd->root == NULL)
		{
			return -1;
		}
	}

	node = get_or_create_node(fsd->root, real_path, len, NULL, &fsd->root);
	if(node == NULL)
	{
		return -1;
	}

	if(node->valid)
	{
		fsd->cleanup(&node->data);
	}

	node->valid = 1;
	memcpy(node->data, data, len);
	return 0;
}

int
fsdata_get(fsdata_t *fsd, const char path[], void *data, size_t len)
{
	node_t *last = NULL;
	node_t *node;
	const void *src;
	char real_path[PATH_MAX + 1];

	if(fsd->root == NULL)
	{
		return -1;
	}

	if(resolve_path(fsd, path, real_path) != 0)
	{
		return -1;
	}

	node = get_or_create_node(fsd->root, real_path, NO_CREATE,
			fsd->prefix ? &last : NULL, NULL);
	if((node == NULL || !node->valid) && last == NULL)
	{
		return -1;
	}

	src = (node != NULL && node->valid) ? node->data : last->data;
	memcpy(data, src, len);
	return 0;
}

/* Looks up a node by its path.  Inserts a node if it doesn't exist and
 * data_size is not equal to NO_CREATE.  If last is not NULL *last is assigned
 * closest valid parent node.  Optionally reallocates and updates *link.
 * Returns the node at the path or NULL on error. */
static node_t *
get_or_create_node(node_t *root, const char path[], size_t data_size,
		node_t **last, node_t **link)
{
	const char *end;
	size_t name_len;
	node_t *prev = NULL, *curr;
	node_t *new_node;

	path = skip_char(path, '/');
	if(*path == '\0')
	{
		if(link != NULL)
		{
			root = realloc(root, sizeof(*root) + data_size);
			if(root != NULL)
			{
				*link = root;
			}
		}
		return root;
	}

	end = until_first(path, '/');

	name_len = end - path;
	curr = root->child;
	while(curr != NULL)
	{
		int comp = strnoscmp(path, curr->name, name_len);
		if(comp == 0 && curr->name_len == name_len)
		{
			if(curr->valid && last != NULL)
			{
				*last = curr;
			}
			return get_or_create_node(curr, end, data_size, last,
					(data_size == NO_CREATE) ? NULL :
					(root->child == curr) ? &root->child : &prev->next);
		}
		else if(comp < 0)
		{
			break;
		}
		prev = curr;
		curr = curr->next;
	}

	if(data_size == NO_CREATE)
	{
		return NULL;
	}

	new_node = make_node(path, name_len, data_size);
	if(new_node == NULL)
	{
		return NULL;
	}

	new_node->next = curr;

	if(root->child == curr)
	{
		root->child = new_node;
	}
	else
	{
		prev->next = new_node;
	}
	/* No need to update anything here, because size is guaranteed to be the
	 * same. */
	return get_or_create_node(new_node, end, data_size, last, NULL);
}

/* Creates new node for the tree.  Returns the node or NULL on memory allocation
 * error. */
static node_t *
make_node(const char name[], size_t name_len, size_t data_size)
{
	node_t *new_node = malloc(sizeof(*new_node) + data_size);
	if(new_node == NULL)
	{
		return NULL;
	}

	new_node->name = malloc(name_len + 1U);
	if(new_node->name == NULL)
	{
		free(new_node);
		return NULL;
	}

	copy_str(new_node->name, name_len + 1U, name);
	new_node->name_len = name_len;
	new_node->valid = 0;
	new_node->child = NULL;
	new_node->next = NULL;

	return new_node;
}

int
fsdata_map_parents(fsdata_t *fsd, const char path[], fsdata_visit_func visitor,
		void *arg)
{
	char real_path[PATH_MAX + 1];
	if(resolve_path(fsd, path, real_path) != 0)
	{
		return 1;
	}

	return map_parents(fsd->root, real_path, visitor, arg);
}

/* Performs optional path resolution (configured at tree creation).  real_path
 * should be at least PATH_MAX chars in length.  Returns zero on success,
 * otherwise non-zero is returned. */
static int
resolve_path(const fsdata_t *fsd, const char path[], char real_path[])
{
	if(fsd->resolve_paths)
	{
		return (os_realpath(path, real_path) != real_path);
	}
	copy_str(real_path, PATH_MAX, path);
	return 0;
}

/* Invokes visitor once per valid parent node of specified path.  Returns zero
 * on success or non-zero if path wasn't found. */
static int
map_parents(node_t *root, const char path[], fsdata_visit_func visitor,
		void *arg)
{
	const char *end;
	size_t name_len;
	node_t *curr;

	path = skip_char(path, '/');
	if(*path == '\0')
	{
		return 0;
	}

	end = until_first(path, '/');

	name_len = end - path;
	curr = root->child;
	while(curr != NULL)
	{
		const int cmp = strnoscmp(path, curr->name, name_len);
		if(cmp == 0 && curr->name_len == name_len)
		{
			if(map_parents(curr, end, visitor, arg) == 0)
			{
				if(root->valid)
				{
					visitor(&root->data, arg);
				}
				return 0;
			}
			break;
		}
		else if(cmp < 0)
		{
			break;
		}
		curr = curr->next;
	}

	return 1;
}

int
fsdata_traverse(fsdata_t *fsd, fsdata_traverser_func traverser, void *arg)
{
	node_t *node;

	if(fsd->root == NULL)
	{
		return 0;
	}

	for(node = fsd->root->child; node != NULL; node = node->next)
	{
		if(traverse_node(node, NULL, traverser, arg) != 0)
		{
			return 1;
		}
	}
	return 0;
}

/* fsdata_traverse() helper which works with node_t type.  Return non-zero if
 * traversing was stopped prematurely, otherwise zero is returned. */
static int
traverse_node(node_t *node, const node_t *parent,
		fsdata_traverser_func traverser, void *arg)
{
	const void *const parent_data = (parent == NULL ? NULL : &parent->data);
	if(traverser(node->name, node->valid, parent_data, &node->data, arg) != 0)
	{
		return 1;
	}

	for(parent = node, node = node->child; node != NULL; node = node->next)
	{
		if(traverse_node(node, parent, traverser, arg) != 0)
		{
			return 1;
		}
	}
	return 0;
}

/* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
/* vim: set cinoptions+=t0 filetype=c : */
Hints

Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://code.reversed.top/user/xaizek/vifm

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@code.reversed.top/user/xaizek/vifm

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a pull request:
... clone the repository ...
... make some changes and some commits ...
git push origin master