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