1 // This file is part of BOINC.
2 // http://boinc.berkeley.edu
3 // Copyright (C) 2017 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 /* Installer.cpp */
19 
20 #define CREATE_LOG 0    /* for general debugging */
21 #define VERBOSE_TEST 0  /* for debugging callPosixSpawn */
22 
23 #include <Carbon/Carbon.h>
24 #include <grp.h>
25 
26 #include <unistd.h>	// getlogin
27 #include <sys/types.h>	// getpwname, getpwuid, getuid
28 #include <pwd.h>	// getpwname, getpwuid, getuid
29 #include <sys/wait.h>	// waitpid
30 #include <dirent.h>
31 #include <sys/param.h>  // for MAXPATHLEN
32 #include <sys/stat.h>
33 
34 #include "str_util.h"
35 #include "str_replace.h"
36 #include "mac_util.h"
37 #include "translate.h"
38 
39 #define boinc_master_user_name "boinc_master"
40 #define boinc_master_group_name "boinc_master"
41 #define boinc_project_user_name "boinc_project"
42 #define boinc_project_group_name "boinc_project"
43 
44 
45 OSErr Initialize(void);	/* function prototypes */
46 Boolean IsUserMemberOfGroup(const char *userName, const char *groupName);
47 Boolean IsRestartNeeded();
48 static void GetPreferredLanguages();
49 static void LoadPreferredLanguages();
50 static void ShowMessage(const char *format, ...);
51 static OSErr QuitAppleEventHandler(const AppleEvent *appleEvt, AppleEvent* reply, UInt32 refcon);
52 int callPosixSpawn(const char *cmd);
53 void print_to_log_file(const char *format, ...);
54 void strip_cr(char *buf);
55 
56 // We can't use translation because the translation catalogs
57 // have not yet been installed when this application is run.
58 #define MAX_LANGUAGES_TO_TRY 5
59 
60 #define REPORT_ERROR(isError) if (isError) print_to_log_file("BOINC Installer error at line %d", __LINE__);
61 
62 static char * Catalog_Name = (char *)"BOINC-Setup";
63 static char Catalogs_Dir[MAXPATHLEN];
64 static char loginName[256];
65 static char tempDirName[MAXPATHLEN];
66 
67 Boolean			gQuitFlag = false;	/* global */
68 
69 #if 0
70 CFStringRef     valueRestartRequired = CFSTR("RequiredRestart");
71 CFStringRef     valueLogoutRequired = CFSTR("RequiredLogout");
72 CFStringRef     valueNoRestart = CFSTR("NoRestart");
73 #endif
74 
main(int argc,char * argv[])75 int main(int argc, char *argv[])
76 {
77     char                    pkgPath[MAXPATHLEN];
78     char                    postInstallAppPath[MAXPATHLEN];
79     char                    temp[MAXPATHLEN];
80     char                    brand[64], s[256];
81     char                    *p;
82     OSStatus                err = noErr;
83     Boolean                 restartNeeded = true;
84     FILE                    *restartNeededFile;
85 
86     if (Initialize() != noErr) {
87         return 0;
88     }
89 
90     strncpy(loginName, getenv("USER"), sizeof(loginName)-1);
91     if (loginName[0] == '\0') {
92         ShowMessage((char *)_("Could not get user login name"));
93         return 0;
94     }
95 
96     snprintf(tempDirName, sizeof(tempDirName), "InstallBOINC-%s", loginName);
97 
98     snprintf(temp, sizeof(temp), "/tmp/%s", tempDirName);
99     mkdir(temp, 0777);
100     chmod(temp, 0777);  // Needed because mkdir sets permissions restricted by umask (022)
101 
102     snprintf(temp, sizeof(temp), "rm -dfR /tmp/%s/BOINC_Installer_Errors", tempDirName);
103     err = callPosixSpawn(temp);
104 
105     snprintf(Catalogs_Dir, sizeof(Catalogs_Dir),
106             "/tmp/%s/BOINC_payload/Library/Application Support/BOINC Data/locale/",
107             tempDirName);
108 
109     // Get the full path to Installer package inside this application's bundle
110     getPathToThisApp(pkgPath, sizeof(pkgPath));
111     strlcpy(temp, pkgPath, sizeof(temp));
112 
113     strlcat(pkgPath, "/Contents/Resources/", sizeof(pkgPath));
114 
115     strlcpy(postInstallAppPath, pkgPath, sizeof(postInstallAppPath));
116     strlcat(postInstallAppPath, "PostInstall.app", sizeof(postInstallAppPath));
117 
118    // To allow for branding, assume name of installer package inside bundle corresponds to name of this application
119     p = strrchr(temp, '/');         // Point to name of this application (e.g., "BOINC Installer.app")
120     if (p == NULL)
121         p = temp - 1;
122     strlcpy(brand, p+1, sizeof(brand));
123     strlcat(pkgPath, p+1, sizeof(pkgPath));
124     p = strrchr(pkgPath, ' ');         // Strip off last space character and everything following
125     if (p)
126         *p = '\0';
127 
128     p = strstr(brand, " Installer.app");  // Strip off trailing " Installer.app"
129     if (p)
130         *p = '\0';
131 
132     strlcat(pkgPath, ".pkg", sizeof(pkgPath));
133 
134     // In the unlikely situation that /tmp has files from an earlier attempt to install
135     // BOINC by a different user, we won't have permission to delete or overwrite them,
136     // so include the current user's name as part of the paths to our temporary files.
137 
138     // Expand the installer package
139     snprintf(temp, sizeof(temp), "rm -dfR /tmp/%s/BOINC.pkg", tempDirName);
140     err = callPosixSpawn(temp);
141     REPORT_ERROR(err);
142     snprintf(temp, sizeof(temp), "rm -dfR /tmp/%s/expanded_BOINC.pkg", tempDirName);
143     err = callPosixSpawn(temp);
144     REPORT_ERROR(err);
145     snprintf(temp, sizeof(temp), "rm -dfR /tmp/%s/PostInstall.app", tempDirName);
146     err = callPosixSpawn(temp);
147     REPORT_ERROR(err);
148     snprintf(temp, sizeof(temp), "rm -f /tmp/%s/BOINC_preferred_languages", tempDirName);
149     err = callPosixSpawn(temp);
150     REPORT_ERROR(err);
151     snprintf(temp, sizeof(temp), "rm -f /tmp/%s/BOINC_restart_flag", tempDirName);
152     err = callPosixSpawn(temp);
153     REPORT_ERROR(err);
154 
155     sprintf(temp, "cp -fpR \"%s\" /tmp/%s/PostInstall.app", postInstallAppPath, tempDirName);
156     err = callPosixSpawn(temp);
157     REPORT_ERROR(err);
158     sprintf(temp, "pkgutil --expand \"%s\" /tmp/%s/expanded_BOINC.pkg", pkgPath, tempDirName);
159     err = callPosixSpawn(temp);
160     REPORT_ERROR(err);
161     if (err == noErr) {
162         GetPreferredLanguages();
163     }
164     if (compareOSVersionTo(10, 6) < 0) {
165         LoadPreferredLanguages();
166         BringAppToFront();
167         p = strrchr(brand, ' ');         // Strip off last space character and everything following
168         if (p)
169             *p = '\0';
170         ShowMessage((char *)_("Sorry, this version of %s requires system 10.6 or higher."), brand);
171 
172         snprintf(temp, sizeof(temp), "rm -dfR /tmp/%s/BOINC_payload", tempDirName);
173         err = callPosixSpawn(temp);
174         REPORT_ERROR(err);
175         return -1;
176     }
177 
178     snprintf(temp, sizeof(temp), "rm -dfR /tmp/%s/BOINC_payload", tempDirName);
179     err = callPosixSpawn(temp);
180     REPORT_ERROR(err);
181 
182     // Remove previous installer package receipt so we can run installer again
183     // (affects only older versions of OS X and fixes a bug in those versions)
184     // "rm -rf /Library/Receipts/GridRepublic.pkg"
185     sprintf(s, "rm -rf \"/Library/Receipts/%s.pkg\"", brand);
186     err = callPosixSpawn (s);
187     REPORT_ERROR(err);
188 
189     restartNeeded = IsRestartNeeded();
190 
191     // Write a temp file to tell our PostInstall.app whether restart is needed
192     snprintf(temp, sizeof(temp), "/tmp/%s/BOINC_restart_flag", tempDirName);
193     restartNeededFile = fopen(temp, "w");
194     if (restartNeededFile) {
195         fputs(restartNeeded ? "1\n" : "0\n", restartNeededFile);
196         fclose(restartNeededFile);
197     }
198 
199     if (restartNeeded) {
200         if (err == noErr) {
201             // Change onConclusion="none" to onConclusion="RequireRestart"
202             snprintf(temp, sizeof(temp), "sed -i \".bak\" s/onConclusion=\"none\"/onConclusion=\"RequireRestart\"/g /tmp/%s/expanded_BOINC.pkg/Distribution", tempDirName);
203             err = callPosixSpawn(temp);
204             REPORT_ERROR(err);
205         }
206         if (err == noErr) {
207             snprintf(temp, sizeof(temp), "rm -dfR /tmp/%s/expanded_BOINC.pkg/Distribution.bak", tempDirName);
208             err = callPosixSpawn(temp);
209             REPORT_ERROR(err);
210             // Flatten the installer package
211             sprintf(temp, "pkgutil --flatten /tmp/%s/expanded_BOINC.pkg /tmp/%s/%s.pkg", tempDirName, tempDirName, brand);
212             err = callPosixSpawn(temp);
213             REPORT_ERROR(err);
214         }
215 
216         if (err == noErr) {
217             snprintf(temp, sizeof(temp), "rm -dfR /tmp/%s/expanded_BOINC.pkg", tempDirName);
218             err = callPosixSpawn(temp);
219             REPORT_ERROR(err);
220             sprintf(temp, "open \"/tmp/%s/%s.pkg\"", tempDirName, brand);
221             err = callPosixSpawn(temp);
222             REPORT_ERROR(err);
223             return err;
224         }
225     }
226 
227     snprintf(temp, sizeof(temp), "rm -dfR /tmp/%s/expanded_BOINC.pkg", tempDirName);
228     err = callPosixSpawn(temp);
229     REPORT_ERROR(err);
230 
231     sprintf(temp, "open \"%s\"", pkgPath);
232     err = callPosixSpawn(temp);
233     REPORT_ERROR(err);
234 
235     return err;
236 }
237 
238 
IsUserMemberOfGroup(const char * userName,const char * groupName)239 Boolean IsUserMemberOfGroup(const char *userName, const char *groupName) {
240     group               *grp;
241     short               i = 0;
242     char                *p;
243 
244     grp = getgrnam(groupName);
245     if (!grp) {
246         printf("getgrnam(%s) failed\n", groupName);
247         fflush(stdout);
248         return false;  // Group not found
249     }
250 
251     while ((p = grp->gr_mem[i]) != NULL) {  // Step through all users in group admin
252         if (strcmp(p, userName) == 0) {
253             return true;
254         }
255         ++i;
256     }
257     return false;
258 }
259 
260 
IsRestartNeeded()261 Boolean IsRestartNeeded()
262 {
263     passwd          *pw = NULL;
264     group           *grp = NULL;
265     gid_t           boinc_master_gid = 0, boinc_project_gid = 0;
266     uid_t           boinc_master_uid = 0, boinc_project_uid = 0;
267 
268     if (compareOSVersionTo(10, 9) >= 0) {
269         return false;
270     }
271 
272     grp = getgrnam(boinc_master_group_name);
273     if (grp == NULL)
274         return true;       // Group boinc_master does not exist
275 
276     boinc_master_gid = grp->gr_gid;
277 
278     grp = getgrnam(boinc_project_group_name);
279     if (grp == NULL)
280         return true;       // Group boinc_project does not exist
281 
282     boinc_project_gid = grp->gr_gid;
283 
284     pw = getpwnam(boinc_master_user_name);
285     if (pw == NULL)
286         return true;       // User boinc_master does not exist
287 
288     boinc_master_uid = pw->pw_uid;
289 
290     if (pw->pw_gid != boinc_master_gid)
291         return true;       // User boinc_master does not have group boinc_master as its primary group
292 
293     pw = getpwnam(boinc_project_user_name);
294     if (pw == NULL)
295         return true;       // User boinc_project does not exist
296 
297     boinc_project_uid = pw->pw_uid;
298 
299     if (pw->pw_gid != boinc_project_gid)
300         return true;       // User boinc_project does not have group boinc_project as its primary group
301 
302     if (compareOSVersionTo(10, 5) >= 0) {
303         if (boinc_master_gid < 501)
304             return true;       // We will change boinc_master_gid to a value > 501
305         if (boinc_project_gid < 501)
306             return true;       // We will change boinc_project_gid to a value > 501
307         if (boinc_master_uid < 501)
308             return true;       // We will change boinc_master_uid to a value > 501
309         if (boinc_project_uid < 501)
310             return true;       // We will change boinc_project_uid to a value > 501
311 }
312 
313     #ifdef SANDBOX
314     if (loginName[0]) {
315         if (IsUserMemberOfGroup(loginName, boinc_master_group_name)) {
316             return false;   // Logged in user is already a member of group boinc_master
317         }
318     }
319 #endif  // SANDBOX
320 
321     return true;
322 }
323 
324 
Initialize()325 OSErr Initialize()	/* Initialize some managers */
326 {
327 //    InitCursor();
328 
329     return AEInstallEventHandler( kCoreEventClass, kAEQuitApplication, NewAEEventHandlerUPP((AEEventHandlerProcPtr)QuitAppleEventHandler), 0, false );
330 }
331 
332 
333 // Because language preferences are set on a per-user basis, we
334 // must get the preferred languages while set to the current
335 // user, before the Apple Installer switches us to root.
336 // So we get the preferred languages here and write them to a
337 // temporary file to be retrieved by our PostInstall app.
GetPreferredLanguages()338 static void GetPreferredLanguages() {
339     DIR *dirp;
340     struct dirent *dp;
341     char temp[MAXPATHLEN];
342     char searchPath[MAXPATHLEN];
343     char savedWD[MAXPATHLEN];
344     struct stat sbuf;
345     CFMutableArrayRef supportedLanguages;
346     CFStringRef aLanguage;
347     char shortLanguage[32];
348     CFArrayRef preferredLanguages;
349     int i, j, k;
350     char * language;
351     char *uscore;
352     FILE *f;
353 
354     getcwd(savedWD, sizeof(savedWD));
355     snprintf(temp, sizeof(temp), "rm -dfR /tmp/%s/BOINC_payload", tempDirName);
356     callPosixSpawn(temp);
357     snprintf(temp, sizeof(temp), "/tmp/%s/BOINC_payload", tempDirName);
358     mkdir(temp, 0777);
359     chmod(temp, 0777);  // Needed because mkdir sets permissions restricted by umask (022)
360     chdir(temp);
361     snprintf(temp, sizeof(temp), "cpio -i -I /tmp/%s/expanded_BOINC.pkg/BOINC.pkg/Payload", tempDirName);
362     callPosixSpawn(temp);
363     chdir(savedWD);
364 
365     // Create an array of all our supported languages
366     supportedLanguages = CFArrayCreateMutable(kCFAllocatorDefault, 100, &kCFTypeArrayCallBacks);
367 
368     aLanguage = CFStringCreateWithCString(NULL, "en", kCFStringEncodingMacRoman);
369     CFArrayAppendValue(supportedLanguages, aLanguage);
370     CFRelease(aLanguage);
371     aLanguage = NULL;
372 
373     dirp = opendir(Catalogs_Dir);
374     if (!dirp) {
375         REPORT_ERROR(true);
376         goto cleanup;
377     }
378     while (true) {
379         dp = readdir(dirp);
380         if (dp == NULL)
381             break;                  // End of list
382 
383         if (dp->d_name[0] == '.')
384             continue;               // Ignore names beginning with '.'
385 
386         strlcpy(searchPath, Catalogs_Dir, sizeof(searchPath));
387         strlcat(searchPath, dp->d_name, sizeof(searchPath));
388         strlcat(searchPath, "/", sizeof(searchPath));
389         strlcat(searchPath, Catalog_Name, sizeof(searchPath));
390         strlcat(searchPath, ".mo", sizeof(searchPath));
391         if (stat(searchPath, &sbuf) != 0) continue;
392 //        printf("Adding %s to supportedLanguages array\n", dp->d_name);
393         aLanguage = CFStringCreateWithCString(NULL, dp->d_name, kCFStringEncodingMacRoman);
394         CFArrayAppendValue(supportedLanguages, aLanguage);
395         CFRelease(aLanguage);
396         aLanguage = NULL;
397 
398         // If it has a region code ("it_IT") also try without region code ("it")
399         // TODO: Find a more general solution
400         strlcpy(shortLanguage, dp->d_name, sizeof(shortLanguage));
401         uscore = strchr(shortLanguage, '_');
402         if (uscore) {
403             *uscore = '\0';
404             aLanguage = CFStringCreateWithCString(NULL, shortLanguage, kCFStringEncodingMacRoman);
405             CFArrayAppendValue(supportedLanguages, aLanguage);
406             CFRelease(aLanguage);
407             aLanguage = NULL;
408         }
409     }
410 
411     closedir(dirp);
412 
413     // Write a temp file to tell our PostInstall.app our preferred languages
414     snprintf(temp, sizeof(temp), "/tmp/%s/BOINC_preferred_languages", tempDirName);
415     f = fopen(temp, "w");
416     if (!f) {
417         REPORT_ERROR(true);
418         goto cleanup;
419     }
420 
421     for (i=0; i<MAX_LANGUAGES_TO_TRY; ++i) {
422 
423         preferredLanguages = CFBundleCopyLocalizationsForPreferences(supportedLanguages, NULL );
424 
425 #if 0   // For testing
426         int c = CFArrayGetCount(preferredLanguages);
427         for (k=0; k<c; ++k) {
428         CFStringRef s = (CFStringRef)CFArrayGetValueAtIndex(preferredLanguages, k);
429             printf("Preferred language %u is %s\n", k, CFStringGetCStringPtr(s, kCFStringEncodingMacRoman));
430         }
431 
432 #endif
433 
434         for (j=0; j<CFArrayGetCount(preferredLanguages); ++j) {
435             aLanguage = (CFStringRef)CFArrayGetValueAtIndex(preferredLanguages, j);
436             language = (char *)CFStringGetCStringPtr(aLanguage, kCFStringEncodingMacRoman);
437             if (language == NULL) {
438                 if (CFStringGetCString(aLanguage, shortLanguage, sizeof(shortLanguage), kCFStringEncodingMacRoman)) {
439                     language = shortLanguage;
440                 }
441             }
442             if (f && language) {
443                 fprintf(f, "%s\n", language);
444 #if CREATE_LOG
445                 print_to_log_file("Adding language: %s\n", language);
446 #endif
447             }
448             // Remove all copies of this language from our list of supported languages
449             // so we can get the next preferred language in order of priority
450             for (k=CFArrayGetCount(supportedLanguages)-1; k>=0; --k) {
451                 if (CFStringCompare(aLanguage, (CFStringRef)CFArrayGetValueAtIndex(supportedLanguages, k), 0) == kCFCompareEqualTo) {
452                     CFArrayRemoveValueAtIndex(supportedLanguages, k);
453                 }
454             }
455 
456             // Since the original strings are English, no
457             // further translation is needed for language en.
458             if (language) {
459                 if (!strcmp(language, "en")) {
460                     fclose(f);
461                     CFRelease(preferredLanguages);
462                     preferredLanguages = NULL;
463                     goto cleanup;
464                 }
465             }
466         }
467 
468         CFRelease(preferredLanguages);
469         preferredLanguages = NULL;
470 
471     }
472 
473     if (f) {
474         fprintf(f, "en\n");
475         fclose(f);
476     }
477 
478 cleanup:
479     CFArrayRemoveAllValues(supportedLanguages);
480     CFRelease(supportedLanguages);
481     supportedLanguages = NULL;
482 #if CREATE_LOG
483     print_to_log_file("Exiting GetPreferredLanguages");
484 #endif
485 }
486 
487 
LoadPreferredLanguages()488 static void LoadPreferredLanguages(){
489     FILE *f;
490     int i;
491     char *p;
492     char language[32];
493     char temp[MAXPATHLEN];
494 
495     BOINCTranslationInit();
496 
497     // GetPreferredLanguages() wrote a list of our preferred languages to a temp file
498     snprintf(temp, sizeof(temp), "/tmp/%s/BOINC_preferred_languages", tempDirName);
499     f = fopen(temp, "r");
500     if (!f) {
501         REPORT_ERROR(true);
502         return;
503     }
504 
505     for (i=0; i<MAX_LANGUAGES_TO_TRY; ++i) {
506         fgets(language, sizeof(language), f);
507         if (feof(f)) break;
508         language[sizeof(language)-1] = '\0';    // Guarantee a null terminator
509         p = strchr(language, '\n');
510         if (p) *p = '\0';           // Replace newline with null terminator
511         if (language[0]) {
512             if (!BOINCTranslationAddCatalog(Catalogs_Dir, language, Catalog_Name)) {
513                 REPORT_ERROR(true);
514                 printf("could not load catalog for langage %s\n", language);
515             }
516         }
517     }
518     fclose(f);
519 }
520 
521 
ShowMessage(const char * format,...)522 static void ShowMessage(const char *format, ...) {
523   // CAUTION: vsprintf will produce undesirable results if the string
524   // contains a % character that is not a format specification!
525   // But CFString is OK!
526 
527     va_list                 args;
528     char                    s[1024];
529     CFOptionFlags           responseFlags;
530     CFURLRef                myIconURLRef = NULL;
531     CFBundleRef             myBundleRef;
532 
533     myBundleRef = CFBundleGetMainBundle();
534     if (myBundleRef) {
535         myIconURLRef = CFBundleCopyResourceURL(myBundleRef, CFSTR("MacInstaller.icns"), NULL, NULL);
536     }
537 
538 #if 1
539     va_start(args, format);
540     vsprintf(s, format, args);
541     va_end(args);
542 #else
543     strcpy(s, format);
544 #endif
545 
546     // If defaultButton is nil or an empty string, a default localized
547     // button title ("OK" in English) is used.
548 
549     CFStringRef myString = CFStringCreateWithCString(NULL, s, kCFStringEncodingUTF8);
550 
551     BringAppToFront();
552     CFUserNotificationDisplayAlert(0.0, kCFUserNotificationPlainAlertLevel,
553                 myIconURLRef, NULL, NULL, CFSTR(" "), myString,
554                 NULL, NULL, NULL,
555                 &responseFlags);
556 
557     if (myIconURLRef) CFRelease(myIconURLRef);
558     if (myString) CFRelease(myString);
559 }
560 
561 
QuitAppleEventHandler(const AppleEvent * appleEvt,AppleEvent * reply,UInt32 refcon)562 static OSErr QuitAppleEventHandler( const AppleEvent *appleEvt, AppleEvent* reply, UInt32 refcon )
563 {
564     gQuitFlag =  true;
565 
566     return noErr;
567 }
568 
569 
570 #define NOT_IN_TOKEN                0
571 #define IN_SINGLE_QUOTED_TOKEN      1
572 #define IN_DOUBLE_QUOTED_TOKEN      2
573 #define IN_UNQUOTED_TOKEN           3
574 
parse_posic_spawn_command_line(char * p,char ** argv)575 static int parse_posic_spawn_command_line(char* p, char** argv) {
576     int state = NOT_IN_TOKEN;
577     int argc=0;
578 
579     while (*p) {
580         switch(state) {
581         case NOT_IN_TOKEN:
582             if (isspace(*p)) {
583             } else if (*p == '\'') {
584                 p++;
585                 argv[argc++] = p;
586                 state = IN_SINGLE_QUOTED_TOKEN;
587                 break;
588             } else if (*p == '\"') {
589                 p++;
590                 argv[argc++] = p;
591                 state = IN_DOUBLE_QUOTED_TOKEN;
592                 break;
593             } else {
594                 argv[argc++] = p;
595                 state = IN_UNQUOTED_TOKEN;
596             }
597             break;
598         case IN_SINGLE_QUOTED_TOKEN:
599             if (*p == '\'') {
600                 if (*(p-1) == '\\') break;
601                 *p = 0;
602                 state = NOT_IN_TOKEN;
603             }
604             break;
605         case IN_DOUBLE_QUOTED_TOKEN:
606             if (*p == '\"') {
607                 if (*(p-1) == '\\') break;
608                 *p = 0;
609                 state = NOT_IN_TOKEN;
610             }
611             break;
612         case IN_UNQUOTED_TOKEN:
613             if (isspace(*p)) {
614                 *p = 0;
615                 state = NOT_IN_TOKEN;
616             }
617             break;
618         }
619         p++;
620     }
621     argv[argc] = 0;
622     return argc;
623 }
624 
625 #include <spawn.h>
626 
callPosixSpawn(const char * cmdline)627 int callPosixSpawn(const char *cmdline) {
628     char command[1024];
629     char progName[1024];
630     char progPath[MAXPATHLEN];
631     char* argv[100];
632     int argc = 0;
633     char *p;
634     pid_t thePid = 0;
635     int result = 0;
636     int status = 0;
637     extern char **environ;
638 
639     // Make a copy of cmdline because parse_posix_spawn_command_line modifies it
640     strlcpy(command, cmdline, sizeof(command));
641     argc = parse_posic_spawn_command_line(const_cast<char*>(command), argv);
642     strlcpy(progPath, argv[0], sizeof(progPath));
643     strlcpy(progName, argv[0], sizeof(progName));
644     p = strrchr(progName, '/');
645     if (p) {
646         argv[0] = p+1;
647     } else {
648         argv[0] = progName;
649     }
650 
651 #if VERBOSE_TEST
652     print_to_log_file("***********");
653     for (int i=0; i<argc; ++i) {
654         print_to_log_file("argv[%d]=%s", i, argv[i]);
655     }
656     print_to_log_file("***********\n");
657 #endif
658 
659     errno = 0;
660 
661     result = posix_spawnp(&thePid, progPath, NULL, NULL, argv, environ);
662 #if VERBOSE_TEST
663     print_to_log_file("callPosixSpawn command: %s", cmdline);
664     print_to_log_file("callPosixSpawn: posix_spawnp returned %d: %s", result, strerror(result));
665 #endif
666     if (result) {
667         return result;
668     }
669 // CAF    int val =
670     waitpid(thePid, &status, WUNTRACED);
671 // CAF        if (val < 0) printf("first waitpid returned %d\n", val);
672     if (status != 0) {
673 #if VERBOSE_TEST
674         print_to_log_file("waitpid() returned status=%d", status);
675 #endif
676         result = status;
677     } else {
678         if (WIFEXITED(status)) {
679             result = WEXITSTATUS(status);
680             if (result == 1) {
681 #if VERBOSE_TEST
682                 print_to_log_file("WEXITSTATUS(status) returned 1, errno=%d: %s", errno, strerror(errno));
683 #endif
684                 result = errno;
685             }
686 #if VERBOSE_TEST
687             else if (result) {
688                 print_to_log_file("WEXITSTATUS(status) returned %d", result);
689             }
690 #endif
691         }   // end if (WIFEXITED(status)) else
692     }       // end if waitpid returned 0 sstaus else
693 
694     return result;
695 }
696 
697 
698 // For debugging
print_to_log_file(const char * format,...)699 void print_to_log_file(const char *format, ...) {
700     FILE *f;
701     va_list args;
702     char buf[MAXPATHLEN];
703     time_t t;
704 //    strlcpy(buf, getenv("HOME"), sizeof(buf));
705 //    strlcat(buf, "/Documents/test_log.txt", sizeof(buf));
706 
707     snprintf(buf, sizeof(buf), "/tmp/%s/BOINC_Installer_Errors", tempDirName);
708     f = fopen(buf, "a");
709     if (!f) return;
710 
711 //  freopen(buf, "a", stdout);
712 //  freopen(buf, "a", stderr);
713 
714     time(&t);
715     strlcpy(buf, asctime(localtime(&t)),sizeof(buf));
716     strip_cr(buf);
717 
718     fputs(buf, f);
719     fputs("   ", f);
720 
721     va_start(args, format);
722     vfprintf(f, format, args);
723     va_end(args);
724 
725     fputs("\n", f);
726     fflush(f);
727     fclose(f);
728 }
729 
strip_cr(char * buf)730 void strip_cr(char *buf)
731 {
732     char *theCR;
733 
734     theCR = strrchr(buf, '\n');
735     if (theCR)
736         *theCR = '\0';
737     theCR = strrchr(buf, '\r');
738     if (theCR)
739         *theCR = '\0';
740 }
741