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 <cstring>
25 #endif
26 
27 #include "parse.h"
28 #include "error_numbers.h"
29 #include "filesys.h"
30 #include "util.h"
31 
32 #include "client_msgs.h"
33 #include "file_names.h"
34 #include "client_state.h"
35 #include "log_flags.h"
36 
37 #include "auto_update.h"
38 
39 // while an update is being downloaded,
40 // it's treated like other project files,
41 // except that a version directory is prepended to filenames.
42 //
43 // When the download is complete,
44 // the version directory is moved to the top level dir.
45 
AUTO_UPDATE()46 AUTO_UPDATE::AUTO_UPDATE() {
47     present = false;
48     install_failed = false;
49 }
50 
init()51 void AUTO_UPDATE::init() {
52     if (!present) return;
53 
54     // if we (the core client) were run by the updater,
55     // and we're not the same as the new version,
56     // the install must have failed.
57     // Mark it as such so we don't try it again.
58     //
59     if (version.greater_than(gstate.core_client_version)) {
60         if (gstate.run_by_updater) {
61             install_failed = true;
62             msg_printf(0, MSG_INTERNAL_ERROR,
63                 "Client auto-update failed; keeping existing version"
64             );
65             gstate.set_client_state_dirty("auto update init");
66         }
67     }
68 }
69 
parse(MIOFILE & in)70 int AUTO_UPDATE::parse(MIOFILE& in) {
71     char buf[256];
72     int retval;
73 
74     while (in.fgets(buf, 256)) {
75         if (match_tag(buf, "</auto_update>")) {
76             return 0;
77         } else if (parse_bool(buf, "install_failed", install_failed)) {
78             continue;
79         } else if (match_tag(buf, "<version>")) {
80             version.parse(in);
81         } else if (match_tag(buf, "<file_ref>")) {
82             FILE_REF fref;
83             retval = fref.parse(in);
84             if (retval) return retval;
85             file_refs.push_back(fref);
86         } else {
87             if (log_flags.unparsed_xml) {
88                 msg_printf(NULL, MSG_INFO, "unparsed in boinc_update: %s", buf);
89             }
90         }
91     }
92     return ERR_XML_PARSE;
93 }
94 
write(MIOFILE & out)95 void AUTO_UPDATE::write(MIOFILE& out) {
96     out.printf(
97         "<auto_update>\n"
98     );
99     version.write(out);
100     if (install_failed) {
101         out.printf("<install_failed>1</install_failed>\n");
102     }
103     for (unsigned int i=0; i<file_refs.size(); i++) {
104         file_refs[i].write(out);
105     }
106     out.printf(
107         "</auto_update>\n"
108     );
109 }
110 
111 // Check whether this <auto_update> is valid,
112 // and if so link it up.
113 //
validate_and_link(PROJECT * proj)114 int AUTO_UPDATE::validate_and_link(PROJECT* proj) {
115     char dir[256];
116     int retval;
117     unsigned int i;
118     FILE_INFO* fip;
119 
120     if (!version.greater_than(gstate.core_client_version)) {
121         msg_printf(NULL, MSG_INFO,
122             "Got request to update to %d.%d.%d; already running %d.%d.%d",
123             version.major, version.minor, version.release,
124             gstate.core_client_version.major,
125             gstate.core_client_version.minor,
126             gstate.core_client_version.release
127         );
128         return ERR_INVALID_PARAM;
129     }
130     if (gstate.auto_update.present) {
131         if (!version.greater_than(gstate.auto_update.version)) {
132             msg_printf(NULL, MSG_INFO,
133                 "Got request to update to %d.%d.%d; already updating to %d.%d.%d",
134                 version.major, version.minor, version.release,
135                 gstate.auto_update.version.major,
136                 gstate.auto_update.version.minor,
137                 gstate.auto_update.version.release
138             );
139             return ERR_INVALID_PARAM;
140         }
141         // TODO: abort in-progress update (and delete files) here
142     }
143     project = proj;
144 
145     int nmain = 0;
146     for (i=0; i<file_refs.size(); i++) {
147         FILE_REF& fref = file_refs[i];
148         fip = gstate.lookup_file_info(project, fref.file_name);
149         if (!fip) {
150             msg_printf(project, MSG_INTERNAL_ERROR,
151                 "missing update file %s", fref.file_name
152             );
153             return ERR_INVALID_PARAM;
154         }
155         fref.file_info = fip;
156         fip->is_auto_update_file = true;
157         if (fref.main_program) nmain++;
158     }
159 
160     if (nmain != 1) {
161         msg_printf(project, MSG_INTERNAL_ERROR,
162             "Auto update has %d main programs", nmain
163         );
164         return ERR_INVALID_PARAM;
165     }
166     // create version directory
167     //
168     boinc_version_dir(*project, version, dir);
169     retval = boinc_mkdir(dir);
170     if (retval) {
171         msg_printf(project, MSG_INTERNAL_ERROR, "Couldn't make version dir %s", dir);
172         return retval;
173     }
174     gstate.auto_update = *this;
175     return 0;
176 }
177 
install()178 void AUTO_UPDATE::install() {
179     unsigned int i;
180     FILE_INFO* fip=0;
181     char version_dir[1024];
182     char cwd[256];
183     char *argv[10];
184     int retval, argc;
185 #ifdef _WIN32
186     HANDLE pid;
187 #else
188     int pid;
189 #endif
190 
191     msg_printf(NULL, MSG_INFO, "Installing new version of BOINC: %d.%d.%d",
192         version.major, version.minor, version.release
193     );
194     for (i=0; i<file_refs.size(); i++) {
195         FILE_REF& fref = file_refs[i];
196         if (fref.main_program) {
197             fip = fref.file_info;
198             break;
199         }
200     }
201     if (!fip) {
202         msg_printf(NULL, MSG_INTERNAL_ERROR, "Main program not found");
203         return;
204     }
205     boinc_version_dir(*project, version, version_dir);
206     boinc_getcwd(cwd);
207     argv[0] = fip->name;
208     argv[1] = "--install_dir";
209     argv[2] = cwd;
210     argv[3] = "--run_core";
211     argv[4] = 0;
212     argc = 4;
213     retval = run_program(version_dir, fip->name, argc, argv, 5, pid);
214     if (retval) {
215         msg_printf(NULL, MSG_INTERNAL_ERROR,
216             "Couldn't launch updater; staying with current version"
217         );
218         install_failed = true;
219         gstate.set_client_state_dirty("auto update install");
220     } else {
221         gstate.requested_exit = true;
222     }
223 }
224 
225 // When an update is ready to install, we may need to wait a little:
226 // 1) If there's a GUI RPC connection from a local screensaver
227 //    (i.e. that has done a get_screensaver_mode())
228 //    wait for the next get_screensaver_mode() and send it SS_STATUS_QUIT
229 // 2) If there's a GUI RPC connection from a GUI
230 //    (i.e. that has done a get_cc_status())
231 //    wait for the next get_cc_status() and return manager_must_quit = true.
232 // Wait an additional 10 seconds in any case
233 
poll()234 void AUTO_UPDATE::poll() {
235 #ifndef SIM
236     if (!present) return;
237     static double last_time = 0;
238     static bool ready_to_install = false;
239     static double quits_sent = 0;
240 
241     if (install_failed) return;
242     if (gstate.now - last_time < 10) return;
243     last_time = gstate.now;
244 
245     if (ready_to_install) {
246         if (quits_sent) {
247             if (gstate.now - quits_sent >= 10) {
248                 install();
249             }
250         } else {
251             if (gstate.gui_rpcs.quits_sent()) {
252                 quits_sent = gstate.now;
253             }
254         }
255     } else {
256         for (unsigned int i=0; i<file_refs.size(); i++) {
257             FILE_REF& fref = file_refs[i];
258             FILE_INFO* fip = fref.file_info;
259             if (fip->status != FILE_PRESENT) return;
260         }
261         ready_to_install = true;
262         gstate.gui_rpcs.send_quits();
263     }
264 #endif
265 }
266 
parse(MIOFILE & in)267 int VERSION_INFO::parse(MIOFILE& in) {
268     char buf[256];
269     while (in.fgets(buf, 256)) {
270         if (match_tag(buf, "</version>")) return 0;
271         if (parse_int(buf, "<major>", major)) continue;
272         if (parse_int(buf, "<minor>", minor)) continue;
273         if (parse_int(buf, "<release>", release)) continue;
274         if (parse_bool(buf, "<prerelease>", prerelease)) continue;
275     }
276     return ERR_XML_PARSE;
277 }
278 
write(MIOFILE & out)279 void VERSION_INFO::write(MIOFILE& out) {
280     out.printf(
281         "<version>\n"
282         "   <major>%d</major>\n"
283         "   <minor>%d</minor>\n"
284         "   <release>%d</release>\n"
285         "   <prerelease>%d</prerelease>\n"
286         "</version>\n",
287         major, minor, release, prerelease
288     );
289 }
290 
greater_than(VERSION_INFO & vi)291 bool VERSION_INFO::greater_than(VERSION_INFO& vi) {
292     if (major > vi.major) return true;
293     if (major < vi.major) return false;
294     if (minor > vi.minor) return true;
295     if (minor < vi.minor) return false;
296     if (release > vi.release) return true;
297     return false;
298 }
299