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