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 #ifdef _WIN32
19 #include "boinc_win.h"
20 #include "win_util.h"
21 #else
22 #include "config.h"
23 #include <cstring>
24 #include <errno.h>
25 #endif
26 
27 #ifdef __APPLE__
28 #include "mac_spawn.h"
29 #endif
30 
31 #ifdef _MSC_VER
32 #define snprintf _snprintf
33 #endif
34 
35 #include "error_numbers.h"
36 #include "filesys.h"
37 #include "md5_file.h"
38 #include "parse.h"
39 #include "str_replace.h"
40 #include "str_util.h"
41 #include "util.h"
42 
43 #include "client_msgs.h"
44 #include "client_state.h"
45 #include "cs_proxy.h"
46 #include "file_names.h"
47 #include "project.h"
48 #include "result.h"
49 
50 #define MAX_STATE_FILE_WRITE_ATTEMPTS 2
51 
set_client_state_dirty(const char * source)52 void CLIENT_STATE::set_client_state_dirty(const char* source) {
53     if (log_flags.statefile_debug) {
54         msg_printf(0, MSG_INFO, "[statefile] set dirty: %s\n", source);
55     }
56     client_state_dirty = true;
57 }
58 
valid_state_file(const char * fname)59 static bool valid_state_file(const char* fname) {
60     char buf[256];
61     FILE* f = boinc_fopen(fname, "r");
62     if (!f) return false;
63     if (!fgets(buf, 256, f)) {
64         fclose(f);
65         return false;
66     }
67     if (!match_tag(buf, "<client_state>")) {
68         fclose(f);
69         return false;
70     }
71     while (fgets(buf, 256, f)) {
72         if (match_tag(buf, "</client_state>")) {
73             fclose(f);
74             return true;
75         }
76     }
77     fclose(f);
78     return false;
79 }
80 
81 // return true if r0 arrived before r1,
82 // with tie-break based on name hash.
83 // used to sort result list
84 //
arrived_first(RESULT * r0,RESULT * r1)85 static inline bool arrived_first(RESULT* r0, RESULT* r1) {
86     if (r0->received_time < r1->received_time) {
87         return true;
88     }
89     if (r0->received_time > r1->received_time) {
90         return false;
91     }
92     return (r0->name_md5 < r1->name_md5);
93 }
94 
95 // Parse the client_state.xml file
96 //
parse_state_file()97 int CLIENT_STATE::parse_state_file() {
98     const char *fname;
99 
100     // Look for a valid state file:
101     // First "next", then regular, then "prev"
102     //
103     if (valid_state_file(STATE_FILE_NEXT)) {
104         fname = STATE_FILE_NEXT;
105         msg_printf(0, MSG_INFO, "Using state file %s", STATE_FILE_NEXT);
106     } else if (valid_state_file(STATE_FILE_NAME)) {
107         fname = STATE_FILE_NAME;
108     } else if (valid_state_file(STATE_FILE_PREV)) {
109         msg_printf(0, MSG_INFO, "Using state file %s", STATE_FILE_PREV);
110         fname = STATE_FILE_PREV;
111     } else {
112         msg_printf(0, MSG_INFO, "Creating new client state file");
113 
114         // avoid warning messages about version
115         //
116         old_major_version = BOINC_MAJOR_VERSION;
117         old_minor_version = BOINC_MINOR_VERSION;
118         old_release = BOINC_RELEASE;
119         return ERR_FOPEN;
120     }
121     return parse_state_file_aux(fname);
122 }
123 
parse_state_file_aux(const char * fname)124 int CLIENT_STATE::parse_state_file_aux(const char* fname) {
125     PROJECT *project=NULL;
126     int retval=0;
127     string stemp;
128 
129     FILE* f = fopen(fname, "r");
130     if (!f) return ERR_FOPEN;
131     MIOFILE mf;
132     XML_PARSER xp(&mf);
133     mf.init_file(f);
134     while (!xp.get_tag()) {
135         if (xp.match_tag("/client_state")) {
136             break;
137         }
138         if (xp.match_tag("client_state")) {
139             continue;
140         }
141         if (xp.match_tag("project")) {
142             PROJECT temp_project;
143             retval = temp_project.parse_state(xp);
144             if (retval) {
145                 msg_printf(NULL, MSG_INTERNAL_ERROR, "Can't parse project in state file");
146             } else {
147 #ifdef SIM
148                 project = new PROJECT;
149                 *project = temp_project;
150                 projects.push_back(project);
151 #else
152                 project = lookup_project(temp_project.master_url);
153                 if (project) {
154                     project->copy_state_fields(temp_project);
155                 } else {
156                     msg_printf(&temp_project, MSG_INTERNAL_ERROR,
157                         "Project %s is in state file but no account file found",
158                         temp_project.get_project_name()
159                     );
160                 }
161 #endif
162             }
163             continue;
164         }
165         if (xp.match_tag("app")) {
166             APP* app = new APP;
167             retval = app->parse(xp);
168             if (!project) {
169                 msg_printf(NULL, MSG_INTERNAL_ERROR,
170                     "Application %s outside project in state file",
171                     app->name
172                 );
173                 delete app;
174                 continue;
175             }
176             if (project->anonymous_platform) {
177                 delete app;
178                 continue;
179             }
180             if (retval) {
181                 msg_printf(NULL, MSG_INTERNAL_ERROR,
182                     "Can't parse application in state file"
183                 );
184                 delete app;
185                 continue;
186             }
187             retval = link_app(project, app);
188             if (retval) {
189                 msg_printf(project, MSG_INTERNAL_ERROR,
190                     "Can't handle application %s in state file",
191                     app->name
192                 );
193                 delete app;
194                 continue;
195             }
196             apps.push_back(app);
197             continue;
198         }
199         if (xp.match_tag("file_info") || xp.match_tag("file")) {
200             FILE_INFO* fip = new FILE_INFO;
201             retval = fip->parse(xp);
202             if (!project) {
203                 msg_printf(NULL, MSG_INTERNAL_ERROR,
204                     "File info outside project in state file"
205                 );
206                 delete fip;
207                 continue;
208             }
209             if (retval) {
210                 msg_printf(NULL, MSG_INTERNAL_ERROR,
211                     "Can't handle file info in state file"
212                 );
213                 delete fip;
214                 continue;
215             }
216             retval = link_file_info(project, fip);
217             if (project->anonymous_platform && retval == ERR_NOT_UNIQUE) {
218                 delete fip;
219                 continue;
220             }
221             if (retval) {
222                 msg_printf(project, MSG_INTERNAL_ERROR,
223                     "Can't handle file info %s in state file",
224                     fip->name
225                 );
226                 delete fip;
227                 continue;
228             }
229             file_infos.push_back(fip);
230 #ifndef SIM
231             // If the file had a failure before,
232             // don't start another file transfer
233             //
234             int failnum;
235             if (fip->had_failure(failnum)) {
236                 if (fip->pers_file_xfer) {
237                     delete fip->pers_file_xfer;
238                     fip->pers_file_xfer = NULL;
239                 }
240             }
241             if (fip->pers_file_xfer) {
242                 retval = fip->pers_file_xfer->init(fip, fip->pers_file_xfer->is_upload);
243                 if (retval) {
244                     msg_printf(project, MSG_INTERNAL_ERROR,
245                         "Can't initialize file transfer for %s",
246                         fip->name
247                     );
248                 }
249                 retval = pers_file_xfers->insert(fip->pers_file_xfer);
250                 if (retval) {
251                     msg_printf(project, MSG_INTERNAL_ERROR,
252                         "Can't start persistent file transfer for %s",
253                         fip->name
254                     );
255                 }
256             }
257 #endif
258             continue;
259         }
260         if (xp.match_tag("app_version")) {
261             APP_VERSION* avp = new APP_VERSION;
262             retval = avp->parse(xp);
263             if (!project) {
264                 msg_printf(NULL, MSG_INTERNAL_ERROR,
265                     "Application version outside project in state file"
266                 );
267                 delete avp;
268                 continue;
269             }
270             if (project->anonymous_platform) {
271                 delete avp;
272                 continue;
273             }
274             if (retval) {
275                 msg_printf(NULL, MSG_INTERNAL_ERROR,
276                     "Can't parse application version in state file"
277                 );
278                 delete avp;
279                 continue;
280             }
281             if (strlen(avp->platform) == 0) {
282                 safe_strcpy(avp->platform, get_primary_platform());
283             } else {
284                 if (!is_supported_platform(avp->platform)) {
285                     // if it's a platform we haven't heard of,
286                     // must be that the user tried out a 64 bit client
287                     // and then reverted to a 32-bit client.
288                     // Let's not throw away the app version and its WUs
289                     //
290 #ifndef SIM
291                     msg_printf(project, MSG_INTERNAL_ERROR,
292                         "App version has unsupported platform %s; changing to %s",
293                         avp->platform, get_primary_platform()
294                     );
295 #endif
296                     safe_strcpy(avp->platform, get_primary_platform());
297                 }
298             }
299             if (avp->missing_coproc) {
300                 msg_printf(project, MSG_INFO,
301                     "Application uses missing %s GPU",
302                     avp->missing_coproc_name
303                 );
304             }
305             retval = link_app_version(project, avp);
306             if (retval) {
307                 delete avp;
308                 continue;
309             }
310             app_versions.push_back(avp);
311             continue;
312         }
313         if (xp.match_tag("workunit")) {
314             WORKUNIT* wup = new WORKUNIT;
315             retval = wup->parse(xp);
316             if (!project) {
317                 msg_printf(NULL, MSG_INTERNAL_ERROR,
318                     "Workunit outside project in state file"
319                 );
320                 delete wup;
321                 continue;
322             }
323             if (retval) {
324                 msg_printf(NULL, MSG_INTERNAL_ERROR,
325                     "Can't parse workunit in state file"
326                 );
327                 delete wup;
328                 continue;
329             }
330             retval = link_workunit(project, wup);
331             if (retval) {
332                 msg_printf(project, MSG_INTERNAL_ERROR,
333                     "Can't handle workunit in state file"
334                 );
335                 delete wup;
336                 continue;
337             }
338             workunits.push_back(wup);
339             continue;
340         }
341         if (xp.match_tag("result")) {
342             RESULT* rp = new RESULT;
343             retval = rp->parse_state(xp);
344             if (!project) {
345                 msg_printf(NULL, MSG_INTERNAL_ERROR,
346                     "Task %s outside project in state file",
347                     rp->name
348                 );
349                 delete rp;
350                 continue;
351             }
352             if (retval) {
353                 msg_printf(NULL, MSG_INTERNAL_ERROR,
354                     "Can't parse task in state file"
355                 );
356                 delete rp;
357                 continue;
358             }
359             retval = link_result(project, rp);
360             if (retval) {
361                 msg_printf(project, MSG_INTERNAL_ERROR,
362                     "Can't link task %s in state file",
363                     rp->name
364                 );
365                 delete rp;
366                 continue;
367             }
368             // handle transition from old clients which didn't store result.platform;
369             // skip for anon platform
370             if (!project->anonymous_platform) {
371                 if (!strlen(rp->platform) || !is_supported_platform(rp->platform)) {
372                     safe_strcpy(rp->platform, get_primary_platform());
373                     rp->version_num = latest_version(rp->wup->app, rp->platform);
374                 }
375             }
376             rp->avp = lookup_app_version(
377                 rp->wup->app, rp->platform, rp->version_num, rp->plan_class
378             );
379             if (!rp->avp) {
380                 msg_printf(project, MSG_INTERNAL_ERROR,
381                     "No application found for task: %s %d %s; discarding",
382                     rp->platform, rp->version_num, rp->plan_class
383                 );
384                 delete rp;
385                 continue;
386             }
387             if (rp->avp->missing_coproc) {
388                 msg_printf(project, MSG_INFO,
389                     "Missing coprocessor for task %s", rp->name
390                 );
391                 rp->coproc_missing = true;
392             }
393             rp->wup->version_num = rp->version_num;
394             results.push_back(rp);
395             continue;
396         }
397         if (xp.match_tag("project_files")) {
398             if (!project) {
399                 msg_printf(NULL, MSG_INTERNAL_ERROR,
400                     "Project files outside project in state file"
401                 );
402                 xp.skip_unexpected();
403                 continue;
404             }
405             parse_project_files(xp, project->project_files);
406             project->link_project_files();
407             continue;
408         }
409         if (xp.match_tag("host_info")) {
410 #ifdef SIM
411             retval = host_info.parse(xp, false);
412             coprocs = host_info.coprocs;
413             coprocs.bound_counts();
414 #else
415             retval = host_info.parse(xp, true);
416 #endif
417             if (retval) {
418                 msg_printf(NULL, MSG_INTERNAL_ERROR,
419                     "Can't parse host info in state file"
420                 );
421             }
422             continue;
423         }
424         if (xp.match_tag("time_stats")) {
425             retval = time_stats.parse(xp);
426             if (retval) {
427                 msg_printf(NULL, MSG_INTERNAL_ERROR,
428                     "Can't parse time stats in state file"
429                 );
430             }
431             continue;
432         }
433         if (xp.match_tag("net_stats")) {
434             retval = net_stats.parse(xp);
435             if (retval) {
436                 msg_printf(NULL, MSG_INTERNAL_ERROR,
437                     "Can't parse network stats in state file"
438                 );
439             }
440             continue;
441         }
442         if (xp.match_tag("active_task_set")) {
443             retval = active_tasks.parse(xp);
444             if (retval) {
445                 msg_printf(NULL, MSG_INTERNAL_ERROR,
446                     "Can't parse active tasks in state file"
447                 );
448             }
449             continue;
450         }
451         if (xp.parse_string("platform_name", statefile_platform_name)) {
452             continue;
453         }
454         if (xp.parse_string("alt_platform", stemp)) {
455             continue;
456         }
457         if (xp.parse_int("user_run_request", retval)) {
458             cpu_run_mode.set(retval, 0);
459             continue;
460         }
461         if (xp.parse_int("user_run_prev_request", retval)) {
462             cpu_run_mode.set_prev(retval);
463             continue;
464         }
465         if (xp.parse_int("user_gpu_request", retval)) {
466             gpu_run_mode.set(retval, 0);
467             continue;
468         }
469         if (xp.parse_int("user_gpu_prev_request", retval)) {
470             gpu_run_mode.set_prev(retval);
471             continue;
472         }
473         if (xp.parse_int("user_network_request", retval)) {
474             network_run_mode.set(retval, 0);
475             continue;
476         }
477         if (xp.parse_int("core_client_major_version", old_major_version)) {
478             continue;
479         }
480         if (xp.parse_int("core_client_minor_version", old_minor_version)) {
481             continue;
482         }
483         if (xp.parse_int("core_client_release", old_release)) {
484             continue;
485         }
486         if (xp.parse_str("language", language, sizeof(language))) {
487             continue;
488         }
489         if (xp.match_tag("proxy_info")) {
490             retval = gui_proxy_info.parse(xp);
491             if (retval) {
492                 msg_printf(NULL, MSG_INTERNAL_ERROR,
493                     "Can't parse proxy info in state file"
494                 );
495             }
496             continue;
497         }
498         if (xp.parse_str("host_venue", main_host_venue, sizeof(main_host_venue))) {
499             continue;
500         }
501         if (xp.parse_double("new_version_check_time", new_version_check_time)) {
502             continue;
503         }
504         if (xp.parse_double("all_projects_list_check_time", all_projects_list_check_time)) {
505             continue;
506         }
507         if (xp.parse_string("newer_version", newer_version)) {
508             continue;
509         }
510 #ifdef ENABLE_AUTO_UPDATE
511         if (xp.match_tag("auto_update")) {
512             if (!project) {
513                 msg_printf(NULL, MSG_INTERNAL_ERROR,
514                     "auto update outside project in state file"
515                 );
516                 xp.skip_unexpected();
517                 continue;
518             }
519             if (!auto_update.parse(xp) && !auto_update.validate_and_link(project)) {
520                 auto_update.present = true;
521             }
522             continue;
523         }
524 #endif
525         if (log_flags.unparsed_xml) {
526             msg_printf(0, MSG_INFO,
527                 "[unparsed_xml] state_file: unrecognized: %s",
528                 xp.parsed_tag
529             );
530         }
531         xp.skip_unexpected();
532     }
533     sort_results();
534     fclose(f);
535 
536     // if total resource share is zero, set all shares to 1
537     //
538     if (projects.size()) {
539         unsigned int i;
540         double x=0;
541         for (i=0; i<projects.size(); i++) {
542             x += projects[i]->resource_share;
543         }
544         if (!x) {
545             msg_printf(NULL, MSG_INFO,
546                 "All projects have zero resource share; setting to 100"
547             );
548             for (i=0; i<projects.size(); i++) {
549                 projects[i]->resource_share = 100;
550             }
551         }
552     }
553     return 0;
554 }
555 
556 // this is called whenever new results are added,
557 // namely at startup and after a scheduler RPC.
558 // Sort results based on (arrival time, name),
559 // then set result.index to their position in this order.
560 // This determines the order in which results are run.
561 //
sort_results()562 void CLIENT_STATE::sort_results() {
563     unsigned int i;
564     for (i=0; i<results.size(); i++) {
565         RESULT* rp = results[i];
566         rp->name_md5 = md5_string(string(rp->name));
567     }
568     std::sort(
569         results.begin(),
570         results.end(),
571         arrived_first
572     );
573     for (i=0; i<results.size(); i++) {
574         RESULT* rp = results[i];
575         rp->index = i;
576     }
577 }
578 
project_name_compare(PROJECT * p0,PROJECT * p1)579 static inline bool project_name_compare(PROJECT* p0, PROJECT* p1) {
580     return strcasecmp(p0->project_name, p1->project_name) < 0;
581 }
582 
sort_projects_by_name()583 void CLIENT_STATE::sort_projects_by_name() {
584     std::sort(
585         projects.begin(),
586         projects.end(),
587         project_name_compare
588     );
589 }
590 
591 #ifndef SIM
592 
593 // Write the client_state.xml file
594 //
write_state_file()595 int CLIENT_STATE::write_state_file() {
596     MFILE mf;
597     int retval, ret1, ret2, attempt;
598 #ifdef _WIN32
599     char win_error_msg[4096];
600 #endif
601 
602     for (attempt=1; attempt<=MAX_STATE_FILE_WRITE_ATTEMPTS; attempt++) {
603         if (attempt > 1) boinc_sleep(1.0);
604 
605         if (log_flags.statefile_debug) {
606             msg_printf(0, MSG_INFO,
607                 "[statefile] Writing state file"
608             );
609         }
610 #ifdef _WIN32
611         retval = mf.open(STATE_FILE_NEXT, "wc");
612 #else
613         retval = mf.open(STATE_FILE_NEXT, "w");
614 #endif
615         if (retval) {
616             if ((attempt == MAX_STATE_FILE_WRITE_ATTEMPTS) || log_flags.statefile_debug) {
617                 msg_printf(0, MSG_INTERNAL_ERROR,
618                     "Can't open %s: %s",
619                     STATE_FILE_NEXT, boincerror(retval)
620                 );
621             }
622             if (attempt < MAX_STATE_FILE_WRITE_ATTEMPTS) continue;
623             return ERR_FOPEN;
624         }
625         MIOFILE miof;
626         miof.init_mfile(&mf);
627         ret1 = write_state(miof);
628         ret2 = mf.close();
629         if (ret1) {
630             if ((attempt == MAX_STATE_FILE_WRITE_ATTEMPTS) || log_flags.statefile_debug) {
631                 msg_printf(NULL, MSG_INTERNAL_ERROR,
632                     "Couldn't write state file: %s", boincerror(retval)
633                 );
634             }
635             if (attempt < MAX_STATE_FILE_WRITE_ATTEMPTS) continue;
636             return ret1;
637         }
638         if (ret2) {
639             if (attempt < MAX_STATE_FILE_WRITE_ATTEMPTS) continue;
640             return ret2;
641         }
642 
643         // only attempt to rename the current state file if it exists.
644         //
645         if (boinc_file_exists(STATE_FILE_NAME)) {
646             if (boinc_file_exists(STATE_FILE_PREV)) {
647                 retval = boinc_delete_file(STATE_FILE_PREV);
648                 if (retval) {
649                     if ((attempt == MAX_STATE_FILE_WRITE_ATTEMPTS) || log_flags.statefile_debug) {
650 #ifdef _WIN32
651                         msg_printf(0, MSG_INFO,
652                             "Can't delete previous state file; %s",
653                             windows_format_error_string(GetLastError(), win_error_msg, sizeof(win_error_msg))
654                         );
655 #else
656                         msg_printf(0, MSG_INFO,
657                             "Can't delete previous state file: %s",
658                             strerror(errno)
659                         );
660 #endif
661                     }
662                     if (attempt < MAX_STATE_FILE_WRITE_ATTEMPTS) continue;
663                 }
664             }
665 
666             retval = boinc_rename(STATE_FILE_NAME, STATE_FILE_PREV);
667             if (retval) {
668                 if ((attempt == MAX_STATE_FILE_WRITE_ATTEMPTS) || log_flags.statefile_debug) {
669 #ifdef _WIN32
670                     msg_printf(0, MSG_INFO,
671                         "Can't rename current state file to previous state file; %s",
672                         windows_format_error_string(GetLastError(), win_error_msg, sizeof(win_error_msg))
673                     );
674 #else
675                     msg_printf(0, MSG_INFO,
676                         "Can't rename current state file to previous state file: %s",
677                         strerror(errno)
678                     );
679 #endif
680                 }
681                 if (attempt < MAX_STATE_FILE_WRITE_ATTEMPTS) continue;
682             }
683         }
684 
685         retval = boinc_rename(STATE_FILE_NEXT, STATE_FILE_NAME);
686         if (log_flags.statefile_debug) {
687             msg_printf(0, MSG_INFO,
688                 "[statefile] Done writing state file"
689             );
690         }
691         if (!retval) break;     // Success!
692 
693         if ((attempt == MAX_STATE_FILE_WRITE_ATTEMPTS) || log_flags.statefile_debug) {
694 #ifdef _WIN32
695             msg_printf(0, MSG_INFO,
696                 "rename error: %s",
697                 windows_format_error_string(GetLastError(), win_error_msg, sizeof(win_error_msg))
698             );
699 #elif defined (__APPLE__)
700             if (log_flags.statefile_debug) {
701                 // system() is deprecated in Mac OS 10.10.
702                 // Apple says to call posix_spawn instead.
703                 callPosixSpawn("ls -al /Library/Application\\ Support/BOINC\\ Data/client*.*");
704             }
705 #endif
706         }
707         if (attempt < MAX_STATE_FILE_WRITE_ATTEMPTS) continue;
708         return ERR_RENAME;
709     }
710     return 0;
711 }
712 
write_state(MIOFILE & f)713 int CLIENT_STATE::write_state(MIOFILE& f) {
714     unsigned int i, j;
715     int retval;
716 
717 #ifdef SIM
718     fprintf(stderr, "simulator shouldn't write state file\n");
719     exit(1);
720 #endif
721     f.printf("<client_state>\n");
722     retval = host_info.write(f, true, true);
723     if (retval) return retval;
724     retval = time_stats.write(f, false);
725     if (retval) return retval;
726     retval = net_stats.write(f);
727     if (retval) return retval;
728     for (j=0; j<projects.size(); j++) {
729         PROJECT* p = projects[j];
730         retval = p->write_state(f);
731         if (retval) return retval;
732         for (i=0; i<apps.size(); i++) {
733             if (apps[i]->project == p) {
734                 retval = apps[i]->write(f);
735                 if (retval) return retval;
736             }
737         }
738         for (i=0; i<file_infos.size(); i++) {
739             if (file_infos[i]->project != p) continue;
740             FILE_INFO* fip = file_infos[i];
741             // don't write file infos for anonymous platform app files
742             //
743             if (fip->anonymous_platform_file) continue;
744             retval = fip->write(f, false);
745             if (retval) return retval;
746         }
747         for (i=0; i<app_versions.size(); i++) {
748             if (app_versions[i]->project == p) {
749                 app_versions[i]->write(f);
750             }
751         }
752         for (i=0; i<workunits.size(); i++) {
753             if (workunits[i]->project == p) workunits[i]->write(f);
754         }
755         for (i=0; i<results.size(); i++) {
756             if (results[i]->project == p) results[i]->write(f, false);
757         }
758         p->write_project_files(f);
759 #ifdef ENABLE_AUTO_UPDATE
760         if (auto_update.present && auto_update.project==p) {
761             auto_update.write(f);
762         }
763 #endif
764     }
765     active_tasks.write(f);
766     f.printf(
767         "<platform_name>%s</platform_name>\n"
768         "<core_client_major_version>%d</core_client_major_version>\n"
769         "<core_client_minor_version>%d</core_client_minor_version>\n"
770         "<core_client_release>%d</core_client_release>\n"
771         "<user_run_request>%d</user_run_request>\n"
772         "<user_run_prev_request>%d</user_run_prev_request>\n"
773         "<user_gpu_request>%d</user_gpu_request>\n"
774         "<user_gpu_prev_request>%d</user_gpu_prev_request>\n"
775         "<user_network_request>%d</user_network_request>\n"
776         "<new_version_check_time>%f</new_version_check_time>\n"
777         "<all_projects_list_check_time>%f</all_projects_list_check_time>\n",
778         get_primary_platform(),
779         core_client_version.major,
780         core_client_version.minor,
781         core_client_version.release,
782         cpu_run_mode.get_perm(),
783         cpu_run_mode.get_prev(),
784         gpu_run_mode.get_perm(),
785         gpu_run_mode.get_prev(),
786         network_run_mode.get_perm(),
787         new_version_check_time,
788         all_projects_list_check_time
789     );
790     if (strlen(language)) {
791         f.printf("<language>%s</language>\n", language);
792     }
793     if (newer_version.size()) {
794         f.printf("<newer_version>%s</newer_version>\n", newer_version.c_str());
795     }
796     for (i=1; i<platforms.size(); i++) {
797         f.printf("<alt_platform>%s</alt_platform>\n", platforms[i].name.c_str());
798     }
799     if (gui_proxy_info.present) {
800         gui_proxy_info.write(f);
801     }
802     if (strlen(main_host_venue)) {
803         f.printf("<host_venue>%s</host_venue>\n", main_host_venue);
804     }
805     f.printf("</client_state>\n");
806     return 0;
807 }
808 
809 // Write the client_state.xml file if necessary
810 // TODO: write no more often than X seconds
811 //
write_state_file_if_needed()812 int CLIENT_STATE::write_state_file_if_needed() {
813     int retval;
814     if (client_state_dirty) {
815         client_state_dirty = false;
816         retval = write_state_file();
817         if (retval) return retval;
818     }
819     return 0;
820 }
821 
822 #endif // ifndef SIM
823 
824 // look for app_versions.xml file in project dir.
825 // If find, get app versions from there,
826 // and use "anonymous platform" mechanism for this project
827 //
check_anonymous()828 void CLIENT_STATE::check_anonymous() {
829     unsigned int i;
830     char path[MAXPATHLEN];
831     FILE* f;
832     int retval;
833 
834     for (i=0; i<projects.size(); i++) {
835         PROJECT* p = projects[i];
836         snprintf(path, sizeof(path), "%s/%s", p->project_dir(), APP_INFO_FILE_NAME);
837         f = fopen(path, "r");
838         if (!f) continue;
839         msg_printf(p, MSG_INFO,
840             "Found %s; using anonymous platform", APP_INFO_FILE_NAME
841         );
842 
843         p->anonymous_platform = true;
844             // flag as anonymous even if can't parse file
845         retval = parse_app_info(p, f);
846         if (retval) {
847             msg_printf_notice(p, false,
848                 "http://boinc.berkeley.edu/manager_links.php?target=notice&controlid=app_info",
849                 "%s",
850                 _("Syntax error in app_info.xml")
851             );
852         }
853         fclose(f);
854     }
855 }
856 
857 // parse a project's app_info.xml (anonymous platform) file
858 //
parse_app_info(PROJECT * p,FILE * in)859 int CLIENT_STATE::parse_app_info(PROJECT* p, FILE* in) {
860     char buf[256], path[MAXPATHLEN];
861     MIOFILE mf;
862     mf.init_file(in);
863     XML_PARSER xp(&mf);
864 
865     while (!xp.get_tag()) {
866         if (xp.match_tag("app_info")) continue;
867         if (xp.match_tag("/app_info")) {
868             notices.remove_notices(p, REMOVE_APP_INFO_MSG);
869             return 0;
870         }
871         if (xp.match_tag("file_info") || xp.match_tag("file")) {
872             FILE_INFO* fip = new FILE_INFO;
873             if (fip->parse(xp)) {
874                 delete fip;
875                 continue;
876             }
877             if (!fip->download_urls.empty() || !fip->upload_urls.empty()) {
878                 msg_printf(p, MSG_INFO,
879                     "Can't specify URLs in app_info.xml"
880                 );
881                 delete fip;
882                 continue;
883             }
884             if (link_file_info(p, fip)) {
885                 delete fip;
886                 continue;
887             }
888             // check that the file is actually there
889             //
890             get_pathname(fip, path, sizeof(path));
891             double size;
892             if (file_size(path, size)) {
893                 safe_strcpy(buf,
894                     _("File referenced in app_info.xml does not exist: ")
895                 );
896                 safe_strcat(buf, fip->name);
897                 msg_printf(p, MSG_USER_ALERT, "%s", buf);
898                 delete fip;
899                 continue;
900             }
901             fip->nbytes = size;
902             fip->status = FILE_PRESENT;
903             fip->anonymous_platform_file = true;
904             file_infos.push_back(fip);
905             continue;
906         }
907         if (xp.match_tag("app")) {
908             APP* app = new APP;
909             if (app->parse(xp)) {
910                 delete app;
911                 continue;
912             }
913             if (lookup_app(p, app->name)) {
914                 delete app;
915                 continue;
916             }
917             link_app(p, app);
918             apps.push_back(app);
919             continue;
920         }
921         if (xp.match_tag("app_version")) {
922             APP_VERSION* avp = new APP_VERSION;
923             if (avp->parse(xp)) {
924                 delete avp;
925                 continue;
926             }
927             if (cc_config.dont_use_vbox && strstr(avp->plan_class, "vbox")) {
928                 msg_printf(p, MSG_INFO,
929                     "skipping vbox app in app_info.xml; vbox disabled in cc_config.xml"
930                 );
931                 delete avp;
932                 continue;
933             }
934             if (strlen(avp->platform) == 0) {
935                 safe_strcpy(avp->platform, get_primary_platform());
936             }
937             if (link_app_version(p, avp)) {
938                 delete avp;
939                 continue;
940             }
941             app_versions.push_back(avp);
942             continue;
943         }
944         if (log_flags.unparsed_xml) {
945             msg_printf(p, MSG_INFO,
946                 "Unparsed line in app_info.xml: %s",
947                 xp.parsed_tag
948             );
949         }
950     }
951     return ERR_XML_PARSE;
952 }
953 
954 #ifndef SIM
955 
write_state_gui(MIOFILE & f)956 int CLIENT_STATE::write_state_gui(MIOFILE& f) {
957     unsigned int i, j;
958     int retval;
959 
960     f.printf("<client_state>\n");
961 
962     retval = host_info.write(f, true, true);
963     if (retval) return retval;
964 
965     // the following are for compatibility with old managers
966     //
967     if (coprocs.have_nvidia()) {
968         f.printf("<have_cuda/>\n");
969     }
970     if (coprocs.have_ati()) {
971         f.printf("<have_ati/>\n");
972     }
973 
974 #if 1
975     // NOTE: the following is not in CC_STATE.
976     // However, BoincView (which does its own parsing) expects it
977     // to be in the get_state() reply, so leave it in for now
978     //
979     retval = net_stats.write(f);
980     if (retval) return retval;
981 #endif
982 
983     retval = time_stats.write(f, true);
984     if (retval) return retval;
985 
986     for (j=0; j<projects.size(); j++) {
987         PROJECT* p = projects[j];
988         retval = p->write_state(f, true);
989         if (retval) return retval;
990         for (i=0; i<apps.size(); i++) {
991             if (apps[i]->project == p) {
992                 retval = apps[i]->write(f);
993                 if (retval) return retval;
994             }
995         }
996         for (i=0; i<app_versions.size(); i++) {
997             if (app_versions[i]->project == p) app_versions[i]->write(f);
998         }
999         for (i=0; i<workunits.size(); i++) {
1000             if (workunits[i]->project == p) workunits[i]->write(f);
1001         }
1002         for (i=0; i<results.size(); i++) {
1003             if (results[i]->project == p) results[i]->write_gui(f);
1004         }
1005     }
1006     f.printf(
1007         "<platform_name>%s</platform_name>\n"
1008         "<core_client_major_version>%d</core_client_major_version>\n"
1009         "<core_client_minor_version>%d</core_client_minor_version>\n"
1010         "<core_client_release>%d</core_client_release>\n"
1011         "<executing_as_daemon>%d</executing_as_daemon>\n",
1012         get_primary_platform(),
1013         core_client_version.major,
1014         core_client_version.minor,
1015         core_client_version.release,
1016         executing_as_daemon?1:0
1017     );
1018     for (i=0; i<platforms.size(); i++) {
1019         f.printf(
1020             "<platform>%s</platform>\n", platforms[i].name.c_str()
1021         );
1022     }
1023 
1024     global_prefs.write(f);
1025 
1026     // the following used by BoincView - don't remove
1027     //
1028     if (strlen(main_host_venue)) {
1029         f.printf("<host_venue>%s</host_venue>\n", main_host_venue);
1030     }
1031 
1032     f.printf("</client_state>\n");
1033     return 0;
1034 }
1035 
write_tasks_gui(MIOFILE & f,bool active_only)1036 int CLIENT_STATE::write_tasks_gui(MIOFILE& f, bool active_only) {
1037     unsigned int i;
1038 
1039     if (active_only) {
1040         for (i=0; i<active_tasks.active_tasks.size(); i++) {
1041             RESULT* rp = active_tasks.active_tasks[i]->result;
1042             rp->write_gui(f);
1043         }
1044     } else {
1045         for (i=0; i<results.size(); i++) {
1046             RESULT* rp = results[i];
1047             rp->write_gui(f);
1048         }
1049     }
1050     return 0;
1051 }
1052 
write_file_transfers_gui(MIOFILE & f)1053 int CLIENT_STATE::write_file_transfers_gui(MIOFILE& f) {
1054     unsigned int i;
1055 
1056     f.printf("<file_transfers>\n");
1057     for (i=0; i<file_infos.size(); i++) {
1058         FILE_INFO* fip = file_infos[i];
1059         if (fip->pers_file_xfer) {
1060             fip->write_gui(f);
1061         }
1062     }
1063     f.printf("</file_transfers>\n");
1064 
1065     return 0;
1066 }
1067 
1068 #endif
1069