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