dircache-simple.c (16328B)
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> 20 #include <stdlib.h> /* malloc(2) */ 21 #include <string.h> /* str*(2), mem*(2) */ 22 #include <unistd.h> /* close(2), rmdir(2), unlink(2) */ 23 24 #include <fcntl.h> /* O_* macros */ 25 #include <fts.h> /* fts_*(3) */ 26 27 #include "defaults.h" 28 #include "config.h" 29 #include "objdb.h" 30 #include "dircache.h" 31 #include "objects.h" 32 #include "helper.h" 33 34 int dircache_simple_get_ops(struct dircache_ops **); 35 static int simple_open(struct dircache_ctx **, struct objdb_ctx *, struct objdb_ops *, const char *, const char *); 36 static int simple_close(struct dircache_ctx *); 37 static int simple_init(struct dircache_ctx *); 38 static int simple_insert(struct dircache_ctx *, const char *); 39 static int simple_commit(struct dircache_ctx *, const char *); 40 static int simple_branch_get(struct dircache_ctx *, char **); 41 static int simple_branch_set(struct dircache_ctx *, const char *); 42 static int simple_workdir_get(struct dircache_ctx *, char **); 43 static int simple_workdir_set(struct dircache_ctx *, const char *); 44 45 static struct dircache_ops simple_ops = { 46 .name = "simple", 47 .version = "1.0", 48 .open = simple_open, 49 .close = simple_close, 50 .init = simple_init, 51 .insert = simple_insert, 52 .remove = NULL, 53 .commit = simple_commit, 54 .branch_get = simple_branch_get, 55 .branch_set = simple_branch_set, 56 .workdir_get = simple_workdir_get, 57 .workdir_set = simple_workdir_set, 58 .fsck = NULL, 59 .compress = NULL, 60 .dedup = NULL 61 }; 62 63 int 64 dircache_simple_get_ops(struct dircache_ops **ops) 65 { 66 if (ops == NULL) 67 return EXIT_FAILURE; 68 *ops = (struct dircache_ops *)malloc(sizeof(struct dircache_ops)); 69 memcpy(*ops, &simple_ops, sizeof(struct dircache_ops)); 70 return EXIT_SUCCESS; 71 } 72 73 /* FIXME */ 74 static char * 75 get_dircache_path(struct dircache_ctx *ctx) 76 { 77 char *path = NULL; 78 79 if (ctx == NULL) 80 return NULL; 81 asprintf(&path, "%s/%s", ctx->repo_baselinepath, BASELINE_DIRCACHE); 82 return path; 83 } 84 85 static const char * 86 strmismatch(const char *first, const char *second) 87 { 88 int i; 89 size_t minlen; 90 91 minlen = strlen(first) < strlen(second) ? strlen(first) : strlen(second); 92 for (i=0 ; i<minlen ; i++) { 93 if (first[i] == second[i]) 94 continue; 95 break; 96 } 97 #ifdef DEUG 98 printf("[DEBUG] mismatch at i = %d\n", i); 99 #endif 100 return &first[i]; 101 } 102 103 static const char* 104 dir_diff(const char *first, const char *second) 105 { 106 const char *mis = strmismatch(first, second); 107 if (*mis == '/') 108 mis++; 109 return mis; 110 } 111 112 static int 113 mkdirp(const char *parent, const char *dir) 114 { 115 char *o, *start, *end; 116 char *path = NULL; 117 int retval; 118 struct stat s; 119 120 o = start = end = strdup(dir); 121 if (*o == '/') { 122 start++; 123 end++; 124 } 125 while (1) { 126 if (*end == '\0') 127 break; 128 if (*end == '/') { 129 *end = '\0'; 130 /* OpenBSD's asprintf(3) uses realloc(), hence no need to free */ 131 asprintf(&path, "%s/%s", parent, start); 132 #ifdef DEBUG 133 printf("[DEBUG] create %s\n", path); 134 #endif 135 if (stat(path, &s) == 0) { 136 if (!S_ISDIR(s.st_mode)) { 137 retval = EXIT_FAILURE; 138 goto ret; 139 } 140 } 141 else { 142 if (mkdir(path, S_IRUSR | S_IWUSR | S_IXUSR) == -1) 143 return EXIT_FAILURE; 144 } 145 *end = '/'; 146 } 147 end++; 148 } 149 ret: 150 free(path); 151 free(o); 152 return EXIT_SUCCESS; 153 } 154 155 static int 156 simple_open(struct dircache_ctx **dc_ctx, struct objdb_ctx *db_ctx, struct objdb_ops *db_ops, const char *rootpath, const char *baselinepath) 157 { 158 if (dc_ctx == NULL || db_ctx == NULL) 159 return EXIT_FAILURE; 160 *dc_ctx = (struct dircache_ctx *)malloc(sizeof(struct dircache_ctx)); 161 (*dc_ctx)->db_ctx = db_ctx; 162 (*dc_ctx)->db_ops = db_ops; 163 (*dc_ctx)->repo_rootpath = strdup(rootpath); 164 (*dc_ctx)->repo_baselinepath = strdup(baselinepath); 165 return EXIT_SUCCESS; 166 } 167 168 static int 169 simple_close(struct dircache_ctx *dc_ctx) 170 { 171 /* TODO */ 172 return EXIT_SUCCESS; 173 } 174 175 static int 176 simple_init(struct dircache_ctx *dc_ctx) 177 { 178 char *branch_name = NULL, *branch_path; 179 char *dc_path; 180 char *workdir_fname; 181 int exist, fd, retval; 182 struct stat s; 183 FILE *branch_fp; 184 185 if (dc_ctx == NULL) 186 return EXIT_FAILURE; 187 dc_path = get_dircache_path(dc_ctx); 188 asprintf(&branch_path, "%s/branch", dc_ctx->repo_baselinepath); 189 /* check if '.baseline/dircache' dir exists */ 190 if (stat(dc_path, &s) == -1) { 191 /* create '.baseline/dircache' dir */ 192 if (mkdir(dc_path, S_IRUSR | S_IWUSR | S_IXUSR) == -1) { 193 return EXIT_FAILURE; 194 } 195 } 196 /* check if '.baseline/branch' file exists */ 197 /* TODO: a lot of that code should be moved to is_init() func or cmd-init file */ 198 /* may be already initialized ... should just return failure? */ 199 if(stat(branch_path, &s) == 0) { 200 if (!S_ISREG(s.st_mode)) { 201 retval = EXIT_FAILURE; 202 goto ret; 203 } 204 if (!(s.st_mode & S_IRUSR) || !(s.st_mode & S_IWUSR)) { 205 retval = EXIT_FAILURE; 206 goto ret; 207 } 208 } 209 else { 210 asprintf(&branch_name, "%s", DEFAULT_BRANCH); 211 /* this piece of code does not belong here */ 212 if (dc_ctx->db_ops->branch_if_exists(dc_ctx->db_ctx, branch_name, &exist) == EXIT_FAILURE) { 213 retval = EXIT_FAILURE; 214 free(branch_name); 215 goto ret; 216 } 217 if (!exist) 218 dc_ctx->db_ops->branch_create(dc_ctx->db_ctx, branch_name); 219 /* create '.baseline/branch' file */ 220 if ((branch_fp = fopen(branch_path, "w")) == NULL) { 221 retval = EXIT_FAILURE; 222 goto ret; 223 } 224 fprintf(branch_fp, "%s", branch_name); 225 fclose(branch_fp); 226 free(branch_name); 227 } 228 /* create '.baseline/workdir' file */ 229 asprintf(&workdir_fname, "%s/workdir", dc_ctx->repo_baselinepath); 230 if ((fd = open(workdir_fname, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) == -1) { 231 free(workdir_fname); 232 retval = EXIT_FAILURE; 233 goto ret; 234 } 235 close(fd); 236 ret: 237 free(dc_path); 238 free(branch_path); 239 return EXIT_SUCCESS; 240 } 241 242 static int 243 simple_insert(struct dircache_ctx *dc_ctx, const char *path) 244 { 245 char *objid, *paths[2], *cache_path; 246 char *dc_path, *p; 247 struct stat s, fs; 248 FILE *fp; 249 FTS *dir; 250 FTSENT *entry; 251 struct file *file; 252 253 dc_path = get_dircache_path(dc_ctx); 254 if (stat(path, &s) == -1) 255 return EXIT_FAILURE; 256 if (S_ISDIR(s.st_mode)) { 257 /* dirs are cached for now, and should be inserted at commit time */ 258 paths[0] = (char *)path; 259 paths[1] = NULL; 260 if ((dir = fts_open(paths, FTS_NOCHDIR, 0)) == NULL) 261 return EXIT_FAILURE; 262 while ((entry = fts_read(dir)) != NULL) { 263 /* skip directories starting with '.', other than our top-level directory */ 264 if (entry->fts_name[0] == '.' && entry->fts_level != FTS_ROOTLEVEL) { 265 fts_set(dir, entry, FTS_SKIP); 266 continue; 267 } 268 if (entry->fts_info & FTS_D) { 269 #ifdef DEBUG 270 printf("DS: %s\n", entry->fts_path); 271 #endif 272 /* FIXME: path check is required */ 273 p = dir_diff(entry->fts_path, dc_ctx->repo_rootpath); 274 if (*p != '\0') { 275 asprintf(&cache_path, "%s/%s", dc_path, p); 276 if (mkdir(cache_path, S_IRUSR | S_IWUSR | S_IXUSR) != 0) { 277 free(cache_path); 278 return EXIT_FAILURE; 279 } 280 #ifdef DEBUG 281 printf("[DEBUG] dircache: created dir %s\n", cache_path); 282 #endif 283 free(cache_path); 284 } 285 } 286 if(entry->fts_info & FTS_F) { 287 #ifdef DEBUG 288 printf("F: %s\n", entry->fts_path); 289 #endif 290 if (stat(entry->fts_path, &fs) == -1) 291 return EXIT_FAILURE; 292 /* TODO: baseline_file_new2() */ 293 file = baseline_file_new(); 294 file->loc = LOC_FS; 295 if ((file->fd = open(entry->fts_path, O_RDONLY, 0)) == -1) 296 return EXIT_FAILURE; 297 dc_ctx->db_ops->insert_file(dc_ctx->db_ctx, file); 298 close(file->fd); 299 objid = strdup(file->id); 300 baseline_file_free(file); 301 #ifdef DEBUG 302 printf("\t ID = %s\n", objid); 303 #endif 304 /* FIXME: path check is required */ 305 asprintf(&cache_path, "%s/%s", dc_path, dir_diff(entry->fts_path, dc_ctx->repo_rootpath)); 306 if ((fp = fopen(cache_path, "w")) == NULL) { 307 free(cache_path); 308 return EXIT_FAILURE; 309 } 310 #ifdef DEBUG 311 printf("[DEBUG] dircache: created file %s\n", cache_path); 312 #endif 313 fprintf(fp, "F %s %06o\n", objid, fs.st_mode); 314 fclose(fp); 315 free(cache_path); 316 } 317 } 318 fts_close(dir); 319 } 320 else { 321 /* it's a file, why the hell would we wait, insert it immediately */ 322 if (stat(path, &fs) == -1) 323 return EXIT_FAILURE; 324 /* TODO: baseline_file_new2() */ 325 file = baseline_file_new(); 326 file->loc = LOC_FS; 327 if ((file->fd = open(path, O_RDONLY, 0)) == -1) 328 return EXIT_FAILURE; 329 dc_ctx->db_ops->insert_file(dc_ctx->db_ctx, file); 330 close(file->fd); 331 objid = strdup(file->id); 332 baseline_file_free(file); 333 334 if (mkdirp(dc_path, dir_diff(path, dc_ctx->repo_rootpath)) == EXIT_FAILURE) 335 return EXIT_FAILURE; 336 asprintf(&cache_path, "%s/%s", dc_path, dir_diff(path, dc_ctx->repo_rootpath)); 337 if ((fp = fopen(cache_path, "w")) == NULL) { 338 free(cache_path); 339 return EXIT_FAILURE; 340 } 341 fprintf(fp, "F %s %06o\n", objid, fs.st_mode); 342 fclose(fp); 343 free(objid); 344 free(cache_path); 345 } 346 return EXIT_SUCCESS; 347 } 348 349 static int 350 gen_dindex(struct dircache_ctx *dc_ctx, char **didx) 351 { 352 char *dircache_path, *didx_path = NULL; 353 char *paths[2]; 354 int didx_fd; 355 struct stat s; 356 FILE *didx_fp; 357 FTS *ftsp; 358 FTSENT *entry; 359 360 dircache_path = get_dircache_path(dc_ctx); 361 if (stat(dircache_path, &s) == -1) 362 return EXIT_FAILURE; 363 if (!S_ISDIR(s.st_mode)) 364 return EXIT_FAILURE; 365 /* create dir-only index */ 366 asprintf(&didx_path, "%s/dindex.XXXXXXX", dc_ctx->repo_baselinepath); 367 if ((didx_fd = mkstemp(didx_path)) == -1) { 368 return EXIT_FAILURE; 369 } 370 if ((didx_fp = fdopen(didx_fd, "w")) == NULL) { 371 unlink(didx_path); 372 close(didx_fd); 373 return EXIT_FAILURE; 374 } 375 paths[0] = (char *)dircache_path; 376 paths[1] = NULL; 377 if ((ftsp = fts_open(paths, FTS_NOCHDIR, 0)) == NULL) 378 return EXIT_FAILURE; 379 while ((entry = fts_read(ftsp)) != NULL) { 380 /* skip directories starting with '.', other than our top-level directory */ 381 if (entry->fts_name[0] == '.' && entry->fts_level != FTS_ROOTLEVEL) { 382 fts_set(ftsp, entry, FTS_SKIP); 383 continue; 384 } 385 if (entry->fts_info & FTS_DP) { 386 /* FIXME: spaces! */ 387 fprintf(didx_fp, "%s\n", entry->fts_path); 388 } 389 } 390 fts_close(ftsp); 391 fclose(didx_fp); 392 free(dircache_path); 393 *didx = didx_path; 394 return EXIT_SUCCESS; 395 } 396 397 static int 398 simple_commit(struct dircache_ctx *dc_ctx, const char *msgfile) 399 { 400 char *cur_branch, *cur_head; 401 char *objid, *path, *paths[2], tmp_objid[1024]; 402 char *dircache_path, *didx_path; 403 struct stat s; 404 FILE *didx_fp, *fp; 405 FTS *dir; 406 FTSENT *entry; 407 unsigned int mode; 408 size_t size; 409 ssize_t len; 410 char type; 411 struct dir *ndir; 412 struct dirent *ent; 413 struct commit *com; 414 415 dircache_path = get_dircache_path(dc_ctx); 416 if (stat(dircache_path, &s) == -1) 417 return EXIT_FAILURE; 418 if (!S_ISDIR(s.st_mode)) 419 return EXIT_FAILURE; 420 /* get current branch */ 421 if (simple_branch_get(dc_ctx, &cur_branch) == EXIT_FAILURE) 422 return EXIT_FAILURE; 423 /* get current branch's head (commit's parent) */ 424 if (dc_ctx->db_ops->branch_get_head(dc_ctx->db_ctx, cur_branch, &cur_head) == EXIT_FAILURE) 425 return EXIT_FAILURE; 426 427 /* FIXME: empty dirache directory */ 428 gen_dindex(dc_ctx, &didx_path); 429 if ((didx_fp = fopen(didx_path, "r")) == NULL) 430 return EXIT_FAILURE; 431 objid = NULL; 432 size = 0; 433 path = NULL; /* if not set to NULL, realloc() will get pissed. And, it can waste your day! */ 434 while ((len = getline(&path, &size, didx_fp)) != -1) { 435 if (path[len-1] == '\n') { 436 path[len-1] = 0; 437 --len; 438 } 439 ndir = baseline_dir_new(); 440 441 paths[0] = (char *)path; 442 paths[1] = NULL; 443 if ((dir = fts_open(paths, FTS_NOCHDIR, 0)) == NULL) 444 return EXIT_FAILURE; 445 while ((entry = fts_read(dir)) != NULL) { 446 if (entry->fts_name[0] == '.' && entry->fts_level != FTS_ROOTLEVEL) { 447 fts_set(dir, entry, FTS_SKIP); 448 continue; 449 } 450 if (entry->fts_info & FTS_F) { 451 #ifdef DEBUG 452 printf("\t FILE: %s\n", entry->fts_path); 453 #endif 454 if ((fp = fopen(entry->fts_path, "r")) == NULL) 455 return EXIT_FAILURE; 456 fscanf(fp, "%c %s %o", &type, tmp_objid, &mode); 457 if (type == 'F') { 458 ent = (struct dirent *)calloc(1, sizeof(struct dirent)); 459 ent->id = strdup(tmp_objid); 460 ent->name = strdup(entry->fts_name); 461 ent->mode = mode; 462 ent->type = T_FILE; 463 baseline_dir_append(ndir, ent); 464 printf("[+] F %06o\t%s\n", mode, entry->fts_name); 465 } 466 else if (type == 'D') { 467 ent = (struct dirent *)calloc(1, sizeof(struct dirent)); 468 ent->id = strdup(tmp_objid); 469 ent->name = strdup(entry->fts_name); 470 ent->mode = mode; 471 ent->type = T_DIR; 472 baseline_dir_append(ndir, ent); 473 /* FIXME: dir mode are copied from dircache, hence not preserved */ 474 printf("[+] D %06o\t%s\n", mode, entry->fts_name); 475 } 476 fclose(fp); 477 unlink(entry->fts_path); 478 } 479 else { 480 if (entry->fts_level >= 1) { 481 fts_set(dir, entry, FTS_SKIP); 482 } 483 } 484 } 485 fts_close(dir); 486 dc_ctx->db_ops->insert_dir(dc_ctx->db_ctx, ndir); 487 free(objid); 488 objid = strdup(ndir->id); 489 baseline_dir_free(ndir); 490 #ifdef DEBUG 491 printf("dir \'%s\' commited with ID = %s\n", path, objid); 492 #endif 493 if (stat(path, &s) == -1) 494 return EXIT_FAILURE; 495 /* FIXME: do not remove '.baseline/dircache' dir */ 496 if (rmdir(path) == -1) 497 return EXIT_FAILURE; 498 if ((fp = fopen(path, "w")) == NULL) 499 return EXIT_FAILURE; 500 fprintf(fp, "%c %s %06o", 'D', objid, s.st_mode); 501 fclose(fp); 502 } 503 fclose(didx_fp); 504 unlink(didx_path); 505 /* temp */ 506 unlink(dircache_path); 507 if (mkdir(dircache_path, S_IRUSR | S_IWUSR | S_IXUSR) == -1) { 508 return EXIT_FAILURE; 509 } 510 /* generate commit */ 511 if ((com = baseline_helper_commit_build(dc_ctx, objid, cur_head, msgfile)) == NULL) 512 return EXIT_FAILURE; 513 /* insert the commit into the db */ 514 dc_ctx->db_ops->insert_commit(dc_ctx->db_ctx, com); 515 printf("commit id: %s\n", com->id); 516 unlink(path); 517 /* update the branch's head */ 518 dc_ctx->db_ops->branch_set_head(dc_ctx->db_ctx, cur_branch, com->id); 519 /* update the working dir */ 520 simple_workdir_set(dc_ctx, com->id); 521 free(cur_branch); 522 free(cur_head); 523 free(path); 524 free(objid); 525 baseline_commit_free(com); 526 return EXIT_SUCCESS; 527 } 528 529 static int 530 simple_branch_get(struct dircache_ctx *dc_ctx, char **branch_name) 531 { 532 char *fname; 533 size_t size = 0; 534 FILE *fp; 535 536 if (dc_ctx == NULL || branch_name == NULL) 537 return EXIT_FAILURE; 538 asprintf(&fname, "%s/branch", dc_ctx->repo_baselinepath); 539 *branch_name = NULL; 540 if ((fp = fopen(fname, "r")) == NULL) 541 return EXIT_FAILURE; 542 if (getline(branch_name, &size, fp) == -1) { 543 free(*branch_name); 544 return EXIT_FAILURE; 545 } 546 fclose(fp); 547 free(fname); 548 return EXIT_SUCCESS; 549 } 550 551 static int 552 simple_branch_set(struct dircache_ctx *dc_ctx, const char *branch_name) 553 { 554 char *fname; 555 FILE *fp; 556 557 if (dc_ctx == NULL || branch_name == NULL) 558 return EXIT_FAILURE; 559 asprintf(&fname, "%s/branch", dc_ctx->repo_baselinepath); 560 if ((fp = fopen(fname, "w")) == NULL) 561 return EXIT_FAILURE; 562 fprintf(fp, "%s", branch_name); 563 fclose(fp); 564 free(fname); 565 return EXIT_SUCCESS; 566 } 567 568 static int 569 simple_workdir_get(struct dircache_ctx *dc_ctx, char **commit_id) 570 { 571 char *fname; 572 size_t size = 0; 573 FILE *fp; 574 575 if (dc_ctx == NULL || commit_id == NULL) 576 return EXIT_FAILURE; 577 asprintf(&fname, "%s/workdir", dc_ctx->repo_baselinepath); 578 if ((fp = fopen(fname, "r")) == NULL) 579 return EXIT_FAILURE; 580 *commit_id = NULL; 581 if (getline(commit_id, &size, fp) == -1) { 582 /* assuming the file is empty */ 583 /* FIXME: check for errors */ 584 *commit_id = NULL; 585 } 586 fclose(fp); 587 free(fname); 588 return EXIT_SUCCESS; 589 } 590 591 static int 592 simple_workdir_set(struct dircache_ctx *dc_ctx, const char *commit_id) 593 { 594 char *fname; 595 FILE *fp; 596 597 if (dc_ctx == NULL || commit_id == NULL) 598 return EXIT_FAILURE; 599 asprintf(&fname, "%s/workdir", dc_ctx->repo_baselinepath); 600 if ((fp = fopen(fname, "w")) == NULL) 601 return EXIT_FAILURE; 602 fprintf(fp, "%s", commit_id); 603 fclose(fp); 604 free(fname); 605 return EXIT_SUCCESS; 606 } 607