1 // This file is part of BOINC.
2 // http://boinc.berkeley.edu
3 // Copyright (C) 2008 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 #include "cpp.h"
19
20 #ifdef _WIN32
21 #include "boinc_win.h"
22 #else
23 #include "config.h"
24 #include <algorithm>
25 #include <cstdio>
26 #include <cstdlib>
27 #include <cstring>
28 #include <cassert>
29 #if HAVE_SYS_STAT_H
30 #include <sys/stat.h>
31 #endif
32 #endif
33
34 #include "error_numbers.h"
35 #include "filesys.h"
36 #include "parse.h"
37 #include "str_replace.h"
38 #include "str_util.h"
39 #include "url.h"
40
41 #include "client_msgs.h"
42 #include "client_state.h"
43 #include "file_names.h"
44 #include "log_flags.h"
45 #include "project.h"
46
47 using std::string;
48 using std::sort;
49
50 // write account_*.xml file.
51 // NOTE: this is called only when
52 // 1) attach to a project, and
53 // 2) after a scheduler RPC
54 // So in either case PROJECT.project_prefs
55 // (which normally is undefined) is valid
56 //
write_account_file()57 int PROJECT::write_account_file() {
58 char path[MAXPATHLEN];
59 FILE* f;
60 int retval;
61
62 get_account_filename(master_url, path, sizeof(path));
63 f = boinc_fopen(TEMP_ACCT_FILE_NAME, "w");
64 if (!f) return ERR_FOPEN;
65
66 fprintf(f,
67 "<account>\n"
68 " <master_url>%s</master_url>\n"
69 " <authenticator>%s</authenticator>\n",
70 master_url,
71 authenticator
72 );
73 // put project name in account file for informational purposes only
74 // (client state file is authoritative)
75 //
76 if (strlen(project_name)) {
77 fprintf(f, " <project_name>%s</project_name>\n", project_name);
78 }
79 fprintf(f, "<project_preferences>\n%s</project_preferences>\n",
80 project_prefs.c_str()
81 );
82 fprintf(f, "%s", gui_urls.c_str());
83 fprintf(f, "</account>\n");
84 fclose(f);
85 retval = boinc_rename(TEMP_ACCT_FILE_NAME, path);
86 if (retval) return ERR_RENAME;
87 return 0;
88 }
89
handle_no_rsc_pref(PROJECT * p,const char * name)90 static void handle_no_rsc_pref(PROJECT* p, const char* name) {
91 int i = rsc_index(name);
92 if (i < 0) return;
93 p->no_rsc_pref[i] = true;
94 }
95
96 // parse an account_*.xml file, ignoring <venue> elements
97 // (since we don't know the host venue yet)
98 //
parse_account(FILE * in)99 int PROJECT::parse_account(FILE* in) {
100 char buf2[256];
101 int retval;
102 bool in_project_prefs = false, btemp;
103 double dtemp;
104
105 for (int i=0; i<coprocs.n_rsc; i++) {
106 no_rsc_pref[i] = false;
107 }
108 MIOFILE mf;
109 XML_PARSER xp(&mf);
110 mf.init_file(in);
111
112 safe_strcpy(master_url, "");
113 safe_strcpy(authenticator, "");
114 while (!xp.get_tag()) {
115 if (xp.match_tag("account")) continue;
116 if (xp.match_tag("project_preferences")) {
117 in_project_prefs = true;
118 continue;
119 }
120 if (xp.match_tag("/project_preferences")) {
121 in_project_prefs = false;
122 continue;
123 }
124 if (xp.match_tag("/account")) {
125 return 0;
126 } else if (xp.match_tag("venue")) {
127 string devnull;
128 retval = copy_element_contents(xp.f->f, "</venue>", devnull);
129 if (retval) return retval;
130 continue;
131 } else if (xp.parse_str("master_url", master_url, sizeof(master_url))) {
132 canonicalize_master_url(master_url, sizeof(master_url));
133 continue;
134 } else if (xp.parse_str("authenticator", authenticator, sizeof(authenticator))) continue;
135 else if (xp.parse_double("resource_share", dtemp)) {
136 if (ams_resource_share < 0) {
137 resource_share = dtemp;
138 }
139 continue;
140 }
141 else if (xp.parse_bool("no_cpu", btemp)) {
142 if (btemp) handle_no_rsc_pref(this, "CPU");
143 continue;
144 }
145
146 // deprecated
147 else if (xp.parse_bool("no_cuda", btemp)) {
148 if (btemp) handle_no_rsc_pref(this, GPU_TYPE_NVIDIA);
149 continue;
150 }
151 else if (xp.parse_bool("no_ati", btemp)) {
152 if (btemp) handle_no_rsc_pref(this, GPU_TYPE_ATI);
153 continue;
154 }
155 else if (xp.parse_bool("no_intel_gpu", btemp)) {
156 if (btemp) handle_no_rsc_pref(this, GPU_TYPE_INTEL);
157 continue;
158 }
159
160 else if (xp.parse_str("no_rsc", buf2, sizeof(buf2))) {
161 handle_no_rsc_pref(this, buf2);
162 continue;
163 }
164 else if (xp.parse_str("project_name", project_name, sizeof(project_name))) continue;
165 else if (xp.match_tag("gui_urls")) {
166 string foo;
167 retval = copy_element_contents(xp.f->f, "</gui_urls>", foo);
168 if (retval) return retval;
169 gui_urls = "<gui_urls>\n"+foo+"</gui_urls>\n";
170 continue;
171 } else if (xp.match_tag("project_specific")) {
172 retval = copy_element_contents(
173 xp.f->f,
174 "</project_specific>",
175 project_specific_prefs
176 );
177 if (retval) return retval;
178 continue;
179 } else {
180 // don't show unparsed XML errors if we're in project prefs
181 //
182 if (!in_project_prefs && log_flags.unparsed_xml) {
183 msg_printf(0, MSG_INFO,
184 "[unparsed_xml] PROJECT::parse_account(): unrecognized: %s\n",
185 xp.parsed_tag
186 );
187 }
188 }
189 }
190 return ERR_XML_PARSE;
191 }
192
193 // scan an account_*.xml file, looking for a <venue> element
194 // that matches this host's venue,
195 // and parsing that for resource share and prefs.
196 // Call this only after client_state.xml has been read
197 // (so that we know the host venue)
198 //
parse_account_file_venue()199 int PROJECT::parse_account_file_venue() {
200 char attr_buf[256], venue[256], path[MAXPATHLEN], buf2[256];
201 int retval;
202 bool in_right_venue = false, btemp;
203 double dtemp;
204
205 get_account_filename(master_url, path, sizeof(path));
206 FILE* in = boinc_fopen(path, "r");
207 if (!in) return ERR_FOPEN;
208
209 //msg_printf(this, MSG_INFO, "parsing project prefs, looking for venue %s", host_venue);
210 MIOFILE mf;
211 XML_PARSER xp(&mf);
212 mf.init_file(in);
213 while (!xp.get_tag(attr_buf, sizeof(attr_buf))) {
214 if (xp.match_tag("/account")) {
215 fclose(in);
216 return 0;
217 } else if (xp.match_tag("venue")) {
218 parse_attr(attr_buf, "name", venue, sizeof(venue));
219 if (!strcmp(venue, host_venue)) {
220 //msg_printf(this, MSG_INFO, "found venue %s", host_venue);
221 using_venue_specific_prefs = true;
222 in_right_venue = true;
223
224 // reset these
225 //
226 for (int i=0; i<coprocs.n_rsc; i++) {
227 no_rsc_pref[i] = false;
228 }
229 } else {
230 string devnull;
231 retval = copy_element_contents(in, "</venue>", devnull);
232 if (retval) return retval;
233 }
234 continue;
235 }
236 if (!in_right_venue) continue;
237 if (xp.match_tag("/venue")) {
238 in_right_venue = false;
239 continue;
240 } else if (xp.match_tag("project_specific")) {
241 retval = copy_element_contents(
242 xp.f->f,
243 "</project_specific>",
244 project_specific_prefs
245 );
246 if (retval) return retval;
247 continue;
248 } else if (xp.parse_double("resource_share", dtemp)) {
249 // if account manager has specified resource share, don't override
250 //
251 if (ams_resource_share < 0) {
252 resource_share = dtemp;
253 }
254 continue;
255 }
256 else if (xp.parse_bool("no_cpu", btemp)) {
257 if (btemp) handle_no_rsc_pref(this, "CPU");
258 continue;
259 }
260
261 // deprecated syntax
262 else if (xp.parse_bool("no_cuda", btemp)) {
263 if (btemp) handle_no_rsc_pref(this, GPU_TYPE_NVIDIA);
264 continue;
265 }
266 else if (xp.parse_bool("no_ati", btemp)) {
267 if (btemp) handle_no_rsc_pref(this, GPU_TYPE_ATI);
268 continue;
269 }
270 else if (xp.parse_bool("no_intel_gpu", btemp)) {
271 if (btemp) handle_no_rsc_pref(this, GPU_TYPE_INTEL);
272 continue;
273 }
274
275 else if (xp.parse_str("no_rsc", buf2, sizeof(buf2))) {
276 handle_no_rsc_pref(this, buf2);
277 continue;
278 }
279 else {
280 // skip project preferences the client doesn't know about
281 //
282 xp.skip_unexpected();
283 }
284 }
285 fclose(in);
286 return ERR_XML_PARSE;
287 }
288
parse_account_file()289 int PROJECT::parse_account_file() {
290 char path[MAXPATHLEN];
291 int retval;
292 FILE* f;
293
294 get_account_filename(master_url, path, sizeof(path));
295 f = boinc_fopen(path, "r");
296 if (!f) return ERR_FOPEN;
297 retval = parse_account(f);
298 fclose(f);
299 if (retval) return retval;
300 if (strlen(host_venue)) {
301 return parse_account_file_venue();
302 }
303 return 0;
304 }
305
parse_account_files_venue()306 int CLIENT_STATE::parse_account_files_venue() {
307 unsigned int i;
308
309 for (i=0; i<projects.size(); i++) {
310 PROJECT* p = projects[i];
311 if (strlen(p->host_venue)) {
312 p->parse_account_file_venue();
313 }
314 }
315 return 0;
316 }
317
parse_account_files()318 int CLIENT_STATE::parse_account_files() {
319 string name;
320 PROJECT* project;
321 FILE* f;
322 int retval;
323
324 DirScanner dir(".");
325 while (dir.scan(name)) {
326 if (!is_file(name.c_str())) continue;
327 if (!is_account_file(name.c_str())) continue;
328
329 f = boinc_fopen(name.c_str(), "r");
330 if (!f) continue;
331 project = new PROJECT;
332
333 // Assume master_url_fetch_pending, sched_rpc_pending are
334 // true until we read client_state.xml
335 //
336 project->master_url_fetch_pending = true;
337 project->sched_rpc_pending = RPC_REASON_INIT;
338 retval = project->parse_account(f);
339 fclose(f);
340 if (retval) {
341 msg_printf(project, MSG_INTERNAL_ERROR,
342 "Couldn't parse account file %s", name.c_str()
343 );
344 delete project;
345 } else {
346 if (lookup_project(project->master_url)) {
347 msg_printf(project, MSG_INFO,
348 "Duplicate account file %s - ignoring", name.c_str()
349 );
350 delete project;
351 } else {
352 projects.push_back(project);
353 }
354 }
355 }
356 return 0;
357 }
358
clear()359 void DAILY_STATS::clear() {
360 memset(this, 0, sizeof(DAILY_STATS));
361 }
362
parse(FILE * in)363 int DAILY_STATS::parse(FILE* in) {
364 MIOFILE mf;
365 XML_PARSER xp(&mf);
366 mf.init_file(in);
367
368 clear();
369 while (!xp.get_tag()) {
370 if (xp.match_tag("/daily_statistics")) {
371 if (day == 0) return ERR_XML_PARSE;
372 return 0;
373 }
374 else if (xp.parse_double("day", day)) continue;
375 else if (xp.parse_double("user_total_credit", user_total_credit)) continue;
376 else if (xp.parse_double("user_expavg_credit", user_expavg_credit)) continue;
377 else if (xp.parse_double("host_total_credit", host_total_credit)) continue;
378 else if (xp.parse_double("host_expavg_credit", host_expavg_credit)) continue;
379 }
380 return ERR_XML_PARSE;
381 }
382
operator <(const DAILY_STATS & x1,const DAILY_STATS & x2)383 bool operator < (const DAILY_STATS& x1, const DAILY_STATS& x2) {
384 return (x1.day < x2.day);
385 }
386
387 // parse an statistics_*.xml file
388 //
parse_statistics(FILE * in)389 int PROJECT::parse_statistics(FILE* in) {
390 int retval;
391
392 MIOFILE mf;
393 XML_PARSER xp(&mf);
394 mf.init_file(in);
395
396 while (!xp.get_tag()) {
397 if (xp.match_tag("/project_statistics")) {
398 sort(statistics.begin(), statistics.end());
399 return 0;
400 }
401 if (xp.match_tag("project_statistics")) continue;
402 if (xp.match_tag("daily_statistics")) {
403 DAILY_STATS daily_stats;
404 retval = daily_stats.parse(in);
405 if (!retval) {
406 statistics.push_back(daily_stats);
407 }
408 continue;
409 }
410 if (xp.parse_str("master_url", master_url, sizeof(master_url))) {
411 canonicalize_master_url(master_url, sizeof(master_url));
412 continue;
413 }
414 if (log_flags.unparsed_xml) {
415 msg_printf(0, MSG_INFO,
416 "[unparsed_xml] PROJECT::parse_statistics(): unrecognized: %s\n",
417 xp.parsed_tag
418 );
419 }
420 }
421 return ERR_XML_PARSE;
422 }
423
parse_statistics_files()424 int CLIENT_STATE::parse_statistics_files() {
425 string name;
426 PROJECT* project;
427 FILE* f;
428 int retval;
429
430 DirScanner dir(".");
431 while (dir.scan(name)) {
432 PROJECT temp;
433 if (is_statistics_file(name.c_str())) {
434 f = boinc_fopen(name.c_str(), "r");
435 if (!f) continue;
436 retval = temp.parse_statistics(f);
437 fclose(f);
438 if (retval) {
439 msg_printf(NULL, MSG_INTERNAL_ERROR,
440 "Couldn't parse %s", name.c_str()
441 );
442 } else {
443 project = lookup_project(temp.master_url);
444 if (project == NULL) {
445 msg_printf(NULL, MSG_INFO,
446 "Project for %s not found - ignoring",
447 name.c_str()
448 );
449 } else {
450 for (std::vector<DAILY_STATS>::const_iterator i=temp.statistics.begin();
451 i!=temp.statistics.end(); ++i
452 ) {
453 project->statistics.push_back(*i);
454 }
455 }
456 }
457 }
458 }
459 return 0;
460 }
461
write_statistics_file()462 int PROJECT::write_statistics_file() {
463 char path[MAXPATHLEN];
464 FILE* f;
465 int retval;
466
467 get_statistics_filename(master_url, path, sizeof(path));
468 f = boinc_fopen(TEMP_STATS_FILE_NAME, "w");
469 if (!f) return ERR_FOPEN;
470 fprintf(f,
471 "<project_statistics>\n"
472 " <master_url>%s</master_url>\n",
473 master_url
474 );
475
476 for (std::vector<DAILY_STATS>::iterator i=statistics.begin();
477 i!=statistics.end(); ++i
478 ) {
479 fprintf(f,
480 " <daily_statistics>\n"
481 " <day>%f</day>\n"
482 " <user_total_credit>%f</user_total_credit>\n"
483 " <user_expavg_credit>%f</user_expavg_credit>\n"
484 " <host_total_credit>%f</host_total_credit>\n"
485 " <host_expavg_credit>%f</host_expavg_credit>\n"
486 " </daily_statistics>\n",
487 i->day,
488 i->user_total_credit,
489 i->user_expavg_credit,
490 i->host_total_credit,
491 i->host_expavg_credit
492 );
493 }
494
495 fprintf(f,
496 "</project_statistics>\n"
497 );
498
499 fclose(f);
500 retval = boinc_rename(TEMP_STATS_FILE_NAME, path);
501 if (retval) return ERR_RENAME;
502 return 0;
503 }
504
add_project(const char * master_url,const char * _auth,const char * project_name,bool attached_via_acct_mgr)505 int CLIENT_STATE::add_project(
506 const char* master_url, const char* _auth, const char* project_name,
507 bool attached_via_acct_mgr
508 ) {
509 char path[MAXPATHLEN], canonical_master_url[256], auth[256];
510 PROJECT* project;
511 FILE* f;
512 int retval;
513
514 if (cc_config.disallow_attach) {
515 return ERR_USER_PERMISSION;
516 }
517
518 safe_strcpy(canonical_master_url, master_url);
519 strip_whitespace(canonical_master_url);
520 canonicalize_master_url(canonical_master_url, sizeof(canonical_master_url));
521 if (!valid_master_url(canonical_master_url)) {
522 msg_printf(0, MSG_INFO, "Invalid URL: %s", canonical_master_url);
523 return ERR_INVALID_URL;
524 }
525
526 safe_strcpy(auth, _auth);
527 strip_whitespace(auth);
528 if (!strlen(auth)) {
529 msg_printf(0, MSG_INFO, "Missing account key");
530 return ERR_AUTHENTICATOR;
531 }
532
533 // check if we're already attached to this project
534 //
535 if (lookup_project(canonical_master_url)) {
536 msg_printf(0, MSG_INFO, "Already attached to %s", canonical_master_url);
537 return ERR_ALREADY_ATTACHED;
538 }
539
540 // create project state
541 //
542 project = new PROJECT;
543 safe_strcpy(project->master_url, canonical_master_url);
544 safe_strcpy(project->authenticator, auth);
545 safe_strcpy(project->project_name, project_name);
546 project->attached_via_acct_mgr = attached_via_acct_mgr;
547
548 retval = project->write_account_file();
549 if (retval) {
550 delete project;
551 return retval;
552 }
553
554 get_account_filename(canonical_master_url, path, sizeof(path));
555 f = boinc_fopen(path, "r");
556 if (!f) {
557 delete project;
558 return ERR_FOPEN;
559 }
560 retval = project->parse_account(f);
561 fclose(f);
562 if (retval) {
563 delete project;
564 return retval;
565 }
566
567 // remove any old files
568 // (unless PROJECT/app_info.xml is found, so that
569 // people using anonymous platform don't have to get apps again)
570 //
571 sprintf(path, "%s/%s", project->project_dir(), APP_INFO_FILE_NAME);
572 if (boinc_file_exists(path)) {
573 project->anonymous_platform = true;
574 f = fopen(path, "r");
575 if (f) {
576 parse_app_info(project, f);
577 fclose(f);
578 }
579 } else {
580 retval = remove_project_dir(*project);
581 }
582
583 retval = make_project_dir(*project);
584 if (retval) {
585 delete project;
586 return retval;
587 }
588 projects.push_back(project);
589 sort_projects_by_name();
590 project->sched_rpc_pending = RPC_REASON_INIT;
591 set_client_state_dirty("Add project");
592 return 0;
593 }
594
parse_preferences_for_user_files()595 int CLIENT_STATE::parse_preferences_for_user_files() {
596 unsigned int i;
597
598 for (i=0; i<projects.size(); i++) {
599 projects[i]->parse_preferences_for_user_files();
600 }
601 return 0;
602 }
603
604