Blob


1 /*
2 * Copyright (c) 2022 Omar Polo <op@openbsd.org>
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 * Apply patches.
17 *
18 * Things that are still missing:
19 * + "No final newline" handling
20 *
21 * Things that we may want to support:
22 * + support indented patches?
23 * + support other kinds of patches?
24 */
26 #include <sys/types.h>
27 #include <sys/queue.h>
28 #include <sys/socket.h>
29 #include <sys/stat.h>
30 #include <sys/uio.h>
32 #include <errno.h>
33 #include <limits.h>
34 #include <stdint.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <unistd.h>
39 #include <imsg.h>
41 #include "got_error.h"
42 #include "got_object.h"
43 #include "got_path.h"
44 #include "got_reference.h"
45 #include "got_cancel.h"
46 #include "got_worktree.h"
47 #include "got_opentemp.h"
48 #include "got_patch.h"
50 #include "got_lib_delta.h"
51 #include "got_lib_object.h"
52 #include "got_lib_privsep.h"
54 #define MIN(a, b) ((a) < (b) ? (a) : (b))
56 struct got_patch_hunk {
57 STAILQ_ENTRY(got_patch_hunk) entries;
58 long old_from;
59 long old_lines;
60 long new_from;
61 long new_lines;
62 size_t len;
63 size_t cap;
64 char **lines;
65 };
67 struct got_patch {
68 char *old;
69 char *new;
70 STAILQ_HEAD(, got_patch_hunk) head;
71 };
73 static const struct got_error *
74 send_patch(struct imsgbuf *ibuf, int fd)
75 {
76 const struct got_error *err = NULL;
78 if (imsg_compose(ibuf, GOT_IMSG_PATCH_FILE, 0, 0, fd,
79 NULL, 0) == -1) {
80 err = got_error_from_errno(
81 "imsg_compose GOT_IMSG_PATCH_FILE");
82 close(fd);
83 return err;
84 }
86 if (imsg_flush(ibuf) == -1) {
87 err = got_error_from_errno("imsg_flush");
88 imsg_clear(ibuf);
89 }
91 return err;
92 }
94 static void
95 patch_free(struct got_patch *p)
96 {
97 struct got_patch_hunk *h;
98 size_t i;
100 while (!STAILQ_EMPTY(&p->head)) {
101 h = STAILQ_FIRST(&p->head);
102 STAILQ_REMOVE_HEAD(&p->head, entries);
104 for (i = 0; i < h->len; ++i)
105 free(h->lines[i]);
106 free(h->lines);
107 free(h);
110 free(p->new);
111 free(p->old);
114 static const struct got_error *
115 pushline(struct got_patch_hunk *h, const char *line)
117 void *t;
118 size_t newcap;
120 if (h->len == h->cap) {
121 if ((newcap = h->cap * 1.5) == 0)
122 newcap = 16;
123 t = recallocarray(h->lines, h->cap, newcap,
124 sizeof(h->lines[0]));
125 if (t == NULL)
126 return got_error_from_errno("recallocarray");
127 h->lines = t;
128 h->cap = newcap;
131 if ((t = strdup(line)) == NULL)
132 return got_error_from_errno("strdup");
134 h->lines[h->len++] = t;
135 return NULL;
138 static const struct got_error *
139 recv_patch(struct imsgbuf *ibuf, int *done, struct got_patch *p)
141 const struct got_error *err = NULL;
142 struct imsg imsg;
143 struct got_imsg_patch_hunk hdr;
144 struct got_imsg_patch patch;
145 struct got_patch_hunk *h = NULL;
146 size_t datalen;
148 memset(p, 0, sizeof(*p));
149 STAILQ_INIT(&p->head);
151 err = got_privsep_recv_imsg(&imsg, ibuf, 0);
152 if (err)
153 return err;
154 if (imsg.hdr.type == GOT_IMSG_PATCH_EOF) {
155 *done = 1;
156 goto done;
158 if (imsg.hdr.type != GOT_IMSG_PATCH) {
159 err = got_error(GOT_ERR_PRIVSEP_MSG);
160 goto done;
162 datalen = imsg.hdr.len - IMSG_HEADER_SIZE;
163 if (datalen != sizeof(patch)) {
164 err = got_error(GOT_ERR_PRIVSEP_LEN);
165 goto done;
167 memcpy(&patch, imsg.data, sizeof(patch));
168 if (*patch.old != '\0' && (p->old = strdup(patch.old)) == NULL) {
169 err = got_error_from_errno("strdup");
170 goto done;
172 if (*patch.new != '\0' && (p->new = strdup(patch.new)) == NULL) {
173 err = got_error_from_errno("strdup");
174 goto done;
176 if (p->old == NULL && p->new == NULL) {
177 err = got_error(GOT_ERR_PATCH_MALFORMED);
178 goto done;
181 imsg_free(&imsg);
183 for (;;) {
184 char *t;
186 err = got_privsep_recv_imsg(&imsg, ibuf, 0);
187 if (err)
188 return err;
190 switch (imsg.hdr.type) {
191 case GOT_IMSG_PATCH_DONE:
192 goto done;
193 case GOT_IMSG_PATCH_HUNK:
194 datalen = imsg.hdr.len - IMSG_HEADER_SIZE;
195 if (datalen != sizeof(hdr)) {
196 err = got_error(GOT_ERR_PRIVSEP_LEN);
197 goto done;
199 memcpy(&hdr, imsg.data, sizeof(hdr));
200 if ((h = calloc(1, sizeof(*h))) == NULL) {
201 err = got_error_from_errno("calloc");
202 goto done;
204 h->old_from = hdr.oldfrom;
205 h->old_lines = hdr.oldlines;
206 h->new_from = hdr.newfrom;
207 h->new_lines = hdr.newlines;
208 STAILQ_INSERT_TAIL(&p->head, h, entries);
209 break;
210 case GOT_IMSG_PATCH_LINE:
211 if (h == NULL) {
212 err = got_error(GOT_ERR_PRIVSEP_MSG);
213 goto done;
215 datalen = imsg.hdr.len - IMSG_HEADER_SIZE;
216 t = imsg.data;
217 /* at least one char plus newline */
218 if (datalen < 2 || t[datalen-1] != '\0') {
219 err = got_error(GOT_ERR_PRIVSEP_MSG);
220 goto done;
222 if (*t != ' ' && *t != '-' && *t != '+') {
223 err = got_error(GOT_ERR_PRIVSEP_MSG);
224 goto done;
226 err = pushline(h, t);
227 if (err)
228 goto done;
229 break;
230 default:
231 err = got_error(GOT_ERR_PRIVSEP_MSG);
232 goto done;
235 imsg_free(&imsg);
238 done:
239 imsg_free(&imsg);
240 return err;
243 /*
244 * Copy data from orig starting at copypos until pos into tmp.
245 * If pos is -1, copy until EOF.
246 */
247 static const struct got_error *
248 copy(FILE *tmp, FILE *orig, off_t copypos, off_t pos)
250 char buf[BUFSIZ];
251 size_t len, r, w;
253 if (fseek(orig, copypos, SEEK_SET) == -1)
254 return got_error_from_errno("fseek");
256 while (pos == -1 || copypos < pos) {
257 len = sizeof(buf);
258 if (pos > 0)
259 len = MIN(len, (size_t)pos - copypos);
260 r = fread(buf, 1, len, orig);
261 if (r != len && ferror(orig))
262 return got_error_from_errno("fread");
263 w = fwrite(buf, 1, r, tmp);
264 if (w != r)
265 return got_error_from_errno("fwrite");
266 copypos += len;
267 if (r != len && feof(orig)) {
268 if (pos == -1)
269 return NULL;
270 return got_error(GOT_ERR_PATCH_DONT_APPLY);
273 return NULL;
276 static const struct got_error *
277 locate_hunk(FILE *orig, struct got_patch_hunk *h, long *lineno)
279 const struct got_error *err = NULL;
280 char *line = NULL;
281 char mode = *h->lines[0];
282 size_t linesize = 0;
283 ssize_t linelen;
284 off_t match = -1;
285 long match_lineno = -1;
287 for (;;) {
288 linelen = getline(&line, &linesize, orig);
289 if (linelen == -1) {
290 if (ferror(orig))
291 err = got_error_from_errno("getline");
292 else if (match == -1)
293 err = got_error(GOT_ERR_PATCH_DONT_APPLY);
294 break;
296 (*lineno)++;
298 if ((mode == ' ' && !strcmp(h->lines[0]+1, line)) ||
299 (mode == '-' && !strcmp(h->lines[0]+1, line)) ||
300 (mode == '+' && *lineno == h->old_from)) {
301 match = ftello(orig);
302 if (match == -1) {
303 err = got_error_from_errno("ftello");
304 break;
306 match -= linelen;
307 match_lineno = (*lineno)-1;
310 if (*lineno >= h->old_from && match != -1)
311 break;
314 if (err == NULL) {
315 *lineno = match_lineno;
316 if (fseek(orig, match, SEEK_SET) == -1)
317 err = got_error_from_errno("fseek");
320 free(line);
321 return err;
324 static const struct got_error *
325 test_hunk(FILE *orig, struct got_patch_hunk *h)
327 const struct got_error *err = NULL;
328 char *line = NULL;
329 size_t linesize = 0, i = 0;
330 ssize_t linelen;
332 for (i = 0; i < h->len; ++i) {
333 switch (*h->lines[i]) {
334 case '+':
335 continue;
336 case ' ':
337 case '-':
338 linelen = getline(&line, &linesize, orig);
339 if (linelen == -1) {
340 if (ferror(orig))
341 err = got_error_from_errno("getline");
342 else
343 err = got_error(
344 GOT_ERR_PATCH_DONT_APPLY);
345 goto done;
347 if (strcmp(h->lines[i]+1, line)) {
348 err = got_error(GOT_ERR_PATCH_DONT_APPLY);
349 goto done;
351 break;
355 done:
356 free(line);
357 return err;
360 static const struct got_error *
361 apply_hunk(FILE *tmp, struct got_patch_hunk *h, long *lineno)
363 size_t i = 0;
365 for (i = 0; i < h->len; ++i) {
366 switch (*h->lines[i]) {
367 case ' ':
368 if (fprintf(tmp, "%s", h->lines[i]+1) < 0)
369 return got_error_from_errno("fprintf");
370 /* fallthrough */
371 case '-':
372 (*lineno)++;
373 break;
374 case '+':
375 if (fprintf(tmp, "%s", h->lines[i]+1) < 0)
376 return got_error_from_errno("fprintf");
377 break;
380 return NULL;
383 static const struct got_error *
384 patch_file(struct got_patch *p, const char *path, FILE *tmp)
386 const struct got_error *err = NULL;
387 struct got_patch_hunk *h;
388 size_t i;
389 long lineno = 0;
390 FILE *orig;
391 off_t copypos, pos;
392 char *line = NULL;
393 size_t linesize = 0;
394 ssize_t linelen;
396 if (p->old == NULL) { /* create */
397 h = STAILQ_FIRST(&p->head);
398 if (h == NULL || STAILQ_NEXT(h, entries) != NULL)
399 return got_error(GOT_ERR_PATCH_MALFORMED);
400 for (i = 0; i < h->len; ++i) {
401 if (fprintf(tmp, "%s", h->lines[i]+1) < 0)
402 return got_error_from_errno("fprintf");
404 return err;
407 if ((orig = fopen(path, "r")) == NULL) {
408 err = got_error_from_errno2("fopen", path);
409 goto done;
412 copypos = 0;
413 STAILQ_FOREACH(h, &p->head, entries) {
414 if (h->lines == NULL)
415 break;
417 tryagain:
418 err = locate_hunk(orig, h, &lineno);
419 if (err != NULL)
420 goto done;
421 if ((pos = ftello(orig)) == -1) {
422 err = got_error_from_errno("ftello");
423 goto done;
425 err = copy(tmp, orig, copypos, pos);
426 if (err != NULL)
427 goto done;
428 copypos = pos;
430 err = test_hunk(orig, h);
431 if (err != NULL && err->code == GOT_ERR_PATCH_DONT_APPLY) {
432 /*
433 * try to apply the hunk again starting the search
434 * after the previous partial match.
435 */
436 if (fseek(orig, pos, SEEK_SET) == -1) {
437 err = got_error_from_errno("fseek");
438 goto done;
440 linelen = getline(&line, &linesize, orig);
441 if (linelen == -1) {
442 err = got_error_from_errno("getline");
443 goto done;
445 lineno++;
446 goto tryagain;
448 if (err != NULL)
449 goto done;
451 err = apply_hunk(tmp, h, &lineno);
452 if (err != NULL)
453 goto done;
455 copypos = ftello(orig);
456 if (copypos == -1) {
457 err = got_error_from_errno("ftello");
458 goto done;
463 if (p->new == NULL) {
464 struct stat sb;
466 if (fstat(fileno(orig), &sb) == -1)
467 err = got_error_from_errno("fstat");
468 else if (sb.st_size != copypos)
469 err = got_error(GOT_ERR_PATCH_DONT_APPLY);
470 } else if (!feof(orig))
471 err = copy(tmp, orig, copypos, -1);
473 done:
474 if (orig != NULL)
475 fclose(orig);
476 return err;
479 static const struct got_error *
480 build_pathlist(const char *p, char **path, struct got_pathlist_head *head,
481 struct got_worktree *worktree)
483 const struct got_error *err;
484 struct got_pathlist_entry *pe;
486 err = got_worktree_resolve_path(path, worktree, p);
487 if (err == NULL)
488 err = got_pathlist_insert(&pe, head, *path, NULL);
489 return err;
492 static const struct got_error *
493 can_rm(void *arg, unsigned char status, unsigned char staged_status,
494 const char *path, struct got_object_id *blob_id,
495 struct got_object_id *staged_blob_id, struct got_object_id *commit_id,
496 int dirfd, const char *de_name)
498 if (status == GOT_STATUS_NONEXISTENT)
499 return got_error_set_errno(ENOENT, path);
500 if (status != GOT_STATUS_NO_CHANGE &&
501 status != GOT_STATUS_ADD &&
502 status != GOT_STATUS_MODIFY &&
503 status != GOT_STATUS_MODE_CHANGE)
504 return got_error_path(path, GOT_ERR_FILE_STATUS);
505 if (staged_status == GOT_STATUS_DELETE)
506 return got_error_path(path, GOT_ERR_FILE_STATUS);
507 return NULL;
510 static const struct got_error *
511 can_add(void *arg, unsigned char status, unsigned char staged_status,
512 const char *path, struct got_object_id *blob_id,
513 struct got_object_id *staged_blob_id, struct got_object_id *commit_id,
514 int dirfd, const char *de_name)
516 if (status != GOT_STATUS_NONEXISTENT)
517 return got_error_path(path, GOT_ERR_FILE_STATUS);
518 return NULL;
521 static const struct got_error *
522 can_edit(void *arg, unsigned char status, unsigned char staged_status,
523 const char *path, struct got_object_id *blob_id,
524 struct got_object_id *staged_blob_id, struct got_object_id *commit_id,
525 int dirfd, const char *de_name)
527 if (status == GOT_STATUS_NONEXISTENT)
528 return got_error_set_errno(ENOENT, path);
529 if (status != GOT_STATUS_NO_CHANGE &&
530 status != GOT_STATUS_ADD &&
531 status != GOT_STATUS_MODIFY)
532 return got_error_path(path, GOT_ERR_FILE_STATUS);
533 if (staged_status == GOT_STATUS_DELETE)
534 return got_error_path(path, GOT_ERR_FILE_STATUS);
535 return NULL;
538 static const struct got_error *
539 check_file_status(struct got_patch *p, int file_renamed,
540 struct got_worktree *worktree, struct got_repository *repo,
541 struct got_pathlist_head *old, struct got_pathlist_head *new,
542 got_cancel_cb cancel_cb, void *cancel_arg)
544 static const struct got_error *err;
546 if (p->old != NULL && p->new == NULL)
547 return got_worktree_status(worktree, old, repo, 0,
548 can_rm, NULL, cancel_cb, cancel_arg);
549 else if (file_renamed) {
550 err = got_worktree_status(worktree, old, repo, 0,
551 can_rm, NULL, cancel_cb, cancel_arg);
552 if (err)
553 return err;
554 return got_worktree_status(worktree, new, repo, 0,
555 can_add, NULL, cancel_cb, cancel_arg);
556 } else if (p->old == NULL)
557 return got_worktree_status(worktree, new, repo, 0,
558 can_add, NULL, cancel_cb, cancel_arg);
559 else
560 return got_worktree_status(worktree, new, repo, 0,
561 can_edit, NULL, cancel_cb, cancel_arg);
564 static const struct got_error *
565 apply_patch(struct got_worktree *worktree, struct got_repository *repo,
566 struct got_patch *p, got_worktree_delete_cb delete_cb, void *delete_arg,
567 got_worktree_checkout_cb add_cb, void *add_arg, got_cancel_cb cancel_cb,
568 void *cancel_arg)
570 const struct got_error *err = NULL;
571 struct got_pathlist_head oldpaths, newpaths;
572 int file_renamed = 0;
573 char *oldpath = NULL, *newpath = NULL;
574 char *tmppath = NULL, *template = NULL;
575 FILE *tmp = NULL;
577 TAILQ_INIT(&oldpaths);
578 TAILQ_INIT(&newpaths);
580 err = build_pathlist(p->old != NULL ? p->old : p->new, &oldpath,
581 &oldpaths, worktree);
582 if (err)
583 goto done;
585 err = build_pathlist(p->new != NULL ? p->new : p->old, &newpath,
586 &newpaths, worktree);
587 if (err)
588 goto done;
590 if (p->old != NULL && p->new != NULL && strcmp(p->old, p->new))
591 file_renamed = 1;
593 err = check_file_status(p, file_renamed, worktree, repo, &oldpaths,
594 &newpaths, cancel_cb, cancel_arg);
595 if (err)
596 goto done;
598 if (asprintf(&template, "%s/got-patch",
599 got_worktree_get_root_path(worktree)) == -1) {
600 err = got_error_from_errno(template);
601 goto done;
604 err = got_opentemp_named(&tmppath, &tmp, template);
605 if (err)
606 goto done;
607 err = patch_file(p, oldpath, tmp);
608 if (err)
609 goto done;
611 if (p->old != NULL && p->new == NULL) {
612 err = got_worktree_schedule_delete(worktree, &oldpaths,
613 0, NULL, delete_cb, delete_arg, repo, 0, 0);
614 goto done;
617 if (rename(tmppath, newpath) == -1) {
618 err = got_error_from_errno3("rename", tmppath, newpath);
619 goto done;
622 if (file_renamed) {
623 err = got_worktree_schedule_delete(worktree, &oldpaths,
624 0, NULL, delete_cb, delete_arg, repo, 0, 0);
625 if (err == NULL)
626 err = got_worktree_schedule_add(worktree, &newpaths,
627 add_cb, add_arg, repo, 1);
628 } else if (p->old == NULL)
629 err = got_worktree_schedule_add(worktree, &newpaths,
630 add_cb, add_arg, repo, 1);
631 else
632 printf("M %s\n", oldpath); /* XXX */
634 done:
635 if (err != NULL && newpath != NULL && (file_renamed || p->old == NULL))
636 unlink(newpath);
637 free(template);
638 if (tmppath != NULL)
639 unlink(tmppath);
640 free(tmppath);
641 got_pathlist_free(&oldpaths);
642 got_pathlist_free(&newpaths);
643 free(oldpath);
644 free(newpath);
645 return err;
648 const struct got_error *
649 got_patch(int fd, struct got_worktree *worktree, struct got_repository *repo,
650 got_worktree_delete_cb delete_cb, void *delete_arg,
651 got_worktree_checkout_cb add_cb, void *add_arg, got_cancel_cb cancel_cb,
652 void *cancel_arg)
654 const struct got_error *err = NULL;
655 struct imsgbuf *ibuf;
656 int imsg_fds[2] = {-1, -1};
657 int done = 0;
658 pid_t pid;
660 ibuf = calloc(1, sizeof(*ibuf));
661 if (ibuf == NULL) {
662 err = got_error_from_errno("calloc");
663 goto done;
666 if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, imsg_fds) == -1) {
667 err = got_error_from_errno("socketpair");
668 goto done;
671 pid = fork();
672 if (pid == -1) {
673 err = got_error_from_errno("fork");
674 goto done;
675 } else if (pid == 0) {
676 got_privsep_exec_child(imsg_fds, GOT_PATH_PROG_READ_PATCH,
677 NULL);
678 /* not reached */
681 if (close(imsg_fds[1]) == -1) {
682 err = got_error_from_errno("close");
683 goto done;
685 imsg_fds[1] = -1;
686 imsg_init(ibuf, imsg_fds[0]);
688 err = send_patch(ibuf, fd);
689 fd = -1;
690 if (err)
691 goto done;
693 while (!done && err == NULL) {
694 struct got_patch p;
696 err = recv_patch(ibuf, &done, &p);
697 if (err || done)
698 break;
700 err = apply_patch(worktree, repo, &p, delete_cb, delete_arg,
701 add_cb, add_arg, cancel_cb, cancel_arg);
702 patch_free(&p);
703 if (err)
704 break;
707 done:
708 if (fd != -1 && close(fd) == -1 && err == NULL)
709 err = got_error_from_errno("close");
710 if (ibuf != NULL)
711 imsg_clear(ibuf);
712 if (imsg_fds[0] != -1 && close(imsg_fds[0]) == -1 && err == NULL)
713 err = got_error_from_errno("close");
714 if (imsg_fds[1] != -1 && close(imsg_fds[1]) == -1 && err == NULL)
715 err = got_error_from_errno("close");
716 return err;