Blob


1 /*
2 * Copyright (c) 2020 Ori Bernstein
3 * Copyright (c) 2021, 2022 Stefan Sperling <stsp@openbsd.org>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
18 #include <sys/types.h>
19 #include <sys/queue.h>
20 #include <sys/uio.h>
22 #include <limits.h>
23 #include <stdio.h>
24 #include <stdint.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <time.h>
28 #include <imsg.h>
29 #include <inttypes.h>
30 #include <unistd.h>
32 #include "got_compat.h"
33 #include "got_error.h"
34 #include "got_cancel.h"
35 #include "got_object.h"
36 #include "got_reference.h"
37 #include "got_repository_admin.h"
38 #include "got_path.h"
40 #include "got_lib_delta.h"
41 #include "got_lib_object.h"
42 #include "got_lib_object_cache.h"
43 #include "got_lib_object_idset.h"
44 #include "got_lib_privsep.h"
45 #include "got_lib_ratelimit.h"
46 #include "got_lib_pack.h"
47 #include "got_lib_pack_create.h"
48 #include "got_lib_repository.h"
50 struct send_id_arg {
51 struct imsgbuf *ibuf;
52 struct got_object_id *ids[GOT_IMSG_OBJ_ID_LIST_MAX_NIDS];
53 size_t nids;
54 };
56 static const struct got_error *
57 send_id(struct got_object_id *id, void *data, void *arg)
58 {
59 const struct got_error *err = NULL;
60 struct send_id_arg *a = arg;
62 a->ids[a->nids++] = id;
64 if (a->nids >= GOT_IMSG_OBJ_ID_LIST_MAX_NIDS) {
65 err = got_privsep_send_object_idlist(a->ibuf, a->ids, a->nids);
66 if (err)
67 return err;
68 a->nids = 0;
69 }
71 return NULL;
72 }
74 static const struct got_error *
75 send_idset(struct imsgbuf *ibuf, struct got_object_idset *idset)
76 {
77 const struct got_error *err;
78 struct send_id_arg sia;
80 memset(&sia, 0, sizeof(sia));
81 sia.ibuf = ibuf;
82 err = got_object_idset_for_each(idset, send_id, &sia);
83 if (err)
84 return err;
86 if (sia.nids > 0) {
87 err = got_privsep_send_object_idlist(ibuf, sia.ids, sia.nids);
88 if (err)
89 return err;
90 }
92 return got_privsep_send_object_idlist_done(ibuf);
93 }
95 static const struct got_error *
96 recv_reused_delta(struct got_imsg_reused_delta *delta,
97 struct got_object_idset *idset, struct got_pack_metavec *v)
98 {
99 struct got_pack_meta *m, *base;
101 if (delta->delta_offset + delta->delta_size < delta->delta_offset ||
102 delta->delta_offset +
103 delta->delta_compressed_size < delta->delta_offset)
104 return got_error(GOT_ERR_BAD_PACKFILE);
106 m = got_object_idset_get(idset, &delta->id);
107 if (m == NULL)
108 return got_error(GOT_ERR_NO_OBJ);
110 base = got_object_idset_get(idset, &delta->base_id);
111 if (base == NULL)
112 return got_error(GOT_ERR_NO_OBJ);
114 m->delta_len = delta->delta_size;
115 m->delta_compressed_len = delta->delta_compressed_size;
116 m->delta_offset = 0;
117 m->prev = base;
118 m->size = delta->result_size;
119 m->reused_delta_offset = delta->delta_offset;
120 m->base_obj_id = got_object_id_dup(&delta->base_id);
121 if (m->base_obj_id == NULL)
122 return got_error_from_errno("got_object_id_dup");
124 return got_pack_add_meta(m, v);
127 const struct got_error *
128 got_pack_search_deltas(struct got_packidx **packidx, struct got_pack **pack,
129 struct got_pack_metavec *v, struct got_object_idset *idset,
130 int ncolored, int nfound, int ntrees, int ncommits,
131 struct got_repository *repo,
132 got_pack_progress_cb progress_cb, void *progress_arg,
133 struct got_ratelimit *rl, got_cancel_cb cancel_cb, void *cancel_arg)
135 const struct got_error *err = NULL;
136 struct got_imsg_reused_delta deltas[GOT_IMSG_REUSED_DELTAS_MAX_NDELTAS];
137 size_t ndeltas, i;
139 *packidx = NULL;
140 *pack = NULL;
142 err = got_pack_find_pack_for_reuse(packidx, repo);
143 if (err)
144 return err;
146 if (*packidx == NULL)
147 return NULL;
149 err = got_pack_cache_pack_for_packidx(pack, *packidx, repo);
150 if (err)
151 goto done;
153 if ((*pack)->privsep_child == NULL) {
154 err = got_pack_start_privsep_child(*pack, *packidx);
155 if (err)
156 goto done;
159 err = got_privsep_send_delta_reuse_req((*pack)->privsep_child->ibuf);
160 if (err)
161 goto done;
163 err = send_idset((*pack)->privsep_child->ibuf, idset);
164 if (err)
165 goto done;
167 for (;;) {
168 int done = 0;
170 if (cancel_cb) {
171 err = (*cancel_cb)(cancel_arg);
172 if (err)
173 break;
176 err = got_privsep_recv_reused_deltas(&done, deltas, &ndeltas,
177 (*pack)->privsep_child->ibuf);
178 if (err || done)
179 break;
181 for (i = 0; i < ndeltas; i++) {
182 struct got_imsg_reused_delta *delta = &deltas[i];
183 err = recv_reused_delta(delta, idset, v);
184 if (err)
185 goto done;
188 err = got_pack_report_progress(progress_cb, progress_arg, rl,
189 ncolored, nfound, ntrees, 0L, ncommits,
190 got_object_idset_num_elements(idset), v->nmeta, 0);
191 if (err)
192 break;
194 done:
195 return err;
198 struct recv_painted_commit_arg {
199 int *ncolored;
200 int *nqueued;
201 int *nskip;
202 struct got_object_id_queue *ids;
203 struct got_object_idset *keep;
204 struct got_object_idset *drop;
205 struct got_object_idset *skip;
206 got_pack_progress_cb progress_cb;
207 void *progress_arg;
208 struct got_ratelimit *rl;
209 got_cancel_cb cancel_cb;
210 void *cancel_arg;
211 };
213 static const struct got_error *
214 recv_painted_commit(void *arg, struct got_object_id *id, intptr_t color)
216 const struct got_error *err = NULL;
217 struct recv_painted_commit_arg *a = arg;
218 struct got_object_qid *qid, *tmp;
220 if (a->cancel_cb) {
221 err = a->cancel_cb(a->cancel_arg);
222 if (err)
223 return err;
226 switch (color) {
227 case COLOR_KEEP:
228 err = got_object_idset_add(a->keep, id, NULL);
229 if (err)
230 return err;
231 (*a->ncolored)++;
232 break;
233 case COLOR_DROP:
234 err = got_object_idset_add(a->drop, id, NULL);
235 if (err)
236 return err;
237 (*a->ncolored)++;
238 break;
239 case COLOR_SKIP:
240 err = got_object_idset_add(a->skip, id, NULL);
241 if (err)
242 return err;
243 break;
244 default:
245 /* should not happen */
246 return got_error_fmt(GOT_ERR_NOT_IMPL,
247 "%s invalid commit color %"PRIdPTR, __func__, color);
250 STAILQ_FOREACH_SAFE(qid, a->ids, entry, tmp) {
251 if (got_object_id_cmp(&qid->id, id) != 0)
252 continue;
253 STAILQ_REMOVE(a->ids, qid, got_object_qid, entry);
254 color = (intptr_t)qid->data;
255 got_object_qid_free(qid);
256 (*a->nqueued)--;
257 if (color == COLOR_SKIP)
258 (*a->nskip)--;
259 break;
262 return got_pack_report_progress(a->progress_cb, a->progress_arg, a->rl,
263 *a->ncolored, 0, 0, 0L, 0, 0, 0, 0);
266 static const struct got_error *
267 paint_packed_commits(struct got_pack *pack, struct got_object_id *id,
268 int idx, intptr_t color, int *ncolored, int *nqueued, int *nskip,
269 struct got_object_id_queue *ids,
270 struct got_object_idset *keep, struct got_object_idset *drop,
271 struct got_object_idset *skip, struct got_repository *repo,
272 got_pack_progress_cb progress_cb, void *progress_arg,
273 struct got_ratelimit *rl, got_cancel_cb cancel_cb, void *cancel_arg)
275 const struct got_error *err = NULL;
276 struct got_object_id_queue next_ids;
277 struct got_object_qid *qid, *tmp;
278 struct recv_painted_commit_arg arg;
280 STAILQ_INIT(&next_ids);
282 err = got_privsep_send_painting_request(pack->privsep_child->ibuf,
283 idx, id, color);
284 if (err)
285 return err;
287 arg.ncolored = ncolored;
288 arg.nqueued = nqueued;
289 arg.nskip = nskip;
290 arg.ids = ids;
291 arg.keep = keep;
292 arg.drop = drop;
293 arg.skip = skip;
294 arg.progress_cb = progress_cb;
295 arg.progress_arg = progress_arg;
296 arg.rl = rl;
297 arg.cancel_cb = cancel_cb;
298 arg.cancel_arg = cancel_arg;
299 err = got_privsep_recv_painted_commits(&next_ids,
300 recv_painted_commit, &arg, pack->privsep_child->ibuf);
301 if (err)
302 return err;
304 STAILQ_FOREACH_SAFE(qid, &next_ids, entry, tmp) {
305 struct got_object_qid *old_id;
306 intptr_t qcolor, ocolor;
307 STAILQ_FOREACH(old_id, ids, entry) {
308 if (got_object_id_cmp(&qid->id, &old_id->id))
309 continue;
310 qcolor = (intptr_t)qid->data;
311 ocolor = (intptr_t)old_id->data;
312 STAILQ_REMOVE(&next_ids, qid, got_object_qid, entry);
313 got_object_qid_free(qid);
314 qid = NULL;
315 if (qcolor != ocolor) {
316 got_pack_paint_commit(old_id, qcolor);
317 if (ocolor == COLOR_SKIP)
318 (*nskip)--;
319 else if (qcolor == COLOR_SKIP)
320 (*nskip)++;
322 break;
325 while (!STAILQ_EMPTY(&next_ids)) {
326 qid = STAILQ_FIRST(&next_ids);
327 STAILQ_REMOVE_HEAD(&next_ids, entry);
328 got_pack_paint_commit(qid, color);
329 STAILQ_INSERT_TAIL(ids, qid, entry);
330 (*nqueued)++;
331 if (color == COLOR_SKIP)
332 (*nskip)++;
335 return err;
338 const struct got_error *
339 got_pack_paint_commits(int *ncolored, struct got_object_id_queue *ids, int nids,
340 struct got_object_idset *keep, struct got_object_idset *drop,
341 struct got_object_idset *skip, struct got_repository *repo,
342 got_pack_progress_cb progress_cb, void *progress_arg,
343 struct got_ratelimit *rl, got_cancel_cb cancel_cb, void *cancel_arg)
345 const struct got_error *err = NULL;
346 struct got_commit_object *commit = NULL;
347 struct got_packidx *packidx = NULL;
348 struct got_pack *pack = NULL;
349 const struct got_object_id_queue *parents;
350 struct got_object_qid *qid = NULL;
351 int nqueued = nids, nskip = 0;
352 int idx;
354 while (!STAILQ_EMPTY(ids) && nskip != nqueued) {
355 intptr_t color;
357 if (cancel_cb) {
358 err = cancel_cb(cancel_arg);
359 if (err)
360 break;
363 qid = STAILQ_FIRST(ids);
364 STAILQ_REMOVE_HEAD(ids, entry);
365 nqueued--;
366 color = (intptr_t)qid->data;
367 if (color == COLOR_SKIP)
368 nskip--;
370 if (got_object_idset_contains(skip, &qid->id)) {
371 got_object_qid_free(qid);
372 qid = NULL;
373 continue;
375 if (color == COLOR_KEEP &&
376 got_object_idset_contains(keep, &qid->id)) {
377 got_object_qid_free(qid);
378 qid = NULL;
379 continue;
381 if (color == COLOR_DROP &&
382 got_object_idset_contains(drop, &qid->id)) {
383 got_object_qid_free(qid);
384 qid = NULL;
385 continue;
388 /* Pinned pack may have moved to different cache slot. */
389 pack = got_repo_get_pinned_pack(repo);
391 if (packidx && pack) {
392 idx = got_packidx_get_object_idx(packidx, &qid->id);
393 if (idx != -1) {
394 err = paint_packed_commits(pack, &qid->id,
395 idx, color, ncolored, &nqueued, &nskip,
396 ids, keep, drop, skip, repo,
397 progress_cb, progress_arg, rl,
398 cancel_cb, cancel_arg);
399 if (err)
400 break;
401 got_object_qid_free(qid);
402 qid = NULL;
403 continue;
407 switch (color) {
408 case COLOR_KEEP:
409 if (got_object_idset_contains(drop, &qid->id)) {
410 err = got_pack_paint_commit(qid, COLOR_SKIP);
411 if (err)
412 goto done;
413 } else
414 (*ncolored)++;
415 err = got_object_idset_add(keep, &qid->id, NULL);
416 if (err)
417 goto done;
418 break;
419 case COLOR_DROP:
420 if (got_object_idset_contains(keep, &qid->id)) {
421 err = got_pack_paint_commit(qid, COLOR_SKIP);
422 if (err)
423 goto done;
424 } else
425 (*ncolored)++;
426 err = got_object_idset_add(drop, &qid->id, NULL);
427 if (err)
428 goto done;
429 break;
430 case COLOR_SKIP:
431 if (!got_object_idset_contains(skip, &qid->id)) {
432 err = got_object_idset_add(skip, &qid->id,
433 NULL);
434 if (err)
435 goto done;
437 break;
438 default:
439 /* should not happen */
440 err = got_error_fmt(GOT_ERR_NOT_IMPL,
441 "%s invalid commit color %"PRIdPTR, __func__,
442 color);
443 goto done;
446 err = got_pack_report_progress(progress_cb, progress_arg, rl,
447 *ncolored, 0, 0, 0L, 0, 0, 0, 0);
448 if (err)
449 break;
451 err = got_object_open_as_commit(&commit, repo, &qid->id);
452 if (err)
453 break;
455 parents = got_object_commit_get_parent_ids(commit);
456 if (parents) {
457 struct got_object_qid *pid;
458 color = (intptr_t)qid->data;
459 STAILQ_FOREACH(pid, parents, entry) {
460 err = got_pack_queue_commit_id(ids, &pid->id,
461 color, repo);
462 if (err)
463 break;
464 nqueued++;
465 if (color == COLOR_SKIP)
466 nskip++;
470 if (pack == NULL && (commit->flags & GOT_COMMIT_FLAG_PACKED)) {
471 if (packidx == NULL) {
472 err = got_pack_find_pack_for_commit_painting(
473 &packidx, ids, nqueued, repo);
474 if (err)
475 goto done;
477 if (packidx != NULL) {
478 err = got_pack_cache_pack_for_packidx(&pack,
479 packidx, repo);
480 if (err)
481 goto done;
482 if (pack->privsep_child == NULL) {
483 err = got_pack_start_privsep_child(
484 pack, packidx);
485 if (err)
486 goto done;
488 err = got_privsep_init_commit_painting(
489 pack->privsep_child->ibuf);
490 if (err)
491 goto done;
492 err = send_idset(pack->privsep_child->ibuf,
493 keep);
494 if (err)
495 goto done;
496 err = send_idset(pack->privsep_child->ibuf, drop);
497 if (err)
498 goto done;
499 err = send_idset(pack->privsep_child->ibuf, skip);
500 if (err)
501 goto done;
502 err = got_repo_pin_pack(repo, packidx, pack);
503 if (err)
504 goto done;
508 got_object_commit_close(commit);
509 commit = NULL;
511 got_object_qid_free(qid);
512 qid = NULL;
514 done:
515 if (pack) {
516 const struct got_error *pack_err;
517 pack_err = got_privsep_send_painting_commits_done(
518 pack->privsep_child->ibuf);
519 if (err == NULL)
520 err = pack_err;
522 if (commit)
523 got_object_commit_close(commit);
524 got_object_qid_free(qid);
525 got_repo_unpin_pack(repo);
526 return err;
529 struct load_packed_obj_arg {
530 /* output parameters: */
531 struct got_object_id *id;
532 char *dpath;
533 time_t mtime;
535 /* input parameters: */
536 uint32_t seed;
537 int want_meta;
538 struct got_object_idset *idset;
539 struct got_object_idset *idset_exclude;
540 int loose_obj_only;
541 int *ncolored;
542 int *nfound;
543 int *ntrees;
544 got_pack_progress_cb progress_cb;
545 void *progress_arg;
546 struct got_ratelimit *rl;
547 got_cancel_cb cancel_cb;
548 void *cancel_arg;
549 };
551 static const struct got_error *
552 load_packed_commit_id(void *arg, time_t mtime, struct got_object_id *id,
553 struct got_repository *repo)
555 struct load_packed_obj_arg *a = arg;
557 if (got_object_idset_contains(a->idset, id) ||
558 got_object_idset_contains(a->idset_exclude, id))
559 return NULL;
561 return got_pack_add_object(a->want_meta,
562 a->want_meta ? a->idset : a->idset_exclude,
563 id, "", GOT_OBJ_TYPE_COMMIT, mtime, a->seed, a->loose_obj_only,
564 repo, a->ncolored, a->nfound, a->ntrees,
565 a->progress_cb, a->progress_arg, a->rl);
568 static const struct got_error *
569 load_packed_tree_ids(void *arg, struct got_tree_object *tree, time_t mtime,
570 struct got_object_id *id, const char *dpath, struct got_repository *repo)
572 const struct got_error *err;
573 struct load_packed_obj_arg *a = arg;
574 const char *relpath;
576 /*
577 * When we receive a tree's ID and path but not the tree itself,
578 * this tree object was not found in the pack file. This is the
579 * last time we are being called for this optimized traversal.
580 * Return from here and switch to loading objects the slow way.
581 */
582 if (tree == NULL) {
583 free(a->id);
584 a->id = got_object_id_dup(id);
585 if (a->id == NULL) {
586 err = got_error_from_errno("got_object_id_dup");
587 free(a->dpath);
588 a->dpath = NULL;
589 return err;
592 free(a->dpath);
593 a->dpath = strdup(dpath);
594 if (a->dpath == NULL) {
595 err = got_error_from_errno("strdup");
596 free(a->id);
597 a->id = NULL;
598 return err;
601 a->mtime = mtime;
602 return NULL;
605 if (got_object_idset_contains(a->idset, id) ||
606 got_object_idset_contains(a->idset_exclude, id))
607 return NULL;
609 relpath = dpath;
610 while (relpath[0] == '/')
611 relpath++;
613 err = got_pack_add_object(a->want_meta,
614 a->want_meta ? a->idset : a->idset_exclude,
615 id, relpath, GOT_OBJ_TYPE_TREE, mtime, a->seed,
616 a->loose_obj_only, repo, a->ncolored, a->nfound, a->ntrees,
617 a->progress_cb, a->progress_arg, a->rl);
618 if (err)
619 return err;
621 return got_pack_load_tree_entries(NULL, a->want_meta, a->idset,
622 a->idset_exclude, tree, dpath, mtime, a->seed, repo,
623 a->loose_obj_only, a->ncolored, a->nfound, a->ntrees,
624 a->progress_cb, a->progress_arg, a->rl,
625 a->cancel_cb, a->cancel_arg);
628 const struct got_error *
629 got_pack_load_packed_object_ids(int *found_all_objects,
630 struct got_object_id **ours, int nours,
631 struct got_object_id **theirs, int ntheirs,
632 int want_meta, uint32_t seed, struct got_object_idset *idset,
633 struct got_object_idset *idset_exclude, int loose_obj_only,
634 struct got_repository *repo, struct got_packidx *packidx,
635 int *ncolored, int *nfound, int *ntrees,
636 got_pack_progress_cb progress_cb, void *progress_arg,
637 struct got_ratelimit *rl, got_cancel_cb cancel_cb, void *cancel_arg)
639 const struct got_error *err = NULL;
640 struct load_packed_obj_arg lpa;
642 memset(&lpa, 0, sizeof(lpa));
643 lpa.seed = seed;
644 lpa.want_meta = want_meta;
645 lpa.idset = idset;
646 lpa.idset_exclude = idset_exclude;
647 lpa.loose_obj_only = loose_obj_only;
648 lpa.ncolored = ncolored;
649 lpa.nfound = nfound;
650 lpa.ntrees = ntrees;
651 lpa.progress_cb = progress_cb;
652 lpa.progress_arg = progress_arg;
653 lpa.rl = rl;
654 lpa.cancel_cb = cancel_cb;
655 lpa.cancel_arg = cancel_arg;
657 /* Attempt to load objects via got-read-pack, as far as possible. */
658 err = got_object_enumerate(found_all_objects, load_packed_commit_id,
659 load_packed_tree_ids, &lpa, ours, nours, theirs, ntheirs,
660 packidx, repo);
661 if (err)
662 return err;
664 if (lpa.id == NULL)
665 return NULL;
667 /*
668 * An incomplete tree hierarchy was present in the pack file
669 * and caused loading to be aborted.
670 * Continue loading trees the slow way.
671 */
672 err = got_pack_load_tree(want_meta, idset, idset_exclude,
673 lpa.id, lpa.dpath, lpa.mtime, seed, repo, loose_obj_only,
674 ncolored, nfound, ntrees, progress_cb, progress_arg, rl,
675 cancel_cb, cancel_arg);
676 free(lpa.id);
677 free(lpa.dpath);
678 return err;