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 /* CustomInstall.cpp */
19
20 /* Customizable installer to allow use of features described at
21 http://boinc.berkeley.edu/client_startup.php
22 Directions for creating a customized installer for the Macintosh:
23 [1] Create a directory, with a name such as SETI@home_Mac_Installer
24 [2] Place this CustomInstall application inside that directory. You
25 may rename this application if you wish.
26 [3] Create a new directory named "boinc_startup_files" inside the
27 directory created in step 1.
28 [4] Place the custom .xml files in the "boinc_startup_files" directory.
29 [5] Zip the directory created in step 1 (with all its contents).
30 */
31
32 #define CREATE_LOG 1 /* for debugging */
33
34 #include <Carbon/Carbon.h>
35
36 #include <curl/curl.h>
37 #include <stdlib.h>
38
39 #include "error_numbers.h"
40 #include "filesys.h"
41 #include "str_util.h"
42
43 #ifdef __cplusplus
44 extern "C" {
45 #endif
46
47 static void Initialize(void); /* function prototypes */
48 static OSStatus download_thread(void* param);
49 static OSErr download_boinc(FILE * out_file);
50 //static curl_progress_callback show_prog;
51 static int show_prog(void *clientp, double dltotal, double dlnow,
52 double ultotal, double ulnow);
53 static OSErr runProgressDlog();
54 static pascal Boolean ProgDlgFilterProc(DialogPtr dp, EventRecord *event, short *item);
55 static void show_message(StringPtr s1);
56 static OSErr QuitAppleEventHandler(const AppleEvent *appleEvt, AppleEvent* reply, UInt32 refcon);
57 static void print_to_log_file(const char *format, ...);
58 static void strip_cr(char *buf);
59
60 #ifdef __cplusplus
61 }
62 #endif
63
64 /* globals */
65 Boolean gQuitFlag = false;
66 short gProgressValue = 0, gOldProgressValue = 0;
67 char gCurlError[CURL_ERROR_SIZE];
68 char * gDL_URL = "http://boinc.berkeley.edu/dl/boinc_5.2.1_macOSX.zip";
69 char * gDownLoadFileName = "boinc_installer.zip";
70 char * gCustomDirectoryName = "boinc_startup_files";
71 MPQueueID gTerminationQueue; /* This queue will report the completion of our threads. */
72 MPTaskID gDownload_thread_id; /* IDs of the threads we create. */
73 CURLcode gResult;
74
75 DialogPtr gProgressDlog;
76
77
main(int argc,char * argv[])78 int main(int argc, char *argv[])
79 {
80 FILE * boinc_installer_zip_file;
81 char path[1024], buf[256], *p;
82 DIRREF dirp;
83 OSStatus err;
84 int retval;
85
86 Initialize();
87
88 #ifdef __APPLE__
89 if (strlen(argv[0]) >= sizeof(path)) {
90 show_message((StringPtr)"\pPath to application is too long.");
91 return 0;
92 }
93 safe_strcpy(path, argv[0]); // Path to this application.
94 p = strstr(path, "/Contents/MacOS/");
95 *p = '\0';
96 p = strrchr(path, '/');
97 *(p+1) = '\0';
98 chdir(path); // Directory containing this application
99 #if 0 // For testing under debugger
100 chdir("/Users/charliefenton/Desktop/FTPTEST/");
101 getcwd(path, 256);
102 #endif
103
104 #else // Not __APPLE__
105 getcwd(path, 256); // Directory containing this application
106 #endif
107
108 // we use multiple threads to be able to run our progress dialogs during
109 // copy and search operations
110 if (!MPLibraryIsLoaded()) {
111 printf("MultiProcessing Library not available.\n");
112 ExitToShell();
113 }
114
115 err = MPCreateQueue(&gTerminationQueue); /* Create the queue which will report the completion of the task. */
116 if (err != noErr) {
117 printf("Cannot create the termination queue. err = %d\n", (short)err);
118 ExitToShell();
119 }
120
121 boinc_installer_zip_file = boinc_fopen(gDownLoadFileName, "w");
122 if (boinc_installer_zip_file == NULL)
123 {
124 show_message((StringPtr)"\pFailed to create zip file.");
125 return -1;
126 }
127
128 err = MPCreateTask(download_thread, /* This is the task function. */
129 boinc_installer_zip_file, /* This is the parameter to the task function. */
130 (500*1024), /* Stack size for thread. */
131 gTerminationQueue, /* We'll use this to sense task completion. */
132 0, /* We won't use the first part of the termination message. */
133 0, /* We won't use the second part of the termination message. */
134 0, /* Use the normal task options. (Currently this *must* be zero!) */
135 &gDownload_thread_id); /* Here's where the ID of the new task will go. */
136
137 if (err != noErr) {
138 (void) MPDeleteQueue(gTerminationQueue);
139 printf("Cannot create the copy thread. err = %d\n", (short)err);
140 ExitToShell();
141 }
142
143 err = runProgressDlog();
144
145 fclose(boinc_installer_zip_file);
146
147 if (gQuitFlag)
148 {
149 show_message((StringPtr)"\pCancelled by user.");
150 return 0;
151 }
152
153 if (gResult)
154 {
155 buf[0] = sprintf(buf+1, "Download error %d:\n%s", gResult, curl_easy_strerror(gResult));
156 show_message((StringPtr)buf);
157 return 0;
158 }
159
160 sprintf(buf, "unzip -o %s", gDownLoadFileName);
161 retval = system(buf);
162
163 if (retval)
164 {
165 show_message((StringPtr)"\pError expanding downloaded zip file.");
166 return 0;
167 }
168
169 // Copy the custom XML files
170 #ifdef __APPLE__
171 // On the Macintosh, BOINC puts its data at a fixed, predetermined path
172 // so we can just copy the custom files there. On other platforms, this
173 // application should copy the custom files to a temporary, intermediate
174 // location which the standard installer can then find; the standard
175 // installer should then copy the files to the correct directory and
176 // possibly delete the temporary ones.
177 retval = system("mkdir -p /Library/Application\\ Support/BOINC\\ Data");
178 if (!retval)
179 {
180 sprintf(buf, "chmod 0644 .%s%s%s*.xml", PATH_SEPARATOR, gCustomDirectoryName, PATH_SEPARATOR);
181 retval = system (buf);
182 }
183 if (!retval)
184 {
185 sprintf(buf, "cp -f .%s%s%s*.xml /Library/Application\\ Support/BOINC\\ Data/",
186 PATH_SEPARATOR, gCustomDirectoryName, PATH_SEPARATOR);
187 retval = system(buf);
188 }
189 #endif
190 if (retval)
191 {
192 show_message((StringPtr)"\pCouldn't copy custom BOINC startup files.");
193 retval = 0; // Should we continue anyway?
194 }
195
196 // Search this directory for the expanded BOINC installer directory; it
197 // should be the only directory other than the custom files directory.
198 sprintf(buf, ".%s", PATH_SEPARATOR);
199
200 dirp = dir_open(path);
201 if (dirp == NULL)
202 {
203 retval = ERR_OPENDIR;
204 } else {
205 do {
206 retval = dir_scan(buf+2, dirp, sizeof(buf)-2);
207 if (!is_dir(buf))
208 continue;
209 if (strcmp(buf+2, gCustomDirectoryName))
210 break;
211 } while (retval == BOINC_SUCCESS);
212 }
213
214 if (retval)
215 {
216 show_message((StringPtr)"\pCouldn't find downloaded additional BOINC installer software.");
217 return 0;
218 }
219
220 // Run the installer
221 retval = chdir(buf);
222 if (!retval)
223 retval = system("open ./BOINC.pkg");
224
225 if (retval)
226 show_message((StringPtr)"\pError running additional BOINC installer software.");
227
228 return 0;
229 }
230
231
Initialize()232 static void Initialize() /* Initialize some managers */
233 {
234 OSErr err;
235
236 InitCursor();
237
238 err = AEInstallEventHandler( kCoreEventClass, kAEQuitApplication, NewAEEventHandlerUPP((AEEventHandlerProcPtr)QuitAppleEventHandler), 0, false );
239 if (err != noErr)
240 ExitToShell();
241 }
242
243
244 ////////////////////////////////////////////////////////////////////////
245 // //
246 // CAUTION - download_thread is a second MPThread, so all Mac //
247 // API calls must be thread-safe! //
248 // //
249 ////////////////////////////////////////////////////////////////////////
download_thread(void * param)250 static OSStatus download_thread(void* param)
251 {
252 return download_boinc((FILE *)param);
253 }
254
255
download_boinc(FILE * out_file)256 static OSErr download_boinc(FILE * out_file)
257 {
258 CURL *myHandle;
259 CURLcode curlErr;
260
261 curlErr = curl_global_init(0);
262
263 myHandle = curl_easy_init();
264 if (myHandle == NULL)
265 {
266 show_message((StringPtr)"\pDownload initialization error.");
267 return -1;
268 }
269
270 curlErr = curl_easy_setopt(myHandle, CURLOPT_VERBOSE, 1);
271 curlErr = curl_easy_setopt(myHandle, CURLOPT_NOPROGRESS, 0);
272 curlErr = curl_easy_setopt(myHandle, CURLOPT_WRITEDATA, out_file);
273 curlErr = curl_easy_setopt(myHandle, CURLOPT_PROGRESSFUNCTION, show_prog);
274 curlErr = curl_easy_setopt(myHandle, CURLOPT_PROGRESSDATA, &gProgressValue);
275 curlErr = curl_easy_setopt(myHandle, CURLOPT_ERRORBUFFER, gCurlError);
276 curlErr = curl_easy_setopt(myHandle, CURLOPT_URL, gDL_URL);
277
278 sleep(2); // Give progress dialog a chance to be drawn
279
280 curlErr = curl_easy_perform(myHandle);
281
282 curl_easy_cleanup(myHandle);
283 curl_global_cleanup();
284
285 return curlErr;
286 }
287
288
289
show_prog(void * clientp,double dltotal,double dlnow,double ultotal,double ulnow)290 static int show_prog(void *clientp, double dltotal, double dlnow,
291 double ultotal, double ulnow)
292 {
293 gProgressValue = (short)(dlnow * 100.0 / dltotal);
294 printf("Progress = %d%%\n", gProgressValue);
295
296 return gQuitFlag;
297 }
298
299
runProgressDlog()300 static OSErr runProgressDlog()
301 {
302 AlertStdAlertParamRec alertParams;
303 DialogItemIndex itemHit = 0;
304 ModalFilterUPP myFilterProcUPP = NewModalFilterUPP(ProgDlgFilterProc);
305 OSStatus err;
306
307 gOldProgressValue = -1;
308
309 alertParams.movable = false;
310 alertParams.helpButton = false;
311 alertParams.filterProc = myFilterProcUPP;
312 alertParams.defaultText = "\pCancel";
313 alertParams.cancelText = NULL;
314 alertParams.otherText = NULL;
315 alertParams.defaultButton = kAlertStdAlertOKButton;
316 alertParams.cancelButton = 0;
317 alertParams.position = kWindowDefaultPosition;
318
319 ParamText("\pDownloading additional BOINC installer software:\n 0% complete", "\p", "\p", "\p");
320
321 err = StandardAlert(kAlertNoteAlert, "\p^0", NULL, &alertParams, &itemHit);
322 if (itemHit == kAlertStdAlertOKButton)
323 gQuitFlag = true;
324
325 return noErr;
326 }
327
328
ProgDlgFilterProc(DialogPtr dp,EventRecord * event,short * item)329 static pascal Boolean ProgDlgFilterProc(DialogPtr dp, EventRecord *event, short *item)
330 {
331 char buf[256];
332 OSStatus err;
333
334 if (gTerminationQueue)
335 {
336 err = MPWaitOnQueue(gTerminationQueue, 0, 0, (void **)&gResult, kDurationImmediate);
337 if (err == noErr)
338 {
339 *item = kAlertStdAlertCancelButton;
340 return true;
341 }
342 }
343
344 if (gProgressValue == gOldProgressValue)
345 return false;
346
347 gOldProgressValue = gProgressValue;
348
349 buf[0] = sprintf(buf+1, "Downloading additional BOINC installer software:\n %3d%% complete", gProgressValue);
350 ParamText("\pDownloading additional BOINC installer software: 0% complete", "\p", "\p", "\p");
351 ParamText((StringPtr)buf, "\p", "\p", "\p");
352
353 DrawDialog(dp);
354
355 return false;
356 }
357
358
359 /********************************************************************
360
361 ShowMessage
362
363 ********************************************************************/
show_message(StringPtr s1)364 static void show_message(StringPtr s1)
365 {
366 DialogItemIndex itemHit;
367 OSErr err;
368
369 err = StandardAlert (kAlertStopAlert, s1, NULL, NULL, &itemHit);
370 }
371
372
QuitAppleEventHandler(const AppleEvent * appleEvt,AppleEvent * reply,UInt32 refcon)373 static OSErr QuitAppleEventHandler( const AppleEvent *appleEvt, AppleEvent* reply, UInt32 refcon )
374 {
375 gQuitFlag = true;
376
377 return noErr;
378 }
379
380
381 // For debugging
print_to_log_file(const char * format,...)382 static void print_to_log_file(const char *format, ...) {
383 #if CREATE_LOG
384 FILE *f;
385 va_list args;
386 char buf[256];
387 time_t t;
388 safe_strcpy(buf, getenv("HOME"));
389 strcat(buf, "/Documents/test_log.txt");
390 f = fopen(buf, "a");
391 if (!f) return;
392
393 // freopen(buf, "a", stdout);
394 // freopen(buf, "a", stderr);
395
396 time(&t);
397 safe_strcpy(buf, asctime(localtime(&t)));
398 strip_cr(buf);
399
400 fputs(buf, f);
401 fputs(" ", f);
402
403 va_start(args, format);
404 vfprintf(f, format, args);
405 va_end(args);
406
407 fputs("\n", f);
408 fflush(f);
409 fclose(f);
410 #endif
411 }
412
413 #if CREATE_LOG
strip_cr(char * buf)414 static void strip_cr(char *buf)
415 {
416 char *theCR;
417
418 theCR = strrchr(buf, '\n');
419 if (theCR)
420 *theCR = '\0';
421 theCR = strrchr(buf, '\r');
422 if (theCR)
423 *theCR = '\0';
424 }
425 #endif // CREATE_LOG
426