this repo has no description
at fixPythonPipStalling 741 lines 18 kB view raw
1/* 2 * Copyright (c) 2000-2008, 2010-2019 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 * March 24, 2000 Allan Nathanson <ajn@apple.com> 31 * - initial revision 32 */ 33 34#include <mach/mach.h> 35#include <mach/mach_error.h> 36#include <servers/bootstrap.h> 37#include <pthread.h> 38#include <sys/time.h> 39#include <os/log.h> 40#include <os/log_private.h> 41 42#include "SCDynamicStoreInternal.h" 43#include "SCD.h" 44#include "config.h" /* MiG generated file */ 45 46#define INSTALL_ENVIRONMENT "__OSINSTALL_ENVIRONMENT" 47 48/* framework variables */ 49int _sc_debug = FALSE; /* non-zero if debugging enabled */ 50int _sc_verbose = FALSE; /* non-zero if verbose logging enabled */ 51int _sc_log = TRUE; /* 0 if SC messages should be written to stdout/stderr, 52 1 if SC messages should be logged w/os_log(3), 53 2 if SC messages should be written to stdout/stderr AND logged */ 54 55 56#pragma mark - 57#pragma mark Thread specific data 58 59 60static pthread_once_t tsKeyInitialized = PTHREAD_ONCE_INIT; 61static pthread_key_t tsDataKey; 62 63 64static void 65__SCThreadSpecificDataFinalize(void *arg) 66{ 67 __SCThreadSpecificDataRef tsd = (__SCThreadSpecificDataRef)arg; 68 69 if (tsd != NULL) { 70 if (tsd->_sc_store != NULL) CFRelease(tsd->_sc_store); 71 CFAllocatorDeallocate(kCFAllocatorSystemDefault, tsd); 72 } 73 return; 74} 75 76 77static void 78__SCThreadSpecificKeyInitialize() 79{ 80 pthread_key_create(&tsDataKey, __SCThreadSpecificDataFinalize); 81 return; 82} 83 84 85__private_extern__ 86__SCThreadSpecificDataRef 87__SCGetThreadSpecificData() 88{ 89 __SCThreadSpecificDataRef tsd; 90 pthread_once(&tsKeyInitialized, __SCThreadSpecificKeyInitialize); 91 92 tsd = pthread_getspecific(tsDataKey); 93 if (tsd == NULL) { 94 tsd = CFAllocatorAllocate(kCFAllocatorSystemDefault, sizeof(__SCThreadSpecificData), 0); 95 tsd->_sc_error = kSCStatusOK; 96 tsd->_sc_store = NULL; 97 pthread_setspecific(tsDataKey, tsd); 98 } 99 100 return tsd; 101} 102 103 104#pragma mark - 105#pragma mark Logging 106 107 108#define ENABLE_SC_FORMATTING 109#ifdef ENABLE_SC_FORMATTING 110// from <CoreFoundation/ForFoundationOnly.h> 111extern CFStringRef _CFStringCreateWithFormatAndArgumentsAux(CFAllocatorRef alloc, CFStringRef (*copyDescFunc)(CFTypeRef, CFDictionaryRef), CFDictionaryRef formatOptions, CFStringRef format, va_list arguments); 112#endif /* ENABLE_SC_FORMATTING */ 113 114 115CFStringRef 116_SCCopyDescription(CFTypeRef cf, CFDictionaryRef formatOptions) 117{ 118#ifdef ENABLE_SC_FORMATTING 119 CFMutableDictionaryRef nFormatOptions; 120 CFStringRef prefix1; 121 CFStringRef prefix2; 122 CFTypeID type = CFGetTypeID(cf); 123 124 if (!formatOptions || 125 !CFDictionaryGetValueIfPresent(formatOptions, CFSTR("PREFIX1"), (const void **)&prefix1)) { 126 prefix1 = CFSTR(""); 127 } 128 129 if (type == CFStringGetTypeID()) { 130 return CFStringCreateWithFormat(NULL, 131 formatOptions, 132 CFSTR("%@%@"), 133 prefix1, 134 cf); 135 } 136 137 if (type == CFBooleanGetTypeID()) { 138 return CFStringCreateWithFormat(NULL, 139 formatOptions, 140 CFSTR("%@%s"), 141 prefix1, 142 CFBooleanGetValue(cf) ? "TRUE" : "FALSE"); 143 } 144 145 if (type == CFDataGetTypeID()) { 146 const uint8_t *data; 147 CFIndex dataLen; 148 CFIndex i; 149 CFMutableStringRef str; 150 151 str = CFStringCreateMutable(NULL, 0); 152 CFStringAppendFormat(str, formatOptions, CFSTR("%@<data> 0x"), prefix1); 153 154 data = CFDataGetBytePtr(cf); 155 dataLen = CFDataGetLength(cf); 156 for (i = 0; i < dataLen; i++) { 157 CFStringAppendFormat(str, NULL, CFSTR("%02x"), data[i]); 158 } 159 160 return str; 161 } 162 163 if (type == CFNumberGetTypeID()) { 164 return CFStringCreateWithFormat(NULL, 165 formatOptions, 166 CFSTR("%@%@"), 167 prefix1, 168 cf); 169 } 170 171 if (type == CFDateGetTypeID()) { 172 CFCalendarRef calendar; 173 CFStringRef str; 174 CFTimeZoneRef tz; 175 int MM, DD, YYYY, hh, mm, ss; 176 177 calendar = CFCalendarCreateWithIdentifier(NULL, kCFGregorianCalendar); 178 tz = CFTimeZoneCopySystem(); 179 CFCalendarSetTimeZone(calendar, tz); 180 CFRelease(tz); 181 CFCalendarDecomposeAbsoluteTime(calendar, 182 CFDateGetAbsoluteTime(cf), 183 "MdyHms", 184 &MM, &DD, &YYYY, &hh, &mm, &ss); 185 CFRelease(calendar); 186 187 str = CFStringCreateWithFormat(NULL, 188 formatOptions, 189 CFSTR("%@%02d/%02d/%04d %02d:%02d:%02d"), 190 prefix1, 191 MM, DD, YYYY, hh, mm, ss); 192 return str; 193 } 194 195 if ((formatOptions == NULL) || 196 !CFDictionaryGetValueIfPresent(formatOptions, CFSTR("PREFIX2"), (const void **)&prefix2)) { 197 prefix2 = prefix1; 198 } 199 200 if (formatOptions != NULL) { 201 nFormatOptions = CFDictionaryCreateMutableCopy(NULL, 0, formatOptions); 202 } else { 203 nFormatOptions = CFDictionaryCreateMutable(NULL, 204 0, 205 &kCFTypeDictionaryKeyCallBacks, 206 &kCFTypeDictionaryValueCallBacks); 207 } 208 assert(nFormatOptions != NULL); 209 210#define N_QUICK 32 211 212 if (type == CFArrayGetTypeID()) { 213 const void * elements_q[N_QUICK]; 214 const void ** elements = elements_q; 215 CFIndex i; 216 CFIndex nElements; 217 CFMutableStringRef str; 218 219 str = CFStringCreateMutable(NULL, 0); 220 CFStringAppendFormat(str, formatOptions, CFSTR("%@<array> {"), prefix1); 221 222 nElements = CFArrayGetCount(cf); 223 if (nElements > 0) { 224 if (nElements > (CFIndex)(sizeof(elements_q)/sizeof(CFTypeRef))) 225 elements = CFAllocatorAllocate(NULL, nElements * sizeof(CFTypeRef), 0); 226 CFArrayGetValues(cf, CFRangeMake(0, nElements), elements); 227 for (i = 0; i < nElements; i++) { 228 CFMutableStringRef nPrefix1; 229 CFMutableStringRef nPrefix2; 230 CFStringRef nStr; 231 CFStringRef vStr; 232 233 nStr = CFStringCreateWithFormat(NULL, NULL, CFSTR("%ld"), i); 234 235 nPrefix1 = CFStringCreateMutable(NULL, 0); 236 CFStringAppendFormat(nPrefix1, 237 formatOptions, 238 CFSTR("%@ %@ : "), 239 prefix2, 240 nStr); 241 nPrefix2 = CFStringCreateMutable(NULL, 0); 242 CFStringAppendFormat(nPrefix2, 243 formatOptions, 244 CFSTR("%@ "), 245 prefix2); 246 247 CFDictionarySetValue(nFormatOptions, CFSTR("PREFIX1"), nPrefix1); 248 CFDictionarySetValue(nFormatOptions, CFSTR("PREFIX2"), nPrefix2); 249 CFRelease(nPrefix1); 250 CFRelease(nPrefix2); 251 CFRelease(nStr); 252 253 vStr = _SCCopyDescription((CFTypeRef)elements[i], nFormatOptions); 254 CFStringAppendFormat(str, 255 formatOptions, 256 CFSTR("\n%@"), 257 vStr); 258 CFRelease(vStr); 259 } 260 if (elements != elements_q) CFAllocatorDeallocate(NULL, elements); 261 } 262 CFStringAppendFormat(str, formatOptions, CFSTR("\n%@}"), prefix2); 263 264 CFRelease(nFormatOptions); 265 return str; 266 } 267 268 if (type == CFDictionaryGetTypeID()) { 269 const void * keys_q[N_QUICK]; 270 const void ** keys = keys_q; 271 CFIndex i; 272 CFIndex nElements; 273 CFMutableStringRef nPrefix1; 274 CFMutableStringRef nPrefix2; 275 CFMutableStringRef str; 276 277 str = CFStringCreateMutable(NULL, 0); 278 CFStringAppendFormat(str, formatOptions, CFSTR("%@<dictionary> {"), prefix1); 279 280 nElements = CFDictionaryGetCount(cf); 281 if (nElements > 0) { 282 CFComparatorFunction compFunc = NULL; 283 CFMutableArrayRef sortedKeys; 284 285 if (nElements > (CFIndex)(sizeof(keys_q) / sizeof(CFTypeRef))) { 286 keys = CFAllocatorAllocate(NULL, nElements * sizeof(CFTypeRef), 0); 287 } 288 CFDictionaryGetKeysAndValues(cf, keys, NULL); 289 290 sortedKeys = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); 291 for (i = 0; i < nElements; i++) { 292 CFArrayAppendValue(sortedKeys, (CFStringRef)keys[i]); 293 } 294 295 if (isA_CFString(keys[0])) { 296 compFunc = (CFComparatorFunction)CFStringCompare; 297 } 298 else if (isA_CFNumber(keys[0])) { 299 compFunc = (CFComparatorFunction)CFNumberCompare; 300 } 301 else if (isA_CFDate(keys[0])) { 302 compFunc = (CFComparatorFunction)CFDateCompare; 303 } 304 305 if (compFunc != NULL) { 306 CFArraySortValues(sortedKeys, 307 CFRangeMake(0, nElements), 308 compFunc, 309 NULL); 310 } 311 312 for (i = 0; i < nElements; i++) { 313 CFStringRef key; 314 CFStringRef kStr; 315 CFTypeRef val; 316 CFStringRef vStr; 317 318 key = CFArrayGetValueAtIndex(sortedKeys, i); 319 kStr = _SCCopyDescription((CFTypeRef)key, NULL); 320 321 nPrefix1 = CFStringCreateMutable(NULL, 0); 322 CFStringAppendFormat(nPrefix1, 323 formatOptions, 324 CFSTR("%@ %@ : "), 325 prefix2, 326 kStr); 327 nPrefix2 = CFStringCreateMutable(NULL, 0); 328 CFStringAppendFormat(nPrefix2, 329 formatOptions, 330 CFSTR("%@ "), 331 prefix2); 332 333 CFDictionarySetValue(nFormatOptions, CFSTR("PREFIX1"), nPrefix1); 334 CFDictionarySetValue(nFormatOptions, CFSTR("PREFIX2"), nPrefix2); 335 CFRelease(nPrefix1); 336 CFRelease(nPrefix2); 337 CFRelease(kStr); 338 339 val = CFDictionaryGetValue(cf, key); 340 vStr = _SCCopyDescription((CFTypeRef)val, nFormatOptions); 341 CFStringAppendFormat(str, 342 formatOptions, 343 CFSTR("\n%@"), 344 vStr); 345 CFRelease(vStr); 346 } 347 348 CFRelease(sortedKeys); 349 350 if (keys != keys_q) { 351 CFAllocatorDeallocate(NULL, keys); 352 } 353 } 354 CFStringAppendFormat(str, formatOptions, CFSTR("\n%@}"), prefix2); 355 356 CFRelease(nFormatOptions); 357 return str; 358 } 359 360 CFRelease(nFormatOptions); 361#endif /* ENABLE_SC_FORMATTING */ 362 363 return CFStringCreateWithFormat(NULL, 364 formatOptions, 365 CFSTR("%@%@"), 366 prefix1, 367 cf); 368} 369 370 371static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; 372 373Boolean 374_SC_isInstallEnvironment() { 375 static dispatch_once_t once; 376 static Boolean is_install; 377 378 dispatch_once(&once, ^{ 379 is_install = (getenv(INSTALL_ENVIRONMENT) != NULL); 380 }); 381 382 return is_install; 383} 384 385 386os_log_t 387_SC_LOG_DEFAULT(void) 388{ 389 static os_log_t log = NULL; 390 391 if (log == NULL) { 392 log = os_log_create("com.apple.SystemConfiguration", ""); 393 } 394 395 return log; 396} 397 398 399os_log_type_t 400_SC_syslog_os_log_mapping(int level) 401{ 402 if (level < 0) { 403 level = ~level; 404 } 405 406 switch (level) { 407 case LOG_EMERG : 408 case LOG_ALERT : 409 case LOG_CRIT : 410 return OS_LOG_TYPE_ERROR; 411 412 case LOG_ERR : 413 case LOG_WARNING : 414 case LOG_NOTICE : 415 return OS_LOG_TYPE_DEFAULT; 416 417 case LOG_INFO : 418 return OS_LOG_TYPE_INFO; 419 420 case LOG_DEBUG : 421 return OS_LOG_TYPE_DEBUG; 422 } 423 424 return OS_LOG_TYPE_DEFAULT; 425}; 426 427static void 428__SCLog(void *ret_addr, os_log_type_t type, const char *formatString, va_list formatArguments) 429{ 430 os_log_with_args(_SC_LOG_DEFAULT(), 431 type, 432 formatString, 433 formatArguments, 434 ret_addr); 435 return; 436} 437 438 439static void 440__SCPrint(FILE *stream, CFStringRef formatString, va_list formatArguments, Boolean trace, Boolean addNL) 441{ 442 char *line; 443 CFStringRef str; 444 CFIndex usedBufLen; 445 446#ifdef ENABLE_SC_FORMATTING 447 str = _CFStringCreateWithFormatAndArgumentsAux(NULL, 448 _SCCopyDescription, 449 NULL, 450 formatString, 451 formatArguments); 452#else /* ENABLE_SC_FORMATTING */ 453 str = CFStringCreateWithFormatAndArguments (NULL, 454 NULL, 455 formatString, 456 formatArguments); 457#endif /* !ENABLE_SC_FORMATTING */ 458 459 line =_SC_cfstring_to_cstring_ext(str, 460 NULL, 461 0, 462 kCFStringEncodingUTF8, 463 (UInt8)'?', 464 &usedBufLen); 465 CFRelease(str); 466 if (!line) { 467 return; 468 } 469 470 pthread_mutex_lock(&lock); 471 if (trace) { 472 struct tm tm_now; 473 struct timeval tv_now; 474 475 (void)gettimeofday(&tv_now, NULL); 476 (void)localtime_r(&tv_now.tv_sec, &tm_now); 477 (void)fprintf(stream, "%2d:%02d:%02d.%03d ", 478 tm_now.tm_hour, tm_now.tm_min, tm_now.tm_sec, tv_now.tv_usec / 1000); 479 } 480 (void)fwrite((const void *)line, usedBufLen, 1, stream); 481 if (addNL) { 482 (void)fputc('\n', stream); 483 } 484 fflush (stream); 485 pthread_mutex_unlock(&lock); 486 CFAllocatorDeallocate(NULL, line); 487 488 return; 489} 490 491 492void 493__SC_Log(int level, CFStringRef format_CF, os_log_t log, os_log_type_t type, const char *format, ...) 494{ 495#pragma unused(level) 496 Boolean do_log = FALSE; 497 Boolean do_print = FALSE; 498 va_list args_log; 499 va_list args_print; 500 501 /* 502 * Note: The following are the expected values for _sc_log 503 * 504 * 0 if SC messages should be written to stdout/stderr 505 * 1 if SC messages should be logged w/os_log(3) 506 * 2 if SC messages should be written to stdout/stderr AND logged 507 */ 508 509 if (_sc_log > 0) { 510 do_log = TRUE; // log requested 511 va_start(args_log, format); 512 513 if (_sc_log > 1) { 514 do_print = TRUE; // log AND print requested 515 va_copy(args_print, args_log); 516 } 517 } else { 518 do_print = TRUE; // print requested 519 va_start(args_print, format); 520 } 521 522 if (do_log) { 523 os_log_with_args(log, 524 type, 525 format, 526 args_log, 527 __builtin_return_address(0)); 528 va_end(args_log); 529 } 530 531 if (do_print) { 532 __SCPrint(stdout, 533 format_CF, 534 args_print, 535 (_sc_log > 0), // trace 536 TRUE); // add newline 537 va_end(args_print); 538 } 539 540 return; 541} 542 543 544void 545SCLog(Boolean condition, int level, CFStringRef formatString, ...) 546{ 547 va_list formatArguments; 548 va_list formatArguments_print; 549 Boolean log = FALSE; 550 Boolean print = FALSE; 551 552 if (!condition) { 553 return; 554 } 555 556 /* 557 * Note: The following are the expected values for _sc_log 558 * 559 * 0 if SC messages should be written to stdout/stderr 560 * 1 if SC messages should be logged w/os_log(3) 561 * 2 if SC messages should be written to stdout/stderr AND logged 562 */ 563 564 if (_sc_log > 0) { 565 log = TRUE; // log requested 566 va_start(formatArguments, formatString); 567 568 if (_sc_log > 1) { 569 print = TRUE; // log AND print requested 570 va_copy(formatArguments_print, formatArguments); 571 } 572 } else { 573 print = TRUE; // print requested 574 va_start(formatArguments_print, formatString); 575 } 576 577 if (log) { 578 const char *__format; 579 580 __format = CFStringGetCStringPtr(formatString, kCFStringEncodingUTF8); 581 if (__format != NULL) { 582 os_log_type_t __type; 583 584 __type = _SC_syslog_os_log_mapping(level); 585 __SCLog(__builtin_return_address(0), __type, __format, formatArguments); 586 } 587 va_end(formatArguments); 588 } 589 590 if (print) { 591 __SCPrint((LOG_PRI(level) > LOG_NOTICE) ? stderr : stdout, 592 formatString, 593 formatArguments_print, 594 (_sc_log > 0), // trace 595 TRUE); // add newline 596 va_end(formatArguments_print); 597 } 598 599 return; 600} 601 602 603void 604SCPrint(Boolean condition, FILE *stream, CFStringRef formatString, ...) 605{ 606 va_list formatArguments; 607 608 if (!condition) { 609 return; 610 } 611 612 va_start(formatArguments, formatString); 613 __SCPrint(stream, formatString, formatArguments, FALSE, FALSE); 614 va_end(formatArguments); 615 616 return; 617} 618 619 620#pragma mark - 621#pragma mark SC error handling / logging 622 623 624const CFStringRef kCFErrorDomainSystemConfiguration = CFSTR("com.apple.SystemConfiguration"); 625 626 627static const struct sc_errmsg { 628 int status; 629 char *message; 630} sc_errmsgs[] = { 631 { kSCStatusAccessError, "Permission denied" }, 632 { kSCStatusConnectionIgnore, "Network connection information not available at this time" }, 633 { kSCStatusConnectionNoService, "Network service for connection not available" }, 634 { kSCStatusFailed, "Failed!" }, 635 { kSCStatusInvalidArgument, "Invalid argument" }, 636 { kSCStatusKeyExists, "Key already defined" }, 637 { kSCStatusLocked, "Lock already held" }, 638 { kSCStatusMaxLink, "Maximum link count exceeded" }, 639 { kSCStatusNeedLock, "Lock required for this operation" }, 640 { kSCStatusNoStoreServer, "Configuration daemon not (no longer) available" }, 641 { kSCStatusNoStoreSession, "Configuration daemon session not active" }, 642 { kSCStatusNoConfigFile, "Configuration file not found" }, 643 { kSCStatusNoKey, "No such key" }, 644 { kSCStatusNoLink, "No such link" }, 645 { kSCStatusNoPrefsSession, "Preference session not active" }, 646 { kSCStatusNotifierActive, "Notifier is currently active" }, 647 { kSCStatusOK, "Success!" }, 648 { kSCStatusPrefsBusy, "Preferences update currently in progress" }, 649 { kSCStatusReachabilityUnknown, "Network reachability cannot be determined" }, 650 { kSCStatusStale, "Write attempted on stale version of object" }, 651}; 652#define nSC_ERRMSGS (sizeof(sc_errmsgs)/sizeof(struct sc_errmsg)) 653 654void 655_SCErrorSet(int error) 656{ 657 __SCThreadSpecificDataRef tsd; 658 659 tsd = __SCGetThreadSpecificData(); 660 tsd->_sc_error = error; 661 return; 662} 663 664 665CFErrorRef 666SCCopyLastError(void) 667{ 668 CFStringRef domain; 669 CFErrorRef error; 670 int i; 671 int code; 672 __SCThreadSpecificDataRef tsd; 673 CFMutableDictionaryRef userInfo = NULL; 674 675 tsd = __SCGetThreadSpecificData(); 676 code =tsd->_sc_error; 677 678 for (i = 0; i < (int)nSC_ERRMSGS; i++) { 679 if (sc_errmsgs[i].status == code) { 680 CFStringRef str; 681 682 domain = kCFErrorDomainSystemConfiguration; 683 userInfo = CFDictionaryCreateMutable(NULL, 684 0, 685 &kCFCopyStringDictionaryKeyCallBacks, 686 &kCFTypeDictionaryValueCallBacks); 687 str = CFStringCreateWithCString(NULL, 688 sc_errmsgs[i].message, 689 kCFStringEncodingASCII); 690 CFDictionarySetValue(userInfo, kCFErrorDescriptionKey, str); 691 CFRelease(str); 692 goto done; 693 } 694 } 695 696 if ((code > 0) && (code <= ELAST)) { 697 domain = kCFErrorDomainPOSIX; 698 goto done; 699 } 700 701 domain = kCFErrorDomainMach; 702 703 done : 704 705 error = CFErrorCreate(NULL, domain, code, userInfo); 706 if (userInfo != NULL) CFRelease(userInfo); 707 return error; 708} 709 710 711int 712SCError(void) 713{ 714 __SCThreadSpecificDataRef tsd; 715 716 tsd = __SCGetThreadSpecificData(); 717 return tsd->_sc_error; 718} 719 720 721const char * 722SCErrorString(int status) 723{ 724 int i; 725 726 for (i = 0; i < (int)nSC_ERRMSGS; i++) { 727 if (sc_errmsgs[i].status == status) { 728 return sc_errmsgs[i].message; 729 } 730 } 731 732 if ((status > 0) && (status <= ELAST)) { 733 return strerror(status); 734 } 735 736 if ((status >= BOOTSTRAP_SUCCESS) && (status <= BOOTSTRAP_NO_MEMORY)) { 737 return bootstrap_strerror(status); 738 } 739 740 return mach_error_string(status); 741}