#include "SCPreferencesInternal.h"
#include "SCHelper_client.h"
#include <grp.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/attr.h>
#include <sys/errno.h>
#include <sys/mount.h>
#include <sys/param.h>
static Boolean
__SCPreferencesLock_helper(SCPreferencesRef prefs, Boolean wait)
{
Boolean ok;
SCPreferencesPrivateRef prefsPrivate = (SCPreferencesPrivateRef)prefs;
uint32_t status = kSCStatusOK;
CFDataRef reply = NULL;
if (prefsPrivate->helper_port == MACH_PORT_NULL) {
ok = __SCPreferencesCreate_helper(prefs);
if (!ok) {
return FALSE;
}
}
status = kSCStatusOK;
reply = NULL;
ok = _SCHelperExec(prefsPrivate->helper_port,
wait ? SCHELPER_MSG_PREFS_LOCKWAIT : SCHELPER_MSG_PREFS_LOCK,
prefsPrivate->signature,
&status,
NULL);
if (!ok) {
goto fail;
}
if (status != kSCStatusOK) {
goto error;
}
__SCPreferencesUpdateLockedState(prefs, TRUE);
return TRUE;
fail :
if (prefsPrivate->helper_port != MACH_PORT_NULL) {
_SCHelperClose(&prefsPrivate->helper_port);
}
status = kSCStatusAccessError;
error :
_SCErrorSet(status);
return FALSE;
}
static int
createParentDirectory(const char *path)
{
char dir[PATH_MAX];
int ret;
char *scan;
char *slash;
if (strlcpy(dir, path, sizeof(dir)) >= sizeof(dir)) {
errno = ENOENT;
return -1;
}
slash = strrchr(dir, '/');
if ((slash == NULL) || (slash == dir)) {
errno = ENOENT;
return -1;
}
*slash = '\0';
for (scan = dir; TRUE; scan = slash) {
mode_t mode = S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH; char sep = '\0';
if (slash != NULL) {
sep = *slash;
*slash = '\0';
}
ret = mkdir(dir, mode);
if (ret == 0) {
static gid_t group = -1;
if (group == (gid_t)-1) {
char buf[256];
struct group grp;
struct group *grpP = NULL;
if ((getgrnam_r("wheel", &grp, buf, sizeof(buf), &grpP) == 0) &&
(grpP != NULL)) {
group = grpP->gr_gid;
} else {
SC_log(LOG_NOTICE, "getgrnam_r() failed: %s", strerror(errno));
group = 0; }
}
if (chown(dir, -1, group) == -1) {
SC_log(LOG_NOTICE, "chown() failed: %s", strerror(errno));
}
if (chmod(dir, mode) == -1) {
SC_log(LOG_NOTICE, "chmod() failed: %s", strerror(errno));
}
if ((slash == NULL) || (scan == dir)) {
return 0;
}
} else if ((errno == ENOENT) && (scan == dir)) {
;
} else if (errno == EROFS) {
return -1;
} else if (errno != EEXIST) {
break;
}
if (slash != NULL) {
*slash = sep;
} else {
break;
}
slash = strchr(scan + 1, '/');
}
SC_log(LOG_NOTICE, "mkdir() failed: %s", strerror(errno));
return -1;
}
static void
reportDelay(SCPreferencesRef prefs, struct timeval *delay, Boolean isStale)
{
SCPreferencesPrivateRef prefsPrivate = (SCPreferencesPrivateRef)prefs;
SC_log(LOG_ERR,
"SCPreferences(%@:%@) lock delayed for %d.%3.3d seconds%s",
prefsPrivate->name,
prefsPrivate->prefsID,
(int)delay->tv_sec,
delay->tv_usec / 1000,
isStale ? " (stale)" : "");
return;
}
static Boolean
has_O_EXLOCK(SCPreferencesPrivateRef prefsPrivate)
{
#pragma pack(push, 4)
struct {
u_int32_t size;
vol_capabilities_attr_t capabilities;
} attrbuf;
#pragma pack(pop)
struct attrlist attrs;
int fd;
int ret;
struct statfs statbuf;
fd = open(prefsPrivate->lockPath, O_WRONLY|O_CREAT, 0644);
if (fd == -1) {
SC_log(LOG_NOTICE, "open() failed: %s", strerror(errno));
return FALSE;
}
ret = fstatfs(fd, &statbuf);
unlink(prefsPrivate->lockPath);
close(fd);
if (ret == -1) {
SC_log(LOG_NOTICE, "fstatfs() failed: %s", strerror(errno));
return FALSE;
}
memset(&attrs, 0, sizeof(attrs));
attrs.bitmapcount = ATTR_BIT_MAP_COUNT;
attrs.volattr = ATTR_VOL_INFO | ATTR_VOL_CAPABILITIES;
memset(&attrbuf, 0, sizeof(attrbuf));
ret = getattrlist(statbuf.f_mntonname, &attrs, &attrbuf, sizeof(attrbuf),
0); if (ret == -1) {
SC_log(LOG_NOTICE, "getattrlist() failed: %s", strerror(errno));
return FALSE;
}
if ((attrbuf.capabilities.capabilities[VOL_CAPABILITIES_INTERFACES] & VOL_CAP_INT_FLOCK) &&
(attrbuf.capabilities.valid [VOL_CAPABILITIES_INTERFACES] & VOL_CAP_INT_FLOCK)) {
return TRUE;
}
return FALSE;
}
static Boolean
lockWithSCDynamicStore(SCPreferencesRef prefs, Boolean wait)
{
CFArrayRef changes;
Boolean locked = FALSE;
Boolean ok;
SCPreferencesPrivateRef prefsPrivate = (SCPreferencesPrivateRef)prefs;
int sc_status = kSCStatusOK;
ok = __SCPreferencesAddSession(prefs);
if (!ok) {
return FALSE;
}
ok = SCDynamicStoreAddWatchedKey(prefsPrivate->session,
prefsPrivate->sessionKeyLock,
FALSE);
if (!ok) {
sc_status = SCError();
SC_log(LOG_INFO, "SCDynamicStoreAddWatchedKey() failed");
}
if (ok) {
prefsPrivate->sessionNoO_EXLOCK = SCDynamicStoreCreate(NULL, prefsPrivate->name, NULL, NULL);
if (prefsPrivate->sessionNoO_EXLOCK == NULL) {
sc_status = SCError();
SC_log(LOG_INFO, "SCDynamicStoreCreate() failed");
ok = FALSE;
}
}
while (ok) {
CFDateRef value;
value = CFDateCreate(NULL, CFAbsoluteTimeGetCurrent());
ok = SCDynamicStoreAddTemporaryValue(prefsPrivate->sessionNoO_EXLOCK,
prefsPrivate->sessionKeyLock,
value);
CFRelease(value);
if (ok) {
locked = TRUE;
break;
}
if (!wait) {
sc_status = kSCStatusPrefsBusy;
break;
}
ok = SCDynamicStoreNotifyWait(prefsPrivate->session);
if (!ok) {
sc_status = SCError();
SC_log(LOG_INFO, "SCDynamicStoreNotifyWait() failed");
break;
}
changes = SCDynamicStoreCopyNotifiedKeys(prefsPrivate->session);
if (changes != NULL) {
CFRelease(changes);
} else {
SC_log(LOG_INFO, "SCDynamicStoreCopyNotifiedKeys() failed");
break;
}
}
(void) SCDynamicStoreRemoveWatchedKey(prefsPrivate->session,
prefsPrivate->sessionKeyLock,
0);
changes = SCDynamicStoreCopyNotifiedKeys(prefsPrivate->session);
if (changes != NULL) {
CFRelease(changes);
}
__SCPreferencesRemoveSession(prefs);
if (!locked && (prefsPrivate->sessionNoO_EXLOCK != NULL)) {
CFRelease(prefsPrivate->sessionNoO_EXLOCK);
prefsPrivate->sessionNoO_EXLOCK = NULL;
}
if (sc_status != kSCStatusOK) {
_SCErrorSet(sc_status);
}
return locked;
}
Boolean
SCPreferencesLock(SCPreferencesRef prefs, Boolean wait)
{
char buf[32];
struct timeval lockStart;
struct timeval lockElapsed;
SCPreferencesPrivateRef prefsPrivate = (SCPreferencesPrivateRef)prefs;
int sc_status = kSCStatusFailed;
struct stat statBuf;
struct stat statBuf2;
if (prefs == NULL) {
_SCErrorSet(kSCStatusNoPrefsSession);
return FALSE;
}
if (prefsPrivate->locked) {
_SCErrorSet(kSCStatusLocked);
return FALSE;
}
if (prefsPrivate->authorizationData != NULL) {
return __SCPreferencesLock_helper(prefs, wait);
}
if (!prefsPrivate->isRoot) {
_SCErrorSet(kSCStatusAccessError);
return FALSE;
}
pthread_mutex_lock(&prefsPrivate->lock);
__SCPreferencesAddSessionKeys(prefs);
if (prefsPrivate->lockPath == NULL) {
char *path;
CFIndex pathLen;
path = prefsPrivate->newPath ? prefsPrivate->newPath : prefsPrivate->path;
pathLen = strlen(path) + sizeof("-lock");
prefsPrivate->lockPath = CFAllocatorAllocate(NULL, pathLen, 0);
snprintf(prefsPrivate->lockPath, pathLen, "%s-lock", path);
}
(void)gettimeofday(&lockStart, NULL);
retry :
if (prefsPrivate->sessionKeyLock != NULL) {
if (lockWithSCDynamicStore(prefs, wait)) {
goto locked;
}
goto error;
}
prefsPrivate->lockFD = open(prefsPrivate->lockPath,
wait ? O_WRONLY|O_CREAT|O_EXLOCK
: O_WRONLY|O_CREAT|O_EXLOCK|O_NONBLOCK,
0644);
if (prefsPrivate->lockFD == -1) {
switch (errno) {
case ENOENT :
if ((prefsPrivate->prefsID == NULL) ||
!CFStringHasPrefix(prefsPrivate->prefsID, CFSTR("/"))) {
int ret;
ret = createParentDirectory(prefsPrivate->lockPath);
if (ret == 0) {
SC_log(LOG_INFO, "created directory for \"%s\"",
prefsPrivate->newPath ? prefsPrivate->newPath : prefsPrivate->path);
goto retry;
} else if (errno == EROFS) {
goto locked;
}
}
break;
case EROFS :
goto locked;
case EWOULDBLOCK :
sc_status = kSCStatusPrefsBusy;
goto error;
case ENOTSUP :
if (!has_O_EXLOCK(prefsPrivate)) {
prefsPrivate->sessionKeyLock = _SCPNotificationKey(NULL,
prefsPrivate->prefsID,
kSCPreferencesKeyLock);
goto retry;
}
errno = ENOTSUP;
break;
default :
break;
}
sc_status = errno;
SC_log(LOG_NOTICE, "open() failed: %s", strerror(errno));
goto error;
}
if ((stat(prefsPrivate->lockPath, &statBuf) == -1) ||
(fstat(prefsPrivate->lockFD, &statBuf2) == -1) ||
(statBuf.st_dev != statBuf2.st_dev) ||
(statBuf.st_ino != statBuf2.st_ino)) {
close(prefsPrivate->lockFD);
prefsPrivate->lockFD = -1;
goto retry;
}
snprintf(buf, sizeof(buf), "%d\n", getpid());
(void) write(prefsPrivate->lockFD, buf, strlen(buf));
locked :
(void)gettimeofday(&prefsPrivate->lockTime, NULL);
timersub(&prefsPrivate->lockTime, &lockStart, &lockElapsed);
if (prefsPrivate->accessed) {
CFDataRef currentSignature;
Boolean match;
if (stat(prefsPrivate->path, &statBuf) == -1) {
if (errno == ENOENT) {
memset(&statBuf, 0, sizeof(statBuf));
} else {
SC_log(LOG_INFO, "stat() failed: %s",
strerror(errno));
goto stale;
}
}
currentSignature = __SCPSignatureFromStatbuf(&statBuf);
match = CFEqual(prefsPrivate->signature, currentSignature);
CFRelease(currentSignature);
if (!match) {
goto stale;
}
}
if (lockElapsed.tv_sec > 0) {
reportDelay(prefs, &lockElapsed, FALSE);
}
SC_log(LOG_DEBUG, "SCPreferences() lock: %s",
prefsPrivate->newPath ? prefsPrivate->newPath : prefsPrivate->path);
__SCPreferencesUpdateLockedState(prefs, TRUE);
pthread_mutex_unlock(&prefsPrivate->lock);
return TRUE;
stale :
sc_status = kSCStatusStale;
unlink(prefsPrivate->lockPath);
if (lockElapsed.tv_sec > 0) {
reportDelay(prefs, &lockElapsed, TRUE);
}
error :
if (prefsPrivate->lockFD != -1) {
close(prefsPrivate->lockFD);
prefsPrivate->lockFD = -1;
}
pthread_mutex_unlock(&prefsPrivate->lock);
_SCErrorSet(sc_status);
return FALSE;
}