baseline

yet another open-source distributed versioning control system
Log | Files | Refs

objdb-fs.c (24899B)


      1 /*
      2  * Copyright (c) 2014 Mohamed Aslan <maslan@sce.carleton.ca>
      3  *
      4  * Permission to use, copy, modify, and distribute this software for any
      5  * purpose with or without fee is hereby granted, provided that the above
      6  * copyright notice and this permission notice appear in all copies.
      7  *
      8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
      9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     15  */
     16 
     17 #include <sys/stat.h>	/* stat(3) */
     18 
     19 #include <stdio.h>	/* rename(2) */
     20 #include <stdlib.h>	/* malloc(2) */
     21 #include <string.h>	/* str*(2) , mem*(2) */
     22 #include <unistd.h>	/* access(2) */
     23 
     24 #include <sha2.h>	/* SHA256*() */
     25 
     26 #include <fts.h>        /* fts_*(3) */
     27 
     28 #include "defaults.h"
     29 #include "objects.h"
     30 #include "objdb.h"
     31 
     32 int objdb_baseline_get_ops(struct objdb_ops **);
     33 static char * get_objdb_dir(struct objdb_ctx *);
     34 static int objdb_bl_open(struct objdb_ctx **, const char *, const char *);
     35 static int objdb_bl_close(struct objdb_ctx *);
     36 static int objdb_bl_init(struct objdb_ctx *);
     37 static int objdb_bl_insert_file(struct objdb_ctx *, struct file *);
     38 static int objdb_bl_insert_dir(struct objdb_ctx *, struct dir *);
     39 static int objdb_bl_insert_commit(struct objdb_ctx *, struct commit *);
     40 static int objdb_bl_select_file(struct objdb_ctx *, const char *, struct file *);
     41 static int objdb_bl_select_dir(struct objdb_ctx *, const char *, struct dir *);
     42 static int objdb_bl_select_commit(struct objdb_ctx *, const char *, struct commit *);
     43 static int objdb_bl_remove(struct objdb_ctx *, const char *, const char *);
     44 static int objdb_bl_branch_create(struct objdb_ctx *, const char *);
     45 static int objdb_bl_branch_create_from(struct objdb_ctx *, const char *, const char *);
     46 static int objdb_bl_branch_if_exists(struct objdb_ctx *, const char *, int *);
     47 static int objdb_bl_branch_set_head(struct objdb_ctx *, const char *, const char *);
     48 static int objdb_bl_branch_get_head(struct objdb_ctx *, const char *, char **);
     49 static int objdb_bl_branch_ls(struct objdb_ctx *);
     50 
     51 
     52 static const struct objdb_ops baseline_objdb_ops = {
     53 	.name = "baseline",
     54 	.version = "1.0",
     55 	.init = objdb_bl_init,
     56 	.open = objdb_bl_open,
     57 	.close = objdb_bl_close,
     58 	.insert_file = objdb_bl_insert_file,
     59 	.insert_dir = objdb_bl_insert_dir,
     60 	.insert_commit = objdb_bl_insert_commit,
     61 	.select_file = objdb_bl_select_file,
     62 	.select_dir = objdb_bl_select_dir,
     63 	.select_commit = objdb_bl_select_commit,
     64 	.remove = objdb_bl_remove,
     65 	.branch_create = objdb_bl_branch_create,
     66 	.branch_create_from = objdb_bl_branch_create_from,
     67 	.branch_if_exists = objdb_bl_branch_if_exists,
     68 	.branch_set_head = objdb_bl_branch_set_head,
     69 	.branch_get_head = objdb_bl_branch_get_head,
     70 	.branch_ls = objdb_bl_branch_ls,
     71 	.fsck = NULL,
     72 	.compress = NULL,
     73 	.dedup = NULL
     74 };
     75 
     76 #define N_MAINDIRS	5
     77 static const char *main_dirs[] = {
     78 	"files",
     79 	"dirs",
     80 	"commits",
     81 	"branches",
     82 	"tags"
     83 };
     84 
     85 int
     86 objdb_baseline_get_ops(struct objdb_ops **ops)
     87 {
     88 	if (ops == NULL)
     89 		return EXIT_FAILURE;
     90 	*ops = (struct objdb_ops *)malloc(sizeof(struct objdb_ops));
     91 	memcpy(*ops, &baseline_objdb_ops, sizeof(struct objdb_ops));
     92 	return EXIT_SUCCESS;
     93 }
     94 
     95 /* FIXME */
     96 static char *
     97 get_objdb_dir(struct objdb_ctx *ctx)
     98 {
     99 	char *db_dir_name = NULL;
    100 	int len;
    101 	if (ctx == NULL)
    102 		return NULL;
    103 	/* malloc() + strlcpy() + strlcat() = asprintf() */
    104 	len = asprintf(&db_dir_name, "%s/%s", ctx->db_path, ctx->db_name);
    105 	return db_dir_name;
    106 }
    107 
    108 static int
    109 read_line(int fd, char *buf, size_t len)
    110 {
    111 	char ch, *ptr = buf;
    112 	int nbytes = 0;
    113 
    114 	while (read(fd, &ch, 1) > 0) {
    115 		if (nbytes++ == len - 1) {
    116 			/* TODO: use err() instead */
    117 			fprintf(stderr, "line too big\n");
    118 			return -1;
    119 		}
    120 		*ptr++ = (ch == '\n') ? '\0' : ch;
    121 		if (ch == '\n')
    122 			break;
    123 	}
    124 	return nbytes > 0 ? nbytes - 1 : 0;
    125 }
    126 
    127 static int
    128 is_hex(const char *str)
    129 {
    130 	if (str == NULL)
    131 		return 0;
    132 	do {
    133 		if (*str == '\0')
    134 			break;
    135 		if ((*str >= '0' && *str <= '9') || (*str >= 'a' && *str <= 'f'))
    136 			continue;
    137 		return 0;
    138 	} while (*str++);
    139 	return 1;
    140 }
    141 
    142 static int
    143 is_dec(const char *str)
    144 {
    145 	if (str == NULL)
    146 		return 0;
    147 	do {
    148 		if (*str == '\0')
    149 			break;
    150 		if (*str >= '0' && *str <= '9')
    151 			continue;
    152 		return 0;
    153 	} while (*str++);
    154 	return 1;
    155 }
    156 
    157 static int
    158 is_oct(const char *str)
    159 {
    160 	if (str == NULL)
    161 		return 0;
    162 	do {
    163 		if (*str == '\0')
    164 			break;
    165 		if (*str >= '0' && *str <= '7')
    166 			continue;
    167 		return 0;
    168 	} while (*str++);
    169 	return 1;
    170 }
    171 
    172 /*
    173  * converts struct commit to char*
    174  */
    175 char*
    176 commit_serialize(struct commit *comm)
    177 {
    178 	char *raw = NULL;
    179 
    180 	if (comm == NULL)
    181 		goto ret;
    182 	if (comm->n_parents == 0)
    183 		asprintf(&raw, "dir %s\nauthor %s <%s> %llu\ncommitter %s <%s> %llu\n%s\n",
    184 			comm->dir, comm->author.name, comm->author.email, comm->author.timestamp,
    185 			comm->committer.name, comm->committer.email, comm->committer.timestamp, comm->message);
    186 	else if (comm->n_parents == 1)
    187 		asprintf(&raw, "dir %s\nparent %s\nauthor %s <%s> %llu\ncommitter %s <%s> %llu\n%s\n",
    188 			comm->dir, comm->parents[0], comm->author.name, comm->author.email, comm->author.timestamp,
    189 			comm->committer.name, comm->committer.email, comm->committer.timestamp, comm->message);
    190 	/* TODO: support more than 1 parent */
    191 ret:
    192 	return raw;
    193 }
    194 
    195 static int
    196 commit_deserialize(int fd, struct commit *comm)
    197 {
    198 	char buf[128], *p1, *p2;
    199 	off_t end, pos;
    200 	size_t len;
    201 	ssize_t n;
    202 
    203 	/* 1. */
    204 	/* find the commit's dir */
    205 	if (read_line(fd, buf, sizeof(buf)) == -1)
    206 		return EXIT_FAILURE;
    207 	/* "dir " + obj id */
    208 	if (strlen(buf) != 4 + SHA256_DIGEST_LENGTH * 2)
    209 		goto parse_error;
    210 	/* check if line starts with "dir " */
    211 	if (strstr(buf, "dir ") != buf)
    212 		goto parse_error;
    213 	/* skip "dir " */
    214 	p1 = buf + 4;
    215 	if (!is_hex(p1) || strlen(p1) != SHA256_DIGEST_LENGTH * 2)
    216 		goto parse_error;
    217 	comm->dir = strdup(p1);
    218 
    219 	/* 2. */
    220 	/* find the commit's parent */
    221 	/* TODO: support more than parent */
    222 	if (read_line(fd, buf, sizeof(buf)) == -1)
    223 		return EXIT_FAILURE;
    224 	/* check if line starts with "parent " */
    225 	if (strstr(buf, "parent ") == buf) {
    226 		p1 = buf + 7;
    227 		if (!is_hex(p1) || strlen(p1) != SHA256_DIGEST_LENGTH * 2)
    228 			goto parse_error;
    229 		comm->n_parents = 1;
    230 		comm->parents[0] = strdup(p1);
    231 
    232 		/* read the next line */
    233 		if (read_line(fd, buf, sizeof(buf)) == -1)
    234 			return EXIT_FAILURE;
    235 	}
    236 	else {
    237 		comm->n_parents = 0;
    238 	}
    239 
    240 	/* 3. */
    241 	/* find the commit's author */
    242 	/* check if line starts with "author " */
    243 	if (strstr(buf, "author ") != buf)
    244 		goto parse_error;
    245 	/* skip "author " */
    246 	p1 = buf + 7;
    247 	/* find author's name */
    248 	if ((p2 = strstr(p1, " <")) == NULL)
    249 		goto parse_error;
    250 	*p2 = '\0';
    251 	comm->author.name = strdup(p1);
    252 	/* find author's email */
    253 	p1 = ++p2;
    254 	if (*p1 != '<')
    255 		goto parse_error;
    256 	p1++;
    257 	if ((p2 = strchr(p1, '>')) == NULL)
    258 		goto parse_error;
    259 	*p2 = '\0';
    260 	comm->author.email = strdup(p1);
    261 	/* find author's timestamp */
    262 	p1 = ++p2;
    263 	if (!is_dec(++p1))
    264 		goto parse_error;
    265 	/* OpenBSD time_t is now 64-bit */
    266 	/* FIXME: other POSIX systems still use 32-bit time_t */
    267 	comm->author.timestamp = atoll(p1);
    268 
    269 	/* 4. */
    270 	/* find the commit's committer */
    271 	if (read_line(fd, buf, sizeof(buf)) == -1)
    272 		return EXIT_FAILURE;
    273 	/* check if line starts with "committer " */
    274 	if (strstr(buf, "committer ") != buf)
    275 		goto parse_error;
    276 	/* skip "committer " */
    277 	p1 = buf + 10;
    278 	/* find committer's name */
    279 	if ((p2 = strstr(p1, " <")) == NULL)
    280 		goto parse_error;
    281 	*p2 = '\0';
    282 	comm->committer.name = strdup(p1);
    283 	/* find committer's email */
    284 	p1 = ++p2;
    285 	if (*p1 != '<')
    286 		goto parse_error;
    287 	p1++;
    288 	if ((p2 = strchr(p1, '>')) == NULL)
    289 		goto parse_error;
    290 	*p2 = '\0';
    291 	comm->committer.email = strdup(p1);
    292 	/* find committer's timestamp */
    293 	p1 = ++p2;
    294 	if (!is_dec(++p1))
    295 		goto parse_error;
    296 	/* OpenBSD time_t is now 64-bit */
    297 	/* FIXME: other POSIX systems still use 32-bit time_t */
    298 	comm->committer.timestamp = atoll(p1);
    299 
    300 	/* 5. */
    301 	/* find the commit's message */
    302 	pos = lseek(fd, 0, SEEK_CUR);
    303 	end = lseek(fd, 0, SEEK_END);
    304 	/* even if off_t is 64-bits, we would never be able to support messages of size more than 32-bits */
    305 	len = (size_t)(end - pos);
    306 	lseek(fd, pos, SEEK_SET);
    307 	/* for now the max. size of a message is 10 MBytes */
    308 	if (len < 1048576) {
    309 		comm->message = (char *)malloc(len);
    310 		if ((n = read(fd, comm->message, len)) == -1) {
    311 			fprintf(stderr, "error reading file.\n");
    312 			return EXIT_FAILURE;
    313 		}
    314 		comm->message[n - 1] = '\0';
    315 	}
    316 	else {
    317 		goto bigmsg_error;
    318 	}
    319 
    320 	return EXIT_SUCCESS;
    321 
    322 parse_error:
    323 	fprintf(stderr, "error parsing file, not a valid commit.\n");
    324 	return EXIT_FAILURE;
    325 bigmsg_error:
    326 	fprintf(stderr, "error parsing file, commit message too big.\n");
    327 	return EXIT_FAILURE;
    328 }
    329 
    330 
    331 /*
    332  * converts struct dir to char*
    333  */
    334 char*
    335 dir_serialize(struct dir *dir)
    336 {
    337 	char *entry = NULL, *raw = NULL, *ptr, ch = ' ';
    338 	size_t size = 1, newsize;
    339 	struct dirent *it;
    340 
    341 	if (dir == NULL)
    342 		goto ret;
    343 	raw = (char *)malloc(sizeof(char));
    344 	*raw = '\0';
    345 	if (dir->children == NULL)
    346 		goto ret;
    347 	for (it = dir->children ; it != NULL ; it = it->next) {
    348 		if (it->type == T_FILE)
    349 			ch = 'F';
    350 		else if (it->type == T_DIR)
    351 			ch = 'D';
    352 		/* assuming asprintf() uses realloc() */
    353 		/* free(entry); */
    354 		asprintf(&entry, "%06o %s %s\n", it->mode, it->id, it->name);
    355 		/* allocation failed, need to do something! */
    356 		if (entry == NULL)
    357 			return NULL;
    358 		newsize = size + strlen(entry);
    359 		if ((ptr = realloc(raw, newsize)) == NULL) {
    360 			/* allocation failed, need to do something! */
    361 			return NULL;
    362 		}
    363 		raw = ptr;
    364 		size = newsize;
    365 		strlcat(raw, entry, size);
    366 	}
    367 	free(entry);
    368 ret:
    369 	return raw;
    370 }
    371 
    372 static int
    373 dir_deserialize(int fd, struct dir *d)
    374 {
    375 	char buf[512], *id, *name, *p1, *p2;
    376 	int n;
    377 	mode_t mode;
    378 	struct dirent *head = NULL, *tail = NULL, *q = NULL;
    379 
    380 	while (1) {
    381 		if ((n = read_line(fd, buf, sizeof(buf))) == -1)
    382 			printf("EXIT\n");
    383 		if (n == 0)
    384 			break;
    385 		/* mode + obj id + at least 1 char name */
    386 		if (n < 9 + SHA256_DIGEST_LENGTH * 2)
    387 			goto parse_error;
    388 
    389 		/* 1. mode */
    390 		p1 = buf;
    391 		if ((p2 = strchr(p1, ' ')) == NULL)
    392 			goto parse_error;
    393 		*p2 = '\0';
    394 		if (!is_oct(p1) || strlen(p1) != 6)
    395 			goto parse_error;
    396 		/* TODO: find a safer alternative */
    397 		sscanf(p1, "%6o", &mode);
    398 
    399 		/* 2. id */
    400 		p1 = ++p2;
    401 		if ((p2 = strchr(p1, ' ')) == NULL)
    402 			goto parse_error;
    403 		*p2 = '\0';
    404 		if (!is_hex(p1) || strlen(p1) != SHA256_DIGEST_LENGTH * 2)
    405 			goto parse_error;
    406 		id = p1;
    407 
    408 		/* 3. name */
    409 		p1 = ++p2;
    410 		if (strlen(p1) == 0)
    411 			goto parse_error;
    412 		name = p1;
    413 
    414 		q = (struct dirent *)malloc(sizeof(struct dirent));
    415 		q->mode = mode;
    416 		q->id = strdup(id);
    417 		q->name = strdup(name);
    418 		q->next = NULL;
    419 		if (head == NULL) {
    420 			head = q;
    421 			tail = q;
    422 		}
    423 		else {
    424 			tail->next = q;
    425 			tail = tail->next;
    426 		}
    427 	}
    428 
    429 	d->children = head;
    430 	return EXIT_SUCCESS;
    431 
    432 parse_error:
    433 	fprintf(stderr, "error parsing file, not a valid directory.\n");
    434 	return EXIT_FAILURE;
    435 }
    436 
    437 static char *
    438 file_gen_id(struct file *obj)
    439 {
    440 	char **objid = NULL, *ptr;
    441 	char buf[4096];
    442 	int i, n;
    443         off_t offset;
    444         u_int8_t digest[SHA256_DIGEST_LENGTH];
    445         SHA2_CTX hash_ctx;
    446 
    447 	SHA256Init(&hash_ctx);
    448 	/* TODO: locking */
    449 	if (((struct file *)obj)->loc == LOC_FS) {
    450 		/* save file offset */
    451 		offset = lseek(obj->fd, 0, SEEK_CUR);
    452 		do {
    453 			n = read(obj->fd, buf, sizeof(buf));
    454 			if (n == -1)
    455 				return NULL;
    456 			SHA256Update(&hash_ctx, buf, n);
    457 			if (n <= sizeof(buf))
    458 				break;
    459 		} while (1);
    460 		/* restore file offset */
    461 		lseek(obj->fd, offset, SEEK_SET);
    462 	}
    463 	else {
    464 		SHA256Update(&hash_ctx, obj->buffer, strlen(obj->buffer));
    465 	}
    466 	objid = &(obj->id);
    467 	SHA256Final(digest, &hash_ctx);
    468 	*objid = (char *)malloc(SHA256_DIGEST_LENGTH * 2 + 1);
    469 	for (i=0, ptr=*objid ; i<SHA256_DIGEST_LENGTH ; i++, ptr+=2) {
    470 		snprintf(ptr, 3, "%02x", digest[i]);
    471 	}
    472 	return *objid;
    473 }
    474 
    475 static char *
    476 commit_gen_id_and_serialize(struct commit *obj, char **raw)
    477 {
    478 	char **objid = NULL, *ptr, *serialized = NULL;
    479 	int i;
    480 	u_int8_t digest[SHA256_DIGEST_LENGTH];
    481 	SHA2_CTX hash_ctx;
    482 
    483 	SHA256Init(&hash_ctx);
    484 	serialized = commit_serialize(obj);
    485 	SHA256Update(&hash_ctx, serialized, strlen(serialized));
    486 	objid = &(obj->id);
    487 	SHA256Final(digest, &hash_ctx);
    488 	*objid = (char *)malloc(SHA256_DIGEST_LENGTH * 2 + 1);
    489 	for (i=0, ptr=*objid ; i<SHA256_DIGEST_LENGTH ; i++, ptr+=2) {
    490 		snprintf(ptr, 3, "%02x", digest[i]);
    491 	}
    492 	*raw = serialized;
    493 	return *objid;
    494 }
    495 
    496 static char *
    497 commit_gen_id(struct commit *obj)
    498 {
    499 	char *raw;
    500 	char *id = commit_gen_id_and_serialize(obj, &raw);
    501 	free(raw);
    502 	return id;
    503 }
    504 
    505 static char *
    506 dir_gen_id_and_serialize(struct dir *obj, char **raw)
    507 {
    508 	char **objid = NULL, *ptr, *serialized = NULL;
    509 	int i;
    510 	u_int8_t digest[SHA256_DIGEST_LENGTH];
    511 	SHA2_CTX hash_ctx;
    512 
    513 	SHA256Init(&hash_ctx);
    514 	serialized = dir_serialize(obj);
    515 	SHA256Update(&hash_ctx, serialized, strlen(serialized));
    516 	objid = &(obj->id);
    517 	SHA256Final(digest, &hash_ctx);
    518 	*objid = (char *)malloc(SHA256_DIGEST_LENGTH * 2 + 1);
    519 	for (i=0, ptr=*objid ; i<SHA256_DIGEST_LENGTH ; i++, ptr+=2) {
    520 		snprintf(ptr, 3, "%02x", digest[i]);
    521 	}
    522 	*raw = serialized;
    523 	return *objid;
    524 }
    525 
    526 static char *
    527 dir_gen_id(struct dir *obj)
    528 {
    529 	char *raw;
    530 	char *id = dir_gen_id_and_serialize(obj, &raw);
    531 	free(raw);
    532 	return id;
    533 }
    534 
    535 static int
    536 objdb_bl_open(struct objdb_ctx **ctx, const char *db_name, const char *db_path)
    537 {
    538 	if (ctx == NULL)
    539 		return EXIT_FAILURE;
    540 	*ctx = (struct objdb_ctx *)malloc(sizeof(struct objdb_ctx));
    541 	asprintf(&((*ctx)->db_name), "%s", db_name);
    542 	asprintf(&((*ctx)->db_path), "%s", db_path);
    543 	asprintf(&((*ctx)->db_version), "1.0");
    544 	return EXIT_SUCCESS;
    545 }
    546 
    547 static int
    548 objdb_bl_close(struct objdb_ctx *ctx)
    549 {
    550 	if (ctx == NULL)
    551 		return EXIT_FAILURE;
    552 	free(ctx->db_name);
    553 	free(ctx->db_path);
    554 	free(ctx->db_version);
    555 	free(ctx);
    556 	return EXIT_SUCCESS;
    557 }
    558 
    559 static int
    560 objdb_bl_init(struct objdb_ctx *ctx)
    561 {
    562 	char *db_dir_name = NULL, *maindir = NULL;
    563 	int i, retval = EXIT_FAILURE;
    564 	struct stat s;
    565 	/* check if path exists */
    566 	if (stat(ctx->db_path, &s) == -1)
    567 		goto ret;
    568 	db_dir_name = get_objdb_dir(ctx);
    569 	if (mkdir(db_dir_name, S_IRUSR | S_IWUSR | S_IXUSR) != 0)
    570 		goto ret;
    571 	/* try to create main sub-dirs as well */
    572 	for (i=0 ; i<N_MAINDIRS ; i++) {
    573 		asprintf(&maindir, "%s/%s", db_dir_name, main_dirs[i]);
    574 		if (mkdir(maindir, S_IRUSR | S_IWUSR | S_IXUSR) != 0) {
    575 			free(maindir);
    576 			goto ret;
    577 		}
    578 		free(maindir);
    579 	}
    580 	retval = EXIT_SUCCESS;
    581 ret:
    582 	if (db_dir_name != NULL)
    583 		free(db_dir_name);
    584 	return retval;
    585 }
    586 
    587 static int
    588 objdb_bl_insert_file(struct objdb_ctx *ctx, struct file *file)
    589 {
    590 	char *db_dir_name, *full_path, *obj_file_name, *obj_hash, *tmp_file_name;
    591 	char *buf[4096];
    592 	int n, retval, tmpfd;
    593 	off_t offset;
    594 
    595 	db_dir_name = get_objdb_dir(ctx);
    596 	if (db_dir_name == NULL) {
    597 		retval = EXIT_FAILURE;
    598 		goto ret;
    599 	}
    600 	asprintf(&full_path, "%s/%s", db_dir_name, "files");
    601 	obj_hash = file_gen_id(file);
    602 	/* create a temp file */
    603 	asprintf(&tmp_file_name, "%s/tmp.XXXXXX", full_path);
    604         if ((tmpfd = mkstemp(tmp_file_name)) == -1) {
    605 		retval =  EXIT_FAILURE;
    606 		goto ret;
    607 	}
    608 	if (file->loc == LOC_FS) {
    609 		/* save file offset */
    610 		offset = lseek(file->fd, 0, SEEK_CUR);
    611 		/* copy file content */
    612 		while((n = read(file->fd, buf, sizeof(buf))) > 0)
    613 			write(tmpfd, buf, n);
    614 		/* restore file offset */
    615 		lseek(file->fd, offset, SEEK_SET);
    616 	}
    617 	else if (file->loc == LOC_MEM) {
    618 		write(tmpfd, file->buffer, strlen(file->buffer));
    619 	}
    620 	close(tmpfd);
    621 	asprintf(&obj_file_name, "%s/%s", full_path, obj_hash);
    622 	/* check if file is already there */
    623 	if (access(obj_file_name, F_OK) != -1) {
    624 		unlink(tmp_file_name);
    625 		goto success;
    626 	}
    627 	if (rename(tmp_file_name, obj_file_name)) {
    628 		retval = EXIT_FAILURE;
    629 		goto ret;
    630 	}
    631 
    632 success:
    633 	retval = EXIT_SUCCESS;
    634 ret:
    635 	/* assuming free(NULL) is safe */
    636 	free(db_dir_name);
    637 	free(full_path);
    638 	free(obj_file_name);
    639 	free(tmp_file_name);
    640 	return retval;
    641 }
    642 
    643 static int
    644 objdb_bl_insert_dir(struct objdb_ctx *ctx, struct dir *dir)
    645 {
    646 	char *db_dir_name, *full_path, *obj_file_name, *obj_hash, *tmp_file_name;
    647 	char *data;
    648 	int retval, tmpfd;
    649 	FILE *tmpfp;
    650 
    651 	db_dir_name = get_objdb_dir(ctx);
    652 	if (db_dir_name == NULL) {
    653 		retval = EXIT_FAILURE;
    654 		goto ret;
    655 	}
    656 	asprintf(&full_path, "%s/%s", db_dir_name, "dirs");
    657 	obj_hash = dir_gen_id_and_serialize(dir, &data);
    658 
    659 	/* create a temp file */
    660 	asprintf(&tmp_file_name, "%s/tmp.XXXXXX", full_path);
    661         if ((tmpfd = mkstemp(tmp_file_name)) == -1) {
    662                 retval =  EXIT_FAILURE;
    663 		goto ret;
    664         }
    665 	if ((tmpfp = fdopen(tmpfd, "w")) == NULL) {
    666                 unlink(tmp_file_name);
    667                 close(tmpfd);
    668                 retval = EXIT_FAILURE;
    669 		goto ret;
    670         }
    671 	fwrite(data, sizeof(u_int8_t), strlen(data), tmpfp);
    672 	fclose(tmpfp);
    673 	asprintf(&obj_file_name, "%s/%s", full_path, obj_hash);
    674 	/* check if file is already there */
    675 	if (access(obj_file_name, F_OK) != -1) {
    676 		unlink(tmp_file_name);
    677 		goto success;
    678 	}
    679 	if (rename(tmp_file_name, obj_file_name)) {
    680 		retval = EXIT_FAILURE;
    681 		goto ret;
    682 	}
    683 
    684 success:
    685 	retval = EXIT_SUCCESS;
    686 ret:
    687 	/* assuming free(NULL) is safe */
    688 	free(db_dir_name);
    689 	free(full_path);
    690 	free(obj_file_name);
    691 	free(tmp_file_name);
    692 	return retval;
    693 }
    694 
    695 static int
    696 objdb_bl_insert_commit(struct objdb_ctx *ctx, struct commit *comm)
    697 {
    698 	char *db_dir_name, *full_path, *obj_file_name, *obj_hash, *tmp_file_name;
    699 	char *data;
    700 	int retval, tmpfd;
    701 	FILE *tmpfp;
    702 
    703 	db_dir_name = get_objdb_dir(ctx);
    704 	if (db_dir_name == NULL) {
    705 		retval = EXIT_FAILURE;
    706 		goto ret;
    707 	}
    708 	asprintf(&full_path, "%s/%s", db_dir_name, "commits");
    709 	obj_hash = commit_gen_id_and_serialize(comm, &data);
    710 
    711 	/* create a temp file */
    712 	asprintf(&tmp_file_name, "%s/tmp.XXXXXX", full_path);
    713         if ((tmpfd = mkstemp(tmp_file_name)) == -1) {
    714                 retval =  EXIT_FAILURE;
    715 		goto ret;
    716         }
    717 	if ((tmpfp = fdopen(tmpfd, "w")) == NULL) {
    718                 unlink(tmp_file_name);
    719                 close(tmpfd);
    720                 retval = EXIT_FAILURE;
    721 		goto ret;
    722         }
    723 	fwrite(data, sizeof(u_int8_t), strlen(data), tmpfp);
    724 	fclose(tmpfp);
    725 	asprintf(&obj_file_name, "%s/%s", full_path, obj_hash);
    726 	/* check if file is already there */
    727 	if (access(obj_file_name, F_OK) != -1) {
    728 		unlink(tmp_file_name);
    729 		goto success;
    730 	}
    731 	if (rename(tmp_file_name, obj_file_name)) {
    732 		retval = EXIT_FAILURE;
    733 		goto ret;
    734 	}
    735 
    736 success:
    737 	retval = EXIT_SUCCESS;
    738 ret:
    739 	/* assuming free(NULL) is safe */
    740 	free(db_dir_name);
    741 	free(full_path);
    742 	free(obj_file_name);
    743 	free(tmp_file_name);
    744 	return retval;
    745 }
    746 
    747 static int
    748 objdb_bl_remove(struct objdb_ctx *ctx, const char *group_name, const char *obj_name)
    749 {
    750 	return EXIT_SUCCESS;
    751 }
    752 
    753 static int
    754 objdb_bl_branch_create(struct objdb_ctx *ctx, const char *branch_name)
    755 {
    756 	char *db_dir_name = NULL;
    757 	char *branch_path = NULL;
    758 	char *branch_head = NULL;
    759 	int retval = EXIT_SUCCESS;
    760 	FILE *fp;
    761 
    762 	if (branch_name == NULL)
    763 		return EXIT_FAILURE;
    764 	/* FIXME: validate branch name & check if already exists */
    765 	db_dir_name = get_objdb_dir(ctx);
    766 	asprintf(&branch_path, "%s/branches/%s", db_dir_name, branch_name);
    767 #ifdef DEBUG
    768 	printf("creating branch %s\n", branch_path);
    769 #endif
    770 	if (mkdir(branch_path, S_IRUSR | S_IWUSR | S_IXUSR) == -1) {
    771 		retval = EXIT_FAILURE;
    772 		goto ret;
    773 	}
    774 	/* create head */
    775 	asprintf(&branch_head, "%s/head", branch_path);
    776 	if ((fp = fopen(branch_head, "w")) == NULL) {
    777 		retval = EXIT_FAILURE;
    778 		goto ret;
    779 	}
    780 	fclose(fp);
    781 ret:	
    782 	free(db_dir_name);
    783 	free(branch_path);
    784 	free(branch_head);
    785 	return retval;
    786 }
    787 
    788 static int
    789 objdb_bl_branch_create_from(struct objdb_ctx *ctx, const char *new_branch, const char *orig_branch)
    790 {
    791 	char *orig_head = NULL;
    792 	int retval = EXIT_FAILURE;
    793 
    794 	if (new_branch == NULL || orig_branch == NULL)
    795 		return EXIT_FAILURE;
    796 	/* get the original branch's name */
    797 	if (objdb_bl_branch_get_head(ctx, orig_branch, &orig_head) == EXIT_FAILURE)
    798 		goto ret;
    799 	/* create the new branch */
    800 	if (objdb_bl_branch_create(ctx, new_branch) == EXIT_FAILURE)
    801 		goto ret;
    802 	/* set the new branch's head */
    803 	if (objdb_bl_branch_set_head(ctx, new_branch, orig_head) == EXIT_FAILURE)
    804 		goto ret;
    805 	retval = EXIT_SUCCESS;
    806 ret:
    807 	free(orig_head);
    808 	return retval;
    809 }
    810 
    811 static int
    812 objdb_bl_branch_if_exists(struct objdb_ctx *ctx, const char *branch_name, int *exist)
    813 {
    814 	char *db_dir_name = NULL;
    815 	char *branch_path = NULL;
    816 	int retval;
    817 	struct stat s;
    818 
    819 	if (branch_name == NULL)
    820 		return EXIT_FAILURE;
    821 	*exist = 0;
    822 	db_dir_name = get_objdb_dir(ctx);
    823 	asprintf(&branch_path, "%s/branches/%s", db_dir_name, branch_name);
    824 	/* TODO: check if path is writable */
    825 	if (stat(branch_path, &s) == 0) {
    826 		if (S_ISDIR(s.st_mode)) {
    827 			*exist = 1;
    828 			retval = EXIT_SUCCESS;
    829 		}
    830 		else {
    831 			*exist = 0;
    832 			retval = EXIT_FAILURE;
    833 		}
    834 	}
    835 	else {
    836 		*exist = 0;
    837 		retval = EXIT_SUCCESS;
    838 	}
    839 	free(db_dir_name);
    840 	free(branch_path);
    841 	return retval;
    842 }
    843 
    844 static int
    845 objdb_bl_branch_set_head(struct objdb_ctx *ctx, const char *branch_name, const char *head_objid)
    846 {
    847 	char *db_dir_name = NULL;
    848 	char *branch_head = NULL;
    849 	int retval = EXIT_SUCCESS;
    850 	FILE *head_fp;
    851 	db_dir_name = get_objdb_dir(ctx);
    852 	asprintf(&branch_head, "%s/branches/%s/head", db_dir_name, branch_name);
    853 	if ((head_fp = fopen(branch_head, "w")) == NULL) {
    854 		retval = EXIT_FAILURE;
    855 		goto ret;
    856 	}
    857 	fprintf(head_fp, "%s", head_objid);
    858 	fclose(head_fp);
    859 ret:
    860 	free(db_dir_name);
    861 	free(branch_head);
    862 	return retval;
    863 }
    864 
    865 static int
    866 objdb_bl_branch_get_head(struct objdb_ctx *ctx, const char *branch_name, char **head_objid)
    867 {
    868 	char *db_dir_name = NULL;
    869 	char *branch_head = NULL;
    870 	int retval = EXIT_SUCCESS;
    871 	size_t size = 0;
    872 	FILE *head_fp;
    873 
    874 	if (ctx == NULL || branch_name == NULL || head_objid == NULL)
    875 		return EXIT_FAILURE;
    876 	db_dir_name = get_objdb_dir(ctx);
    877 	asprintf(&branch_head, "%s/branches/%s/head", db_dir_name, branch_name);
    878 	if ((head_fp = fopen(branch_head, "r")) == NULL) {
    879 		retval = EXIT_FAILURE;
    880 		goto ret;
    881 	}
    882 	*head_objid = NULL;
    883 	if (getline(head_objid, &size, head_fp) == -1) {
    884 		/* assuming the head file is empty */
    885 		/* FIXME: check for errors */
    886 		*head_objid = NULL;
    887 	}
    888 	fclose(head_fp);
    889 ret:
    890 	free(db_dir_name);
    891 	free(branch_head);
    892 	return retval;
    893 }
    894 
    895 static int
    896 objdb_bl_branch_ls(struct objdb_ctx *ctx)
    897 {
    898 	char *branches_path, *db_dir_name;
    899 	char *paths[2];
    900 	FTS *dir;
    901 	FTSENT *entry;
    902 
    903 	db_dir_name = get_objdb_dir(ctx);
    904 	asprintf(&branches_path, "%s/branches", db_dir_name);
    905 
    906 	paths[0] = (char *)branches_path;
    907 	paths[1] = NULL;
    908 	if ((dir = fts_open(paths, FTS_NOCHDIR, 0)) == NULL)
    909 		return EXIT_FAILURE;
    910 	while ((entry = fts_read(dir)) != NULL) {
    911 		if (entry->fts_level == FTS_ROOTLEVEL)
    912 			continue;
    913 		if (entry->fts_info & FTS_D) {
    914 			printf("* %s\n", entry->fts_name);
    915 		}
    916 		else {
    917 			if (entry->fts_level >= 1)
    918 				fts_set(dir, entry, FTS_SKIP);
    919 		}
    920 	}
    921 	fts_close(dir);
    922 	free(db_dir_name);
    923 	free(branches_path);
    924 	return EXIT_SUCCESS;
    925 }
    926 
    927 static int
    928 objdb_bl_select_commit(struct objdb_ctx *ctx, const char *objid, struct commit *comm)
    929 {
    930 	char *db_dir_name, *full_path;
    931 	int fd, retval;
    932 
    933 	db_dir_name = get_objdb_dir(ctx);
    934 	if (db_dir_name == NULL) {
    935 		retval = EXIT_FAILURE;
    936 		goto ret;
    937 	}
    938 	asprintf(&full_path, "%s/%s/%s", db_dir_name, "commits", objid);
    939 	/* check if file is already there */
    940 	if (access(full_path, F_OK) == -1) {
    941 		retval = EXIT_FAILURE;
    942 		goto ret;
    943 	}
    944 
    945 	if ((fd = open(full_path, O_RDONLY)) == -1) {
    946 		retval = EXIT_FAILURE;
    947 		goto ret;
    948 	}
    949 
    950 	comm->id = strdup(objid);
    951 	commit_deserialize(fd, comm);
    952 
    953 	close(fd);
    954 
    955 success:
    956 	retval = EXIT_SUCCESS;
    957 ret:
    958 	free(db_dir_name);
    959 	free(full_path);
    960 	return retval;
    961 }
    962 
    963 static int
    964 objdb_bl_select_dir(struct objdb_ctx *ctx, const char *objid, struct dir *d)
    965 {
    966 	char *db_dir_name, *full_path;
    967 	int fd, retval;
    968 
    969 	db_dir_name = get_objdb_dir(ctx);
    970 	if (db_dir_name == NULL) {
    971 		retval = EXIT_FAILURE;
    972 		goto ret;
    973 	}
    974 	asprintf(&full_path, "%s/%s/%s", db_dir_name, "dirs", objid);
    975 	/* check if file is already there */
    976 	if (access(full_path, F_OK) == -1) {
    977 		retval = EXIT_FAILURE;
    978 		goto ret;
    979 	}
    980 
    981 	if ((fd = open(full_path, O_RDONLY)) == -1) {
    982 		retval = EXIT_FAILURE;
    983 		goto ret;
    984 	}
    985 
    986 	d->id = strdup(objid);
    987 	dir_deserialize(fd, d);
    988 
    989 	close(fd);
    990 
    991 success:
    992 	retval = EXIT_SUCCESS;
    993 ret:
    994 	free(db_dir_name);
    995 	free(full_path);
    996 	return retval;
    997 }
    998 
    999 static int
   1000 objdb_bl_select_file(struct objdb_ctx *ctx, const char *objid, struct file *f)
   1001 {
   1002 	char *db_dir_name, *full_path;
   1003 	int fd, retval;
   1004 
   1005 	db_dir_name = get_objdb_dir(ctx);
   1006 	if (db_dir_name == NULL) {
   1007 		retval = EXIT_FAILURE;
   1008 		goto ret;
   1009 	}
   1010 	asprintf(&full_path, "%s/%s/%s", db_dir_name, "files", objid);
   1011 	/* check if file is already there */
   1012 	if (access(full_path, F_OK) == -1) {
   1013 		retval = EXIT_FAILURE;
   1014 		goto ret;
   1015 	}
   1016 
   1017 	if ((fd = open(full_path, O_RDONLY)) == -1) {
   1018 		retval = EXIT_FAILURE;
   1019 		goto ret;
   1020 	}
   1021 
   1022 	f->id = strdup(objid);
   1023 	f->loc = LOC_FS;
   1024 	f->fd = fd;
   1025 
   1026 	/* the caller should close the file descriptor */
   1027 
   1028 success:
   1029 	retval = EXIT_SUCCESS;
   1030 ret:
   1031 	free(db_dir_name);
   1032 	free(full_path);
   1033 	return retval;
   1034 }
   1035