projects/aports·blob v3.4.0_rc5 · refs

222 lines · 4.9 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 <stdio.h>
33#include <stdlib.h>
34#include <string.h>
35#include <unistd.h>
36
37static char *program;
38static char lockfile[PATH_MAX] = "";
39
40struct cmdarray {
41 size_t argc;
42 char *argv[32];
43};
44
45void add_opt(struct cmdarray *cmd, char *opt)
46{
47 cmd->argv[cmd->argc++] = opt;
48 cmd->argv[cmd->argc] = NULL;
49}
50
51int usage(int eval)
52{
53 printf("usage: %s [-h] [-d DESTDIR] URL\n", program);
54 return eval;
55}
56
57int fork_exec(char *argv[], int showerr)
58{
59 int r = 202;
60 int status = 0;
61 pid_t childpid = fork();
62 if (childpid < 0 )
63 err(200, "fork");
64
65 if (childpid == 0) {
66 execvp(argv[0], argv);
67 if (showerr)
68 warn("%s", argv[0]);
69 _exit(201);
70 }
71
72 /* wait for curl/wget and get the exit code */
73 wait(&status);
74 if (WIFEXITED(status))
75 r = WEXITSTATUS(status);
76 return r;
77}
78
79/* create or wait for an NFS-safe lockfile and fetch url with curl or wget */
80int fetch(char *url, const char *destdir)
81{
82 int lockfd, status=0;
83 char outfile[PATH_MAX], partfile[PATH_MAX];
84 char *name, *p;
85 struct flock fl = {
86 .l_type = F_WRLCK,
87 .l_whence = SEEK_SET,
88 .l_start = 1,
89 .l_len = 0,
90 };
91 struct cmdarray curlcmd = {
92 .argc = 5,
93 .argv = { "curl", "-L", "-f", "-o", partfile, NULL }
94 };
95 struct cmdarray wgetcmd = {
96 .argc = 3,
97 .argv = { "wget", "-O", partfile, NULL }
98 };
99
100 name = strrchr(url, '/');
101 if (name == NULL)
102 errx(1, "%s: no '/' in url", url);
103 p = strstr(url, "::");
104 if (p != NULL) {
105 name = url;
106 *p = '\0';
107 url = p + 2;
108 } else {
109 name++;
110 }
111
112 snprintf(outfile, sizeof(outfile), "%s/%s", destdir, name);
113 snprintf(lockfile, sizeof(lockfile), "%s.lock", outfile);
114 snprintf(partfile, sizeof(partfile), "%s.part", outfile);
115
116 lockfd = open(lockfile, O_WRONLY|O_CREAT, 0660);
117 if (lockfd < 0)
118 err(1, "%s", lockfile);
119
120 if (fcntl(lockfd, F_SETLK, &fl) < 0) {
121 int i;
122 printf("Waiting for %s ...\n", lockfile);
123 for (i=0; i<10; i++) {
124 int r = fcntl(lockfd, F_SETLKW, &fl);
125 if (r == 0)
126 break;
127 if (r == -1 && errno != ESTALE)
128 err(1, "fcntl(F_SETLKW)");
129 sleep(1);
130 }
131 }
132
133 if (access(outfile, F_OK) == 0)
134 goto fetch_done;
135
136 if (access(partfile, F_OK) == 0) {
137 printf("Partial download found. Trying to resume.\n");
138 add_opt(&curlcmd, "-C");
139 add_opt(&curlcmd, "-");
140 add_opt(&wgetcmd, "-c");
141 }
142
143 add_opt(&curlcmd, url);
144 add_opt(&wgetcmd, url);
145
146 status = fork_exec(curlcmd.argv, 0);
147
148 /* CURLE_RANGE_ERROR (33)
149 The server does not support or accept range requests. */
150 if (status == 33)
151 unlink(partfile);
152
153 /* is we failed execute curl, then fallback to wget */
154 if (status == 201)
155 status = fork_exec(wgetcmd.argv, 1);
156
157 /* only rename completed downloads */
158 if (status == 0)
159 rename(partfile, outfile);
160
161fetch_done:
162 unlink(lockfile);
163 close(lockfd);
164 lockfile[0] = '\0';
165 return status;
166}
167
168void sighandler(int sig)
169{
170 switch(sig) {
171 case SIGABRT:
172 case SIGINT:
173 case SIGQUIT:
174 case SIGTERM:
175 unlink(lockfile);
176 exit(0);
177 break;
178 default:
179 break;
180 }
181}
182
183/* exit codes get passed through from curl/wget (so we can check in abuild
184 whether the server does not support resuming). Additional exit codes:
185 200: fork failed
186 201: curl/wget could not be started
187 202: curl/wget did not terminate normally
188 203: usage displayed */
189int main(int argc, char *argv[])
190{
191 int opt;
192 char *destdir = "/var/cache/distfiles";
193
194 program = argv[0];
195 while ((opt = getopt(argc, argv, "hd:")) != -1) {
196 switch (opt) {
197 case 'h':
198 return usage(0);
199 break;
200 case 'd':
201 destdir = optarg;
202 break;
203 default:
204 printf("Unknown option '%c'\n", opt);
205 return usage(1);
206 break;
207 }
208 }
209
210 argv += optind;
211 argc -= optind;
212
213 if (argc != 1)
214 return usage(203);
215
216 signal(SIGABRT, sighandler);
217 signal(SIGINT, sighandler);
218 signal(SIGQUIT, sighandler);
219 signal(SIGTERM, sighandler);
220
221 return fetch(argv[0], destdir);
222}