projects/aports·blob 3.7.0 · refs

241 lines · 5.4 KiB

1/* MIT license
2
3Copyright (C) 2015 Natanael Copa <[email protected]>
4
5Permission is hereby granted, free of charge, to any person obtaining a copy
6of this software and associated documentation files (the "Software"), to deal
7in the Software without restriction, including without limitation the rights
8to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9copies of the Software, and to permit persons to whom the Software is
10furnished to do so, subject to the following conditions:
11
12The above copyright notice and this permission notice shall be included in
13all copies or substantial portions of the Software.
14
15THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21THE SOFTWARE.
22
23*/
24
25
26#include <sys/wait.h>
27
28#include <err.h>
29#include <errno.h>
30#include <fcntl.h>
31#include <limits.h>
32#include <stdbool.h>
33#include <stdio.h>
34#include <stdlib.h>
35#include <string.h>
36#include <unistd.h>
37
38static char *program;
39static char lockfile[PATH_MAX] = "";
40
41struct cmdarray {
42 size_t argc;
43 char *argv[32];
44};
45
46void add_opt(struct cmdarray *cmd, char *opt)
47{
48 cmd->argv[cmd->argc++] = opt;
49 cmd->argv[cmd->argc] = NULL;
50}
51
52int usage(int eval)
53{
54 printf("usage: %s [-h] [-d DESTDIR] URL\n", program);
55 return eval;
56}
57
58int fork_exec(char *argv[], int showerr)
59{
60 int r = 202;
61 int status = 0;
62 pid_t childpid = fork();
63 if (childpid < 0 )
64 err(200, "fork");
65
66 if (childpid == 0) {
67 execvp(argv[0], argv);
68 if (showerr)
69 warn("%s", argv[0]);
70 _exit(201);
71 }
72
73 /* wait for curl/wget and get the exit code */
74 wait(&status);
75 if (WIFEXITED(status))
76 r = WEXITSTATUS(status);
77 return r;
78}
79
80/* create or wait for an NFS-safe lockfile and fetch url with curl or wget */
81int fetch(char *url, const char *destdir, bool insecure)
82{
83 int lockfd, status=0;
84 char outfile[PATH_MAX-5], partfile[PATH_MAX];
85 char *name, *p;
86 struct flock fl = {
87 .l_type = F_WRLCK,
88 .l_whence = SEEK_SET,
89 .l_start = 1,
90 .l_len = 0,
91 };
92 struct cmdarray curlcmd = {
93 .argc = 5,
94 .argv = { "curl", "-L", "-f", "-o", partfile, NULL }
95 };
96 struct cmdarray wgetcmd = {
97 .argc = 3,
98 .argv = { "wget", "-O", partfile, NULL }
99 };
100
101 name = strrchr(url, '/');
102 if (name == NULL)
103 errx(1, "%s: no '/' in url", url);
104 p = strstr(url, "::");
105 if (p != NULL) {
106 name = url;
107 *p = '\0';
108 url = p + 2;
109 } else {
110 name++;
111 }
112
113 snprintf(outfile, sizeof(outfile), "%s/%s", destdir, name);
114 snprintf(lockfile, sizeof(lockfile), "%s.lock", outfile);
115 snprintf(partfile, sizeof(partfile), "%s.part", outfile);
116
117 lockfd = open(lockfile, O_WRONLY|O_CREAT, 0660);
118 if (lockfd < 0)
119 err(1, "%s", lockfile);
120
121 if (fcntl(lockfd, F_SETLK, &fl) < 0) {
122 int i;
123 printf("Waiting for %s ...\n", lockfile);
124 for (i=0; i<10; i++) {
125 int r = fcntl(lockfd, F_SETLKW, &fl);
126 if (r == 0)
127 break;
128 if (r == -1 && errno != ESTALE)
129 err(1, "fcntl(F_SETLKW)");
130 sleep(1);
131 }
132 }
133
134 if (access(outfile, F_OK) == 0)
135 goto fetch_done;
136
137 /* enable insecure mode when http. This may be useful when it redirects to https */
138 if (insecure || strstr(url, "http://") == url) {
139 add_opt(&curlcmd, "--insecure");
140 add_opt(&wgetcmd, "--no-check-certificate");
141 }
142
143 if (access(partfile, F_OK) == 0) {
144 printf("Partial download found. Trying to resume.\n");
145 add_opt(&curlcmd, "--continue-at");
146 add_opt(&curlcmd, "-");
147 add_opt(&wgetcmd, "-c");
148 }
149
150 add_opt(&curlcmd, url);
151 add_opt(&wgetcmd, url);
152
153 status = fork_exec(curlcmd.argv, 0);
154
155 /* CURLE_RANGE_ERROR (33)
156 The server does not support or accept range requests. */
157 if (status == 33) {
158 unlink(partfile);
159 if( curlcmd.argc >=3) {
160 /* remove --continue-at - options */
161 curlcmd.argv[curlcmd.argc-3] = curlcmd.argv[curlcmd.argc-1];
162 curlcmd.argv[curlcmd.argc-2] = NULL;
163 curlcmd.argc -= 2;
164 status = fork_exec(curlcmd.argv, 0);
165 }
166 }
167
168 /* is we failed execute curl, then fallback to wget */
169 if (status == 201)
170 status = fork_exec(wgetcmd.argv, 1);
171
172 /* only rename completed downloads */
173 if (status == 0)
174 rename(partfile, outfile);
175
176fetch_done:
177 unlink(lockfile);
178 close(lockfd);
179 lockfile[0] = '\0';
180 return status;
181}
182
183void sighandler(int sig)
184{
185 switch(sig) {
186 case SIGABRT:
187 case SIGINT:
188 case SIGQUIT:
189 case SIGTERM:
190 unlink(lockfile);
191 exit(0);
192 break;
193 default:
194 break;
195 }
196}
197
198/* exit codes get passed through from curl/wget (so we can check in abuild
199 whether the server does not support resuming). Additional exit codes:
200 200: fork failed
201 201: curl/wget could not be started
202 202: curl/wget did not terminate normally
203 203: usage displayed */
204int main(int argc, char *argv[])
205{
206 int opt;
207 bool insecure = false;
208 char *destdir = "/var/cache/distfiles";
209
210 program = argv[0];
211 while ((opt = getopt(argc, argv, "hd:k")) != -1) {
212 switch (opt) {
213 case 'h':
214 return usage(0);
215 break;
216 case 'd':
217 destdir = optarg;
218 break;
219 case 'k':
220 insecure = true;
221 break;
222 default:
223 printf("Unknown option '%c'\n", opt);
224 return usage(1);
225 break;
226 }
227 }
228
229 argv += optind;
230 argc -= optind;
231
232 if (argc != 1)
233 return usage(203);
234
235 signal(SIGABRT, sighandler);
236 signal(SIGINT, sighandler);
237 signal(SIGQUIT, sighandler);
238 signal(SIGTERM, sighandler);
239
240 return fetch(argv[0], destdir, insecure);
241}