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