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/uio.h>
31 #include <errno.h>
32 #include <limits.h>
33 #include <sha1.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, off_t *pos, 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 *pos = match;
316 *lineno = match_lineno;
317 if (fseek(orig, match, SEEK_SET) == -1)
318 err = got_error_from_errno("fseek");
321 free(line);
322 return err;
325 static const struct got_error *
326 test_hunk(FILE *orig, struct got_patch_hunk *h)
328 const struct got_error *err = NULL;
329 char *line = NULL;
330 size_t linesize = 0, i = 0;
331 ssize_t linelen;
333 for (i = 0; i < h->len; ++i) {
334 switch (*h->lines[i]) {
335 case '+':
336 continue;
337 case ' ':
338 case '-':
339 linelen = getline(&line, &linesize, orig);
340 if (linelen == -1) {
341 if (ferror(orig))
342 err = got_error_from_errno("getline");
343 else
344 err = got_error(
345 GOT_ERR_PATCH_DONT_APPLY);
346 goto done;
348 if (strcmp(h->lines[i]+1, line)) {
349 err = got_error(GOT_ERR_PATCH_DONT_APPLY);
350 goto done;
352 break;
356 done:
357 free(line);
358 return err;
361 static const struct got_error *
362 apply_hunk(FILE *tmp, struct got_patch_hunk *h, long *lineno)
364 size_t i = 0;
366 for (i = 0; i < h->len; ++i) {
367 switch (*h->lines[i]) {
368 case ' ':
369 if (fprintf(tmp, "%s", h->lines[i]+1) < 0)
370 return got_error_from_errno("fprintf");
371 /* fallthrough */
372 case '-':
373 (*lineno)++;
374 break;
375 case '+':
376 if (fprintf(tmp, "%s", h->lines[i]+1) < 0)
377 return got_error_from_errno("fprintf");
378 break;
381 return NULL;
384 static const struct got_error *
385 patch_file(struct got_patch *p, const char *path, FILE *tmp)
387 const struct got_error *err = NULL;
388 struct got_patch_hunk *h;
389 size_t i;
390 long lineno = 0;
391 FILE *orig;
392 off_t copypos, pos;
393 char *line = NULL;
394 size_t linesize = 0;
395 ssize_t linelen;
397 if (p->old == NULL) { /* create */
398 h = STAILQ_FIRST(&p->head);
399 if (h == NULL || STAILQ_NEXT(h, entries) != NULL)
400 return got_error(GOT_ERR_PATCH_MALFORMED);
401 for (i = 0; i < h->len; ++i) {
402 if (fprintf(tmp, "%s", h->lines[i]+1) < 0)
403 return got_error_from_errno("fprintf");
405 return err;
408 if ((orig = fopen(path, "r")) == NULL) {
409 err = got_error_from_errno2("fopen", path);
410 goto done;
413 copypos = 0;
414 STAILQ_FOREACH(h, &p->head, entries) {
415 if (h->lines == NULL)
416 break;
418 tryagain:
419 err = locate_hunk(orig, h, &pos, &lineno);
420 if (err != NULL)
421 goto done;
422 err = copy(tmp, orig, copypos, pos);
423 if (err != NULL)
424 goto done;
425 copypos = pos;
427 err = test_hunk(orig, h);
428 if (err != NULL && err->code == GOT_ERR_PATCH_DONT_APPLY) {
429 /*
430 * try to apply the hunk again starting the search
431 * after the previous partial match.
432 */
433 if (fseek(orig, pos, SEEK_SET) == -1) {
434 err = got_error_from_errno("fseek");
435 goto done;
437 linelen = getline(&line, &linesize, orig);
438 if (linelen == -1) {
439 err = got_error_from_errno("getline");
440 goto done;
442 lineno++;
443 goto tryagain;
445 if (err != NULL)
446 goto done;
448 err = apply_hunk(tmp, h, &lineno);
449 if (err != NULL)
450 goto done;
452 copypos = ftello(orig);
453 if (copypos == -1) {
454 err = got_error_from_errno("ftello");
455 goto done;
459 if (!feof(orig))
460 err = copy(tmp, orig, copypos, -1);
462 done:
463 if (orig != NULL)
464 fclose(orig);
465 return err;
468 static const struct got_error *
469 build_pathlist(const char *p, char **path, struct got_pathlist_head *head,
470 struct got_worktree *worktree)
472 const struct got_error *err;
473 struct got_pathlist_entry *pe;
475 err = got_worktree_resolve_path(path, worktree, p);
476 if (err == NULL)
477 err = got_pathlist_insert(&pe, head, *path, NULL);
478 return err;
481 static const struct got_error *
482 can_rm(void *arg, unsigned char status, unsigned char staged_status,
483 const char *path, struct got_object_id *blob_id,
484 struct got_object_id *staged_blob_id, struct got_object_id *commit_id,
485 int dirfd, const char *de_name)
487 if (status == GOT_STATUS_NONEXISTENT)
488 return got_error_set_errno(ENOENT, path);
489 if (status != GOT_STATUS_NO_CHANGE &&
490 status != GOT_STATUS_ADD &&
491 status != GOT_STATUS_MODIFY &&
492 status != GOT_STATUS_MODE_CHANGE)
493 return got_error_path(path, GOT_ERR_FILE_STATUS);
494 if (staged_status == GOT_STATUS_DELETE)
495 return got_error_path(path, GOT_ERR_FILE_STATUS);
496 return NULL;
499 static const struct got_error *
500 can_add(void *arg, unsigned char status, unsigned char staged_status,
501 const char *path, struct got_object_id *blob_id,
502 struct got_object_id *staged_blob_id, struct got_object_id *commit_id,
503 int dirfd, const char *de_name)
505 if (status != GOT_STATUS_NONEXISTENT)
506 return got_error_path(path, GOT_ERR_FILE_STATUS);
507 return NULL;
510 static const struct got_error *
511 can_edit(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_set_errno(ENOENT, path);
518 if (status != GOT_STATUS_NO_CHANGE &&
519 status != GOT_STATUS_ADD &&
520 status != GOT_STATUS_MODIFY)
521 return got_error_path(path, GOT_ERR_FILE_STATUS);
522 if (staged_status == GOT_STATUS_DELETE)
523 return got_error_path(path, GOT_ERR_FILE_STATUS);
524 return NULL;
527 static const struct got_error *
528 check_file_status(struct got_patch *p, int file_renamed,
529 struct got_worktree *worktree, struct got_repository *repo,
530 struct got_pathlist_head *old, struct got_pathlist_head *new,
531 got_cancel_cb cancel_cb, void *cancel_arg)
533 static const struct got_error *err;
535 if (p->old != NULL && p->new == NULL)
536 return got_worktree_status(worktree, old, repo, 0,
537 can_rm, NULL, cancel_cb, cancel_arg);
538 else if (file_renamed) {
539 err = got_worktree_status(worktree, old, repo, 0,
540 can_rm, NULL, cancel_cb, cancel_arg);
541 if (err)
542 return err;
543 return got_worktree_status(worktree, new, repo, 0,
544 can_add, NULL, cancel_cb, cancel_arg);
545 } else if (p->old == NULL)
546 return got_worktree_status(worktree, new, repo, 0,
547 can_add, NULL, cancel_cb, cancel_arg);
548 else
549 return got_worktree_status(worktree, new, repo, 0,
550 can_edit, NULL, cancel_cb, cancel_arg);
553 static const struct got_error *
554 apply_patch(struct got_worktree *worktree, struct got_repository *repo,
555 struct got_patch *p, got_worktree_delete_cb delete_cb, void *delete_arg,
556 got_worktree_checkout_cb add_cb, void *add_arg, got_cancel_cb cancel_cb,
557 void *cancel_arg)
559 const struct got_error *err = NULL;
560 struct got_pathlist_head oldpaths, newpaths;
561 int file_renamed = 0;
562 char *oldpath = NULL, *newpath = NULL;
563 char *tmppath = NULL, *template = NULL;
564 FILE *tmp = NULL;
566 TAILQ_INIT(&oldpaths);
567 TAILQ_INIT(&newpaths);
569 err = build_pathlist(p->old != NULL ? p->old : p->new, &oldpath,
570 &oldpaths, worktree);
571 if (err)
572 goto done;
574 err = build_pathlist(p->new != NULL ? p->new : p->old, &newpath,
575 &newpaths, worktree);
576 if (err)
577 goto done;
579 if (p->old != NULL && p->new != NULL && strcmp(p->old, p->new))
580 file_renamed = 1;
582 err = check_file_status(p, file_renamed, worktree, repo, &oldpaths,
583 &newpaths, cancel_cb, cancel_arg);
584 if (err)
585 goto done;
587 if (p->old != NULL && p->new == NULL) {
588 /*
589 * special case: delete a file. don't try to match
590 * the lines but just schedule the removal.
591 */
592 err = got_worktree_schedule_delete(worktree, &oldpaths,
593 0, NULL, delete_cb, delete_arg, repo, 0, 0);
594 goto done;
597 if (asprintf(&template, "%s/got-patch",
598 got_worktree_get_root_path(worktree)) == -1) {
599 err = got_error_from_errno(template);
600 goto done;
603 err = got_opentemp_named(&tmppath, &tmp, template);
604 if (err)
605 goto done;
606 err = patch_file(p, oldpath, tmp);
607 if (err)
608 goto done;
610 if (rename(tmppath, newpath) == -1) {
611 err = got_error_from_errno3("rename", tmppath, newpath);
612 goto done;
615 if (file_renamed) {
616 err = got_worktree_schedule_delete(worktree, &oldpaths,
617 0, NULL, delete_cb, delete_arg, repo, 0, 0);
618 if (err == NULL)
619 err = got_worktree_schedule_add(worktree, &newpaths,
620 add_cb, add_arg, repo, 1);
621 } else if (p->old == NULL)
622 err = got_worktree_schedule_add(worktree, &newpaths,
623 add_cb, add_arg, repo, 1);
624 else
625 printf("M %s\n", oldpath); /* XXX */
627 done:
628 if (err != NULL && newpath != NULL && (file_renamed || p->old == NULL))
629 unlink(newpath);
630 free(template);
631 if (tmppath != NULL)
632 unlink(tmppath);
633 free(tmppath);
634 got_pathlist_free(&oldpaths);
635 got_pathlist_free(&newpaths);
636 free(oldpath);
637 free(newpath);
638 return err;
641 const struct got_error *
642 got_patch(int fd, struct got_worktree *worktree, struct got_repository *repo,
643 got_worktree_delete_cb delete_cb, void *delete_arg,
644 got_worktree_checkout_cb add_cb, void *add_arg, got_cancel_cb cancel_cb,
645 void *cancel_arg)
647 const struct got_error *err = NULL;
648 struct imsgbuf *ibuf;
649 int imsg_fds[2] = {-1, -1};
650 int done = 0;
651 pid_t pid;
653 ibuf = calloc(1, sizeof(*ibuf));
654 if (ibuf == NULL) {
655 err = got_error_from_errno("calloc");
656 goto done;
659 if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, imsg_fds) == -1) {
660 err = got_error_from_errno("socketpair");
661 goto done;
664 pid = fork();
665 if (pid == -1) {
666 err = got_error_from_errno("fork");
667 goto done;
668 } else if (pid == 0) {
669 got_privsep_exec_child(imsg_fds, GOT_PATH_PROG_READ_PATCH,
670 NULL);
671 /* not reached */
674 if (close(imsg_fds[1]) == -1) {
675 err = got_error_from_errno("close");
676 goto done;
678 imsg_fds[1] = -1;
679 imsg_init(ibuf, imsg_fds[0]);
681 err = send_patch(ibuf, fd);
682 fd = -1;
683 if (err)
684 goto done;
686 while (!done && err == NULL) {
687 struct got_patch p;
689 err = recv_patch(ibuf, &done, &p);
690 if (err || done)
691 break;
693 err = apply_patch(worktree, repo, &p, delete_cb, delete_arg,
694 add_cb, add_arg, cancel_cb, cancel_arg);
695 patch_free(&p);
696 if (err)
697 break;
700 done:
701 if (fd != -1 && close(fd) == -1 && err == NULL)
702 err = got_error_from_errno("close");
703 if (ibuf != NULL)
704 imsg_clear(ibuf);
705 if (imsg_fds[0] != -1 && close(imsg_fds[0]) == -1 && err == NULL)
706 err = got_error_from_errno("close");
707 if (imsg_fds[1] != -1 && close(imsg_fds[1]) == -1 && err == NULL)
708 err = got_error_from_errno("close");
709 return err;