1 // This file is part of BOINC.
2 // http://boinc.berkeley.edu
3 // Copyright (C) 2012 University of California
4 //
5 // BOINC is free software; you can redistribute it and/or modify it
6 // under the terms of the GNU Lesser General Public License
7 // as published by the Free Software Foundation,
8 // either version 3 of the License, or (at your option) any later version.
9 //
10 // BOINC is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 // See the GNU Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public License
16 // along with BOINC.  If not, see <http://www.gnu.org/licenses/>.
17 
18 // logic for "asynchronous" file copy and unzip/verify operations.
19 // "asynchronous" means that the the operations are done in 64KB chunks
20 // in the client's polling loop,
21 // so that the client continues to respond to GUI RPCs
22 // and the manager won't freeze.
23 
24 #ifdef _WIN32
25 #include "boinc_win.h"
26 #else
27 #include <string.h>
28 #endif
29 
30 #ifdef _MSC_VER
31 #define snprintf _snprintf
32 #endif
33 
34 #include "crypt.h"
35 #include "error_numbers.h"
36 #include "filesys.h"
37 #include "md5_file.h"
38 #include "str_replace.h"
39 
40 #include "app.h"
41 #include "client_msgs.h"
42 #include "client_state.h"
43 #include "project.h"
44 #include "sandbox.h"
45 
46 #include "async_file.h"
47 
48 using std::vector;
49 
50 vector<ASYNC_VERIFY*> async_verifies;
51 vector<ASYNC_COPY*> async_copies;
52 
53 #define BUFSIZE 64*1024
54 
55 // set up an async copy operation.
56 //
init(ACTIVE_TASK * _atp,FILE_INFO * _fip,const char * from_path,const char * _to_path)57 int ASYNC_COPY::init(
58     ACTIVE_TASK* _atp, FILE_INFO* _fip,
59     const char* from_path, const char* _to_path
60 ) {
61     atp = _atp;
62     fip = _fip;
63     safe_strcpy(to_path, _to_path);
64 
65     if (log_flags.async_file_debug) {
66         msg_printf(atp->wup->project, MSG_INFO,
67             "[async] started async copy of %s", from_path
68         );
69     }
70     in = boinc_fopen(from_path, "rb");
71     if (!in) return ERR_FOPEN;
72 
73     // Arrange to copy the file to a temp file in the target directory.
74     // It will be renamed later.
75     // Use mkstemp() rather than tempnam() because the latter gives priority
76     // to env var for temp directory; don't want this.
77     //
78     char dir[MAXPATHLEN];
79     boinc_path_to_dir(to_path, dir);
80 #ifdef _WIN32
81     out = boinc_temp_file(dir, "cpy", temp_path, fip->nbytes);
82 #else
83     out = boinc_temp_file(dir, "copy", temp_path);
84 #endif
85     if (!out) {
86         fclose(in);
87         return ERR_FOPEN;
88     }
89     atp->async_copy = this;
90     async_copies.push_back(this);
91     return 0;
92 }
93 
ASYNC_COPY()94 ASYNC_COPY::ASYNC_COPY() {
95     in = out = NULL;
96     atp = NULL;
97     fip = NULL;
98     safe_strcpy(to_path, "");
99     safe_strcpy(temp_path, "");
100 }
101 
~ASYNC_COPY()102 ASYNC_COPY::~ASYNC_COPY() {
103     if (in) fclose(in);
104     if (out) fclose(out);
105     if (atp) {
106         atp->async_copy = NULL;
107     }
108 }
109 
110 // copy a 64KB chunk.
111 // return nonzero if we're done (success or fail)
112 //
copy_chunk()113 int ASYNC_COPY::copy_chunk() {
114     unsigned char buf[BUFSIZE];
115     int retval;
116 
117     size_t n = fread(buf, 1, BUFSIZE, in);
118     if (n == 0) {
119         // copy done.  rename temp file
120         //
121         fclose(in);
122         fclose(out);
123         in = out = NULL;
124         retval = boinc_rename(temp_path, to_path);
125         if (retval) {
126             error(retval);
127             return 1;
128         }
129 
130         if (log_flags.async_file_debug) {
131             msg_printf(atp->wup->project, MSG_INFO,
132                 "[async] async copy of %s finished", to_path
133             );
134         }
135 
136         atp->async_copy = NULL;
137         fip->set_permissions(to_path);
138 
139         // If task is still scheduled, start it.
140         //
141         if (atp->scheduler_state == CPU_SCHED_SCHEDULED) {
142             retval = atp->start();
143             if (retval) {
144                 error(retval);
145             }
146         }
147         return 1;       // tell caller we're done
148     } else {
149         size_t m = fwrite(buf, 1, n, out);
150         if (m != n) {
151             error(ERR_FWRITE);
152             return 1;
153         }
154     }
155     return 0;
156 }
157 
158 // handle the failure of a copy; error out the result
159 //
error(int retval)160 void ASYNC_COPY::error(int retval) {
161     char err_msg[4096];
162     atp->set_task_state(PROCESS_COULDNT_START, "ASYNC_COPY::error");
163     sprintf(err_msg, "Couldn't copy file: %s", boincerror(retval));
164     gstate.report_result_error(*(atp->result), err_msg);
165     gstate.request_schedule_cpus("start failed");
166 }
167 
remove_async_copy(ASYNC_COPY * acp)168 void remove_async_copy(ASYNC_COPY* acp) {
169     vector<ASYNC_COPY*>::iterator i = async_copies.begin();
170     while (i != async_copies.end()) {
171         if (*i == acp) {
172             async_copies.erase(i);
173             break;
174         }
175         ++i;
176     }
177     delete acp;
178 }
179 
init(FILE_INFO * _fip)180 int ASYNC_VERIFY::init(FILE_INFO* _fip) {
181     fip = _fip;
182     md5_init(&md5_state);
183     get_pathname(fip, inpath, sizeof(inpath));
184 
185     if (log_flags.async_file_debug) {
186         msg_printf(fip->project, MSG_INFO,
187             "[async] started async MD5%s of %s",
188             fip->download_gzipped?" and uncompress":"", fip->name
189         );
190     }
191     if (fip->download_gzipped) {
192         safe_strcpy(outpath, inpath);
193         char dir[MAXPATHLEN];
194         boinc_path_to_dir(outpath, dir);
195 #ifdef _WIN32
196         out = boinc_temp_file(dir, "vfy", temp_path, fip->nbytes);
197 #else
198         out = boinc_temp_file(dir, "verify", temp_path);
199 #endif
200         if (!out) {
201             fclose(in);
202             return ERR_FOPEN;
203         }
204 
205         safe_strcat(inpath, ".gz");
206         gzin = gzopen(inpath, "rb");
207         if (gzin == Z_NULL) {
208             fclose(out);
209             boinc_delete_file(temp_path);
210             return ERR_FOPEN;
211         }
212     } else {
213         in = boinc_fopen(inpath, "rb");
214         if (!in) return ERR_FOPEN;
215     }
216     async_verifies.push_back(this);
217     fip->async_verify = this;
218     return 0;
219 }
220 
221 // the MD5 has been computed.  Finish up.
222 //
finish()223 void ASYNC_VERIFY::finish() {
224     unsigned char binout[16];
225     char md5_buf[64];
226     int retval;
227 
228     md5_finish(&md5_state, binout);
229     for (int i=0; i<16; i++) {
230         sprintf(md5_buf+2*i, "%02x", binout[i]);
231     }
232     md5_buf[32] = 0;
233     if (fip->signature_required) {
234         bool verified;
235         retval = check_file_signature2(md5_buf, fip->file_signature,
236             fip->project->code_sign_key, verified
237         );
238         if (retval) {
239             error(retval);
240             return;
241         }
242         if (!verified) {
243             error(ERR_RSA_FAILED);
244             return;
245         }
246     } else {
247         if (strcmp(md5_buf, fip->md5_cksum)) {
248             error(ERR_MD5_FAILED);
249             return;
250         }
251     }
252     if (log_flags.async_file_debug) {
253         msg_printf(fip->project, MSG_INFO,
254             "[async] async verify of %s finished", fip->name
255         );
256     }
257     fip->async_verify = NULL;
258     fip->status = FILE_PRESENT;
259     fip->set_permissions();
260 }
261 
error(int retval)262 void ASYNC_VERIFY::error(int retval) {
263     if (log_flags.async_file_debug) {
264         msg_printf(fip->project, MSG_INFO,
265             "[async] async verify of %s failed: %s",
266             fip->name, boincerror(retval)
267         );
268     }
269     fip->async_verify = NULL;
270     fip->status = retval;
271 }
272 
verify_chunk()273 int ASYNC_VERIFY::verify_chunk() {
274     int n;
275     unsigned char buf[BUFSIZE];
276     if (fip->download_gzipped) {
277         n = gzread(gzin, buf, BUFSIZE);
278         if (n <=0) {
279             // done
280             //
281             gzclose(gzin);
282             fclose(out);
283             delete_project_owned_file(inpath, true);
284             boinc_rename(temp_path, outpath);
285             finish();
286             return 1;
287         } else {
288             int m = (int)fwrite(buf, 1, n, out);
289             if (m != n) {
290                 // write failed
291                 //
292                 error(ERR_FWRITE);
293                 return 1;
294             }
295             md5_append(&md5_state, buf, n);
296         }
297     } else {
298         n = (int)fread(buf, 1, BUFSIZE, in);
299         if (n <= 0) {
300             fclose(in);
301             finish();
302             return 1;
303         } else {
304             md5_append(&md5_state, buf, n);
305         }
306     }
307     return 0;
308 }
309 
remove_async_verify(ASYNC_VERIFY * avp)310 void remove_async_verify(ASYNC_VERIFY* avp) {
311     vector<ASYNC_VERIFY*>::iterator i = async_verifies.begin();
312     while (i != async_verifies.end()) {
313         if (*i == avp) {
314             async_verifies.erase(i);
315             break;
316         }
317         ++i;
318     }
319     delete avp;
320 }
321 
322 // If there are any async file operations,
323 // do a 64KB chunk of the first one and return true.
324 //
325 // Note: if there are lots of pending operations,
326 // it's better to finish the oldest one before starting the rest
327 //
do_async_file_op()328 void do_async_file_op() {
329     if (async_copies.size()) {
330         ASYNC_COPY* acp = async_copies[0];
331         if (acp->copy_chunk()) {
332             async_copies.erase(async_copies.begin());
333             delete acp;
334         }
335         return;
336     }
337     if (async_verifies.size()) {
338         if (async_verifies[0]->verify_chunk()) {
339             async_verifies.erase(async_verifies.begin());
340         }
341     }
342 }
343 
344