/src/clib/src/common/clib-package.c
Line | Count | Source (jump to first uncovered line) |
1 | | // |
2 | | // clib-package.c |
3 | | // |
4 | | // Copyright (c) 2014 Stephen Mathieson |
5 | | // Copyright (c) 2014-2020 clib authors |
6 | | // MIT license |
7 | | // |
8 | | |
9 | | #if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) |
10 | | #include <unistd.h> |
11 | | #endif |
12 | | |
13 | | #include "asprintf/asprintf.h" |
14 | | #include "clib-cache.h" |
15 | | #include "clib-package.h" |
16 | | #include "clib-settings.h" |
17 | | #include "debug/debug.h" |
18 | | #include "fs/fs.h" |
19 | | #include "hash/hash.h" |
20 | | #include "http-get/http-get.h" |
21 | | #include "logger/logger.h" |
22 | | #include "mkdirp/mkdirp.h" |
23 | | #include "parse-repo/parse-repo.h" |
24 | | #include "parson/parson.h" |
25 | | #include "path-join/path-join.h" |
26 | | #include "rimraf/rimraf.h" |
27 | | #include "strdup/strdup.h" |
28 | | #include "substr/substr.h" |
29 | | #include "tempdir/tempdir.h" |
30 | | #include <curl/curl.h> |
31 | | #include <libgen.h> |
32 | | #include <limits.h> |
33 | | #include <stdarg.h> |
34 | | #include <stdio.h> |
35 | | #include <stdlib.h> |
36 | | #include <string.h> |
37 | | |
38 | | #ifdef HAVE_PTHREADS |
39 | | #include <pthread.h> |
40 | | #endif |
41 | | |
42 | | #ifndef DEFAULT_REPO_VERSION |
43 | 0 | #define DEFAULT_REPO_VERSION "master" |
44 | | #endif |
45 | | |
46 | | #ifndef DEFAULT_REPO_OWNER |
47 | 4 | #define DEFAULT_REPO_OWNER "clibs" |
48 | | #endif |
49 | | |
50 | 0 | #define GITHUB_CONTENT_URL "https://raw.githubusercontent.com/" |
51 | 0 | #define GITHUB_CONTENT_URL_WITH_TOKEN "https://%s@raw.githubusercontent.com/" |
52 | | |
53 | | #if defined(_WIN32) || defined(WIN32) || defined(__MINGW32__) || \ |
54 | | defined(__MINGW64__) |
55 | | #define setenv(k, v, _) _putenv_s(k, v) |
56 | | #define realpath(a, b) _fullpath(a, b, strlen(a)) |
57 | | #endif |
58 | | |
59 | | static hash_t *visited_packages = 0; |
60 | | |
61 | | #ifdef HAVE_PTHREADS |
62 | | typedef struct fetch_package_file_thread_data fetch_package_file_thread_data_t; |
63 | | struct fetch_package_file_thread_data { |
64 | | clib_package_t *pkg; |
65 | | const char *dir; |
66 | | char *file; |
67 | | int verbose; |
68 | | pthread_t thread; |
69 | | pthread_attr_t attr; |
70 | | void *data; |
71 | | }; |
72 | | |
73 | | typedef struct clib_package_lock clib_package_lock_t; |
74 | | struct clib_package_lock { |
75 | | pthread_mutex_t mutex; |
76 | | }; |
77 | | |
78 | | static clib_package_lock_t lock = {PTHREAD_MUTEX_INITIALIZER}; |
79 | | |
80 | | #endif |
81 | | |
82 | | CURLSH *clib_package_curl_share; |
83 | | debug_t _debugger; |
84 | | |
85 | | #define _debug(...) \ |
86 | 23 | ({ \ |
87 | 23 | if (!(_debugger.name)) \ |
88 | 23 | debug_init(&_debugger, "clib-package"); \ |
89 | 23 | debug(&_debugger, __VA_ARGS__); \ |
90 | 23 | }) |
91 | | |
92 | | #define E_FORMAT(...) \ |
93 | 0 | ({ \ |
94 | 0 | rc = asprintf(__VA_ARGS__); \ |
95 | 0 | if (-1 == rc) \ |
96 | 0 | goto cleanup; \ |
97 | 0 | }); |
98 | | |
99 | | static clib_package_opts_t opts = { |
100 | | #ifdef HAVE_PTHREADS |
101 | | .concurrency = MAX_THREADS, |
102 | | #endif |
103 | | .skip_cache = 1, |
104 | | .prefix = 0, |
105 | | .global = 0, |
106 | | .force = 0, |
107 | | .token = 0, |
108 | | }; |
109 | | |
110 | | /** |
111 | | * Pre-declare prototypes. |
112 | | */ |
113 | | |
114 | | static inline char *json_object_get_string_safe(JSON_Object *, const char *); |
115 | | |
116 | | static inline char *json_array_get_string_safe(JSON_Array *, int); |
117 | | |
118 | | static inline char *clib_package_file_url(const char *, const char *); |
119 | | |
120 | | static inline char *clib_package_slug(const char *, const char *, const char *); |
121 | | |
122 | | static inline char *clib_package_repo(const char *, const char *); |
123 | | |
124 | | static inline list_t *parse_package_deps(JSON_Object *); |
125 | | |
126 | | static inline int install_packages(list_t *, const char *, int); |
127 | | |
128 | 0 | void clib_package_set_opts(clib_package_opts_t o) { |
129 | 0 | if (1 == opts.skip_cache && 0 == o.skip_cache) { |
130 | 0 | opts.skip_cache = 0; |
131 | 0 | } else if (0 == opts.skip_cache && 1 == o.skip_cache) { |
132 | 0 | opts.skip_cache = 1; |
133 | 0 | } |
134 | |
|
135 | 0 | if (1 == opts.global && 0 == o.global) { |
136 | 0 | opts.global = 0; |
137 | 0 | } else if (0 == opts.global && 1 == o.global) { |
138 | 0 | opts.global = 1; |
139 | 0 | } |
140 | |
|
141 | 0 | if (1 == opts.force && 0 == o.force) { |
142 | 0 | opts.force = 0; |
143 | 0 | } else if (0 == opts.force && 1 == o.force) { |
144 | 0 | opts.force = 1; |
145 | 0 | } |
146 | |
|
147 | 0 | if (0 != o.prefix) { |
148 | 0 | if (0 == strlen(o.prefix)) { |
149 | 0 | opts.prefix = 0; |
150 | 0 | } else { |
151 | 0 | opts.prefix = o.prefix; |
152 | 0 | } |
153 | 0 | } |
154 | |
|
155 | 0 | if (0 != o.token) { |
156 | 0 | if (0 == strlen(o.token)) { |
157 | 0 | opts.token = 0; |
158 | 0 | } else { |
159 | 0 | opts.token = o.token; |
160 | 0 | } |
161 | 0 | } |
162 | |
|
163 | 0 | if (o.concurrency) { |
164 | 0 | opts.concurrency = o.concurrency; |
165 | 0 | } else if (o.concurrency < 0) { |
166 | 0 | opts.concurrency = 0; |
167 | 0 | } |
168 | |
|
169 | 0 | if (opts.concurrency < 0) { |
170 | 0 | opts.concurrency = 0; |
171 | 0 | } |
172 | 0 | } |
173 | | |
174 | | /** |
175 | | * Create a copy of the result of a `json_object_get_string` |
176 | | * invocation. This allows us to `json_value_free()` the |
177 | | * parent `JSON_Value` without destroying the string. |
178 | | */ |
179 | | |
180 | | static inline char *json_object_get_string_safe(JSON_Object *obj, |
181 | 59 | const char *key) { |
182 | 59 | const char *val = json_object_get_string(obj, key); |
183 | 59 | if (!val) |
184 | 55 | return NULL; |
185 | 4 | return strdup(val); |
186 | 59 | } |
187 | | |
188 | | /** |
189 | | * Create a copy of the result of a `json_array_get_string` |
190 | | * invocation. This allows us to `json_value_free()` the |
191 | | * parent `JSON_Value` without destroying the string. |
192 | | */ |
193 | | |
194 | 127 | static inline char *json_array_get_string_safe(JSON_Array *array, int index) { |
195 | 127 | const char *val = json_array_get_string(array, index); |
196 | 127 | if (!val) |
197 | 0 | return NULL; |
198 | 127 | return strdup(val); |
199 | 127 | } |
200 | | |
201 | | /** |
202 | | * Build a URL for `file` of the package belonging to `url` |
203 | | */ |
204 | | |
205 | 0 | static inline char *clib_package_file_url(const char *url, const char *file) { |
206 | 0 | if (!url || !file) |
207 | 0 | return NULL; |
208 | | |
209 | 0 | int size = strlen(url) + 1 // / |
210 | 0 | + strlen(file) + 1 // \0 |
211 | 0 | ; |
212 | |
|
213 | 0 | char *res = malloc(size); |
214 | 0 | if (res) { |
215 | 0 | memset(res, 0, size); |
216 | 0 | sprintf(res, "%s/%s", url, file); |
217 | 0 | } |
218 | 0 | return res; |
219 | 0 | } |
220 | | |
221 | | /** |
222 | | * Build a slug |
223 | | */ |
224 | | |
225 | | static inline char *clib_package_slug(const char *author, const char *name, |
226 | 0 | const char *version) { |
227 | 0 | int size = strlen(author) + 1 // / |
228 | 0 | + strlen(name) + 1 // @ |
229 | 0 | + strlen(version) + 1 // \0 |
230 | 0 | ; |
231 | |
|
232 | 0 | char *slug = malloc(size); |
233 | 0 | if (slug) { |
234 | 0 | memset(slug, '\0', size); |
235 | 0 | sprintf(slug, "%s/%s@%s", author, name, version); |
236 | 0 | } |
237 | 0 | return slug; |
238 | 0 | } |
239 | | |
240 | | /** |
241 | | * Load a local package with a manifest. |
242 | | */ |
243 | | |
244 | | clib_package_t *clib_package_load_from_manifest(const char *manifest, |
245 | 12 | int verbose) { |
246 | 12 | clib_package_t *pkg = NULL; |
247 | | |
248 | 12 | if (-1 == fs_exists(manifest)) { |
249 | 0 | logger_error("error", "Missing %s", manifest); |
250 | 0 | return NULL; |
251 | 0 | } |
252 | | |
253 | 12 | logger_info("info", "reading local %s", manifest); |
254 | | |
255 | 12 | char *json = fs_read(manifest); |
256 | 12 | if (NULL == json) |
257 | 0 | goto e1; |
258 | | |
259 | 12 | pkg = clib_package_new(json, verbose); |
260 | | |
261 | 12 | e1: |
262 | 12 | free(json); |
263 | | |
264 | 12 | return pkg; |
265 | 12 | } |
266 | | |
267 | | /** |
268 | | * Load a manifest from the current path. |
269 | | */ |
270 | | |
271 | 0 | clib_package_t *clib_package_load_local_manifest(int verbose) { |
272 | 0 | clib_package_t *pkg = NULL; |
273 | 0 | int i = 0; |
274 | |
|
275 | 0 | do { |
276 | 0 | const char *name = NULL; |
277 | 0 | name = manifest_names[i]; |
278 | 0 | pkg = clib_package_load_from_manifest(name, verbose); |
279 | 0 | } while (pkg == NULL && NULL != manifest_names[++i]); |
280 | |
|
281 | 0 | return pkg; |
282 | 0 | } |
283 | | |
284 | | /** |
285 | | * Build a repo |
286 | | */ |
287 | | |
288 | 0 | static inline char *clib_package_repo(const char *author, const char *name) { |
289 | 0 | int size = strlen(author) + 1 // / |
290 | 0 | + strlen(name) + 1 // \0 |
291 | 0 | ; |
292 | |
|
293 | 0 | char *repo = malloc(size); |
294 | 0 | if (repo) { |
295 | 0 | memset(repo, '\0', size); |
296 | 0 | sprintf(repo, "%s/%s", author, name); |
297 | 0 | } |
298 | 0 | return repo; |
299 | 0 | } |
300 | | |
301 | | /** |
302 | | * Parse the dependencies in the given `obj` into a `list_t` |
303 | | */ |
304 | | |
305 | 1 | static inline list_t *parse_package_deps(JSON_Object *obj) { |
306 | 1 | list_t *list = NULL; |
307 | | |
308 | 1 | if (!obj) |
309 | 0 | goto done; |
310 | 1 | if (!(list = list_new())) |
311 | 0 | goto done; |
312 | 1 | list->free = clib_package_dependency_free; |
313 | | |
314 | 5 | for (unsigned int i = 0; i < json_object_get_count(obj); i++) { |
315 | 4 | const char *name = NULL; |
316 | 4 | char *version = NULL; |
317 | 4 | clib_package_dependency_t *dep = NULL; |
318 | 4 | int error = 1; |
319 | | |
320 | 4 | if (!(name = json_object_get_name(obj, i))) |
321 | 0 | goto loop_cleanup; |
322 | 4 | if (!(version = json_object_get_string_safe(obj, name))) |
323 | 0 | goto loop_cleanup; |
324 | 4 | if (!(dep = clib_package_dependency_new(name, version))) |
325 | 0 | goto loop_cleanup; |
326 | | |
327 | 4 | list_node_t *dep_node = list_node_new(dep); |
328 | | // note: if we fail to allocate the node itself, |
329 | | // `dep` will never be pushed on the list |
330 | 4 | if (!dep_node) { |
331 | 0 | clib_package_dependency_free(dep); |
332 | 0 | goto loop_cleanup; |
333 | 0 | } |
334 | | |
335 | 4 | if (!(list_rpush(list, dep_node))) |
336 | 0 | goto loop_cleanup; |
337 | | |
338 | 4 | error = 0; |
339 | | |
340 | 4 | loop_cleanup: |
341 | 4 | if (version) |
342 | 4 | free(version); |
343 | 4 | if (error) { |
344 | 0 | list_destroy(list); |
345 | 0 | list = NULL; |
346 | 0 | break; |
347 | 0 | } |
348 | 4 | } |
349 | | |
350 | 1 | done: |
351 | 1 | return list; |
352 | 1 | } |
353 | | |
354 | 0 | static inline int install_packages(list_t *list, const char *dir, int verbose) { |
355 | 0 | list_node_t *node = NULL; |
356 | 0 | list_iterator_t *iterator = NULL; |
357 | 0 | int rc = -1; |
358 | 0 | list_t *freelist = NULL; |
359 | |
|
360 | 0 | if (!list || !dir) |
361 | 0 | goto cleanup; |
362 | | |
363 | 0 | iterator = list_iterator_new(list, LIST_HEAD); |
364 | 0 | if (NULL == iterator) |
365 | 0 | goto cleanup; |
366 | | |
367 | 0 | freelist = list_new(); |
368 | |
|
369 | 0 | while ((node = list_iterator_next(iterator))) { |
370 | 0 | clib_package_dependency_t *dep = NULL; |
371 | 0 | char *slug = NULL; |
372 | 0 | clib_package_t *pkg = NULL; |
373 | 0 | int error = 1; |
374 | |
|
375 | 0 | dep = (clib_package_dependency_t *)node->val; |
376 | 0 | slug = clib_package_slug(dep->author, dep->name, dep->version); |
377 | 0 | if (NULL == slug) |
378 | 0 | goto loop_cleanup; |
379 | | |
380 | 0 | pkg = clib_package_new_from_slug(slug, verbose); |
381 | 0 | if (NULL == pkg) |
382 | 0 | goto loop_cleanup; |
383 | | |
384 | 0 | if (-1 == clib_package_install(pkg, dir, verbose)) |
385 | 0 | goto loop_cleanup; |
386 | | |
387 | 0 | list_rpush(freelist, list_node_new(pkg)); |
388 | 0 | error = 0; |
389 | |
|
390 | 0 | loop_cleanup: |
391 | 0 | if (slug) |
392 | 0 | free(slug); |
393 | 0 | if (error) { |
394 | 0 | list_iterator_destroy(iterator); |
395 | 0 | iterator = NULL; |
396 | 0 | rc = -1; |
397 | 0 | goto cleanup; |
398 | 0 | } |
399 | 0 | } |
400 | | |
401 | 0 | rc = 0; |
402 | |
|
403 | 0 | cleanup: |
404 | 0 | if (iterator) |
405 | 0 | list_iterator_destroy(iterator); |
406 | |
|
407 | 0 | if (freelist) { |
408 | 0 | iterator = list_iterator_new(freelist, LIST_HEAD); |
409 | 0 | while ((node = list_iterator_next(iterator))) { |
410 | 0 | clib_package_t *pkg = node->val; |
411 | 0 | if (pkg) |
412 | 0 | clib_package_free(pkg); |
413 | 0 | } |
414 | 0 | list_iterator_destroy(iterator); |
415 | 0 | list_destroy(freelist); |
416 | 0 | } |
417 | 0 | return rc; |
418 | 0 | } |
419 | | |
420 | | #ifdef HAVE_PTHREADS |
421 | | static void curl_lock_callback(CURL *handle, curl_lock_data data, |
422 | 0 | curl_lock_access access, void *userptr) { |
423 | 0 | pthread_mutex_lock(&lock.mutex); |
424 | 0 | } |
425 | | |
426 | | static void curl_unlock_callback(CURL *handle, curl_lock_data data, |
427 | 0 | curl_lock_access access, void *userptr) { |
428 | 0 | pthread_mutex_unlock(&lock.mutex); |
429 | 0 | } |
430 | | |
431 | 0 | static void init_curl_share() { |
432 | 0 | if (0 == clib_package_curl_share) { |
433 | 0 | pthread_mutex_lock(&lock.mutex); |
434 | 0 | clib_package_curl_share = curl_share_init(); |
435 | 0 | curl_share_setopt(clib_package_curl_share, CURLSHOPT_SHARE, |
436 | 0 | CURL_LOCK_DATA_CONNECT); |
437 | 0 | curl_share_setopt(clib_package_curl_share, CURLSHOPT_LOCKFUNC, |
438 | 0 | curl_lock_callback); |
439 | 0 | curl_share_setopt(clib_package_curl_share, CURLSHOPT_UNLOCKFUNC, |
440 | 0 | curl_unlock_callback); |
441 | 0 | curl_share_setopt(clib_package_curl_share, CURLOPT_NETRC, |
442 | 0 | CURL_NETRC_OPTIONAL); |
443 | 0 | pthread_mutex_unlock(&lock.mutex); |
444 | 0 | } |
445 | 0 | } |
446 | | #endif |
447 | | |
448 | | /** |
449 | | * Create a new clib package from the given `json` |
450 | | */ |
451 | | |
452 | 12 | clib_package_t *clib_package_new(const char *json, int verbose) { |
453 | 12 | clib_package_t *pkg = NULL; |
454 | 12 | JSON_Value *root = NULL; |
455 | 12 | JSON_Object *json_object = NULL; |
456 | 12 | JSON_Array *src = NULL; |
457 | 12 | JSON_Object *deps = NULL; |
458 | 12 | JSON_Object *devs = NULL; |
459 | 12 | int error = 1; |
460 | | |
461 | 12 | if (!json) { |
462 | 0 | if (verbose) { |
463 | 0 | logger_error("error", "missing JSON to parse"); |
464 | 0 | } |
465 | 0 | goto cleanup; |
466 | 0 | } |
467 | | |
468 | 12 | if (!(root = json_parse_string(json))) { |
469 | 7 | if (verbose) { |
470 | 0 | logger_error("error", "unable to parse JSON"); |
471 | 0 | } |
472 | 7 | goto cleanup; |
473 | 7 | } |
474 | | |
475 | 5 | if (!(json_object = json_value_get_object(root))) { |
476 | 0 | if (verbose) { |
477 | 0 | logger_error("error", "invalid clib.json or package.json file"); |
478 | 0 | } |
479 | 0 | goto cleanup; |
480 | 0 | } |
481 | | |
482 | 5 | if (!(pkg = malloc(sizeof(clib_package_t)))) { |
483 | 0 | goto cleanup; |
484 | 0 | } |
485 | | |
486 | 5 | memset(pkg, 0, sizeof(clib_package_t)); |
487 | | |
488 | 5 | pkg->json = strdup(json); |
489 | 5 | pkg->name = json_object_get_string_safe(json_object, "name"); |
490 | 5 | pkg->repo = json_object_get_string_safe(json_object, "repo"); |
491 | 5 | pkg->version = json_object_get_string_safe(json_object, "version"); |
492 | 5 | pkg->license = json_object_get_string_safe(json_object, "license"); |
493 | 5 | pkg->description = json_object_get_string_safe(json_object, "description"); |
494 | 5 | pkg->configure = json_object_get_string_safe(json_object, "configure"); |
495 | 5 | pkg->install = json_object_get_string_safe(json_object, "install"); |
496 | 5 | pkg->makefile = json_object_get_string_safe(json_object, "makefile"); |
497 | 5 | pkg->prefix = json_object_get_string_safe(json_object, "prefix"); |
498 | 5 | pkg->flags = json_object_get_string_safe(json_object, "flags"); |
499 | | |
500 | 5 | if (!pkg->flags) { |
501 | 5 | pkg->flags = json_object_get_string_safe(json_object, "cflags"); |
502 | 5 | } |
503 | | |
504 | | // try as array |
505 | 5 | if (!pkg->flags) { |
506 | 5 | JSON_Array *flags = json_object_get_array(json_object, "flags"); |
507 | | |
508 | 5 | if (!flags) { |
509 | 4 | flags = json_object_get_array(json_object, "cflags"); |
510 | 4 | } |
511 | | |
512 | 5 | if (flags) { |
513 | 128 | for (unsigned int i = 0; i < json_array_get_count(flags); i++) { |
514 | 127 | char *flag = json_array_get_string_safe(flags, i); |
515 | 127 | if (flag) { |
516 | 127 | if (!pkg->flags) { |
517 | 1 | pkg->flags = ""; |
518 | 1 | } |
519 | | |
520 | 127 | if (-1 == asprintf(&pkg->flags, "%s %s", pkg->flags, flag)) { |
521 | 0 | goto cleanup; |
522 | 0 | } |
523 | | |
524 | 127 | free(flag); |
525 | 127 | } |
526 | 127 | } |
527 | 1 | } |
528 | 5 | } |
529 | | |
530 | 5 | if (!pkg->repo && pkg->author && pkg->name) { |
531 | 0 | asprintf(&pkg->repo, "%s/%s", pkg->author, pkg->name); |
532 | 0 | _debug("creating package: %s", pkg->repo); |
533 | 0 | } |
534 | | |
535 | 5 | if (!pkg->author) { |
536 | 5 | _debug("unable to determine package author for: %s", pkg->name); |
537 | 5 | } |
538 | | |
539 | | // TODO npm-style "repository" (thlorenz/gumbo-parser.c#1) |
540 | 5 | if (pkg->repo) { |
541 | 0 | pkg->author = parse_repo_owner(pkg->repo, DEFAULT_REPO_OWNER); |
542 | | // repo name may not be package name (thing.c -> thing) |
543 | 0 | pkg->repo_name = parse_repo_name(pkg->repo); |
544 | 5 | } else { |
545 | 5 | if (verbose) { |
546 | 0 | logger_warn("warning", |
547 | 0 | "missing repo in clib.json or package.json file for %s", |
548 | 0 | pkg->name); |
549 | 0 | } |
550 | 5 | pkg->author = NULL; |
551 | 5 | pkg->repo_name = NULL; |
552 | 5 | } |
553 | | |
554 | 5 | src = json_object_get_array(json_object, "src"); |
555 | | |
556 | 5 | if (!src) { |
557 | 5 | src = json_object_get_array(json_object, "files"); |
558 | 5 | } |
559 | | |
560 | 5 | if (src) { |
561 | 0 | if (!(pkg->src = list_new())) |
562 | 0 | goto cleanup; |
563 | 0 | pkg->src->free = free; |
564 | 0 | for (unsigned int i = 0; i < json_array_get_count(src); i++) { |
565 | 0 | char *file = json_array_get_string_safe(src, i); |
566 | 0 | _debug("file: %s", file); |
567 | 0 | if (!file) |
568 | 0 | goto cleanup; |
569 | 0 | if (!(list_rpush(pkg->src, list_node_new(file)))) |
570 | 0 | goto cleanup; |
571 | 0 | } |
572 | 5 | } else { |
573 | 5 | _debug("no src files listed in clib.json or package.json file"); |
574 | 5 | pkg->src = NULL; |
575 | 5 | } |
576 | | |
577 | 5 | if ((deps = json_object_get_object(json_object, "dependencies"))) { |
578 | 0 | if (!(pkg->dependencies = parse_package_deps(deps))) { |
579 | 0 | goto cleanup; |
580 | 0 | } |
581 | 5 | } else { |
582 | 5 | _debug("no dependencies listed in clib.json or package.json file"); |
583 | 5 | pkg->dependencies = NULL; |
584 | 5 | } |
585 | | |
586 | 5 | if ((devs = json_object_get_object(json_object, "development"))) { |
587 | 1 | if (!(pkg->development = parse_package_deps(devs))) { |
588 | 0 | goto cleanup; |
589 | 0 | } |
590 | 4 | } else { |
591 | 4 | _debug( |
592 | 4 | "no development dependencies listed in clib.json or package.json file"); |
593 | 4 | pkg->development = NULL; |
594 | 4 | } |
595 | | |
596 | 5 | error = 0; |
597 | | |
598 | 12 | cleanup: |
599 | 12 | if (root) |
600 | 5 | json_value_free(root); |
601 | 12 | if (error && pkg) { |
602 | 0 | clib_package_free(pkg); |
603 | 0 | pkg = NULL; |
604 | 0 | } |
605 | 12 | return pkg; |
606 | 5 | } |
607 | | |
608 | | static clib_package_t * |
609 | | clib_package_new_from_slug_with_package_name(const char *slug, int verbose, |
610 | 0 | const char *file) { |
611 | 0 | char *author = NULL; |
612 | 0 | char *name = NULL; |
613 | 0 | char *version = NULL; |
614 | 0 | char *url = NULL; |
615 | 0 | char *json_url = NULL; |
616 | 0 | char *repo = NULL; |
617 | 0 | char *json = NULL; |
618 | 0 | char *log = NULL; |
619 | 0 | http_get_response_t *res = NULL; |
620 | 0 | clib_package_t *pkg = NULL; |
621 | 0 | int retries = 3; |
622 | | |
623 | | // parse chunks |
624 | 0 | if (!slug) |
625 | 0 | goto error; |
626 | 0 | _debug("creating package: %s", slug); |
627 | 0 | if (!(author = parse_repo_owner(slug, DEFAULT_REPO_OWNER))) |
628 | 0 | goto error; |
629 | 0 | if (!(name = parse_repo_name(slug))) |
630 | 0 | goto error; |
631 | 0 | if (!(version = parse_repo_version(slug, DEFAULT_REPO_VERSION))) |
632 | 0 | goto error; |
633 | 0 | if (!(url = clib_package_url(author, name, version))) |
634 | 0 | goto error; |
635 | 0 | if (!(json_url = clib_package_file_url(url, file))) |
636 | 0 | goto error; |
637 | | |
638 | 0 | _debug("author: %s", author); |
639 | 0 | _debug("name: %s", name); |
640 | 0 | _debug("version: %s", version); |
641 | |
|
642 | 0 | #ifdef HAVE_PTHREADS |
643 | 0 | pthread_mutex_lock(&lock.mutex); |
644 | 0 | #endif |
645 | | // fetch json |
646 | 0 | if (clib_cache_has_json(author, name, version)) { |
647 | 0 | if (opts.skip_cache) { |
648 | 0 | clib_cache_delete_json(author, name, version); |
649 | 0 | goto download; |
650 | 0 | } |
651 | | |
652 | 0 | json = clib_cache_read_json(author, name, version); |
653 | |
|
654 | 0 | if (!json) { |
655 | 0 | goto download; |
656 | 0 | } |
657 | | |
658 | 0 | log = "cache"; |
659 | 0 | #ifdef HAVE_PTHREADS |
660 | 0 | pthread_mutex_unlock(&lock.mutex); |
661 | 0 | #endif |
662 | 0 | } else { |
663 | 0 | download: |
664 | 0 | #ifdef HAVE_PTHREADS |
665 | 0 | pthread_mutex_unlock(&lock.mutex); |
666 | 0 | #endif |
667 | 0 | if (retries-- <= 0) { |
668 | 0 | goto error; |
669 | 0 | } else { |
670 | 0 | #ifdef HAVE_PTHREADS |
671 | 0 | init_curl_share(); |
672 | 0 | _debug("GET %s", json_url); |
673 | | // clean up when retrying |
674 | 0 | http_get_free(res); |
675 | 0 | res = http_get_shared(json_url, clib_package_curl_share); |
676 | | #else |
677 | | res = http_get(json_url); |
678 | | #endif |
679 | 0 | json = res->data; |
680 | 0 | _debug("status: %d", res->status); |
681 | 0 | if (!res || !res->ok) { |
682 | 0 | goto download; |
683 | 0 | } |
684 | 0 | log = "fetch"; |
685 | 0 | } |
686 | 0 | } |
687 | | |
688 | 0 | if (verbose) { |
689 | 0 | logger_info(log, "%s/%s:%s", author, name, file); |
690 | 0 | } |
691 | |
|
692 | 0 | free(json_url); |
693 | 0 | json_url = NULL; |
694 | 0 | free(name); |
695 | 0 | name = NULL; |
696 | |
|
697 | 0 | if (json) { |
698 | | // build package |
699 | 0 | pkg = clib_package_new(json, verbose); |
700 | 0 | } |
701 | |
|
702 | 0 | if (!pkg) |
703 | 0 | goto error; |
704 | | |
705 | | // force version number |
706 | 0 | if (pkg->version) { |
707 | 0 | if (0 != strcmp(version, DEFAULT_REPO_VERSION)) { |
708 | 0 | _debug("forcing version number: %s (%s)", version, pkg->version); |
709 | 0 | free(pkg->version); |
710 | 0 | pkg->version = version; |
711 | 0 | } else { |
712 | 0 | free(version); |
713 | 0 | version = NULL; |
714 | 0 | } |
715 | 0 | } else { |
716 | 0 | pkg->version = version; |
717 | 0 | } |
718 | | |
719 | | // force package author (don't know how this could fail) |
720 | 0 | if (pkg->author) { |
721 | 0 | if (0 != strcmp(author, pkg->author)) { |
722 | 0 | free(pkg->author); |
723 | 0 | pkg->author = author; |
724 | 0 | } else { |
725 | 0 | free(author); |
726 | 0 | author = NULL; |
727 | 0 | } |
728 | 0 | } else { |
729 | 0 | pkg->author = strdup(author); |
730 | 0 | } |
731 | |
|
732 | 0 | if (!(repo = clib_package_repo(pkg->author, pkg->name))) { |
733 | 0 | goto error; |
734 | 0 | } |
735 | | |
736 | 0 | if (pkg->repo) { |
737 | 0 | if (0 != strcmp(repo, pkg->repo)) { |
738 | 0 | free(url); |
739 | 0 | if (!(url = clib_package_url_from_repo(pkg->repo, pkg->version))) |
740 | 0 | goto error; |
741 | 0 | } |
742 | 0 | free(repo); |
743 | 0 | repo = NULL; |
744 | 0 | } else { |
745 | 0 | pkg->repo = repo; |
746 | 0 | } |
747 | | |
748 | 0 | pkg->url = url; |
749 | |
|
750 | 0 | #ifdef HAVE_PTHREADS |
751 | 0 | pthread_mutex_lock(&lock.mutex); |
752 | 0 | #endif |
753 | | // cache json |
754 | 0 | if (pkg && pkg->author && pkg->name && pkg->version) { |
755 | 0 | if (-1 == |
756 | 0 | clib_cache_save_json(pkg->author, pkg->name, pkg->version, json)) { |
757 | 0 | _debug("failed to cache JSON for: %s/%s@%s", pkg->author, pkg->name, |
758 | 0 | pkg->version); |
759 | 0 | } else { |
760 | 0 | _debug("cached json: %s/%s@%s", pkg->author, pkg->name, pkg->version); |
761 | 0 | } |
762 | 0 | } |
763 | 0 | #ifdef HAVE_PTHREADS |
764 | 0 | pthread_mutex_unlock(&lock.mutex); |
765 | 0 | #endif |
766 | |
|
767 | 0 | if (res) { |
768 | 0 | http_get_free(res); |
769 | 0 | json = NULL; |
770 | 0 | res = NULL; |
771 | 0 | } else { |
772 | 0 | free(json); |
773 | 0 | json = NULL; |
774 | 0 | } |
775 | |
|
776 | 0 | return pkg; |
777 | | |
778 | 0 | error: |
779 | 0 | if (0 == retries) { |
780 | 0 | if (verbose && author && name && file) { |
781 | 0 | logger_warn("warning", "unable to fetch %s/%s:%s", author, name, file); |
782 | 0 | } |
783 | 0 | } |
784 | |
|
785 | 0 | free(author); |
786 | 0 | free(name); |
787 | 0 | free(version); |
788 | 0 | free(url); |
789 | 0 | free(json_url); |
790 | 0 | free(repo); |
791 | 0 | if (!res && json) |
792 | 0 | free(json); |
793 | 0 | if (res) |
794 | 0 | http_get_free(res); |
795 | 0 | if (pkg) |
796 | 0 | clib_package_free(pkg); |
797 | 0 | return NULL; |
798 | 0 | } |
799 | | |
800 | | /** |
801 | | * Create a package from the given repo `slug` |
802 | | */ |
803 | | |
804 | 0 | clib_package_t *clib_package_new_from_slug(const char *slug, int verbose) { |
805 | 0 | clib_package_t *package = NULL; |
806 | 0 | const char *name = NULL; |
807 | 0 | unsigned int i = 0; |
808 | |
|
809 | 0 | do { |
810 | 0 | name = manifest_names[i]; |
811 | 0 | package = clib_package_new_from_slug_with_package_name(slug, verbose, name); |
812 | 0 | if (NULL != package) { |
813 | 0 | package->filename = (char *)name; |
814 | 0 | } |
815 | 0 | } while (NULL != manifest_names[++i] && NULL == package); |
816 | |
|
817 | 0 | return package; |
818 | 0 | } |
819 | | |
820 | | /** |
821 | | * Get a slug for the package `author/name@version` |
822 | | */ |
823 | | |
824 | | char *clib_package_url(const char *author, const char *name, |
825 | 0 | const char *version) { |
826 | 0 | if (!author || !name || !version) |
827 | 0 | return NULL; |
828 | 0 | int size = strlen(GITHUB_CONTENT_URL) + strlen(author) + 1 // / |
829 | 0 | + strlen(name) + 1 // / |
830 | 0 | + strlen(version) + 1 // \0 |
831 | 0 | ; |
832 | |
|
833 | 0 | if (0 != opts.token) { |
834 | 0 | size += strlen(opts.token); |
835 | 0 | size += 1; // @ |
836 | 0 | } |
837 | |
|
838 | 0 | char *slug = malloc(size); |
839 | 0 | if (slug) { |
840 | 0 | memset(slug, '\0', size); |
841 | 0 | if (0 != opts.token) { |
842 | 0 | sprintf(slug, GITHUB_CONTENT_URL_WITH_TOKEN "%s/%s/%s", opts.token, |
843 | 0 | author, name, version); |
844 | 0 | } else { |
845 | 0 | sprintf(slug, GITHUB_CONTENT_URL "%s/%s/%s", author, name, version); |
846 | 0 | } |
847 | 0 | } |
848 | |
|
849 | 0 | return slug; |
850 | 0 | } |
851 | | |
852 | 0 | char *clib_package_url_from_repo(const char *repo, const char *version) { |
853 | 0 | if (!repo || !version) |
854 | 0 | return NULL; |
855 | 0 | int size = strlen(GITHUB_CONTENT_URL) + strlen(repo) + 1 // / |
856 | 0 | + strlen(version) + 1 // \0 |
857 | 0 | ; |
858 | |
|
859 | 0 | if (0 != opts.token) { |
860 | 0 | size += strlen(opts.token); |
861 | 0 | size += 1; // @ |
862 | 0 | } |
863 | |
|
864 | 0 | char *slug = malloc(size); |
865 | 0 | if (slug) { |
866 | 0 | memset(slug, '\0', size); |
867 | 0 | if (0 != opts.token) { |
868 | 0 | sprintf(slug, GITHUB_CONTENT_URL_WITH_TOKEN "%s/%s", opts.token, repo, |
869 | 0 | version); |
870 | 0 | } else { |
871 | 0 | sprintf(slug, GITHUB_CONTENT_URL "%s/%s", repo, version); |
872 | 0 | } |
873 | 0 | } |
874 | 0 | return slug; |
875 | 0 | } |
876 | | |
877 | | /** |
878 | | * Parse the package author from the given `slug` |
879 | | */ |
880 | | |
881 | 4 | char *clib_package_parse_author(const char *slug) { |
882 | 4 | return parse_repo_owner(slug, DEFAULT_REPO_OWNER); |
883 | 4 | } |
884 | | |
885 | | /** |
886 | | * Parse the package version from the given `slug` |
887 | | */ |
888 | | |
889 | 0 | char *clib_package_parse_version(const char *slug) { |
890 | 0 | return parse_repo_version(slug, DEFAULT_REPO_VERSION); |
891 | 0 | } |
892 | | |
893 | | /** |
894 | | * Parse the package name from the given `slug` |
895 | | */ |
896 | | |
897 | 4 | char *clib_package_parse_name(const char *slug) { |
898 | 4 | return parse_repo_name(slug); |
899 | 4 | } |
900 | | |
901 | | /** |
902 | | * Create a new package dependency from the given `repo` and `version` |
903 | | */ |
904 | | |
905 | | clib_package_dependency_t *clib_package_dependency_new(const char *repo, |
906 | 4 | const char *version) { |
907 | 4 | if (!repo || !version) |
908 | 0 | return NULL; |
909 | | |
910 | 4 | clib_package_dependency_t *dep = malloc(sizeof(clib_package_dependency_t)); |
911 | 4 | if (!dep) { |
912 | 0 | return NULL; |
913 | 0 | } |
914 | | |
915 | 4 | dep->version = 0 == strcmp("*", version) ? strdup(DEFAULT_REPO_VERSION) |
916 | 4 | : strdup(version); |
917 | 4 | dep->name = clib_package_parse_name(repo); |
918 | 4 | dep->author = clib_package_parse_author(repo); |
919 | | |
920 | 4 | _debug("dependency: %s/%s@%s", dep->author, dep->name, dep->version); |
921 | 4 | return dep; |
922 | 4 | } |
923 | | |
924 | | static int fetch_package_file_work(clib_package_t *pkg, const char *dir, |
925 | 0 | char *file, int verbose) { |
926 | 0 | char *url = NULL; |
927 | 0 | char *path = NULL; |
928 | 0 | int saved = 0; |
929 | 0 | int rc = 0; |
930 | |
|
931 | 0 | _debug("fetch file: %s/%s", pkg->repo, file); |
932 | |
|
933 | 0 | if (NULL == pkg) { |
934 | 0 | return 1; |
935 | 0 | } |
936 | | |
937 | 0 | if (NULL == pkg->url) { |
938 | 0 | return 1; |
939 | 0 | } |
940 | | |
941 | 0 | if (0 == strncmp(file, "http", 4)) { |
942 | 0 | url = strdup(file); |
943 | 0 | } else if (!(url = clib_package_file_url(pkg->url, file))) { |
944 | 0 | return 1; |
945 | 0 | } |
946 | | |
947 | 0 | _debug("file URL: %s", url); |
948 | |
|
949 | 0 | char *base_path = strdup(basename(file)); |
950 | |
|
951 | 0 | if (!base_path) { |
952 | 0 | rc = 1; |
953 | 0 | goto cleanup; |
954 | 0 | } |
955 | | |
956 | 0 | path = path_join(dir, base_path); |
957 | |
|
958 | 0 | free(base_path); |
959 | |
|
960 | 0 | if (!path) { |
961 | 0 | rc = 1; |
962 | 0 | goto cleanup; |
963 | 0 | } |
964 | | |
965 | 0 | #ifdef HAVE_PTHREADS |
966 | 0 | pthread_mutex_lock(&lock.mutex); |
967 | 0 | #endif |
968 | |
|
969 | 0 | if (1 == opts.force || -1 == fs_exists(path)) { |
970 | 0 | if (verbose) { |
971 | 0 | logger_info("fetch", "%s:%s", pkg->repo, file); |
972 | 0 | fflush(stdout); |
973 | 0 | } |
974 | |
|
975 | 0 | #ifdef HAVE_PTHREADS |
976 | 0 | pthread_mutex_unlock(&lock.mutex); |
977 | 0 | #endif |
978 | |
|
979 | 0 | rc = http_get_file_shared(url, path, clib_package_curl_share); |
980 | 0 | saved = 1; |
981 | 0 | } else { |
982 | 0 | #ifdef HAVE_PTHREADS |
983 | 0 | pthread_mutex_unlock(&lock.mutex); |
984 | 0 | #endif |
985 | 0 | } |
986 | |
|
987 | 0 | if (-1 == rc) { |
988 | 0 | if (verbose) { |
989 | 0 | #ifdef HAVE_PTHREADS |
990 | 0 | pthread_mutex_lock(&lock.mutex); |
991 | 0 | #endif |
992 | 0 | logger_error("error", "unable to fetch %s:%s", pkg->repo, file); |
993 | 0 | fflush(stderr); |
994 | 0 | rc = 1; |
995 | 0 | #ifdef HAVE_PTHREADS |
996 | 0 | pthread_mutex_unlock(&lock.mutex); |
997 | 0 | #endif |
998 | 0 | goto cleanup; |
999 | 0 | } |
1000 | 0 | } |
1001 | | |
1002 | 0 | if (saved) { |
1003 | 0 | if (verbose) { |
1004 | 0 | #ifdef HAVE_PTHREADS |
1005 | 0 | pthread_mutex_lock(&lock.mutex); |
1006 | 0 | #endif |
1007 | 0 | logger_info("save", path); |
1008 | 0 | fflush(stdout); |
1009 | 0 | #ifdef HAVE_PTHREADS |
1010 | 0 | pthread_mutex_unlock(&lock.mutex); |
1011 | 0 | #endif |
1012 | 0 | } |
1013 | 0 | } |
1014 | |
|
1015 | 0 | cleanup: |
1016 | |
|
1017 | 0 | free(url); |
1018 | 0 | free(path); |
1019 | 0 | return rc; |
1020 | 0 | } |
1021 | | |
1022 | | #ifdef HAVE_PTHREADS |
1023 | 0 | static void *fetch_package_file_thread(void *arg) { |
1024 | 0 | fetch_package_file_thread_data_t *data = arg; |
1025 | 0 | int *status = malloc(sizeof(int)); |
1026 | 0 | int rc = |
1027 | 0 | fetch_package_file_work(data->pkg, data->dir, data->file, data->verbose); |
1028 | 0 | *status = rc; |
1029 | 0 | (void)data->pkg->refs--; |
1030 | 0 | pthread_exit((void *)status); |
1031 | 0 | return (void *)(intptr_t)rc; |
1032 | 0 | } |
1033 | | #endif |
1034 | | |
1035 | | /** |
1036 | | * Fetch a file associated with the given `pkg`. |
1037 | | * |
1038 | | * Returns 0 on success. |
1039 | | */ |
1040 | | |
1041 | | static int fetch_package_file(clib_package_t *pkg, const char *dir, char *file, |
1042 | 0 | int verbose, void **data) { |
1043 | | #ifndef HAVE_PTHREADS |
1044 | | return fetch_package_file_work(pkg, dir, file, verbose); |
1045 | | #else |
1046 | 0 | fetch_package_file_thread_data_t *fetch = malloc(sizeof(*fetch)); |
1047 | 0 | int rc = 0; |
1048 | |
|
1049 | 0 | if (0 == fetch) { |
1050 | 0 | return -1; |
1051 | 0 | } |
1052 | | |
1053 | 0 | *data = 0; |
1054 | |
|
1055 | 0 | memset(fetch, 0, sizeof(*fetch)); |
1056 | |
|
1057 | 0 | fetch->pkg = pkg; |
1058 | 0 | fetch->dir = dir; |
1059 | 0 | fetch->file = file; |
1060 | 0 | fetch->verbose = verbose; |
1061 | |
|
1062 | 0 | rc = pthread_attr_init(&fetch->attr); |
1063 | |
|
1064 | 0 | if (0 != rc) { |
1065 | 0 | free(fetch); |
1066 | 0 | return rc; |
1067 | 0 | } |
1068 | | |
1069 | 0 | (void)pkg->refs++; |
1070 | 0 | rc = pthread_create(&fetch->thread, NULL, fetch_package_file_thread, fetch); |
1071 | |
|
1072 | 0 | if (0 != rc) { |
1073 | 0 | pthread_attr_destroy(&fetch->attr); |
1074 | 0 | free(fetch); |
1075 | 0 | return rc; |
1076 | 0 | } |
1077 | | |
1078 | 0 | rc = pthread_attr_destroy(&fetch->attr); |
1079 | |
|
1080 | 0 | if (0 != rc) { |
1081 | 0 | pthread_cancel(fetch->thread); |
1082 | 0 | free(fetch); |
1083 | 0 | return rc; |
1084 | 0 | } |
1085 | | |
1086 | 0 | *data = fetch; |
1087 | |
|
1088 | 0 | return rc; |
1089 | 0 | #endif |
1090 | 0 | } |
1091 | | |
1092 | 0 | static void set_prefix(clib_package_t *pkg, long path_max) { |
1093 | 0 | if (NULL != opts.prefix || NULL != pkg->prefix) { |
1094 | 0 | char path[path_max]; |
1095 | 0 | memset(path, 0, path_max); |
1096 | |
|
1097 | 0 | if (opts.prefix) { |
1098 | 0 | realpath(opts.prefix, path); |
1099 | 0 | } else { |
1100 | 0 | realpath(pkg->prefix, path); |
1101 | 0 | } |
1102 | |
|
1103 | 0 | _debug("env: PREFIX: %s", path); |
1104 | 0 | setenv("PREFIX", path, 1); |
1105 | 0 | mkdirp(path, 0777); |
1106 | 0 | } |
1107 | 0 | } |
1108 | | |
1109 | | int clib_package_install_executable(clib_package_t *pkg, const char *dir, |
1110 | 0 | int verbose) { |
1111 | 0 | #ifdef PATH_MAX |
1112 | 0 | long path_max = PATH_MAX; |
1113 | | #elif defined(_PC_PATH_MAX) |
1114 | | long path_max = pathconf(dir, _PC_PATH_MAX); |
1115 | | #else |
1116 | | long path_max = 4096; |
1117 | | #endif |
1118 | |
|
1119 | 0 | int rc; |
1120 | 0 | char *url = NULL; |
1121 | 0 | char *file = NULL; |
1122 | 0 | char *tarball = NULL; |
1123 | 0 | char *command = NULL; |
1124 | 0 | char *unpack_dir = NULL; |
1125 | 0 | char *deps = NULL; |
1126 | 0 | char *tmp = NULL; |
1127 | 0 | char *reponame = NULL; |
1128 | 0 | char dir_path[path_max]; |
1129 | |
|
1130 | 0 | _debug("install executable %s", pkg->repo); |
1131 | |
|
1132 | 0 | tmp = gettempdir(); |
1133 | |
|
1134 | 0 | if (NULL == tmp) { |
1135 | 0 | if (verbose) { |
1136 | 0 | logger_error("error", "gettempdir() out of memory"); |
1137 | 0 | } |
1138 | 0 | return -1; |
1139 | 0 | } |
1140 | | |
1141 | 0 | if (!pkg->repo) { |
1142 | 0 | if (verbose) { |
1143 | 0 | logger_error("error", "repo field required to install executable"); |
1144 | 0 | } |
1145 | 0 | return -1; |
1146 | 0 | } |
1147 | | |
1148 | 0 | reponame = strrchr(pkg->repo, '/'); |
1149 | 0 | if (reponame && *reponame != '\0') |
1150 | 0 | reponame++; |
1151 | 0 | else { |
1152 | 0 | if (verbose) { |
1153 | 0 | logger_error("error", |
1154 | 0 | "malformed repo field, must be in the form user/pkg"); |
1155 | 0 | } |
1156 | 0 | return -1; |
1157 | 0 | } |
1158 | | |
1159 | 0 | E_FORMAT(&url, "https://github.com/%s/archive/%s.tar.gz", pkg->repo, |
1160 | 0 | pkg->version); |
1161 | |
|
1162 | 0 | E_FORMAT(&file, "%s-%s.tar.gz", reponame, pkg->version); |
1163 | |
|
1164 | 0 | E_FORMAT(&tarball, "%s/%s", tmp, file); |
1165 | |
|
1166 | 0 | rc = http_get_file_shared(url, tarball, clib_package_curl_share); |
1167 | |
|
1168 | 0 | if (0 != rc) { |
1169 | 0 | if (verbose) { |
1170 | 0 | logger_error("error", "download failed for '%s@%s' - HTTP GET '%s'", |
1171 | 0 | pkg->repo, pkg->version, url); |
1172 | 0 | } |
1173 | |
|
1174 | 0 | goto cleanup; |
1175 | 0 | } |
1176 | | |
1177 | 0 | E_FORMAT(&command, "cd %s && gzip -dc %s | tar x", tmp, file); |
1178 | |
|
1179 | 0 | _debug("download url: %s", url); |
1180 | 0 | _debug("file: %s", file); |
1181 | 0 | _debug("tarball: %s", tarball); |
1182 | 0 | _debug("command(extract): %s", command); |
1183 | | |
1184 | | // cheap untar |
1185 | 0 | rc = system(command); |
1186 | 0 | if (0 != rc) |
1187 | 0 | goto cleanup; |
1188 | | |
1189 | 0 | free(command); |
1190 | 0 | command = NULL; |
1191 | |
|
1192 | 0 | set_prefix(pkg, path_max); |
1193 | |
|
1194 | 0 | const char *configure = pkg->configure; |
1195 | |
|
1196 | 0 | if (0 == configure) { |
1197 | 0 | configure = ":"; |
1198 | 0 | } |
1199 | |
|
1200 | 0 | memset(dir_path, 0, path_max); |
1201 | 0 | realpath(dir, dir_path); |
1202 | |
|
1203 | 0 | char *version = pkg->version; |
1204 | 0 | if ('v' == version[0]) { |
1205 | 0 | (void)version++; |
1206 | 0 | } |
1207 | |
|
1208 | 0 | E_FORMAT(&unpack_dir, "%s/%s-%s", tmp, reponame, version); |
1209 | |
|
1210 | 0 | _debug("dir: %s", unpack_dir); |
1211 | |
|
1212 | 0 | if (pkg->dependencies) { |
1213 | 0 | E_FORMAT(&deps, "%s/deps", unpack_dir); |
1214 | 0 | _debug("deps: %s", deps); |
1215 | 0 | rc = clib_package_install_dependencies(pkg, deps, verbose); |
1216 | 0 | if (-1 == rc) |
1217 | 0 | goto cleanup; |
1218 | 0 | } |
1219 | | |
1220 | 0 | if (!opts.global && pkg->makefile) { |
1221 | 0 | E_FORMAT(&command, "cp -fr %s/%s/%s %s", dir_path, pkg->name, |
1222 | 0 | basename(pkg->makefile), unpack_dir); |
1223 | |
|
1224 | 0 | rc = system(command); |
1225 | 0 | if (0 != rc) { |
1226 | 0 | goto cleanup; |
1227 | 0 | } |
1228 | | |
1229 | 0 | free(command); |
1230 | 0 | } |
1231 | | |
1232 | 0 | if (pkg->flags) { |
1233 | 0 | char *flags = NULL; |
1234 | | #ifdef _GNU_SOURCE |
1235 | | char *cflags = secure_getenv("CFLAGS"); |
1236 | | #else |
1237 | 0 | char *cflags = getenv("CFLAGS"); |
1238 | 0 | #endif |
1239 | |
|
1240 | 0 | if (cflags) { |
1241 | 0 | asprintf(&flags, "%s %s", cflags, pkg->flags); |
1242 | 0 | } else { |
1243 | 0 | asprintf(&flags, "%s", pkg->flags); |
1244 | 0 | } |
1245 | |
|
1246 | 0 | setenv("CFLAGS", cflags, 1); |
1247 | 0 | } |
1248 | |
|
1249 | 0 | E_FORMAT(&command, "cd %s && %s", unpack_dir, pkg->install); |
1250 | |
|
1251 | 0 | _debug("command(install): %s", command); |
1252 | 0 | rc = system(command); |
1253 | |
|
1254 | 0 | cleanup: |
1255 | 0 | free(tmp); |
1256 | 0 | free(command); |
1257 | 0 | free(tarball); |
1258 | 0 | free(file); |
1259 | 0 | free(url); |
1260 | 0 | return rc; |
1261 | 0 | } |
1262 | | |
1263 | | /** |
1264 | | * Install the given `pkg` in `dir` |
1265 | | */ |
1266 | | |
1267 | 0 | int clib_package_install(clib_package_t *pkg, const char *dir, int verbose) { |
1268 | 0 | list_iterator_t *iterator = NULL; |
1269 | 0 | char *package_json = NULL; |
1270 | 0 | char *pkg_dir = NULL; |
1271 | 0 | char *command = NULL; |
1272 | 0 | int pending = 0; |
1273 | 0 | int rc = 0; |
1274 | 0 | int i = 0; |
1275 | |
|
1276 | 0 | #ifdef PATH_MAX |
1277 | 0 | long path_max = PATH_MAX; |
1278 | | #elif defined(_PC_PATH_MAX) |
1279 | | long path_max = pathconf(dir, _PC_PATH_MAX); |
1280 | | #else |
1281 | | long path_max = 4096; |
1282 | | #endif |
1283 | |
|
1284 | 0 | #ifdef HAVE_PTHREADS |
1285 | 0 | int max = opts.concurrency; |
1286 | 0 | #endif |
1287 | |
|
1288 | | #ifdef CLIB_PACKAGE_PREFIX |
1289 | | if (0 == opts.prefix) { |
1290 | | #ifdef HAVE_PTHREADS |
1291 | | pthread_mutex_lock(&lock.mutex); |
1292 | | #endif |
1293 | | opts.prefix = CLIB_PACKAGE_PREFIX; |
1294 | | #ifdef HAVE_PTHREADS |
1295 | | pthread_mutex_unlock(&lock.mutex); |
1296 | | #endif |
1297 | | } |
1298 | | #endif |
1299 | |
|
1300 | 0 | if (0 == opts.prefix) { |
1301 | 0 | #ifdef HAVE_PTHREADS |
1302 | 0 | pthread_mutex_lock(&lock.mutex); |
1303 | 0 | #endif |
1304 | | #ifdef _GNU_SOURCE |
1305 | | char *prefix = secure_getenv("PREFIX"); |
1306 | | #else |
1307 | 0 | char *prefix = getenv("PREFIX"); |
1308 | 0 | #endif |
1309 | |
|
1310 | 0 | if (prefix) { |
1311 | 0 | opts.prefix = prefix; |
1312 | 0 | } |
1313 | 0 | #ifdef HAVE_PTHREADS |
1314 | 0 | pthread_mutex_unlock(&lock.mutex); |
1315 | 0 | #endif |
1316 | 0 | } |
1317 | |
|
1318 | 0 | if (0 == visited_packages) { |
1319 | 0 | #ifdef HAVE_PTHREADS |
1320 | 0 | pthread_mutex_lock(&lock.mutex); |
1321 | 0 | #endif |
1322 | |
|
1323 | 0 | visited_packages = hash_new(); |
1324 | | // initial write because sometimes `hash_set()` crashes |
1325 | 0 | hash_set(visited_packages, strdup(""), ""); |
1326 | |
|
1327 | 0 | #ifdef HAVE_PTHREADS |
1328 | 0 | pthread_mutex_unlock(&lock.mutex); |
1329 | 0 | #endif |
1330 | 0 | } |
1331 | |
|
1332 | 0 | if (0 == opts.force && pkg && pkg->name) { |
1333 | 0 | #ifdef HAVE_PTHREADS |
1334 | 0 | pthread_mutex_lock(&lock.mutex); |
1335 | 0 | #endif |
1336 | |
|
1337 | 0 | if (hash_has(visited_packages, pkg->name)) { |
1338 | 0 | #ifdef HAVE_PTHREADS |
1339 | 0 | pthread_mutex_unlock(&lock.mutex); |
1340 | 0 | #endif |
1341 | 0 | return 0; |
1342 | 0 | } |
1343 | | |
1344 | 0 | #ifdef HAVE_PTHREADS |
1345 | 0 | pthread_mutex_unlock(&lock.mutex); |
1346 | 0 | #endif |
1347 | 0 | } |
1348 | | |
1349 | 0 | #ifdef HAVE_PTHREADS |
1350 | 0 | fetch_package_file_thread_data_t **fetchs = 0; |
1351 | 0 | if (NULL != pkg && NULL != pkg->src) { |
1352 | 0 | if (pkg->src->len > 0) { |
1353 | 0 | fetchs = malloc(pkg->src->len * sizeof(fetch_package_file_thread_data_t)); |
1354 | 0 | } |
1355 | 0 | } |
1356 | |
|
1357 | 0 | if (fetchs) { |
1358 | 0 | memset(fetchs, 0, pkg->src->len * sizeof(fetch_package_file_thread_data_t)); |
1359 | 0 | } |
1360 | |
|
1361 | 0 | #endif |
1362 | |
|
1363 | 0 | if (!pkg || !dir) { |
1364 | 0 | rc = -1; |
1365 | 0 | goto cleanup; |
1366 | 0 | } |
1367 | | |
1368 | 0 | set_prefix(pkg, path_max); |
1369 | |
|
1370 | 0 | if (!(pkg_dir = path_join(dir, pkg->name))) { |
1371 | 0 | rc = -1; |
1372 | 0 | goto cleanup; |
1373 | 0 | } |
1374 | | |
1375 | 0 | if (!opts.global) { |
1376 | 0 | _debug("mkdir -p %s", pkg_dir); |
1377 | | // create directory for pkg |
1378 | 0 | if (-1 == mkdirp(pkg_dir, 0777)) { |
1379 | 0 | rc = -1; |
1380 | 0 | goto cleanup; |
1381 | 0 | } |
1382 | 0 | } |
1383 | | |
1384 | 0 | if (NULL == pkg->url) { |
1385 | 0 | pkg->url = clib_package_url(pkg->author, pkg->repo_name, pkg->version); |
1386 | |
|
1387 | 0 | if (NULL == pkg->url) { |
1388 | 0 | rc = -1; |
1389 | 0 | goto cleanup; |
1390 | 0 | } |
1391 | 0 | } |
1392 | | |
1393 | | // write clib.json or package.json |
1394 | 0 | if (!(package_json = path_join(pkg_dir, pkg->filename))) { |
1395 | 0 | rc = -1; |
1396 | 0 | goto cleanup; |
1397 | 0 | } |
1398 | | |
1399 | 0 | if (!opts.global && NULL != pkg->src) { |
1400 | 0 | _debug("write: %s", package_json); |
1401 | 0 | if (-1 == fs_write(package_json, pkg->json)) { |
1402 | 0 | if (verbose) { |
1403 | 0 | logger_error("error", "Failed to write %s", package_json); |
1404 | 0 | } |
1405 | |
|
1406 | 0 | rc = -1; |
1407 | 0 | goto cleanup; |
1408 | 0 | } |
1409 | 0 | } |
1410 | | |
1411 | 0 | if (pkg->name) { |
1412 | 0 | #ifdef HAVE_PTHREADS |
1413 | 0 | pthread_mutex_lock(&lock.mutex); |
1414 | 0 | #endif |
1415 | 0 | if (!hash_has(visited_packages, pkg->name)) { |
1416 | 0 | hash_set(visited_packages, strdup(pkg->name), "t"); |
1417 | 0 | } |
1418 | 0 | #ifdef HAVE_PTHREADS |
1419 | 0 | pthread_mutex_unlock(&lock.mutex); |
1420 | 0 | #endif |
1421 | 0 | } |
1422 | | |
1423 | | // fetch makefile |
1424 | 0 | if (!opts.global && pkg->makefile) { |
1425 | 0 | _debug("fetch: %s/%s", pkg->repo, pkg->makefile); |
1426 | 0 | void *fetch = 0; |
1427 | 0 | rc = fetch_package_file(pkg, pkg_dir, pkg->makefile, verbose, &fetch); |
1428 | 0 | if (0 != rc) { |
1429 | 0 | goto cleanup; |
1430 | 0 | } |
1431 | | |
1432 | 0 | #ifdef HAVE_PTHREADS |
1433 | 0 | if (0 != fetch) { |
1434 | 0 | fetch_package_file_thread_data_t *data = fetch; |
1435 | 0 | int *status; |
1436 | 0 | pthread_join(data->thread, (void **)&status); |
1437 | 0 | if (NULL != status) { |
1438 | 0 | rc = *status; |
1439 | 0 | free(status); |
1440 | 0 | status = 0; |
1441 | 0 | if (0 != rc) { |
1442 | 0 | rc = 0; |
1443 | 0 | logger_warn("warning", "unable to fetch Makefile (%s) for '%s'", |
1444 | 0 | pkg->makefile, pkg->name); |
1445 | 0 | } |
1446 | 0 | } |
1447 | 0 | } |
1448 | 0 | #endif |
1449 | 0 | } |
1450 | | |
1451 | | // if no sources are listed, just install |
1452 | 0 | if (opts.global || NULL == pkg->src) |
1453 | 0 | goto install; |
1454 | | |
1455 | 0 | #ifdef HAVE_PTHREADS |
1456 | 0 | pthread_mutex_lock(&lock.mutex); |
1457 | 0 | #endif |
1458 | |
|
1459 | 0 | if (clib_cache_has_package(pkg->author, pkg->name, pkg->version)) { |
1460 | 0 | if (opts.skip_cache) { |
1461 | 0 | clib_cache_delete_package(pkg->author, pkg->name, pkg->version); |
1462 | 0 | #ifdef HAVE_PTHREADS |
1463 | 0 | pthread_mutex_unlock(&lock.mutex); |
1464 | 0 | #endif |
1465 | 0 | goto download; |
1466 | 0 | } |
1467 | | |
1468 | 0 | if (0 != clib_cache_load_package(pkg->author, pkg->name, pkg->version, |
1469 | 0 | pkg_dir)) { |
1470 | 0 | #ifdef HAVE_PTHREADS |
1471 | 0 | pthread_mutex_unlock(&lock.mutex); |
1472 | 0 | #endif |
1473 | 0 | goto download; |
1474 | 0 | } |
1475 | | |
1476 | 0 | if (verbose) { |
1477 | 0 | logger_info("cache", pkg->repo); |
1478 | 0 | } |
1479 | |
|
1480 | 0 | #ifdef HAVE_PTHREADS |
1481 | 0 | pthread_mutex_unlock(&lock.mutex); |
1482 | 0 | #endif |
1483 | |
|
1484 | 0 | goto install; |
1485 | 0 | } |
1486 | | |
1487 | 0 | #ifdef HAVE_PTHREADS |
1488 | 0 | pthread_mutex_unlock(&lock.mutex); |
1489 | 0 | #endif |
1490 | |
|
1491 | 0 | download: |
1492 | |
|
1493 | 0 | iterator = list_iterator_new(pkg->src, LIST_HEAD); |
1494 | 0 | list_node_t *source; |
1495 | |
|
1496 | 0 | while ((source = list_iterator_next(iterator))) { |
1497 | 0 | void *fetch = NULL; |
1498 | 0 | rc = fetch_package_file(pkg, pkg_dir, source->val, verbose, &fetch); |
1499 | |
|
1500 | 0 | if (0 != rc) { |
1501 | 0 | list_iterator_destroy(iterator); |
1502 | 0 | iterator = NULL; |
1503 | 0 | rc = -1; |
1504 | 0 | goto cleanup; |
1505 | 0 | } |
1506 | | |
1507 | 0 | #ifdef HAVE_PTHREADS |
1508 | 0 | if (i < 0) { |
1509 | 0 | i = 0; |
1510 | 0 | } |
1511 | |
|
1512 | 0 | fetchs[i] = fetch; |
1513 | |
|
1514 | 0 | (void)pending++; |
1515 | |
|
1516 | 0 | if (i < (max - 1)) { |
1517 | 0 | (void)i++; |
1518 | 0 | } else { |
1519 | 0 | for (int j = 0; j <= i; j++) { |
1520 | 0 | fetch_package_file_thread_data_t *data = fetchs[j]; |
1521 | 0 | int *status; |
1522 | 0 | pthread_join(data->thread, (void **)&status); |
1523 | 0 | free(data); |
1524 | 0 | fetchs[j] = NULL; |
1525 | |
|
1526 | 0 | (void)pending--; |
1527 | |
|
1528 | 0 | if (NULL != status) { |
1529 | 0 | rc = *status; |
1530 | 0 | free(status); |
1531 | 0 | status = 0; |
1532 | 0 | } |
1533 | |
|
1534 | 0 | if (0 != rc) { |
1535 | 0 | rc = -1; |
1536 | 0 | goto cleanup; |
1537 | 0 | } |
1538 | 0 | } |
1539 | 0 | i = 0; |
1540 | 0 | } |
1541 | 0 | #endif |
1542 | 0 | } |
1543 | | |
1544 | 0 | #ifdef HAVE_PTHREADS |
1545 | | // Here there are i-1 threads running. |
1546 | 0 | for (int j = 0; j < i; j++) { |
1547 | 0 | fetch_package_file_thread_data_t *data = fetchs[j]; |
1548 | 0 | int *status; |
1549 | |
|
1550 | 0 | pthread_join(data->thread, (void **)&status); |
1551 | |
|
1552 | 0 | (void)pending--; |
1553 | 0 | free(data); |
1554 | 0 | fetchs[j] = NULL; |
1555 | |
|
1556 | 0 | if (NULL != status) { |
1557 | 0 | rc = *status; |
1558 | 0 | free(status); |
1559 | 0 | status = 0; |
1560 | 0 | } |
1561 | |
|
1562 | 0 | if (0 != rc) { |
1563 | 0 | rc = -1; |
1564 | 0 | goto cleanup; |
1565 | 0 | } |
1566 | 0 | } |
1567 | 0 | #endif |
1568 | | |
1569 | 0 | install: |
1570 | 0 | if (pkg->configure) { |
1571 | 0 | E_FORMAT(&command, "cd %s/%s && %s", dir, pkg->name, pkg->configure); |
1572 | |
|
1573 | 0 | _debug("command(configure): %s", command); |
1574 | |
|
1575 | 0 | rc = system(command); |
1576 | 0 | if (0 != rc) |
1577 | 0 | goto cleanup; |
1578 | 0 | } |
1579 | | |
1580 | 0 | if (0 == rc && pkg->install) { |
1581 | 0 | rc = clib_package_install_executable(pkg, dir, verbose); |
1582 | 0 | } |
1583 | |
|
1584 | 0 | if (0 == rc) { |
1585 | 0 | rc = clib_package_install_dependencies(pkg, dir, verbose); |
1586 | 0 | } |
1587 | |
|
1588 | 0 | #ifdef HAVE_PTHREADS |
1589 | 0 | pthread_mutex_lock(&lock.mutex); |
1590 | 0 | #endif |
1591 | 0 | if (0 == rc) { |
1592 | 0 | clib_cache_save_package(pkg->author, pkg->name, pkg->version, pkg_dir); |
1593 | 0 | _debug("cached package: %s/%s@%s", pkg->author, pkg->name, pkg->version); |
1594 | 0 | } |
1595 | 0 | #ifdef HAVE_PTHREADS |
1596 | 0 | pthread_mutex_unlock(&lock.mutex); |
1597 | 0 | #endif |
1598 | |
|
1599 | 0 | cleanup: |
1600 | 0 | if (pkg_dir) { |
1601 | 0 | if (0 != rc) { |
1602 | 0 | rimraf(pkg_dir); |
1603 | 0 | _debug("deleted inconsistent package dir: %s", pkg_dir); |
1604 | 0 | } |
1605 | |
|
1606 | 0 | free(pkg_dir); |
1607 | 0 | } |
1608 | 0 | if (package_json) |
1609 | 0 | free(package_json); |
1610 | 0 | if (iterator) |
1611 | 0 | list_iterator_destroy(iterator); |
1612 | 0 | if (command) |
1613 | 0 | free(command); |
1614 | 0 | #ifdef HAVE_PTHREADS |
1615 | 0 | if (NULL != pkg && NULL != pkg->src) { |
1616 | 0 | if (pkg->src->len > 0) { |
1617 | 0 | if (fetchs) { |
1618 | 0 | free(fetchs); |
1619 | 0 | } |
1620 | 0 | } |
1621 | 0 | } |
1622 | 0 | fetchs = NULL; |
1623 | 0 | #endif |
1624 | |
|
1625 | 0 | #ifdef HAVE_PTHREADS |
1626 | 0 | pthread_mutex_lock(&lock.mutex); |
1627 | 0 | #endif |
1628 | 0 | if (0 != rc && pkg) { |
1629 | 0 | clib_cache_delete_json(pkg->author, pkg->name, pkg->version); |
1630 | 0 | _debug("deleted json cache: %s/%s@%s", pkg->author, pkg->name, |
1631 | 0 | pkg->version); |
1632 | 0 | } |
1633 | 0 | #ifdef HAVE_PTHREADS |
1634 | 0 | pthread_mutex_unlock(&lock.mutex); |
1635 | 0 | #endif |
1636 | |
|
1637 | 0 | return rc; |
1638 | 0 | } |
1639 | | |
1640 | | /** |
1641 | | * Install the given `pkg`'s dependencies in `dir` |
1642 | | */ |
1643 | | |
1644 | | int clib_package_install_dependencies(clib_package_t *pkg, const char *dir, |
1645 | 0 | int verbose) { |
1646 | 0 | if (!pkg || !dir) |
1647 | 0 | return -1; |
1648 | 0 | if (NULL == pkg->dependencies) |
1649 | 0 | return 0; |
1650 | | |
1651 | 0 | return install_packages(pkg->dependencies, dir, verbose); |
1652 | 0 | } |
1653 | | |
1654 | | /** |
1655 | | * Install the given `pkg`'s development dependencies in `dir` |
1656 | | */ |
1657 | | |
1658 | | int clib_package_install_development(clib_package_t *pkg, const char *dir, |
1659 | 0 | int verbose) { |
1660 | 0 | if (!pkg || !dir) |
1661 | 0 | return -1; |
1662 | 0 | if (NULL == pkg->development) |
1663 | 0 | return 0; |
1664 | | |
1665 | 0 | return install_packages(pkg->development, dir, verbose); |
1666 | 0 | } |
1667 | | |
1668 | | /** |
1669 | | * Free a clib package |
1670 | | */ |
1671 | | |
1672 | 5 | void clib_package_free(clib_package_t *pkg) { |
1673 | 5 | if (NULL == pkg) { |
1674 | 0 | return; |
1675 | 0 | } |
1676 | | |
1677 | 5 | if (0 != pkg->refs) { |
1678 | 0 | return; |
1679 | 0 | } |
1680 | | |
1681 | 5 | #define FREE(k) \ |
1682 | 65 | if (pkg->k) { \ |
1683 | 6 | free(pkg->k); \ |
1684 | 6 | pkg->k = 0; \ |
1685 | 6 | } |
1686 | 5 | FREE(author); |
1687 | 5 | FREE(description); |
1688 | 5 | FREE(install); |
1689 | 5 | FREE(json); |
1690 | 5 | FREE(license); |
1691 | 5 | FREE(name); |
1692 | 5 | FREE(makefile); |
1693 | 5 | FREE(configure); |
1694 | 5 | FREE(repo); |
1695 | 5 | FREE(repo_name); |
1696 | 5 | FREE(url); |
1697 | 5 | FREE(version); |
1698 | 5 | FREE(flags); |
1699 | 5 | #undef FREE |
1700 | | |
1701 | 5 | if (pkg->src) |
1702 | 0 | list_destroy(pkg->src); |
1703 | 5 | pkg->src = 0; |
1704 | | |
1705 | 5 | if (pkg->dependencies) |
1706 | 0 | list_destroy(pkg->dependencies); |
1707 | 5 | pkg->dependencies = 0; |
1708 | | |
1709 | 5 | if (pkg->development) |
1710 | 1 | list_destroy(pkg->development); |
1711 | 5 | pkg->development = 0; |
1712 | | |
1713 | 5 | free(pkg); |
1714 | 5 | pkg = 0; |
1715 | 5 | } |
1716 | | |
1717 | 4 | void clib_package_dependency_free(void *_dep) { |
1718 | 4 | clib_package_dependency_t *dep = (clib_package_dependency_t *)_dep; |
1719 | 4 | free(dep->name); |
1720 | 4 | free(dep->author); |
1721 | 4 | free(dep->version); |
1722 | 4 | free(dep); |
1723 | 4 | } |
1724 | | |
1725 | 0 | void clib_package_cleanup() { |
1726 | 0 | if (0 != visited_packages) { |
1727 | 0 | hash_each(visited_packages, { |
1728 | 0 | free((void *)key); |
1729 | 0 | (void)val; |
1730 | 0 | }); |
1731 | |
|
1732 | 0 | hash_free(visited_packages); |
1733 | 0 | visited_packages = 0; |
1734 | 0 | } |
1735 | |
|
1736 | 0 | curl_share_cleanup(clib_package_curl_share); |
1737 | 0 | } |