cmd-diff.c (10140B)
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 <stdio.h> /* printf(3) */ 18 #include <stdlib.h> /* EXIT_SUCCESS */ 19 #include <string.h> /* strdup(3) */ 20 #include <fcntl.h> /* open(2) */ 21 #include <err.h> 22 #include <unistd.h> /* read(2), write(2) */ 23 #include <sys/stat.h> /* S_ISDIR */ 24 #include <sys/wait.h> /* waitpid(2) */ 25 26 #include "cmd.h" 27 #include "session.h" 28 29 #include "objects.h" 30 31 #include "common.h" 32 33 34 static char * 35 make_tmpdir() 36 { 37 char *name = NULL; 38 39 name = strdup("/tmp/baseline.XXXXXX"); 40 if (mkdtemp(name) == NULL) { 41 errx(EXIT_FAILURE, "error, failed to create a temporary directory (%s).", name); 42 } 43 return name; 44 } 45 46 static char * 47 make_fifo(const char *path, const char *name) 48 { 49 char *fifo_name = NULL; 50 51 asprintf(&fifo_name, "%s/%s", path, name); 52 if (mkfifo(fifo_name, S_IRUSR | S_IWUSR) == -1) { 53 errx(EXIT_FAILURE, "error, failed to create FIFO \'%s\'.", fifo_name); 54 } 55 return fifo_name; 56 } 57 58 static int 59 open_fifo(const char *fifo) 60 { 61 int fd; 62 63 if ((fd = open(fifo, O_WRONLY)) == -1) { 64 errx(EXIT_FAILURE, "error, failed to open FIFO \'%s\'.", fifo); 65 } 66 return fd; 67 } 68 69 static void 70 copy_to_fifo(struct file *f, int fifo) 71 { 72 char buf[2048]; 73 ssize_t n; 74 75 while ((n = read(f->fd, buf, sizeof(buf))) > 0) { 76 write(fifo, buf, n); 77 } 78 if (n == -1) { 79 errx(EXIT_FAILURE, "error, failed to read file."); 80 } 81 } 82 83 static void 84 exec_ext_diff(const char *old, const char *old_label, const char *new, const char *new_label) 85 { 86 char buf[2048]; 87 char *cmd = NULL; 88 size_t n; 89 FILE *fp; 90 91 if (old == NULL && new == NULL) 92 return; 93 94 if (old == NULL) 95 asprintf(&cmd, "diff -u %s %s -L %s -L %s", "/dev/null", new, "/dev/null", new_label == NULL ? new : new_label); 96 else if(new == NULL) 97 asprintf(&cmd, "diff -u %s %s -L %s -L %s", old, "/dev/null", old_label == NULL ? old : old_label, "/dev/null"); 98 else 99 asprintf(&cmd, "diff -u %s %s -L %s -L %s", old, new, old_label == NULL ? old : old_label, new_label == NULL ? new : new_label); 100 101 if ((fp = popen(cmd, "r")) == NULL) 102 errx(EXIT_FAILURE, "error, failed to run external diff."); 103 /* 104 while ((n = fread(buf, sizeof(char), sizeof(buf), pf)) > 0) 105 fwrite(buf, sizeof(char), n, stdout); 106 */ 107 while ((n = read(fileno(fp), buf, sizeof(buf))) > 0) 108 write(1, buf, n); 109 if (n == -1) 110 errx(EXIT_FAILURE, "error, reading output from external diff."); 111 pclose(fp); 112 free(cmd); 113 } 114 115 static void 116 ext_diff_proc(struct session *s, struct dirent *ent, char *fifo) 117 { 118 int fifo_fd; 119 struct file *f; 120 121 fifo_fd = open_fifo(fifo); 122 123 f = baseline_file_new(); 124 s->db_ops->select_file(s->db_ctx, ent->id, f); 125 126 copy_to_fifo(f, fifo_fd); 127 close(fifo_fd); 128 unlink(fifo); 129 130 baseline_file_free(f); 131 } 132 133 static void 134 ext_diff(struct session *s, const char *tmpdir, struct dirent *ent1, struct dirent *ent2, const char *path) 135 { 136 char *dir1 = NULL, *dir2 = NULL; 137 char *label1, *label2; 138 char *fifo1 = NULL, *fifo2 = NULL; 139 pid_t pid1, pid2; 140 141 label1 = NULL; 142 label2 = NULL; 143 if (ent1 == NULL && ent2 == NULL) 144 return; 145 146 if (ent1 != NULL) { 147 asprintf(&dir1, "%s/XXXXXXX", tmpdir); 148 if (mkdtemp(dir1) == NULL) 149 errx(EXIT_FAILURE, "error, failed to create a temporary directory (%s).", dir1); 150 fifo1 = make_fifo(dir1, ent1->id); 151 //free(dir1); 152 if (strlen(path) > 0) 153 asprintf(&label1, "%s/%s", path, ent1->name); 154 else 155 asprintf(&label1, "%s", ent1->name); 156 } 157 if(ent2 != NULL) { 158 asprintf(&dir2, "%s/XXXXXXX", tmpdir); 159 if (mkdtemp(dir2) == NULL) 160 errx(EXIT_FAILURE, "error, failed to create a temporary directory (%s).", dir2); 161 fifo2 = make_fifo(dir2, ent2->id); 162 //free(dir); 163 if (strlen(path) > 0) 164 asprintf(&label2, "%s/%s", path, ent2->name); 165 else 166 asprintf(&label2, "%s", ent2->name); 167 } 168 169 pid1 = fork(); 170 if (pid1 < 0) { 171 errx(EXIT_FAILURE, "error, failed to create a new process."); 172 } 173 else if (pid1 == 0) { 174 exec_ext_diff(fifo1, label1, fifo2, label2); 175 exit(0); 176 } 177 else { 178 if (ent1 != NULL && ent2 != NULL) { 179 pid2 = fork(); 180 181 if (pid2 < 0) { 182 errx(EXIT_FAILURE, "error, failed to create a new process."); 183 } 184 else if (pid2 == 0) { 185 ext_diff_proc(s, ent1, fifo1); 186 exit(0); 187 } 188 else { 189 ext_diff_proc(s, ent2, fifo2); 190 } 191 } 192 else { 193 if (ent1 != NULL) 194 ext_diff_proc(s, ent1, fifo1); 195 else 196 ext_diff_proc(s, ent2, fifo2); 197 } 198 waitpid(pid1, NULL, 0); 199 } 200 remove(dir1); 201 remove(dir2); 202 free(dir1); 203 free(dir2); 204 free(fifo1); 205 free(fifo2); 206 } 207 208 209 static void 210 diff_r(struct session *s, struct dir *d1, struct dir *d2, const char *p, const char *tmpdir) 211 { 212 char *pnext = NULL; 213 struct dir *child1, *child2; 214 struct dirent *ent1 = NULL, *ent2 = NULL; 215 216 if (d1 == NULL && d2 == NULL) 217 return; 218 if (p == NULL || tmpdir == NULL) 219 return; 220 if (d1 != NULL) 221 ent1 = d1->children; 222 if (d2 != NULL) 223 ent2 = d2->children; 224 if (*p == '/') 225 p++; 226 227 while (1) { 228 if (ent1 == NULL && ent2 == NULL) 229 break; 230 else if (ent1 == NULL) { 231 /* +++ ent2 */ 232 if (S_ISREG(ent2->mode)) 233 ext_diff(s, tmpdir, NULL, ent2, p); 234 else if (S_ISDIR(ent2->mode)) { 235 asprintf(&pnext, "%s/%s", p, ent2->name); 236 child2 = baseline_dir_new(); 237 s->db_ops->select_dir(s->db_ctx, ent2->id, child2); 238 diff_r(s, NULL, child2, pnext, tmpdir); 239 baseline_dir_free(child2); 240 free(pnext); 241 } 242 else 243 errx(EXIT_FAILURE, "error, file mode not supported."); 244 ent2 = ent2->next; 245 } 246 else if (ent2 == NULL) { 247 /* --- ent1 */ 248 if (S_ISREG(ent1->mode)) 249 ext_diff(s, tmpdir, ent1, NULL, p); 250 else if (S_ISDIR(ent1->mode)) { 251 asprintf(&pnext, "%s/%s", p, ent1->name); 252 child1 = baseline_dir_new(); 253 s->db_ops->select_dir(s->db_ctx, ent1->id, child1); 254 diff_r(s, child1, NULL, pnext, tmpdir); 255 baseline_dir_free(child1); 256 free(pnext); 257 } 258 else 259 errx(EXIT_FAILURE, "error, file mode not supported."); 260 ent1 = ent1->next; 261 } 262 else if (!strcmp(ent1->name, ent2->name)) { 263 if (strcmp(ent1->id, ent2->id)) { 264 /* *** ent1 & ent2 */ 265 if (S_ISREG(ent1->mode) && S_ISREG(ent2->mode)) 266 ext_diff(s, tmpdir, ent1, ent2, p); 267 else if (S_ISREG(ent1->mode) && S_ISDIR(ent2->mode)) { 268 /* delete old file */ 269 ext_diff(s, tmpdir, ent1, NULL, p); 270 /* add new dir */ 271 asprintf(&pnext, "%s/%s", p, ent2->name); 272 child2 = baseline_dir_new(); 273 s->db_ops->select_dir(s->db_ctx, ent2->id, child2); 274 diff_r(s, NULL, child2, pnext, tmpdir); 275 baseline_dir_free(child2); 276 free(pnext); 277 } 278 else if (S_ISDIR(ent1->mode) && S_ISREG(ent2->mode)) { 279 /* delete old dir */ 280 asprintf(&pnext, "%s/%s", p, ent1->name); 281 child1 = baseline_dir_new(); 282 s->db_ops->select_dir(s->db_ctx, ent1->id, child1); 283 diff_r(s, child1, NULL, pnext, tmpdir); 284 baseline_dir_free(child1); 285 free(pnext); 286 /* add new file */ 287 ext_diff(s, tmpdir, NULL, ent2, p); 288 } 289 else if (S_ISDIR(ent1->mode) && S_ISDIR(ent2->mode)) { 290 asprintf(&pnext, "%s/%s", p, ent1->name); 291 child1 = baseline_dir_new(); 292 child2 = baseline_dir_new(); 293 s->db_ops->select_dir(s->db_ctx, ent1->id, child1); 294 s->db_ops->select_dir(s->db_ctx, ent2->id, child2); 295 diff_r(s, child1, child2, pnext, tmpdir); 296 baseline_dir_free(child1); 297 baseline_dir_free(child2); 298 free(pnext); 299 } 300 else 301 errx(EXIT_FAILURE, "error, file mode not supported."); 302 } 303 ent1 = ent1->next; 304 ent2 = ent2->next; 305 } 306 else if (strcmp(ent1->name, ent2->name) < 0) { 307 /* --- ent1 */ 308 if (S_ISREG(ent1->mode)) 309 ext_diff(s, tmpdir, ent1, NULL, p); 310 else if (S_ISDIR(ent1->mode)) { 311 asprintf(&pnext, "%s/%s", p, ent1->name); 312 child1 = baseline_dir_new(); 313 s->db_ops->select_dir(s->db_ctx, ent1->id, child1); 314 diff_r(s, child1, NULL, pnext, tmpdir); 315 baseline_dir_free(child1); 316 free(pnext); 317 } 318 else 319 errx(EXIT_FAILURE, "error, file mode not supported."); 320 ent1 = ent1->next; 321 } 322 else if (strcmp(ent1->name, ent2->name) > 0) { 323 /* +++ ent2 */ 324 if (S_ISREG(ent2->mode)) 325 ext_diff(s, tmpdir, NULL, ent2, p); 326 else if (S_ISDIR(ent2->mode)) { 327 asprintf(&pnext, "%s/%s", p, ent2->name); 328 child2 = baseline_dir_new(); 329 s->db_ops->select_dir(s->db_ctx, ent2->id, child2); 330 diff_r(s, NULL, child2, pnext, tmpdir); 331 baseline_dir_free(child2); 332 free(pnext); 333 } 334 else 335 errx(EXIT_FAILURE, "error, file mode not supported."); 336 ent2 = ent2->next; 337 } 338 } 339 } 340 341 int 342 cmd_diff(int argc, char **argv) 343 { 344 char *old = NULL, *new = NULL; 345 char *tmpdir = NULL; 346 struct session s; 347 struct commit *comm_old, *comm_new; 348 struct dir *dir_old, *dir_new; 349 350 baseline_session_begin(&s, 0); 351 352 if (argc == 2) { 353 new = strdup(argv[1]); 354 } 355 else if (argc == 3) { 356 old = strdup(argv[1]); 357 new = strdup(argv[2]); 358 } 359 else { 360 errx(EXIT_FAILURE, "wrong number of arguments (%d)\n", argc); 361 } 362 363 tmpdir = make_tmpdir(); 364 365 comm_new = baseline_commit_new(); 366 s.db_ops->select_commit(s.db_ctx, new, comm_new); 367 368 dir_new = baseline_dir_new(); 369 s.db_ops->select_dir(s.db_ctx, comm_new->dir, dir_new); 370 371 if (old == NULL) { 372 if (comm_new->n_parents > 0) 373 old = comm_new->parents[0]; 374 else 375 goto ret; 376 } 377 378 comm_old = baseline_commit_new(); 379 s.db_ops->select_commit(s.db_ctx, old, comm_old); 380 381 dir_old = baseline_dir_new(); 382 s.db_ops->select_dir(s.db_ctx, comm_old->dir, dir_old); 383 384 diff_r(&s, dir_old, dir_new, "", tmpdir); 385 386 baseline_dir_free(dir_old); 387 baseline_dir_free(dir_new); 388 389 ret: 390 remove(tmpdir); 391 free(tmpdir); 392 free(old); 393 free(new); 394 baseline_session_end(&s); 395 return EXIT_SUCCESS; 396 } 397