1/* 2 * Copyright (c) 2000-2010, 2013 Apple Inc. All rights reserved. 3 * 4 * @APPLE_LICENSE_HEADER_START@ 5 * 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 11 * file. 12 * 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. 20 * 21 * @APPLE_LICENSE_HEADER_END@ 22 */ 23 24/* 25 * Modification History 26 * 27 * June 1, 2001 Allan Nathanson <ajn@apple.com> 28 * - public API conversion 29 * 30 * November 9, 2000 Allan Nathanson <ajn@apple.com> 31 * - initial revision 32 */ 33 34#include <TargetConditionals.h> 35#include <SystemConfiguration/SystemConfiguration.h> 36#include <SystemConfiguration/SCPrivate.h> 37#include "SCPreferencesInternal.h" 38#include "SCHelper_client.h" 39 40#include <grp.h> 41#include <fcntl.h> 42#include <stdio.h> 43#include <unistd.h> 44#include <pthread.h> 45#include <sys/attr.h> 46#include <sys/errno.h> 47#include <sys/mount.h> 48#include <sys/param.h> 49 50 51 52static Boolean 53__SCPreferencesLock_helper(SCPreferencesRef prefs, Boolean wait) 54{ 55 Boolean ok; 56 SCPreferencesPrivateRef prefsPrivate = (SCPreferencesPrivateRef)prefs; 57 uint32_t status = kSCStatusOK; 58 CFDataRef reply = NULL; 59 60 if (prefsPrivate->helper_port == MACH_PORT_NULL) { 61 ok = __SCPreferencesCreate_helper(prefs); 62 if (!ok) { 63 return FALSE; 64 } 65 } 66 67 // have the helper "lock" the prefs 68 status = kSCStatusOK; 69 reply = NULL; 70 ok = _SCHelperExec(prefsPrivate->helper_port, 71 wait ? SCHELPER_MSG_PREFS_LOCKWAIT : SCHELPER_MSG_PREFS_LOCK, 72 prefsPrivate->signature, 73 &status, 74 NULL); 75 if (!ok) { 76 goto fail; 77 } 78 79 if (status != kSCStatusOK) { 80 goto error; 81 } 82 83 prefsPrivate->locked = TRUE; 84 return TRUE; 85 86 fail : 87 88 // close helper 89 if (prefsPrivate->helper_port != MACH_PORT_NULL) { 90 _SCHelperClose(&prefsPrivate->helper_port); 91 } 92 93 status = kSCStatusAccessError; 94 95 error : 96 97 // return error 98 _SCErrorSet(status); 99 return FALSE; 100} 101 102 103static int 104createParentDirectory(const char *path) 105{ 106 char dir[PATH_MAX]; 107 int ret; 108 char *scan; 109 char *slash; 110 111 // get parent directory path 112 if (strlcpy(dir, path, sizeof(dir)) >= sizeof(dir)) { 113 errno = ENOENT; 114 return -1; 115 } 116 117 slash = strrchr(dir, '/'); 118 if ((slash == NULL) || (slash == dir)) { 119 errno = ENOENT; 120 return -1; 121 } 122 *slash = '\0'; 123 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 127 char sep = '\0'; 128 129 if (slash != NULL) { 130 sep = *slash; 131 *slash = '\0'; 132 } 133 134 ret = mkdir(dir, mode); 135 if (ret == 0) { 136 static gid_t group = -1; 137 138 // set group 139 if (group == -1) { 140 char buf[256]; 141 struct group grp; 142 struct group *grpP = NULL; 143 144 if ((getgrnam_r("wheel", &grp, buf, sizeof(buf), &grpP) == 0) && 145 (grpP != NULL)) { 146 group = grpP->gr_gid; 147 } else { 148 SCLog(TRUE, LOG_ERR, 149 CFSTR("SCPreferencesLock getgrnam_r() failed: %s"), 150 strerror(errno)); 151 group = 0; // wheel 152 } 153 } 154 155 if (chown(dir, -1, group) == -1) { 156 SCLog(TRUE, LOG_ERR, 157 CFSTR("SCPreferencesLock chown() failed: %s"), 158 strerror(errno)); 159 } 160 161 // set [force] mode 162 if (chmod(dir, mode) == -1) { 163 SCLog(TRUE, LOG_ERR, 164 CFSTR("SCPreferencesLock chmod() failed: %s"), 165 strerror(errno)); 166 } 167 168 if ((slash == NULL) || (scan == dir)) { 169 return 0; 170 } 171 } else if ((errno == ENOENT) && (scan == dir)) { 172 // the initial mkdir (of the full dir path) can fail 173 ; 174 } else if (errno == EROFS) { 175 return -1; 176 } else if (errno != EEXIST) { 177 break; 178 } 179 180 if (slash != NULL) { 181 *slash = sep; 182 } else { 183 break; 184 } 185 slash = strchr(scan + 1, '/'); 186 } 187 188 SCLog(TRUE, LOG_ERR, 189 CFSTR("SCPreferencesLock mkdir() failed: %s"), 190 strerror(errno)); 191 return -1; 192} 193 194 195static void 196reportDelay(SCPreferencesRef prefs, struct timeval *delay, Boolean isStale) 197{ 198 asl_object_t m; 199 SCPreferencesPrivateRef prefsPrivate = (SCPreferencesPrivateRef)prefs; 200 char str[256]; 201 202 m = asl_new(ASL_TYPE_MSG); 203 asl_set(m, "com.apple.message.domain", "com.apple.SystemConfiguration.SCPreferencesLock"); 204 (void) _SC_cfstring_to_cstring(prefsPrivate->name, str, sizeof(str), kCFStringEncodingUTF8); 205 asl_set(m, "com.apple.message.signature", str); 206 (void) _SC_cfstring_to_cstring(prefsPrivate->prefsID, str, sizeof(str), kCFStringEncodingUTF8); 207 asl_set(m, "com.apple.message.signature2", str); 208 (void) snprintf(str, sizeof(str), 209 "%d.%3.3d", 210 (int)delay->tv_sec, 211 delay->tv_usec / 1000); 212 asl_set(m, "com.apple.message.value", str); 213 SCLOG(NULL, m, ASL_LEVEL_DEBUG, 214 CFSTR("SCPreferences(%@:%@) lock delayed for %d.%3.3d seconds%s"), 215 prefsPrivate->name, 216 prefsPrivate->prefsID, 217 (int)delay->tv_sec, 218 delay->tv_usec / 1000, 219 isStale ? " (stale)" : ""); 220 asl_release(m); 221 222 return; 223} 224 225 226static Boolean 227has_O_EXLOCK(SCPreferencesPrivateRef prefsPrivate) 228{ 229#pragma pack(push, 4) 230 struct { 231 u_int32_t size; 232 vol_capabilities_attr_t capabilities; 233 } attrbuf; 234#pragma pack(pop) 235 struct attrlist attrs; 236 int fd; 237 int ret; 238 struct statfs statbuf; 239 240 fd = open(prefsPrivate->lockPath, O_WRONLY|O_CREAT, 0644); 241 if (fd == -1) { 242 SCLog(TRUE, LOG_ERR, 243 CFSTR("SCPreferencesLock open() failed: %s"), 244 strerror(errno)); 245 return FALSE; 246 } 247 248 ret = fstatfs(fd, &statbuf); 249 unlink(prefsPrivate->lockPath); 250 close(fd); 251 if (ret == -1) { 252 SCLog(TRUE, LOG_ERR, 253 CFSTR("SCPreferencesLock fstatfs() failed: %s"), 254 strerror(errno)); 255 return FALSE; 256 } 257 258 bzero(&attrs, sizeof(attrs)); 259 attrs.bitmapcount = ATTR_BIT_MAP_COUNT; 260 attrs.volattr = ATTR_VOL_INFO | ATTR_VOL_CAPABILITIES; 261 bzero(&attrbuf, sizeof(attrbuf)); 262 ret = getattrlist(statbuf.f_mntonname, // path (of mount point) 263 &attrs, // attribute list 264 &attrbuf, // attribute buffer 265 sizeof(attrbuf), 266 0); // options 267 if (ret == -1) { 268 SCLog(TRUE, LOG_ERR, 269 CFSTR("SCPreferencesLock getattrlist() failed: %s"), 270 strerror(errno)); 271 return FALSE; 272 } 273 274 if ((attrbuf.capabilities.capabilities[VOL_CAPABILITIES_INTERFACES] & VOL_CAP_INT_FLOCK) && 275 (attrbuf.capabilities.valid [VOL_CAPABILITIES_INTERFACES] & VOL_CAP_INT_FLOCK)) { 276 return TRUE; 277 } 278 279 return FALSE; 280} 281 282 283static Boolean 284lockWithSCDynamicStore(SCPreferencesPrivateRef prefsPrivate, Boolean wait) 285{ 286 CFArrayRef changes; 287 Boolean locked = FALSE; 288 Boolean ok; 289 int sc_status = kSCStatusOK; 290 291 // add [lock] notification 292 ok = SCDynamicStoreAddWatchedKey(prefsPrivate->session, 293 prefsPrivate->sessionKeyLock, 294 FALSE); 295 if (!ok) { 296 sc_status = SCError(); 297 SCLog(_sc_verbose, LOG_ERR, CFSTR("SCPreferencesLock SCDynamicStoreAddWatchedKey() failed")); 298 } 299 300 while (ok) { 301 CFDateRef value; 302 303 // Attempt to acquire the lock 304 value = CFDateCreate(NULL, CFAbsoluteTimeGetCurrent()); 305 ok = SCDynamicStoreAddTemporaryValue(prefsPrivate->session, 306 prefsPrivate->sessionKeyLock, 307 value); 308 CFRelease(value); 309 if (ok) { 310 locked = TRUE; 311 break; 312 } 313 314 if (!wait) { 315 sc_status = kSCStatusPrefsBusy; 316 break; 317 } 318 319 // wait for the lock to be released 320 ok = SCDynamicStoreNotifyWait(prefsPrivate->session); 321 if (!ok) { 322 sc_status = SCError(); 323 SCLog(_sc_verbose, LOG_ERR, CFSTR("SCPreferencesLock SCDynamicStoreNotifyWait() failed")); 324 break; 325 } 326 327 // clear out any notifications 328 changes = SCDynamicStoreCopyNotifiedKeys(prefsPrivate->session); 329 if (changes != NULL) { 330 CFRelease(changes); 331 } else { 332 SCLog(_sc_verbose, LOG_ERR, CFSTR("SCPreferencesLock SCDynamicStoreCopyNotifiedKeys() failed")); 333 break; 334 } 335 } 336 337 // remove [lock] notification 338 (void) SCDynamicStoreRemoveWatchedKey(prefsPrivate->session, 339 prefsPrivate->sessionKeyLock, 340 0); 341 342 // clear out any notifications 343 changes = SCDynamicStoreCopyNotifiedKeys(prefsPrivate->session); 344 if (changes != NULL) { 345 CFRelease(changes); 346 } 347 348 if (sc_status != kSCStatusOK) { 349 _SCErrorSet(sc_status); 350 } 351 return locked; 352} 353 354 355Boolean 356SCPreferencesLock(SCPreferencesRef prefs, Boolean wait) 357{ 358 char buf[32]; 359 struct timeval lockStart; 360 struct timeval lockElapsed; 361 SCPreferencesPrivateRef prefsPrivate = (SCPreferencesPrivateRef)prefs; 362 int sc_status = kSCStatusFailed; 363 struct stat statBuf; 364 struct stat statBuf2; 365 366 if (prefs == NULL) { 367 /* sorry, you must provide a session */ 368 _SCErrorSet(kSCStatusNoPrefsSession); 369 return FALSE; 370 } 371 372 if (prefsPrivate->locked) { 373 /* sorry, you already have the lock */ 374 _SCErrorSet(kSCStatusLocked); 375 return FALSE; 376 } 377 378 if (prefsPrivate->authorizationData != NULL) { 379 return __SCPreferencesLock_helper(prefs, wait); 380 } 381 382 if (!prefsPrivate->isRoot) { 383 _SCErrorSet(kSCStatusAccessError); 384 return FALSE; 385 } 386 387 388 pthread_mutex_lock(&prefsPrivate->lock); 389 390 if (prefsPrivate->session == NULL) { 391 __SCPreferencesAddSession(prefs); 392 } 393 394 if (prefsPrivate->lockPath == NULL) { 395 char *path; 396 CFIndex pathLen; 397 398 path = prefsPrivate->newPath ? prefsPrivate->newPath : prefsPrivate->path; 399 pathLen = strlen(path) + sizeof("-lock"); 400 prefsPrivate->lockPath = CFAllocatorAllocate(NULL, pathLen, 0); 401 snprintf(prefsPrivate->lockPath, pathLen, "%s-lock", path); 402 } 403 404 (void)gettimeofday(&lockStart, NULL); 405 406 retry : 407 408 if (prefsPrivate->sessionKeyLock != NULL) { 409 if (lockWithSCDynamicStore(prefsPrivate, wait)) { 410 goto locked; 411 } 412 413 goto error; 414 } 415 416 prefsPrivate->lockFD = open(prefsPrivate->lockPath, 417 wait ? O_WRONLY|O_CREAT|O_EXLOCK 418 : O_WRONLY|O_CREAT|O_EXLOCK|O_NONBLOCK, 419 0644); 420 if (prefsPrivate->lockFD == -1) { 421 switch (errno) { 422 case ENOENT : 423 if ((prefsPrivate->prefsID == NULL) || 424 !CFStringHasPrefix(prefsPrivate->prefsID, CFSTR("/"))) { 425 int ret; 426 427 // create parent (/Library/Preferences/SystemConfiguration) 428 ret = createParentDirectory(prefsPrivate->lockPath); 429 if (ret == 0) { 430 SCLog(TRUE, LOG_NOTICE, 431 CFSTR("created directory for \"%s\""), 432 prefsPrivate->newPath ? prefsPrivate->newPath : prefsPrivate->path); 433 goto retry; 434 } else if (errno == EROFS) { 435 goto locked; 436 } 437 } 438 break; 439 case EROFS : 440 // if read-only filesystem 441 goto locked; 442 case EWOULDBLOCK : 443 // if already locked (and we are not blocking) 444 sc_status = kSCStatusPrefsBusy; 445 goto error; 446 case ENOTSUP : 447 if (!has_O_EXLOCK(prefsPrivate)) { 448 // O_EXLOCK *not* available, use SCDynamicStore 449 prefsPrivate->sessionKeyLock = _SCPNotificationKey(NULL, 450 prefsPrivate->prefsID, 451 kSCPreferencesKeyLock); 452 goto retry; 453 } 454 errno = ENOTSUP; 455 break; 456 default : 457 break; 458 } 459 460 sc_status = errno; 461 SCLog(TRUE, LOG_ERR, 462 CFSTR("SCPreferencesLock open() failed: %s"), 463 strerror(errno)); 464 goto error; 465 } 466 467 if ((stat(prefsPrivate->lockPath, &statBuf) == -1) || 468 (fstat(prefsPrivate->lockFD, &statBuf2) == -1) || 469 (statBuf.st_dev != statBuf2.st_dev) || 470 (statBuf.st_ino != statBuf2.st_ino)) { 471 // if the lock file was unlinked or re-created 472 close(prefsPrivate->lockFD); 473 prefsPrivate->lockFD = -1; 474 goto retry; 475 } 476 477 // we have the lock 478 479 snprintf(buf, sizeof(buf), "%d\n", getpid()); 480 write(prefsPrivate->lockFD, buf, strlen(buf)); 481 482 locked : 483 484 (void)gettimeofday(&prefsPrivate->lockTime, NULL); 485 timersub(&prefsPrivate->lockTime, &lockStart, &lockElapsed); 486 487 if (prefsPrivate->accessed) { 488 CFDataRef currentSignature; 489 Boolean match; 490 491 /* 492 * the preferences have been accessed since the 493 * session was created so we need to compare 494 * the signature of the stored preferences. 495 */ 496 if (stat(prefsPrivate->path, &statBuf) == -1) { 497 if (errno == ENOENT) { 498 bzero(&statBuf, sizeof(statBuf)); 499 } else { 500 SCLog(TRUE, LOG_DEBUG, 501 CFSTR("SCPreferencesLock stat() failed: %s"), 502 strerror(errno)); 503 goto stale; 504 } 505 } 506 507 currentSignature = __SCPSignatureFromStatbuf(&statBuf); 508 match = CFEqual(prefsPrivate->signature, currentSignature); 509 CFRelease(currentSignature); 510 if (!match) { 511 /* 512 * the preferences have been updated since the 513 * session was accessed so we've got no choice 514 * but to deny the lock request. 515 */ 516 goto stale; 517 } 518// } else { 519// /* 520// * the file contents have changed but since we 521// * haven't accessed any of the preference data we 522// * don't need to return an error. Simply proceed. 523// */ 524 } 525 526 if (lockElapsed.tv_sec > 0) { 527 // if we waited more than 1 second to acquire the lock 528 reportDelay(prefs, &lockElapsed, FALSE); 529 } 530 531 prefsPrivate->locked = TRUE; 532 pthread_mutex_unlock(&prefsPrivate->lock); 533 return TRUE; 534 535 stale : 536 537 sc_status = kSCStatusStale; 538 unlink(prefsPrivate->lockPath); 539 540 if (lockElapsed.tv_sec > 0) { 541 // if we waited more than 1 second to acquire the lock 542 reportDelay(prefs, &lockElapsed, TRUE); 543 } 544 545 error : 546 547 if (prefsPrivate->lockFD != -1) { 548 close(prefsPrivate->lockFD); 549 prefsPrivate->lockFD = -1; 550 } 551 552 pthread_mutex_unlock(&prefsPrivate->lock); 553 _SCErrorSet(sc_status); 554 return FALSE; 555} 556