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 // Logic related to general (also known as global) preferences:
19 // when to compute, how much disk to use, etc.
20 //
21 
22 #include "cpp.h"
23 
24 #ifdef _WIN32
25 #include "boinc_win.h"
26 #else
27 #include "config.h"
28 #if HAVE_SYS_STAT_H
29 #include <sys/stat.h>
30 #endif
31 #if HAVE_SYS_TYPES_H
32 #include <sys/types.h>
33 #endif
34 #endif
35 
36 #include "common_defs.h"
37 #include "filesys.h"
38 #include "parse.h"
39 #include "str_util.h"
40 #include "str_replace.h"
41 #include "util.h"
42 
43 #include "client_msgs.h"
44 #include "client_state.h"
45 #include "cpu_benchmark.h"
46 #include "file_names.h"
47 #include "project.h"
48 
49 using std::min;
50 using std::max;
51 using std::string;
52 
53 #define MAX_PROJ_PREFS_LEN  65536
54     // max length of project-specific prefs
55 
56 // Return the maximum allowed disk usage by projects as determined by user preferences.
57 // There are three different settings in the prefs;
58 // return the least of the three.
59 //
allowed_disk_usage(double boinc_total)60 double CLIENT_STATE::allowed_disk_usage(double boinc_total) {
61     double limit = boinc_total + host_info.d_free - global_prefs.disk_min_free_gb*GIGA;
62     if (global_prefs.disk_max_used_pct) {
63         double limit_pct = host_info.d_total*global_prefs.disk_max_used_pct/100.0;
64         limit = min(limit, limit_pct);
65     }
66 
67     if (global_prefs.disk_max_used_gb) {
68         double limit_abs = global_prefs.disk_max_used_gb*(GIGA);
69         limit = min(limit, limit_abs);
70     }
71     return max(limit, 0.);
72 }
73 
74 #ifndef SIM
75 
76 // populate:
77 // PROJECT::disk_usage for all projects
78 // GLOBAL_STATE::client_disk_usage
79 // GLOBAL_STATE::total_disk_usage
80 //
get_disk_usages()81 int CLIENT_STATE::get_disk_usages() {
82     unsigned int i;
83     double size;
84     PROJECT* p;
85     int retval;
86     char buf[MAXPATHLEN];
87 
88     client_disk_usage = 0;
89     total_disk_usage = 0;
90     for (i=0; i<projects.size(); i++) {
91         p = projects[i];
92         p->disk_usage = 0;
93         retval = dir_size(p->project_dir(), size);
94         if (!retval) p->disk_usage = size;
95     }
96 
97     for (i=0; i<active_tasks.active_tasks.size(); i++) {
98         ACTIVE_TASK* atp = active_tasks.active_tasks[i];
99         get_slot_dir(atp->slot, buf, sizeof(buf));
100         retval = dir_size(buf, size);
101         if (retval) continue;
102         atp->wup->project->disk_usage += size;
103     }
104     for (i=0; i<projects.size(); i++) {
105         p = projects[i];
106         total_disk_usage += p->disk_usage;
107     }
108     retval = dir_size(".", size, false);
109     if (!retval) {
110         client_disk_usage = size;
111         total_disk_usage += size;
112     }
113     return 0;
114 }
115 
116 // populate PROJECT::disk_share for all projects,
117 // i.e. the max space we should allocate to the project.
118 // This is calculated as follows:
119 // - each project has a "disk_resource_share" (DRS)
120 //   This is the resource share plus .1*(max resource share).
121 //   This ensures that backup projects get some disk.
122 // - each project as a "desired_disk_usage (DDU)",
123 //   which is either its current usage
124 //   or an amount sent from the scheduler.
125 // - each project has a "quota": (available space)*(drs/total_drs).
126 // - a project is "greedy" if DDU > quota.
127 // - if a project is non-greedy, share = quota
128 // - X = available space - space used by non-greedy projects
129 // - if a project is greedy, share = quota
130 //   + X*drs/(total drs of greedy projects)
131 //
get_disk_shares()132 void CLIENT_STATE::get_disk_shares() {
133     PROJECT* p;
134     unsigned int i;
135 
136     // compute disk resource shares
137     //
138     double trs = 0;
139     double max_rs = 0;
140     for (i=0; i<projects.size(); i++) {
141         p = projects[i];
142         p->ddu = std::max(p->disk_usage, p->desired_disk_usage);
143         double rs = p->resource_share;
144         trs += rs;
145         if (rs > max_rs) max_rs = rs;
146     }
147     if (trs) {
148         max_rs /= 10;
149         for (i=0; i<projects.size(); i++) {
150             p = projects[i];
151             p->disk_resource_share = p->resource_share + max_rs;
152         }
153     } else {
154         for (i=0; i<projects.size(); i++) {
155             p = projects[i];
156             p->disk_resource_share = 1;
157         }
158     }
159 
160     // Compute:
161     // greedy_drs: total disk resource share of greedy projects
162     // non_greedy_ddu: total desired disk usage of non-greedy projects
163     //
164     double greedy_drs = 0;
165     double non_greedy_ddu = 0;
166     double allowed = allowed_disk_usage(total_disk_usage);
167     for (i=0; i<projects.size(); i++) {
168         p = projects[i];
169         p->disk_quota = allowed*p->disk_resource_share/trs;
170         if (p->ddu > p->disk_quota) {
171             greedy_drs += p->disk_resource_share;
172         } else {
173             non_greedy_ddu += p->ddu;
174         }
175     }
176 
177     double greedy_allowed = allowed - non_greedy_ddu;
178     if (log_flags.disk_usage_debug) {
179         msg_printf(0, MSG_INFO,
180             "[disk_usage] allowed %.2fMB used %.2fMB",
181             allowed/MEGA, total_disk_usage/MEGA
182         );
183     }
184     for (i=0; i<projects.size(); i++) {
185         p = projects[i];
186         double rs = p->disk_resource_share/trs;
187         if (p->ddu > allowed*rs) {
188             p->disk_share = greedy_allowed*p->disk_resource_share/greedy_drs;
189         } else {
190             p->disk_share = p->disk_quota;
191         }
192         if (log_flags.disk_usage_debug) {
193             msg_printf(p, MSG_INFO,
194                 "[disk_usage] usage %.2fMB share %.2fMB",
195                 p->disk_usage/MEGA, p->disk_share/MEGA
196             );
197         }
198     }
199 }
200 
201 // See if we should suspend CPU and/or GPU processing;
202 // return the CPU suspend_reason,
203 // and if it's zero set gpu_suspend_reason
204 //
check_suspend_processing()205 int CLIENT_STATE::check_suspend_processing() {
206     if (benchmarks_running) {
207         return SUSPEND_REASON_BENCHMARKS;
208     }
209 
210     if (cc_config.start_delay && now < time_stats.client_start_time + cc_config.start_delay) {
211         return SUSPEND_REASON_INITIAL_DELAY;
212     }
213 
214     if (os_requested_suspend) {
215         return SUSPEND_REASON_OS;
216     }
217 
218     switch (cpu_run_mode.get_current()) {
219     case RUN_MODE_ALWAYS: break;
220     case RUN_MODE_NEVER:
221         return SUSPEND_REASON_USER_REQ;
222     default:
223         // "run according to prefs" checks:
224         //
225         if (!global_prefs.run_on_batteries
226             && host_info.host_is_running_on_batteries()
227         ) {
228             return SUSPEND_REASON_BATTERIES;
229         }
230 #ifndef ANDROID
231         // perform this check after SUSPEND_REASON_BATTERY_CHARGING on Android
232         if (!global_prefs.run_if_user_active && user_active) {
233             return SUSPEND_REASON_USER_ACTIVE;
234         }
235 #endif
236         if (global_prefs.cpu_times.suspended(now)) {
237             return SUSPEND_REASON_TIME_OF_DAY;
238         }
239         if (global_prefs.suspend_if_no_recent_input) {
240             bool idle = host_info.users_idle(
241                 check_all_logins, global_prefs.suspend_if_no_recent_input
242             );
243             if (idle) {
244                 return SUSPEND_REASON_NO_RECENT_INPUT;
245             }
246         }
247         if (now - exclusive_app_running < MEMORY_USAGE_PERIOD + EXCLUSIVE_APP_WAIT) {
248             return SUSPEND_REASON_EXCLUSIVE_APP_RUNNING;
249         }
250         if (global_prefs.suspend_cpu_usage && non_boinc_cpu_usage*100 > global_prefs.suspend_cpu_usage) {
251             return SUSPEND_REASON_CPU_USAGE;
252         }
253     }
254 
255 #ifdef ANDROID
256     if (now > device_status_time + ANDROID_KEEPALIVE_TIMEOUT) {
257         requested_exit = true;
258         return SUSPEND_REASON_NO_GUI_KEEPALIVE;
259     }
260 
261     // check for hot battery
262     //
263     if (device_status.battery_state == BATTERY_STATE_OVERHEATED) {
264         return SUSPEND_REASON_BATTERY_OVERHEATED;
265     }
266     if (device_status.battery_temperature_celsius > global_prefs.battery_max_temperature) {
267         return SUSPEND_REASON_BATTERY_OVERHEATED;
268     }
269 
270     // on some devices, running jobs can drain the battery even
271     // while it's recharging.
272     // So compute only if 95% charged or more.
273     //
274     int cp = device_status.battery_charge_pct;
275     if (cp >= 0) {
276         if (cp < global_prefs.battery_charge_min_pct) {
277             return SUSPEND_REASON_BATTERY_CHARGING;
278         }
279     }
280 
281     // user active.
282     // Do this check after checks that user can not influence on Android.
283     // E.g.
284     // 1. "connect to charger to continue computing"
285     // 2. "charge battery until 90%"
286     // 3. "turn screen off to continue computing"
287     if (!global_prefs.run_if_user_active && user_active) {
288         return SUSPEND_REASON_USER_ACTIVE;
289     }
290 #endif
291 
292 #ifndef NEW_CPU_THROTTLE
293     // CPU throttling.
294     // Do this check last; that way if suspend_reason is CPU_THROTTLE,
295     // the GUI knows there's no other source of suspension
296     //
297     if (global_prefs.cpu_usage_limit < 99) {        // round-off?
298         static double last_time=0, debt=0;
299         double diff = now - last_time;
300         last_time = now;
301         if (diff >= POLL_INTERVAL/2. && diff < POLL_INTERVAL*10.) {
302             debt += diff*global_prefs.cpu_usage_limit/100;
303             if (debt < 0) {
304                 return SUSPEND_REASON_CPU_THROTTLE;
305             } else {
306                 debt -= diff;
307             }
308         }
309     }
310 #endif
311 
312     // CPU is not suspended.  See if GPUs are
313     //
314     if (!coprocs.none()) {
315         int old_gpu_suspend_reason = gpu_suspend_reason;
316         gpu_suspend_reason = 0;
317         switch (gpu_run_mode.get_current()) {
318         case RUN_MODE_ALWAYS:
319             break;
320         case RUN_MODE_NEVER:
321             gpu_suspend_reason = SUSPEND_REASON_USER_REQ;
322             break;
323         default:
324             if (now - exclusive_gpu_app_running < MEMORY_USAGE_PERIOD + EXCLUSIVE_APP_WAIT) {
325                 gpu_suspend_reason = SUSPEND_REASON_EXCLUSIVE_APP_RUNNING;
326                 break;
327             }
328             if (user_active && !global_prefs.run_gpu_if_user_active) {
329                 gpu_suspend_reason = SUSPEND_REASON_USER_ACTIVE;
330                 break;
331             }
332         }
333 
334         if (old_gpu_suspend_reason && !gpu_suspend_reason) {
335             if (log_flags.task) {
336                 msg_printf(NULL, MSG_INFO, "Resuming GPU computation");
337             }
338             request_schedule_cpus("GPU resumption");
339         } else if (!old_gpu_suspend_reason && gpu_suspend_reason) {
340             if (log_flags.task) {
341                 msg_printf(NULL, MSG_INFO, "Suspending GPU computation - %s",
342                     suspend_reason_string(gpu_suspend_reason)
343                 );
344             }
345             request_schedule_cpus("GPU suspension");
346         }
347     }
348 
349     return 0;
350 }
351 
show_suspend_tasks_message(int reason)352 void CLIENT_STATE::show_suspend_tasks_message(int reason) {
353     if (reason != SUSPEND_REASON_CPU_THROTTLE) {
354         if (log_flags.task) {
355             msg_printf(NULL, MSG_INFO,
356                 "Suspending computation - %s",
357                 suspend_reason_string(reason)
358             );
359         }
360         switch (reason) {
361         case SUSPEND_REASON_BATTERY_OVERHEATED:
362             if (log_flags.task) {
363                 msg_printf(NULL, MSG_INFO,
364                     "(battery temperature %.1f > limit %.1f Celsius)",
365                     device_status.battery_temperature_celsius,
366                     global_prefs.battery_max_temperature
367                 );
368             }
369             break;
370         case SUSPEND_REASON_BATTERY_CHARGING:
371             if (log_flags.task) {
372                 msg_printf(NULL, MSG_INFO,
373                     "(battery charge level %.1f%% < threshold %.1f%%",
374                     device_status.battery_charge_pct,
375                     global_prefs.battery_charge_min_pct
376                 );
377             }
378             break;
379         }
380     }
381 }
382 
resume_tasks(int reason)383 int CLIENT_STATE::resume_tasks(int reason) {
384     if (reason == SUSPEND_REASON_CPU_THROTTLE) {
385         active_tasks.unsuspend_all(SUSPEND_REASON_CPU_THROTTLE);
386     } else {
387         if (log_flags.task) {
388             msg_printf(NULL, MSG_INFO, "Resuming computation");
389         }
390         active_tasks.unsuspend_all();
391         request_schedule_cpus("Resuming computation");
392     }
393     return 0;
394 }
395 
396 // Check whether to set network_suspended and file_xfers_suspended.
397 //
check_suspend_network()398 void CLIENT_STATE::check_suspend_network() {
399     network_suspended = false;
400     file_xfers_suspended = false;
401     network_suspend_reason = 0;
402     bool recent_rpc;
403 
404     // don't start network ops if system is shutting down
405     //
406     if (os_requested_suspend) {
407         network_suspend_reason = SUSPEND_REASON_OS;
408         network_suspended = true;
409         goto done;
410     }
411 
412     // no network traffic if we're allowing unsigned apps
413     //
414     if (cc_config.unsigned_apps_ok) {
415         network_suspended = true;
416         file_xfers_suspended = true;
417         network_suspend_reason = SUSPEND_REASON_USER_REQ;
418         goto done;
419     }
420 
421     // was there a recent GUI RPC that needs network?
422     //
423     recent_rpc = gui_rpcs.recent_rpc_needs_network(
424         ALLOW_NETWORK_IF_RECENT_RPC_PERIOD
425     );
426 
427     switch(network_run_mode.get_current()) {
428     case RUN_MODE_ALWAYS:
429         goto done;
430     case RUN_MODE_NEVER:
431         file_xfers_suspended = true;
432         if (!recent_rpc) network_suspended = true;
433         network_suspend_reason = SUSPEND_REASON_USER_REQ;
434         goto done;
435     }
436 
437 #ifdef ANDROID
438     if (now > device_status_time + ANDROID_KEEPALIVE_TIMEOUT) {
439         requested_exit = true;
440         file_xfers_suspended = true;
441         if (!recent_rpc) network_suspended = true;
442         network_suspend_reason = SUSPEND_REASON_NO_GUI_KEEPALIVE;
443     }
444     // use only WiFi
445     //
446     if (global_prefs.network_wifi_only && !device_status.wifi_online) {
447         file_xfers_suspended = true;
448         if (!recent_rpc) network_suspended = true;
449         network_suspend_reason = SUSPEND_REASON_WIFI_STATE;
450     }
451 #endif
452 
453     if (global_prefs.daily_xfer_limit_mb && global_prefs.daily_xfer_period_days) {
454         double up, down;
455         daily_xfer_history.totals(
456             global_prefs.daily_xfer_period_days, up, down
457         );
458         if (up+down > global_prefs.daily_xfer_limit_mb*MEGA) {
459             file_xfers_suspended = true;
460             if (!recent_rpc) network_suspended = true;
461             network_suspend_reason = SUSPEND_REASON_NETWORK_QUOTA_EXCEEDED;
462         }
463     }
464 
465 #ifndef ANDROID
466     // allow network transfers while user active, i.e. screen on.
467     // otherwise nothing (visible to the user) happens after initial attach
468     //
469     if (!global_prefs.run_if_user_active && user_active) {
470         file_xfers_suspended = true;
471         if (!recent_rpc) network_suspended = true;
472         network_suspend_reason = SUSPEND_REASON_USER_ACTIVE;
473     }
474 #endif
475     if (global_prefs.net_times.suspended(now)) {
476         file_xfers_suspended = true;
477         if (!recent_rpc) network_suspended = true;
478         network_suspend_reason = SUSPEND_REASON_TIME_OF_DAY;
479     }
480     if (now - exclusive_app_running < MEMORY_USAGE_PERIOD + EXCLUSIVE_APP_WAIT) {
481         file_xfers_suspended = true;
482         if (!recent_rpc) network_suspended = true;
483         network_suspend_reason = SUSPEND_REASON_EXCLUSIVE_APP_RUNNING;
484     }
485 
486 done:
487     if (log_flags.suspend_debug) {
488         msg_printf(0, MSG_INFO, "[suspend] net_susp: %s; file_xfer_susp: %s; reason: %s",
489             network_suspended?"yes":"no",
490             file_xfers_suspended?"yes":"no",
491             suspend_reason_string(network_suspend_reason)
492         );
493     }
494 }
495 
496 #endif // ifndef SIM
497 
498 // call this only after parsing global prefs
499 //
global_prefs_source_project()500 PROJECT* CLIENT_STATE::global_prefs_source_project() {
501     return lookup_project(global_prefs.source_project);
502 }
503 
show_global_prefs_source(bool found_venue)504 void CLIENT_STATE::show_global_prefs_source(bool found_venue) {
505     PROJECT* pp = global_prefs_source_project();
506     if (pp) {
507         msg_printf(pp, MSG_INFO,
508             "General prefs: from %s (last modified %s)",
509             pp->get_project_name(), time_to_string(global_prefs.mod_time)
510         );
511     } else {
512         msg_printf(NULL, MSG_INFO,
513             "General prefs: from %s (last modified %s)",
514             global_prefs.source_project,
515             time_to_string(global_prefs.mod_time)
516         );
517     }
518     if (strlen(main_host_venue)) {
519         msg_printf(pp, MSG_INFO, "Computer location: %s", main_host_venue);
520         if (found_venue) {
521             msg_printf(NULL, MSG_INFO,
522                 "General prefs: using separate prefs for %s", main_host_venue
523             );
524         } else {
525             msg_printf(pp, MSG_INFO,
526                 "General prefs: no separate prefs for %s; using your defaults",
527                 main_host_venue
528             );
529         }
530     } else {
531         msg_printf(pp, MSG_INFO, "Host location: none");
532         msg_printf(pp, MSG_INFO, "General prefs: using your defaults");
533     }
534 }
535 
536 // parse user's project preferences,
537 // generating FILE_REF and FILE_INFO objects for each <app_file> element.
538 //
parse_preferences_for_user_files()539 int PROJECT::parse_preferences_for_user_files() {
540     char buf[1024];
541     string timestamp, open_name, url, filename;
542     FILE_INFO* fip;
543     FILE_REF fr;
544 
545     user_files.clear();
546     size_t n=0, start, end;
547     while (1) {
548         start = project_specific_prefs.find("<app_file>", n);
549         if (start == string::npos) break;
550         end = project_specific_prefs.find("</app_file>", n);
551         if (end == string::npos) break;
552         start += strlen("<app_file>");
553         string x = project_specific_prefs.substr(start, end);
554         n = end + strlen("</app_file>");
555 
556         strlcpy(buf, x.c_str(), sizeof(buf));
557         if (!parse_str(buf, "<timestamp>", timestamp)) break;
558         if (!parse_str(buf, "<open_name>", open_name)) break;
559         if (!parse_str(buf, "<url>", url)) break;
560 
561         filename = open_name + "_" + timestamp;
562         fip = gstate.lookup_file_info(this, filename.c_str());
563         if (!fip) {
564             fip = new FILE_INFO;
565             fip->project = this;
566             fip->download_urls.add(url);
567             safe_strcpy(fip->name, filename.c_str());
568             fip->is_user_file = true;
569             gstate.file_infos.push_back(fip);
570         }
571 
572         fr.file_info = fip;
573         safe_strcpy(fr.open_name, open_name.c_str());
574         user_files.push_back(fr);
575     }
576     return 0;
577 }
578 
579 // Read global preferences into the global_prefs structure.
580 // 1) read the override file to get venue in case it's there
581 // 2) read global_prefs.xml
582 // 3) read the override file again
583 //
584 // This is called:
585 // - on startup
586 // - on completion of a scheduler or AMS RPC, if they sent prefs
587 // - in response to read_global_prefs_override GUI RPC
588 //
read_global_prefs(const char * fname,const char * override_fname)589 void CLIENT_STATE::read_global_prefs(
590     const char* fname, const char* override_fname
591 ) {
592     bool found_venue;
593     bool venue_specified_in_override = false;
594     int retval;
595     FILE* f;
596     string foo;
597 
598 #ifdef USE_NET_PREFS
599     if (override_fname) {
600         retval = read_file_string(override_fname, foo);
601         if (!retval) {
602             parse_str(foo.c_str(), "<host_venue>", main_host_venue, sizeof(main_host_venue));
603             if (strlen(main_host_venue)) {
604                 venue_specified_in_override = true;
605             }
606         }
607     }
608 
609     retval = global_prefs.parse_file(
610         fname, main_host_venue, found_venue
611     );
612     if (retval) {
613         if (retval == ERR_FOPEN) {
614             msg_printf(NULL, MSG_INFO,
615                 "No general preferences found - using defaults"
616             );
617         } else {
618             msg_printf(NULL, MSG_INFO,
619                 "Couldn't parse preferences file - using defaults"
620             );
621             boinc_delete_file(fname);
622         }
623         global_prefs.init();
624     } else {
625         if (!venue_specified_in_override) {
626             // check that the source project's venue matches main_host_venue.
627             // If not, read file again.
628             // This is a fix for cases where main_host_venue is out of synch
629             //
630             PROJECT* p = global_prefs_source_project();
631             if (p && strcmp(main_host_venue, p->host_venue)) {
632                 safe_strcpy(main_host_venue, p->host_venue);
633                 global_prefs.parse_file(fname, main_host_venue, found_venue);
634             }
635         }
636         show_global_prefs_source(found_venue);
637     }
638 #endif
639 
640     // read the override file
641     //
642     global_prefs.override_file_present = false;
643     if (override_fname) {
644         f = fopen(override_fname, "r");
645         if (f) {
646             MIOFILE mf;
647             GLOBAL_PREFS_MASK mask;
648             mf.init_file(f);
649             XML_PARSER xp(&mf);
650             global_prefs.parse_override(xp, "", found_venue, mask);
651             msg_printf(NULL, MSG_INFO, "Reading preferences override file");
652             fclose(f);
653             global_prefs.override_file_present = true;
654         }
655     }
656 
657     msg_printf(NULL, MSG_INFO, "Preferences:");
658     msg_printf(NULL, MSG_INFO,
659         "   max memory usage when active: %.2f MB",
660         (host_info.m_nbytes*global_prefs.ram_max_used_busy_frac)/MEGA
661     );
662     msg_printf(NULL, MSG_INFO,
663         "   max memory usage when idle: %.2f MB",
664         (host_info.m_nbytes*global_prefs.ram_max_used_idle_frac)/MEGA
665     );
666 #ifndef SIM
667     get_disk_usages();
668     msg_printf(NULL, MSG_INFO,
669         "   max disk usage: %.2f GB",
670         allowed_disk_usage(total_disk_usage)/GIGA
671     );
672 #endif
673     // max_cpus, bandwidth limits may have changed
674     //
675     set_ncpus();
676     if (ncpus != host_info.p_ncpus) {
677         msg_printf(NULL, MSG_INFO,
678             "   max CPUs used: %d", ncpus
679         );
680     }
681     if (!global_prefs.run_if_user_active) {
682         msg_printf(NULL, MSG_INFO, "   don't compute while active");
683 #ifdef ANDROID
684     } else {
685         msg_printf(NULL, MSG_INFO, "   Android: don't compute while active");
686         global_prefs.run_if_user_active = false;
687 #endif
688     }
689     if (!global_prefs.run_gpu_if_user_active) {
690         msg_printf(NULL, MSG_INFO, "   don't use GPU while active");
691     }
692     if (global_prefs.suspend_cpu_usage) {
693         msg_printf(NULL, MSG_INFO,
694             "   suspend work if non-BOINC CPU load exceeds %.0f%%",
695             global_prefs.suspend_cpu_usage
696         );
697     }
698     if (global_prefs.max_bytes_sec_down) {
699         msg_printf(NULL, MSG_INFO,
700             "   max download rate: %.0f bytes/sec",
701             global_prefs.max_bytes_sec_down
702         );
703     }
704     if (global_prefs.max_bytes_sec_up) {
705         msg_printf(NULL, MSG_INFO,
706             "   max upload rate: %.0f bytes/sec",
707             global_prefs.max_bytes_sec_up
708         );
709     }
710 #ifndef SIM
711     file_xfers->set_bandwidth_limits(true);
712     file_xfers->set_bandwidth_limits(false);
713 #endif
714     msg_printf(NULL, MSG_INFO,
715         "   (to change preferences, visit a project web site or select Preferences in the Manager)"
716     );
717     request_schedule_cpus("Prefs update");
718     request_work_fetch("Prefs update");
719 #ifndef SIM
720     active_tasks.request_reread_app_info();
721 #endif
722 }
723 
save_global_prefs(char * global_prefs_xml,char * master_url,char * scheduler_url)724 int CLIENT_STATE::save_global_prefs(
725     char* global_prefs_xml, char* master_url, char* scheduler_url
726 ) {
727     FILE* f = boinc_fopen(GLOBAL_PREFS_FILE_NAME, "w");
728     if (!f) return ERR_FOPEN;
729     fprintf(f,
730         "<global_preferences>\n"
731     );
732 
733     // tag with the project and scheduler URL,
734     // but only if not already tagged
735     //
736     if (!strstr(global_prefs_xml, "<source_project>")) {
737         fprintf(f,
738             "    <source_project>%s</source_project>\n"
739             "    <source_scheduler>%s</source_scheduler>\n",
740             master_url,
741             scheduler_url
742         );
743     }
744     fprintf(f,
745         "%s"
746         "</global_preferences>\n",
747         global_prefs_xml
748     );
749     fclose(f);
750     return 0;
751 }
752 
753 // amount of RAM usable now
754 //
available_ram()755 double CLIENT_STATE::available_ram() {
756     if (user_active) {
757         return host_info.m_nbytes * global_prefs.ram_max_used_busy_frac;
758     } else {
759         return host_info.m_nbytes * global_prefs.ram_max_used_idle_frac;
760     }
761 }
762 
763 // max amount that will ever be usable
764 //
max_available_ram()765 double CLIENT_STATE::max_available_ram() {
766     return host_info.m_nbytes*std::max(
767         global_prefs.ram_max_used_busy_frac, global_prefs.ram_max_used_idle_frac
768     );
769 }
770