// This file is part of BOINC.
// http://boinc.berkeley.edu
// Copyright (C) 2008 University of California
//
// BOINC is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License
// as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
//
// BOINC is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with BOINC. If not, see .
// SetupSecurity.cpp
#include
#include
#include // getgrname, getgrgid
#include // getpwnam, getpwuid, getuid
#include // usleep
#include // for MAXPATHLEN
#include
#include
#include
#include "file_names.h"
#include "SetupSecurity.h"
static OSStatus UpdateNestedDirectories(char * basepath);
static OSStatus MakeXMLFilesPrivate(char * basepath);
static OSStatus GetAuthorization(void);
OSStatus DoPrivilegedExec(const char *pathToTool, char *arg1, char *arg2, char *arg3, char *arg4, char *arg5, char *arg6);
#ifndef __x86_64__
static pascal Boolean ErrorDlgFilterProc(DialogPtr theDialog, EventRecord *theEvent, short *theItemHit);
#endif
static void SleepTicks(UInt32 ticksToSleep);
#ifdef _DEBUG
static OSStatus SetFakeMasterNames(void);
#endif
static OSStatus CreateUserAndGroup(char * user_name, char * group_name);
static OSStatus ResynchSystem(void);
static AuthorizationRef gOurAuthRef = NULL;
#define DELAY_TICKS 3
#define DELAY_TICKS_R 10
#define REAL_BOINC_MASTER_NAME "boinc_master"
#define REAL_BOINC_PROJECT_NAME "boinc_project"
#ifdef _DEBUG
// GDB can't attach to applications which are running as a diferent user or group so
// it ignores the S_ISUID and S_ISGID permisison bits when launching an application.
// To work around this, the _DEBUG version uses the current user and group.
//
// NOTE: The Manager and Client call these routines only "#ifdef _DEBUG" (i.e.,
// only from the DEVELOPMENT BUILD), never from the Deployment build.
//
static char boinc_master_user_name[64];
static char boinc_master_group_name[64];
static char boinc_project_user_name[64];
static char boinc_project_group_name[64];
#else
#define boinc_master_user_name REAL_BOINC_MASTER_NAME
#define boinc_master_group_name REAL_BOINC_MASTER_NAME
#define boinc_project_user_name REAL_BOINC_PROJECT_NAME
#define boinc_project_group_name REAL_BOINC_PROJECT_NAME
#endif
#define MIN_ID 501 /* Minimum user ID / Group ID to create */
static char dsclPath[] = "/usr/bin/dscl";
static char chmodPath[] = "/bin/chmod";
static char chownPath[] = "/usr/sbin/chown";
#define RIGHTS_COUNT 3 /* Count of the 3 above items */
int CreateBOINCUsersAndGroups() {
OSStatus err = noErr;
err = CreateUserAndGroup(REAL_BOINC_MASTER_NAME, REAL_BOINC_MASTER_NAME);
if (err != noErr)
return err;
err = CreateUserAndGroup(REAL_BOINC_PROJECT_NAME, REAL_BOINC_PROJECT_NAME);
if (err != noErr)
return err;
err = ResynchSystem();
if (err != noErr)
return err;
return noErr;
}
// Pass NULL for path when calling this routine from within BOINC Manager
int SetBOINCAppOwnersGroupsAndPermissions(char *path) {
char fullpath[MAXPATHLEN];
char dir_path[MAXPATHLEN];
char buf1[80];
ProcessSerialNumber ourPSN;
FSRef ourFSRef, ref;
char *p;
Boolean isDirectory;
OSStatus err = noErr;
#define NUMBRANDS 3
char *saverName[NUMBRANDS];
saverName[0] = "BOINCSaver";
saverName[1] = "GridRepublic";
saverName[2] = "Progress Thru Processors";
#ifdef _DEBUG
err = SetFakeMasterNames();
if (err)
return err;
#endif
if (path == NULL) { // NULL means we were called from within BOINC Manager
// Get the full path to this application's bundle (BOINC Manager's bundle)
err = GetCurrentProcess (&ourPSN);
if (err)
return err; // Should never happen
err = GetProcessBundleLocation(&ourPSN, &ourFSRef);
if (err)
return err; // Should never happen
err = FSRefMakePath (&ourFSRef, (UInt8*)dir_path, sizeof(dir_path));
if (err)
return err; // Should never happen
} else
strlcpy(dir_path, path, MAXPATHLEN); // Path to BOINC Manager's bundle was passed as argument
if (strlen(fullpath) >= (MAXPATHLEN-1)) {
ShowSecurityError("SetBOINCAppOwnersGroupsAndPermissions: path to Manager is too long");
return -1;
}
strlcpy(fullpath, dir_path, sizeof(fullpath));
#ifdef _DEBUG
// chmod -R u=rwx,g=rwx,o=rx path/BOINCManager.app
// 0775 = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH
// Set read, write permission for user; read and execute permission for group and others
err = DoPrivilegedExec(chmodPath, "-R", "u=rwx,g=rwx,o=rx", fullpath, NULL, NULL, NULL);
#else
// chmod -R u=rx,g=rx,o=rx path/BOINCManager.app
// 0555 = S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH
// Set read, write permission for user; read and execute permission for group and others
err = DoPrivilegedExec(chmodPath, "-R", "u=rx,g=rx,o=rx", fullpath, NULL, NULL, NULL);
#endif
if (err)
return err;
// Get the full path to BOINC Manager executable inside this application's bundle
strlcat(fullpath, "/Contents/MacOS/", sizeof(fullpath));
// To allow for branding, assume name of executable inside bundle is same as name of bundle
p = strrchr(dir_path, '/'); // Assume name of executable inside bundle is same as name of bundle
if (p == NULL)
p = dir_path - 1;
strlcat(fullpath, p+1, sizeof(fullpath));
p = strrchr(fullpath, '.'); // Strip off bundle extension (".app")
if (p)
*p = '\0';
sprintf(buf1, "%s:%s", boinc_master_user_name, boinc_master_group_name);
// chown boinc_master:boinc_master path/BOINCManager.app/Contents/MacOS/BOINCManager
err = DoPrivilegedExec(chownPath, buf1, fullpath, NULL, NULL, NULL, NULL);
if (err)
return err;
#ifdef _DEBUG
// chmod u=rwx,g=rwx,o=rx path/BOINCManager.app/Contents/MacOS/BOINCManager
// 0775 = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IXOTH
// Set read, write and execute permission for user & group, read & execute for others
err = DoPrivilegedExec(chmodPath, "u=rwx,g=rwx,o=rx", fullpath, NULL, NULL, NULL, NULL);
#else
// chmod u=rx,g=rx,o=rx path/BOINCManager.app/Contents/MacOS/BOINCManager
// 0555 = S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH
// Set read and execute permission for user, group & others
err = DoPrivilegedExec(chmodPath, "u=rx,g=rx,o=rx", fullpath, NULL, NULL, NULL, NULL);
#endif
if (err)
return err;
// Get the full path to BOINC Clients inside this application's bundle
strlcpy(fullpath, dir_path, sizeof(fullpath));
strlcat(fullpath, "/Contents/Resources/boinc", sizeof(fullpath));
if (strlen(fullpath) >= (MAXPATHLEN-1)) {
ShowSecurityError("SetBOINCAppOwnersGroupsAndPermissions: path to client is too long");
return -1;
}
sprintf(buf1, "%s:%s", boinc_master_user_name, boinc_master_group_name);
// chown boinc_master:boinc_master path/BOINCManager.app/Contents/Resources/boinc
err = DoPrivilegedExec(chownPath, buf1, fullpath, NULL, NULL, NULL, NULL);
if (err)
return err;
#ifdef _DEBUG
// chmod u=rwsx,g=rwsx,o=rx path/BOINCManager.app/Contents/Resources/boinc
// 06775 = S_ISUID | S_ISGID | S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IXOTH
// Set setuid-on-execution, setgid-on-execution plus read, write and execute permission for user & group, read & execute for others
err = DoPrivilegedExec(chmodPath, "u=rwsx,g=rwsx,o=rx", fullpath, NULL, NULL, NULL, NULL);
#else
// chmod u=rsx,g=rsx,o=rx path/BOINCManager.app/Contents/Resources/boinc
// 06555 = S_ISUID | S_ISGID | S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH
// Set setuid-on-execution, setgid-on-execution plus read and execute permission for user, group & others
err = DoPrivilegedExec(chmodPath, "u=rsx,g=rsx,o=rx", fullpath, NULL, NULL, NULL, NULL);
#endif
if (err)
return err;
for (int i=0; id_name[0] == '.')
continue; // Ignore names beginning with '.'
len = strlen(dp->d_name);
if (len < 5)
continue;
if (strcmp(dp->d_name+len-4, ".xml"))
continue;
strlcpy(fullpath, basepath, sizeof(fullpath));
strlcat(fullpath, "/", sizeof(fullpath));
strlcat(fullpath, dp->d_name, sizeof(fullpath));
// chmod u+rw,g+rw,o= "/Library/Application Support/BOINC Data/????.xml"
// 0660 = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP0
// Set read and write permission for user and group, no access for others
retval = DoPrivilegedExec(chmodPath, "u+rw,g+rw,o=", fullpath, NULL, NULL, NULL, NULL);
if (retval)
break;
} // End while (true)
closedir(dirp);
return retval;
}
static OSStatus UpdateNestedDirectories(char * basepath) {
Boolean isDirectory;
char fullpath[MAXPATHLEN];
struct stat sbuf;
OSStatus retval = 0;
DIR *dirp;
dirent *dp;
dirp = opendir(basepath);
if (dirp == NULL) // Should never happen
return -1;
while (true) {
dp = readdir(dirp);
if (dp == NULL)
break; // End of list
if (dp->d_name[0] == '.')
continue; // Ignore names beginning with '.'
strlcpy(fullpath, basepath, sizeof(fullpath));
strlcat(fullpath, "/", sizeof(fullpath));
strlcat(fullpath, dp->d_name, sizeof(fullpath));
retval = stat(fullpath, &sbuf);
if (retval) {
if (lstat(fullpath, &sbuf) == 0) {
// A broken symlink in a slot directory may be OK if slot is no longer in use
if (S_ISLNK(sbuf.st_mode)) {
retval = 0;
continue;
}
}
break; // Should never happen
}
isDirectory = S_ISDIR(sbuf.st_mode);
if (isDirectory) {
// chmod u=rwx,g=rwx,o=rx fullpath
// 0775 = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IXOTH
// Set read, write and execute permission for user & group; read and execute permission for others
retval = DoPrivilegedExec(chmodPath, "u=rwx,g=rwx,o=rx", fullpath, NULL, NULL, NULL, NULL);
if (retval)
break;
retval = UpdateNestedDirectories(fullpath);
if (retval)
break;
} else {
// Since we are changing ownership from boinc_project to boinc_master,
// make sure executable-by-group bit is set if executable-by-owner is set
if ((sbuf.st_mode & 0110) == 0100) { // If executable by owner but not by group
retval = DoPrivilegedExec(chmodPath, "g+x", fullpath, NULL, NULL, NULL, NULL);
}
}
} // End while (true)
closedir(dirp);
return retval;
}
static OSStatus CreateUserAndGroup(char * user_name, char * group_name) {
OSStatus err = noErr;
passwd *pw = NULL;
group *grp = NULL;
uid_t userid = 0;
gid_t groupid = 0;
gid_t usergid = 0;
Boolean userExists = false;
Boolean groupExists = false;
short i;
static short start_id = MIN_ID;
char buf1[80];
char buf2[80];
char buf3[80];
char buf4[80];
SInt32 response;
err = Gestalt(gestaltSystemVersion, &response);
if (err) return err;
// OS 10.4 has problems with Accounts pane if we create uid or gid > 501
if (response < 0x1050) {
start_id = 25;
}
pw = getpwnam(user_name);
if (pw) {
userid = pw->pw_uid;
userExists = true;
}
grp = getgrnam(group_name);
if (grp) {
groupid = grp->gr_gid;
groupExists = true;
}
sprintf(buf1, "/groups/%s", group_name);
sprintf(buf2, "/users/%s", user_name);
if ( userExists && groupExists )
goto setRealName; // User and group already exist
// If only user or only group exists, try to use the same ID for the one we create
if (userExists) { // User exists but group does not
usergid = pw->pw_gid;
if (usergid) {
grp = getgrgid(usergid);
if (grp == NULL) // Set the group ID = users existing group if this group ID is available
groupid = usergid;
}
if (groupid == 0) {
grp = getgrgid(userid);
if (grp == NULL) // Set the group ID = user ID if this group ID is available
groupid = userid;
}
} else {
if (groupExists) { // Group exists but user does not
pw = getpwuid(groupid);
if (pw == NULL) // Set the user ID = group ID if this user ID is available
userid = groupid;
}
}
// We need to find an available user ID, group ID, or both. Find a value that is currently
// neither a user ID or a group ID.
// If we need both a new user ID and a new group ID, finds a value that can be used for both.
if ( (userid == 0) || (groupid == 0) ) {
for(i=start_id; ; i++) {
if ((uid_t)i != userid) {
pw = getpwuid((uid_t)i);
if (pw)
continue; // Already exists as a user ID of a different user
}
if ((gid_t)i != groupid) {
grp = getgrgid((gid_t)i);
if (grp)
continue; // Already exists as a group ID of a different group
}
if (! userExists)
userid = (uid_t)i;
if (! groupExists)
groupid = (gid_t)i;
start_id = i + 1; // Start with next higher value next time
break; // Success!
}
}
sprintf(buf3, "%d", groupid);
sprintf(buf4, "%d", userid);
if (! groupExists) { // If we need to create group
// Something like "dscl . -create /groups/boinc_master"
err = DoPrivilegedExec(dsclPath, ".", "-create", buf1, NULL, NULL, NULL);
if (err)
return err;
// Something like "dscl . -create /groups/boinc_master gid 33"
err = DoPrivilegedExec(dsclPath, ".", "-create", buf1, "gid", buf3, NULL);
if (err)
return err;
} // if (! groupExists)
if (! userExists) { // If we need to create user
// Something like "dscl . -create /users/boinc_master"
err = DoPrivilegedExec(dsclPath, ".", "-create", buf2, NULL, NULL, NULL);
if (err)
return err;
// Something like "dscl . -create /users/boinc_master uid 33"
err = DoPrivilegedExec(dsclPath, ".", "-create", buf2, "uid", buf4, NULL);
if (err)
return err;
// Prevent a security hole by not allowing a login from this user
// Something like "dscl . -create /users/boinc_master shell /usr/bin/false"
err = DoPrivilegedExec(dsclPath, ".", "-create", buf2, "shell", "/usr/bin/false", NULL);
if (err)
return err;
// Something like "dscl . -create /users/boinc_master home /var/empty"
err = DoPrivilegedExec(dsclPath, ".", "-create", buf2, "home", "/var/empty", NULL);
if (err)
return err;
} // if (! userExists)
// Always set the user gid if we created either the user or the group or both
// Something like "dscl . -create /users/boinc_master gid 33"
err = DoPrivilegedExec(dsclPath, ".", "-create", buf2, "gid", buf3, NULL);
if (err)
return err;
setRealName:
// Always set the RealName field to an empty string
// Note: create RealName with empty string fails under OS 10.7, but
// creating it with non-empty string and changing to empty string does work.
//
// Something like "dscl . -create /users/boinc_master RealName tempName"
err = DoPrivilegedExec(dsclPath, ".", "-create", buf2, "RealName", user_name, NULL);
if (err)
return err;
// Something like 'dscl . -change /users/boinc_master RealName ""'
err = DoPrivilegedExec(dsclPath, ".", "-change", buf2, "RealName", user_name, "");
if (err)
return err;
err = ResynchSystem();
if (err != noErr)
return err;
SleepTicks(120);
return noErr;
}
int AddAdminUserToGroups(char *user_name, bool add_to_boinc_project) {
#ifndef _DEBUG
char buf1[80];
OSStatus err = noErr;
sprintf(buf1, "/groups/%s", boinc_master_group_name);
// "dscl . -merge /groups/boinc_master users user_name"
err = DoPrivilegedExec(dsclPath, ".", "-merge", buf1, "users", user_name, NULL);
if (err)
return err;
if (add_to_boinc_project) {
sprintf(buf1, "/groups/%s", boinc_project_group_name);
// "dscl . -merge /groups/boinc_project users user_name"
err = DoPrivilegedExec(dsclPath, ".", "-merge", buf1, "users", user_name, NULL);
if (err)
return err;
}
err = ResynchSystem();
if (err != noErr)
return err;
#endif // ! _DEBUG
return noErr;
}
static OSStatus ResynchSystem() {
SInt32 response;
OSStatus err = noErr;
err = Gestalt(gestaltSystemVersion, &response);
if (err) return err;
if (response >= 0x1050) {
// OS 10.5
err = system("dscacheutil -flushcache");
err = system("dsmemberutil flushcache");
return noErr;
}
err = system("lookupd -flushcache");
err = Gestalt(gestaltSystemVersion, &response);
if ((err == noErr) && (response >= 0x1040))
err = system("memberd -r"); // Available only in OS 10.4
return noErr;
}
#ifdef _DEBUG
// GDB can't attach to applications which are running as a diferent user or group so
// it ignores the S_ISUID and S_ISGID permisison bits when launching an application.
// To work around this, the _DEBUG version uses the current user and group.
static OSStatus SetFakeMasterNames() {
passwd *pw;
group *grp;
gid_t boinc_master_gid;
uid_t boinc_master_uid;
long response;
OSStatus err = noErr;
boinc_master_uid = geteuid();
pw = getpwuid(boinc_master_uid);
if (pw == NULL)
return -1; // Should never happen
strlcpy(boinc_master_user_name, pw->pw_name, sizeof(boinc_master_user_name));
boinc_master_gid = getegid();
grp = getgrgid(boinc_master_gid);
if (grp == NULL)
return -1;
strlcpy(boinc_master_group_name, grp->gr_name, sizeof(boinc_master_group_name));
err = Gestalt(gestaltSystemVersion, (SInt32*)&response);
#ifndef DEBUG_WITH_FAKE_PROJECT_USER_AND_GROUP
if ((err == noErr) && (response >= 0x1040)) {
// For better debugging of SANDBOX permissions logic
strlcpy(boinc_project_user_name, REAL_BOINC_PROJECT_NAME, sizeof(boinc_project_user_name));
strlcpy(boinc_project_group_name, REAL_BOINC_PROJECT_NAME, sizeof(boinc_project_group_name));
} else
#endif
{
// For easier debugging of project applications; required under OS 10.3.x
strlcpy(boinc_project_user_name, pw->pw_name, sizeof(boinc_project_user_name));
strlcpy(boinc_project_group_name, grp->gr_name, sizeof(boinc_project_group_name));
}
return noErr;
}
#endif
static OSStatus GetAuthorization (void) {
static Boolean sIsAuthorized = false;
AuthorizationRights ourAuthRights;
AuthorizationFlags ourAuthFlags;
AuthorizationItem ourAuthRightsItem[RIGHTS_COUNT];
AuthorizationEnvironment ourAuthEnvironment;
AuthorizationItem ourAuthEnvItem[1];
char prompt[] = "BOINC needs to have certain permissions set up.\n\n";
OSStatus err = noErr;
if (sIsAuthorized)
return noErr;
ourAuthRights.count = 0;
ourAuthRights.items = NULL;
err = AuthorizationCreate (&ourAuthRights, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &gOurAuthRef);
if (err != noErr) {
ShowSecurityError("AuthorizationCreate returned error %d", err);
return err;
}
ourAuthRightsItem[0].name = kAuthorizationRightExecute;
ourAuthRightsItem[0].value = dsclPath;
ourAuthRightsItem[0].valueLength = strlen (dsclPath);
ourAuthRightsItem[0].flags = 0;
ourAuthRightsItem[1].name = kAuthorizationRightExecute;
ourAuthRightsItem[1].value = chmodPath;
ourAuthRightsItem[1].valueLength = strlen (chmodPath);
ourAuthRightsItem[1].flags = 0;
ourAuthRightsItem[2].name = kAuthorizationRightExecute;
ourAuthRightsItem[2].value = chownPath;
ourAuthRightsItem[2].valueLength = strlen (chownPath);
ourAuthRightsItem[2].flags = 0;
#if AUTHORIZE_LOOKUPD_MEMBERD
ourAuthRightsItem[3].name = kAuthorizationRightExecute;
ourAuthRightsItem[3].value = lookupdPath;
ourAuthRightsItem[3].valueLength = strlen (lookupdPath);
ourAuthRightsItem[3].flags = 0;
ourAuthRightsItem[4].name = kAuthorizationRightExecute;
ourAuthRightsItem[4].value = memberdPath;
ourAuthRightsItem[4].valueLength = strlen (memberdPath);
ourAuthRightsItem[4].flags = 0;
#endif
ourAuthRights.count = RIGHTS_COUNT;
ourAuthRights.items = ourAuthRightsItem;
ourAuthEnvItem[0].name = kAuthorizationEnvironmentPrompt;
ourAuthEnvItem[0].value = prompt;
ourAuthEnvItem[0].valueLength = strlen (prompt);
ourAuthEnvItem[0].flags = 0;
ourAuthEnvironment.count = 1;
ourAuthEnvironment.items = ourAuthEnvItem;
ourAuthFlags = kAuthorizationFlagInteractionAllowed | kAuthorizationFlagExtendRights;
// When this is called from the installer, the installer has already authenticated.
// In that case we are already running with full root privileges so AuthorizationCopyRights()
// does not request a password from the user again.
err = AuthorizationCopyRights (gOurAuthRef, &ourAuthRights, &ourAuthEnvironment, ourAuthFlags, NULL);
if (err == noErr)
sIsAuthorized = true;
return err;
}
OSStatus DoPrivilegedExec(const char *pathToTool, char *arg1, char *arg2, char *arg3, char *arg4, char *arg5, char *arg6) {
short i;
char *args[8];
OSStatus err;
FILE *ioPipe = NULL;
char *p, junk[256];
err = GetAuthorization();
if (err != noErr) {
if (err == errAuthorizationCanceled)
return err;
ShowSecurityError("GetAuthorization returned error %d", err);
} else {
for (i=0; i<5; i++) { // Retry 5 times if error
args[0] = arg1;
args[1] = arg2;
args[2] = arg3;
args[3] = arg4;
args[4] = arg5;
args[5] = arg6;
args[6] = NULL;
err = AuthorizationExecuteWithPrivileges (gOurAuthRef, pathToTool, 0, args, &ioPipe);
if (ioPipe) {
// We use the pipe to signal us when the command has completed
do {
p = fgets(junk, sizeof(junk), ioPipe);
} while (p);
fclose (ioPipe);
}
// AuthorizationExecuteWithPrivileges() does a fork() and so
// leaves a zombie process. Clear these so we don't exceed
// the system-imposed limit of processes per user (MAXUPRC).
while (waitpid(-1, 0, WNOHANG) > 0);
#if 0
if (strcmp(arg2, "-R") == 0)
SleepTicks(DELAY_TICKS_R);
else
SleepTicks(DELAY_TICKS);
#endif
if (err == noErr)
break;
}
}
if (err != noErr)
ShowSecurityError("\"%s %s %s %s %s %s\" returned error %d", pathToTool,
arg1 ? arg1 : "", arg2 ? arg2 : "", arg3 ? arg3 : "",
arg4 ? arg4 : "", arg5 ? arg5 : "", err);
return err;
}
void ShowSecurityError(const char *format, ...) {
va_list args;
#ifdef __x86_64__
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
#else
char s[1024];
short itemHit;
AlertStdAlertParamRec alertParams;
ModalFilterUPP ErrorDlgFilterProcUPP;
ProcessSerialNumber ourProcess;
va_start(args, format);
s[0] = vsprintf(s+1, format, args);
va_end(args);
ErrorDlgFilterProcUPP = NewModalFilterUPP(ErrorDlgFilterProc);
alertParams.movable = true;
alertParams.helpButton = false;
alertParams.filterProc = ErrorDlgFilterProcUPP;
alertParams.defaultText = "\pOK";
alertParams.cancelText = NULL;
alertParams.otherText = NULL;
alertParams.defaultButton = kAlertStdAlertOKButton;
alertParams.cancelButton = 0;
alertParams.position = kWindowDefaultPosition;
::GetCurrentProcess (&ourProcess);
::SetFrontProcess(&ourProcess);
StandardAlert (kAlertStopAlert, (StringPtr)s, NULL, &alertParams, &itemHit);
DisposeModalFilterUPP(ErrorDlgFilterProcUPP);
#endif
}
#ifndef __x86_64__
static pascal Boolean ErrorDlgFilterProc(DialogPtr theDialog, EventRecord *theEvent, short *theItemHit) {
// We need this because this is a command-line application so it does not get normal events
if (Button()) {
*theItemHit = kStdOkItemIndex;
return true;
}
return StdFilterProc(theDialog, theEvent, theItemHit);
}
#endif
// Uses usleep to sleep for full duration even if a signal is received
static void SleepTicks(UInt32 ticksToSleep) {
UInt32 endSleep, timeNow, ticksRemaining;
timeNow = TickCount();
ticksRemaining = ticksToSleep;
endSleep = timeNow + ticksToSleep;
while ( (timeNow < endSleep) && (ticksRemaining <= ticksToSleep) ) {
usleep(16667 * ticksRemaining);
timeNow = TickCount();
ticksRemaining = endSleep - timeNow;
}
}