2 * Copyright (c) 2000-2010, 2013, 2015 Apple Inc. All rights reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
21 * @APPLE_LICENSE_HEADER_END@
25 * Modification History
27 * June 1, 2001 Allan Nathanson <ajn@apple.com>
28 * - public API conversion
30 * November 9, 2000 Allan Nathanson <ajn@apple.com>
34 #include <TargetConditionals.h>
35 #include <SystemConfiguration/SystemConfiguration.h>
36 #include <SystemConfiguration/SCPrivate.h>
37 #include "SCPreferencesInternal.h"
38 #include "SCHelper_client.h"
46 #include <sys/errno.h>
47 #include <sys/mount.h>
48 #include <sys/param.h>
53 __SCPreferencesLock_helper(SCPreferencesRef prefs
, Boolean wait
)
56 SCPreferencesPrivateRef prefsPrivate
= (SCPreferencesPrivateRef
)prefs
;
57 uint32_t status
= kSCStatusOK
;
58 CFDataRef reply
= NULL
;
60 if (prefsPrivate
->helper_port
== MACH_PORT_NULL
) {
61 ok
= __SCPreferencesCreate_helper(prefs
);
67 // have the helper "lock" the prefs
70 ok
= _SCHelperExec(prefsPrivate
->helper_port
,
71 wait
? SCHELPER_MSG_PREFS_LOCKWAIT
: SCHELPER_MSG_PREFS_LOCK
,
72 prefsPrivate
->signature
,
79 if (status
!= kSCStatusOK
) {
83 prefsPrivate
->locked
= TRUE
;
89 if (prefsPrivate
->helper_port
!= MACH_PORT_NULL
) {
90 _SCHelperClose(&prefsPrivate
->helper_port
);
93 status
= kSCStatusAccessError
;
104 createParentDirectory(const char *path
)
111 // get parent directory path
112 if (strlcpy(dir
, path
, sizeof(dir
)) >= sizeof(dir
)) {
117 slash
= strrchr(dir
, '/');
118 if ((slash
== NULL
) || (slash
== dir
)) {
124 // create parent directories
125 for (scan
= dir
; TRUE
; scan
= slash
) {
126 mode_t mode
= S_IRWXU
|S_IRGRP
|S_IXGRP
|S_IROTH
|S_IXOTH
; // 755
134 ret
= mkdir(dir
, mode
);
136 static gid_t group
= -1;
142 struct group
*grpP
= NULL
;
144 if ((getgrnam_r("wheel", &grp
, buf
, sizeof(buf
), &grpP
) == 0) &&
146 group
= grpP
->gr_gid
;
148 SC_log(LOG_NOTICE
, "getgrnam_r() failed: %s", strerror(errno
));
153 if (chown(dir
, -1, group
) == -1) {
154 SC_log(LOG_NOTICE
, "chown() failed: %s", strerror(errno
));
158 if (chmod(dir
, mode
) == -1) {
159 SC_log(LOG_NOTICE
, "chmod() failed: %s", strerror(errno
));
162 if ((slash
== NULL
) || (scan
== dir
)) {
165 } else if ((errno
== ENOENT
) && (scan
== dir
)) {
166 // the initial mkdir (of the full dir path) can fail
168 } else if (errno
== EROFS
) {
170 } else if (errno
!= EEXIST
) {
179 slash
= strchr(scan
+ 1, '/');
182 SC_log(LOG_NOTICE
, "mkdir() failed: %s", strerror(errno
));
188 reportDelay(SCPreferencesRef prefs
, struct timeval
*delay
, Boolean isStale
)
191 SCPreferencesPrivateRef prefsPrivate
= (SCPreferencesPrivateRef
)prefs
;
194 m
= asl_new(ASL_TYPE_MSG
);
195 asl_set(m
, "com.apple.message.domain", "com.apple.SystemConfiguration.SCPreferencesLock");
196 (void) _SC_cfstring_to_cstring(prefsPrivate
->name
, str
, sizeof(str
), kCFStringEncodingUTF8
);
197 asl_set(m
, "com.apple.message.signature", str
);
198 (void) _SC_cfstring_to_cstring(prefsPrivate
->prefsID
, str
, sizeof(str
), kCFStringEncodingUTF8
);
199 asl_set(m
, "com.apple.message.signature2", str
);
200 (void) snprintf(str
, sizeof(str
),
203 delay
->tv_usec
/ 1000);
204 asl_set(m
, "com.apple.message.value", str
);
205 SCLOG(NULL
, m
, ASL_LEVEL_DEBUG
,
206 CFSTR("SCPreferences(%@:%@) lock delayed for %d.%3.3d seconds%s"),
208 prefsPrivate
->prefsID
,
210 delay
->tv_usec
/ 1000,
211 isStale
? " (stale)" : "");
219 has_O_EXLOCK(SCPreferencesPrivateRef prefsPrivate
)
221 #pragma pack(push, 4)
224 vol_capabilities_attr_t capabilities
;
227 struct attrlist attrs
;
230 struct statfs statbuf
;
232 fd
= open(prefsPrivate
->lockPath
, O_WRONLY
|O_CREAT
, 0644);
234 SC_log(LOG_NOTICE
, "open() failed: %s", strerror(errno
));
238 ret
= fstatfs(fd
, &statbuf
);
239 unlink(prefsPrivate
->lockPath
);
242 SC_log(LOG_NOTICE
, "fstatfs() failed: %s", strerror(errno
));
246 bzero(&attrs
, sizeof(attrs
));
247 attrs
.bitmapcount
= ATTR_BIT_MAP_COUNT
;
248 attrs
.volattr
= ATTR_VOL_INFO
| ATTR_VOL_CAPABILITIES
;
249 bzero(&attrbuf
, sizeof(attrbuf
));
250 ret
= getattrlist(statbuf
.f_mntonname
, // path (of mount point)
251 &attrs
, // attribute list
252 &attrbuf
, // attribute buffer
256 SC_log(LOG_NOTICE
, "getattrlist() failed: %s", strerror(errno
));
260 if ((attrbuf
.capabilities
.capabilities
[VOL_CAPABILITIES_INTERFACES
] & VOL_CAP_INT_FLOCK
) &&
261 (attrbuf
.capabilities
.valid
[VOL_CAPABILITIES_INTERFACES
] & VOL_CAP_INT_FLOCK
)) {
270 lockWithSCDynamicStore(SCPreferencesRef prefs
, Boolean wait
)
273 Boolean locked
= FALSE
;
275 SCPreferencesPrivateRef prefsPrivate
= (SCPreferencesPrivateRef
)prefs
;
276 int sc_status
= kSCStatusOK
;
278 // add SCDynamicStore session (for lock monitoring)
279 ok
= __SCPreferencesAddSession(prefs
);
284 // add [lock] notification
285 ok
= SCDynamicStoreAddWatchedKey(prefsPrivate
->session
,
286 prefsPrivate
->sessionKeyLock
,
289 sc_status
= SCError();
290 SC_log(LOG_INFO
, "SCDynamicStoreAddWatchedKey() failed");
293 // add SCDynamicStore session (for the actual lock)
295 prefsPrivate
->sessionNoO_EXLOCK
= SCDynamicStoreCreate(NULL
, prefsPrivate
->name
, NULL
, NULL
);
296 if (prefsPrivate
->sessionNoO_EXLOCK
== NULL
) {
297 sc_status
= SCError();
298 SC_log(LOG_INFO
, "SCDynamicStoreCreate() failed");
306 // Attempt to acquire the lock
307 value
= CFDateCreate(NULL
, CFAbsoluteTimeGetCurrent());
308 ok
= SCDynamicStoreAddTemporaryValue(prefsPrivate
->sessionNoO_EXLOCK
,
309 prefsPrivate
->sessionKeyLock
,
318 sc_status
= kSCStatusPrefsBusy
;
322 // wait for the lock to be released
323 ok
= SCDynamicStoreNotifyWait(prefsPrivate
->session
);
325 sc_status
= SCError();
326 SC_log(LOG_INFO
, "SCDynamicStoreNotifyWait() failed");
330 // clear out any notifications
331 changes
= SCDynamicStoreCopyNotifiedKeys(prefsPrivate
->session
);
332 if (changes
!= NULL
) {
335 SC_log(LOG_INFO
, "SCDynamicStoreCopyNotifiedKeys() failed");
340 // remove [lock] notification
341 (void) SCDynamicStoreRemoveWatchedKey(prefsPrivate
->session
,
342 prefsPrivate
->sessionKeyLock
,
345 // clear out any notifications
346 changes
= SCDynamicStoreCopyNotifiedKeys(prefsPrivate
->session
);
347 if (changes
!= NULL
) {
351 __SCPreferencesRemoveSession(prefs
);
353 if (!locked
&& (prefsPrivate
->sessionNoO_EXLOCK
!= NULL
)) {
354 CFRelease(prefsPrivate
->sessionNoO_EXLOCK
);
355 prefsPrivate
->sessionNoO_EXLOCK
= NULL
;
358 if (sc_status
!= kSCStatusOK
) {
359 _SCErrorSet(sc_status
);
367 SCPreferencesLock(SCPreferencesRef prefs
, Boolean wait
)
370 struct timeval lockStart
;
371 struct timeval lockElapsed
;
372 SCPreferencesPrivateRef prefsPrivate
= (SCPreferencesPrivateRef
)prefs
;
373 int sc_status
= kSCStatusFailed
;
375 struct stat statBuf2
;
378 /* sorry, you must provide a session */
379 _SCErrorSet(kSCStatusNoPrefsSession
);
383 if (prefsPrivate
->locked
) {
384 /* sorry, you already have the lock */
385 _SCErrorSet(kSCStatusLocked
);
389 if (prefsPrivate
->authorizationData
!= NULL
) {
390 return __SCPreferencesLock_helper(prefs
, wait
);
393 if (!prefsPrivate
->isRoot
) {
394 _SCErrorSet(kSCStatusAccessError
);
399 pthread_mutex_lock(&prefsPrivate
->lock
);
401 __SCPreferencesAddSessionKeys(prefs
);
403 if (prefsPrivate
->lockPath
== NULL
) {
407 path
= prefsPrivate
->newPath
? prefsPrivate
->newPath
: prefsPrivate
->path
;
408 pathLen
= strlen(path
) + sizeof("-lock");
409 prefsPrivate
->lockPath
= CFAllocatorAllocate(NULL
, pathLen
, 0);
410 snprintf(prefsPrivate
->lockPath
, pathLen
, "%s-lock", path
);
413 (void)gettimeofday(&lockStart
, NULL
);
417 if (prefsPrivate
->sessionKeyLock
!= NULL
) {
418 if (lockWithSCDynamicStore(prefs
, wait
)) {
425 prefsPrivate
->lockFD
= open(prefsPrivate
->lockPath
,
426 wait
? O_WRONLY
|O_CREAT
|O_EXLOCK
427 : O_WRONLY
|O_CREAT
|O_EXLOCK
|O_NONBLOCK
,
429 if (prefsPrivate
->lockFD
== -1) {
432 if ((prefsPrivate
->prefsID
== NULL
) ||
433 !CFStringHasPrefix(prefsPrivate
->prefsID
, CFSTR("/"))) {
436 // create parent (/Library/Preferences/SystemConfiguration)
437 ret
= createParentDirectory(prefsPrivate
->lockPath
);
439 SC_log(LOG_INFO
, "created directory for \"%s\"",
440 prefsPrivate
->newPath
? prefsPrivate
->newPath
: prefsPrivate
->path
);
442 } else if (errno
== EROFS
) {
448 // if read-only filesystem
451 // if already locked (and we are not blocking)
452 sc_status
= kSCStatusPrefsBusy
;
455 if (!has_O_EXLOCK(prefsPrivate
)) {
456 // O_EXLOCK *not* available, use SCDynamicStore
457 prefsPrivate
->sessionKeyLock
= _SCPNotificationKey(NULL
,
458 prefsPrivate
->prefsID
,
459 kSCPreferencesKeyLock
);
469 SC_log(LOG_NOTICE
, "open() failed: %s", strerror(errno
));
473 if ((stat(prefsPrivate
->lockPath
, &statBuf
) == -1) ||
474 (fstat(prefsPrivate
->lockFD
, &statBuf2
) == -1) ||
475 (statBuf
.st_dev
!= statBuf2
.st_dev
) ||
476 (statBuf
.st_ino
!= statBuf2
.st_ino
)) {
477 // if the lock file was unlinked or re-created
478 close(prefsPrivate
->lockFD
);
479 prefsPrivate
->lockFD
= -1;
485 snprintf(buf
, sizeof(buf
), "%d\n", getpid());
486 write(prefsPrivate
->lockFD
, buf
, strlen(buf
));
490 (void)gettimeofday(&prefsPrivate
->lockTime
, NULL
);
491 timersub(&prefsPrivate
->lockTime
, &lockStart
, &lockElapsed
);
493 if (prefsPrivate
->accessed
) {
494 CFDataRef currentSignature
;
498 * the preferences have been accessed since the
499 * session was created so we need to compare
500 * the signature of the stored preferences.
502 if (stat(prefsPrivate
->path
, &statBuf
) == -1) {
503 if (errno
== ENOENT
) {
504 bzero(&statBuf
, sizeof(statBuf
));
506 SC_log(LOG_INFO
, "stat() failed: %s",
512 currentSignature
= __SCPSignatureFromStatbuf(&statBuf
);
513 match
= CFEqual(prefsPrivate
->signature
, currentSignature
);
514 CFRelease(currentSignature
);
517 * the preferences have been updated since the
518 * session was accessed so we've got no choice
519 * but to deny the lock request.
525 // * the file contents have changed but since we
526 // * haven't accessed any of the preference data we
527 // * don't need to return an error. Simply proceed.
531 if (lockElapsed
.tv_sec
> 0) {
532 // if we waited more than 1 second to acquire the lock
533 reportDelay(prefs
, &lockElapsed
, FALSE
);
536 prefsPrivate
->locked
= TRUE
;
537 pthread_mutex_unlock(&prefsPrivate
->lock
);
542 sc_status
= kSCStatusStale
;
543 unlink(prefsPrivate
->lockPath
);
545 if (lockElapsed
.tv_sec
> 0) {
546 // if we waited more than 1 second to acquire the lock
547 reportDelay(prefs
, &lockElapsed
, TRUE
);
552 if (prefsPrivate
->lockFD
!= -1) {
553 close(prefsPrivate
->lockFD
);
554 prefsPrivate
->lockFD
= -1;
557 pthread_mutex_unlock(&prefsPrivate
->lock
);
558 _SCErrorSet(sc_status
);