2 * Copyright (c) 2022 Stefan Sperling <stsp@openbsd.org>
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.
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.
17 #include <sys/queue.h>
27 #include "got_compat.h"
29 #include "got_object.h"
30 #include "got_error.h"
32 #include "got_lib_delta.h"
33 #include "got_lib_inflate.h"
34 #include "got_lib_object.h"
35 #include "got_lib_object_qid.h"
36 #include "got_lib_object_idset.h"
37 #include "got_lib_object_parse.h"
39 #define GOT_OBJECT_IDSET_MIN_BUCKETS 64
41 struct got_object_idset
{
42 struct got_object_id_queue
*ids
;
46 #define GOT_OBJECT_IDSET_F_TRAVERSAL 0x01
47 #define GOT_OBJECT_IDSET_F_NOMEM 0x02
51 struct got_object_idset
*
52 got_object_idset_alloc(void)
54 struct got_object_idset
*set
;
57 set
= malloc(sizeof(*set
));
61 set
->ids
= calloc(sizeof(set
->ids
[0]), GOT_OBJECT_IDSET_MIN_BUCKETS
);
62 if (set
->ids
== NULL
) {
66 for (i
= 0; i
< GOT_OBJECT_IDSET_MIN_BUCKETS
; i
++)
67 STAILQ_INIT(&set
->ids
[i
]);
70 set
->nbuckets
= GOT_OBJECT_IDSET_MIN_BUCKETS
;
72 arc4random_buf(&set
->key
, sizeof(set
->key
));
77 got_object_idset_free(struct got_object_idset
*set
)
80 struct got_object_qid
*qid
;
82 for (i
= 0; i
< set
->nbuckets
; i
++) {
83 while (!STAILQ_EMPTY(&set
->ids
[i
])) {
84 qid
= STAILQ_FIRST(&set
->ids
[i
]);
85 STAILQ_REMOVE(&set
->ids
[i
], qid
, got_object_qid
, entry
);
86 got_object_qid_free(qid
);
89 /* User data should be freed by caller. */
95 idset_hash(struct got_object_idset
*set
, struct got_object_id
*id
)
97 return SipHash24(&set
->key
, id
->sha1
, sizeof(id
->sha1
));
100 static const struct got_error
*
101 idset_resize(struct got_object_idset
*set
, size_t nbuckets
)
103 struct got_object_id_queue
*ids
;
106 ids
= calloc(nbuckets
, sizeof(ids
[0]));
109 return got_error_from_errno("calloc");
110 /* Proceed with our current amount of hash buckets. */
111 set
->flags
|= GOT_OBJECT_IDSET_F_NOMEM
;
115 for (i
= 0; i
< nbuckets
; i
++)
116 STAILQ_INIT(&ids
[i
]);
118 arc4random_buf(&set
->key
, sizeof(set
->key
));
120 for (i
= 0; i
< set
->nbuckets
; i
++) {
121 while (!STAILQ_EMPTY(&set
->ids
[i
])) {
122 struct got_object_qid
*qid
;
124 qid
= STAILQ_FIRST(&set
->ids
[i
]);
125 STAILQ_REMOVE(&set
->ids
[i
], qid
, got_object_qid
, entry
);
126 idx
= idset_hash(set
, &qid
->id
) % nbuckets
;
127 STAILQ_INSERT_HEAD(&ids
[idx
], qid
, entry
);
133 set
->nbuckets
= nbuckets
;
137 static const struct got_error
*
138 idset_grow(struct got_object_idset
*set
)
142 if (set
->flags
& GOT_OBJECT_IDSET_F_NOMEM
)
145 if (set
->nbuckets
>= UINT_MAX
/ 2)
148 nbuckets
= set
->nbuckets
* 2;
150 return idset_resize(set
, nbuckets
);
153 const struct got_error
*
154 got_object_idset_add(struct got_object_idset
*set
, struct got_object_id
*id
,
157 const struct got_error
*err
;
158 struct got_object_qid
*qid
;
160 struct got_object_id_queue
*head
;
162 /* This function may resize the set. */
163 if (set
->flags
& GOT_OBJECT_IDSET_F_TRAVERSAL
)
164 return got_error_msg(GOT_ERR_NOT_IMPL
,
165 "cannot add elements to idset during traversal");
167 if (set
->totelem
== UINT_MAX
)
168 return got_error(GOT_ERR_NO_SPACE
);
170 err
= got_object_qid_alloc_partial(&qid
);
173 memcpy(&qid
->id
, id
, sizeof(qid
->id
));
176 idx
= idset_hash(set
, id
) % set
->nbuckets
;
177 head
= &set
->ids
[idx
];
178 STAILQ_INSERT_HEAD(head
, qid
, entry
);
181 if (set
->nbuckets
< set
->totelem
)
182 err
= idset_grow(set
);
187 static struct got_object_qid
*
188 find_element(struct got_object_idset
*set
, struct got_object_id
*id
)
190 uint64_t idx
= idset_hash(set
, id
) % set
->nbuckets
;
191 struct got_object_id_queue
*head
= &set
->ids
[idx
];
192 struct got_object_qid
*qid
;
194 STAILQ_FOREACH(qid
, head
, entry
) {
195 if (got_object_id_cmp(&qid
->id
, id
) == 0)
203 got_object_idset_get(struct got_object_idset
*set
, struct got_object_id
*id
)
205 struct got_object_qid
*qid
= find_element(set
, id
);
206 return qid
? qid
->data
: NULL
;
209 const struct got_error
*
210 got_object_idset_remove(void **data
, struct got_object_idset
*set
,
211 struct got_object_id
*id
)
214 struct got_object_id_queue
*head
;
215 struct got_object_qid
*qid
;
220 if (set
->totelem
== 0)
221 return got_error(GOT_ERR_NO_OBJ
);
224 /* Remove a "random" element. */
225 for (idx
= 0; idx
< set
->nbuckets
; idx
++) {
226 head
= &set
->ids
[idx
];
227 qid
= STAILQ_FIRST(head
);
232 idx
= idset_hash(set
, id
) % set
->nbuckets
;
233 head
= &set
->ids
[idx
];
234 STAILQ_FOREACH(qid
, head
, entry
) {
235 if (got_object_id_cmp(&qid
->id
, id
) == 0)
239 return got_error_no_obj(id
);
244 STAILQ_REMOVE(head
, qid
, got_object_qid
, entry
);
245 got_object_qid_free(qid
);
252 got_object_idset_contains(struct got_object_idset
*set
,
253 struct got_object_id
*id
)
255 struct got_object_qid
*qid
= find_element(set
, id
);
259 const struct got_error
*
260 got_object_idset_for_each(struct got_object_idset
*set
,
261 const struct got_error
*(*cb
)(struct got_object_id
*, void *, void *),
264 const struct got_error
*err
= NULL
;
265 struct got_object_id_queue
*head
;
266 struct got_object_qid
*qid
, *tmp
;
269 set
->flags
|= GOT_OBJECT_IDSET_F_TRAVERSAL
;
270 for (i
= 0; i
< set
->nbuckets
; i
++) {
272 STAILQ_FOREACH_SAFE(qid
, head
, entry
, tmp
) {
273 err
= (*cb
)(&qid
->id
, qid
->data
, arg
);
279 set
->flags
&= ~GOT_OBJECT_IDSET_F_TRAVERSAL
;
284 got_object_idset_num_elements(struct got_object_idset
*set
)