1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "base/mac/mac_util.h" 6 7 #import <Cocoa/Cocoa.h> 8 #import <IOKit/IOKitLib.h> 9 10 #include <errno.h> 11 #include <string.h> 12 #include <sys/utsname.h> 13 #include <sys/xattr.h> 14 15 #include "base/files/file_path.h" 16 #include "base/logging.h" 17 #include "base/mac/bundle_locations.h" 18 #include "base/mac/foundation_util.h" 19 #include "base/mac/mac_logging.h" 20 #include "base/mac/scoped_cftyperef.h" 21 #include "base/mac/scoped_ioobject.h" 22 #include "base/mac/scoped_nsobject.h" 23 #include "base/mac/sdk_forward_declarations.h" 24 #include "base/strings/string_number_conversions.h" 25 #include "base/strings/string_piece.h" 26 #include "base/strings/sys_string_conversions.h" 27 28 namespace base { 29 namespace mac { 30 31 namespace { 32 33 // The current count of outstanding requests for full screen mode from browser 34 // windows, plugins, etc. 35 int g_full_screen_requests[kNumFullScreenModes] = { 0 }; 36 37 // Sets the appropriate application presentation option based on the current 38 // full screen requests. Since only one presentation option can be active at a 39 // given time, full screen requests are ordered by priority. If there are no 40 // outstanding full screen requests, reverts to normal mode. If the correct 41 // presentation option is already set, does nothing. 42 void SetUIMode() { 43 NSApplicationPresentationOptions current_options = 44 [NSApp presentationOptions]; 45 46 // Determine which mode should be active, based on which requests are 47 // currently outstanding. More permissive requests take precedence. For 48 // example, plugins request |kFullScreenModeAutoHideAll|, while browser 49 // windows request |kFullScreenModeHideDock| when the fullscreen overlay is 50 // down. Precedence goes to plugins in this case, so AutoHideAll wins over 51 // HideDock. 52 NSApplicationPresentationOptions desired_options = 53 NSApplicationPresentationDefault; 54 if (g_full_screen_requests[kFullScreenModeAutoHideAll] > 0) { 55 desired_options = NSApplicationPresentationHideDock | 56 NSApplicationPresentationAutoHideMenuBar; 57 } else if (g_full_screen_requests[kFullScreenModeHideDock] > 0) { 58 desired_options = NSApplicationPresentationHideDock; 59 } else if (g_full_screen_requests[kFullScreenModeHideAll] > 0) { 60 desired_options = NSApplicationPresentationHideDock | 61 NSApplicationPresentationHideMenuBar; 62 } 63 64 // Mac OS X bug: if the window is fullscreened (Lion-style) and 65 // NSApplicationPresentationDefault is requested, the result is that the menu 66 // bar doesn't auto-hide. rdar://13576498 http://www.openradar.me/13576498 67 // 68 // As a workaround, in that case, explicitly set the presentation options to 69 // the ones that are set by the system as it fullscreens a window. 70 if (desired_options == NSApplicationPresentationDefault && 71 current_options & NSApplicationPresentationFullScreen) { 72 desired_options |= NSApplicationPresentationFullScreen | 73 NSApplicationPresentationAutoHideMenuBar | 74 NSApplicationPresentationAutoHideDock; 75 } 76 77 if (current_options != desired_options) 78 [NSApp setPresentationOptions:desired_options]; 79 } 80 81 // Looks into Shared File Lists corresponding to Login Items for the item 82 // representing the current application. If such an item is found, returns a 83 // retained reference to it. Caller is responsible for releasing the reference. 84 LSSharedFileListItemRef GetLoginItemForApp() { 85 ScopedCFTypeRef<LSSharedFileListRef> login_items(LSSharedFileListCreate( 86 NULL, kLSSharedFileListSessionLoginItems, NULL)); 87 88 if (!login_items.get()) { 89 DLOG(ERROR) << "Couldn't get a Login Items list."; 90 return NULL; 91 } 92 93 base::scoped_nsobject<NSArray> login_items_array( 94 CFToNSCast(LSSharedFileListCopySnapshot(login_items, NULL))); 95 96 NSURL* url = [NSURL fileURLWithPath:[base::mac::MainBundle() bundlePath]]; 97 98 for(NSUInteger i = 0; i < [login_items_array count]; ++i) { 99 LSSharedFileListItemRef item = reinterpret_cast<LSSharedFileListItemRef>( 100 [login_items_array objectAtIndex:i]); 101 CFURLRef item_url_ref = NULL; 102 103 if (LSSharedFileListItemResolve(item, 0, &item_url_ref, NULL) == noErr) { 104 ScopedCFTypeRef<CFURLRef> item_url(item_url_ref); 105 if (CFEqual(item_url, url)) { 106 CFRetain(item); 107 return item; 108 } 109 } 110 } 111 112 return NULL; 113 } 114 115 bool IsHiddenLoginItem(LSSharedFileListItemRef item) { 116 ScopedCFTypeRef<CFBooleanRef> hidden(reinterpret_cast<CFBooleanRef>( 117 LSSharedFileListItemCopyProperty(item, 118 reinterpret_cast<CFStringRef>(kLSSharedFileListLoginItemHidden)))); 119 120 return hidden && hidden == kCFBooleanTrue; 121 } 122 123 } // namespace 124 125 std::string PathFromFSRef(const FSRef& ref) { 126 ScopedCFTypeRef<CFURLRef> url( 127 CFURLCreateFromFSRef(kCFAllocatorDefault, &ref)); 128 NSString *path_string = [(NSURL *)url.get() path]; 129 return [path_string fileSystemRepresentation]; 130 } 131 132 bool FSRefFromPath(const std::string& path, FSRef* ref) { 133 OSStatus status = FSPathMakeRef((const UInt8*)path.c_str(), 134 ref, nil); 135 return status == noErr; 136 } 137 138 CGColorSpaceRef GetGenericRGBColorSpace() { 139 // Leaked. That's OK, it's scoped to the lifetime of the application. 140 static CGColorSpaceRef g_color_space_generic_rgb( 141 CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB)); 142 DLOG_IF(ERROR, !g_color_space_generic_rgb) << 143 "Couldn't get the generic RGB color space"; 144 return g_color_space_generic_rgb; 145 } 146 147 CGColorSpaceRef GetSRGBColorSpace() { 148 // Leaked. That's OK, it's scoped to the lifetime of the application. 149 static CGColorSpaceRef g_color_space_sRGB = 150 CGColorSpaceCreateWithName(kCGColorSpaceSRGB); 151 DLOG_IF(ERROR, !g_color_space_sRGB) << "Couldn't get the sRGB color space"; 152 return g_color_space_sRGB; 153 } 154 155 CGColorSpaceRef GetSystemColorSpace() { 156 // Leaked. That's OK, it's scoped to the lifetime of the application. 157 // Try to get the main display's color space. 158 static CGColorSpaceRef g_system_color_space = 159 CGDisplayCopyColorSpace(CGMainDisplayID()); 160 161 if (!g_system_color_space) { 162 // Use a generic RGB color space. This is better than nothing. 163 g_system_color_space = CGColorSpaceCreateDeviceRGB(); 164 165 if (g_system_color_space) { 166 DLOG(WARNING) << 167 "Couldn't get the main display's color space, using generic"; 168 } else { 169 DLOG(ERROR) << "Couldn't get any color space"; 170 } 171 } 172 173 return g_system_color_space; 174 } 175 176 // Add a request for full screen mode. Must be called on the main thread. 177 void RequestFullScreen(FullScreenMode mode) { 178 DCHECK_LT(mode, kNumFullScreenModes); 179 if (mode >= kNumFullScreenModes) 180 return; 181 182 DCHECK_GE(g_full_screen_requests[mode], 0); 183 if (mode < 0) 184 return; 185 186 g_full_screen_requests[mode] = std::max(g_full_screen_requests[mode] + 1, 1); 187 SetUIMode(); 188 } 189 190 // Release a request for full screen mode. Must be called on the main thread. 191 void ReleaseFullScreen(FullScreenMode mode) { 192 DCHECK_LT(mode, kNumFullScreenModes); 193 if (mode >= kNumFullScreenModes) 194 return; 195 196 DCHECK_GE(g_full_screen_requests[mode], 0); 197 if (mode < 0) 198 return; 199 200 g_full_screen_requests[mode] = std::max(g_full_screen_requests[mode] - 1, 0); 201 SetUIMode(); 202 } 203 204 // Switches full screen modes. Releases a request for |from_mode| and adds a 205 // new request for |to_mode|. Must be called on the main thread. 206 void SwitchFullScreenModes(FullScreenMode from_mode, FullScreenMode to_mode) { 207 DCHECK_LT(from_mode, kNumFullScreenModes); 208 DCHECK_LT(to_mode, kNumFullScreenModes); 209 if (from_mode >= kNumFullScreenModes || to_mode >= kNumFullScreenModes) 210 return; 211 212 DCHECK_GT(g_full_screen_requests[from_mode], 0); 213 DCHECK_GE(g_full_screen_requests[to_mode], 0); 214 g_full_screen_requests[from_mode] = 215 std::max(g_full_screen_requests[from_mode] - 1, 0); 216 g_full_screen_requests[to_mode] = 217 std::max(g_full_screen_requests[to_mode] + 1, 1); 218 SetUIMode(); 219 } 220 221 void SetCursorVisibility(bool visible) { 222 if (visible) 223 [NSCursor unhide]; 224 else 225 [NSCursor hide]; 226 } 227 228 bool ShouldWindowsMiniaturizeOnDoubleClick() { 229 // We use an undocumented method in Cocoa; if it doesn't exist, default to 230 // |true|. If it ever goes away, we can do (using an undocumented pref key): 231 // NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; 232 // return ![defaults objectForKey:@"AppleMiniaturizeOnDoubleClick"] || 233 // [defaults boolForKey:@"AppleMiniaturizeOnDoubleClick"]; 234 BOOL methodImplemented = 235 [NSWindow respondsToSelector:@selector(_shouldMiniaturizeOnDoubleClick)]; 236 DCHECK(methodImplemented); 237 return !methodImplemented || 238 [NSWindow performSelector:@selector(_shouldMiniaturizeOnDoubleClick)]; 239 } 240 241 void ActivateProcess(pid_t pid) { 242 ProcessSerialNumber process; 243 OSStatus status = GetProcessForPID(pid, &process); 244 if (status == noErr) { 245 SetFrontProcess(&process); 246 } else { 247 OSSTATUS_DLOG(WARNING, status) << "Unable to get process for pid " << pid; 248 } 249 } 250 251 bool AmIForeground() { 252 ProcessSerialNumber foreground_psn = { 0 }; 253 OSErr err = GetFrontProcess(&foreground_psn); 254 if (err != noErr) { 255 OSSTATUS_DLOG(WARNING, err) << "GetFrontProcess"; 256 return false; 257 } 258 259 ProcessSerialNumber my_psn = { 0, kCurrentProcess }; 260 261 Boolean result = FALSE; 262 err = SameProcess(&foreground_psn, &my_psn, &result); 263 if (err != noErr) { 264 OSSTATUS_DLOG(WARNING, err) << "SameProcess"; 265 return false; 266 } 267 268 return result; 269 } 270 271 bool SetFileBackupExclusion(const FilePath& file_path) { 272 NSString* file_path_ns = 273 [NSString stringWithUTF8String:file_path.value().c_str()]; 274 NSURL* file_url = [NSURL fileURLWithPath:file_path_ns]; 275 276 // When excludeByPath is true the application must be running with root 277 // privileges (admin for 10.6 and earlier) but the URL does not have to 278 // already exist. When excludeByPath is false the URL must already exist but 279 // can be used in non-root (or admin as above) mode. We use false so that 280 // non-root (or admin) users don't get their TimeMachine drive filled up with 281 // unnecessary backups. 282 OSStatus os_err = 283 CSBackupSetItemExcluded(base::mac::NSToCFCast(file_url), TRUE, FALSE); 284 if (os_err != noErr) { 285 OSSTATUS_DLOG(WARNING, os_err) 286 << "Failed to set backup exclusion for file '" 287 << file_path.value().c_str() << "'"; 288 } 289 return os_err == noErr; 290 } 291 292 bool CheckLoginItemStatus(bool* is_hidden) { 293 ScopedCFTypeRef<LSSharedFileListItemRef> item(GetLoginItemForApp()); 294 if (!item.get()) 295 return false; 296 297 if (is_hidden) 298 *is_hidden = IsHiddenLoginItem(item); 299 300 return true; 301 } 302 303 void AddToLoginItems(bool hide_on_startup) { 304 ScopedCFTypeRef<LSSharedFileListItemRef> item(GetLoginItemForApp()); 305 if (item.get() && (IsHiddenLoginItem(item) == hide_on_startup)) { 306 return; // Already is a login item with required hide flag. 307 } 308 309 ScopedCFTypeRef<LSSharedFileListRef> login_items(LSSharedFileListCreate( 310 NULL, kLSSharedFileListSessionLoginItems, NULL)); 311 312 if (!login_items.get()) { 313 DLOG(ERROR) << "Couldn't get a Login Items list."; 314 return; 315 } 316 317 // Remove the old item, it has wrong hide flag, we'll create a new one. 318 if (item.get()) { 319 LSSharedFileListItemRemove(login_items, item); 320 } 321 322 NSURL* url = [NSURL fileURLWithPath:[base::mac::MainBundle() bundlePath]]; 323 324 BOOL hide = hide_on_startup ? YES : NO; 325 NSDictionary* properties = 326 [NSDictionary 327 dictionaryWithObject:[NSNumber numberWithBool:hide] 328 forKey:(NSString*)kLSSharedFileListLoginItemHidden]; 329 330 ScopedCFTypeRef<LSSharedFileListItemRef> new_item; 331 new_item.reset(LSSharedFileListInsertItemURL( 332 login_items, kLSSharedFileListItemLast, NULL, NULL, 333 reinterpret_cast<CFURLRef>(url), 334 reinterpret_cast<CFDictionaryRef>(properties), NULL)); 335 336 if (!new_item.get()) { 337 DLOG(ERROR) << "Couldn't insert current app into Login Items list."; 338 } 339 } 340 341 void RemoveFromLoginItems() { 342 ScopedCFTypeRef<LSSharedFileListItemRef> item(GetLoginItemForApp()); 343 if (!item.get()) 344 return; 345 346 ScopedCFTypeRef<LSSharedFileListRef> login_items(LSSharedFileListCreate( 347 NULL, kLSSharedFileListSessionLoginItems, NULL)); 348 349 if (!login_items.get()) { 350 DLOG(ERROR) << "Couldn't get a Login Items list."; 351 return; 352 } 353 354 LSSharedFileListItemRemove(login_items, item); 355 } 356 357 bool WasLaunchedAsLoginOrResumeItem() { 358 ProcessSerialNumber psn = { 0, kCurrentProcess }; 359 ProcessInfoRec info = {}; 360 info.processInfoLength = sizeof(info); 361 362 if (GetProcessInformation(&psn, &info) == noErr) { 363 ProcessInfoRec parent_info = {}; 364 parent_info.processInfoLength = sizeof(parent_info); 365 if (GetProcessInformation(&info.processLauncher, &parent_info) == noErr) 366 return parent_info.processSignature == 'lgnw'; 367 } 368 return false; 369 } 370 371 bool WasLaunchedAsLoginItemRestoreState() { 372 // "Reopen windows..." option was added for Lion. Prior OS versions should 373 // not have this behavior. 374 if (IsOSSnowLeopard() || !WasLaunchedAsLoginOrResumeItem()) 375 return false; 376 377 CFStringRef app = CFSTR("com.apple.loginwindow"); 378 CFStringRef save_state = CFSTR("TALLogoutSavesState"); 379 ScopedCFTypeRef<CFPropertyListRef> plist( 380 CFPreferencesCopyAppValue(save_state, app)); 381 // According to documentation, com.apple.loginwindow.plist does not exist on a 382 // fresh installation until the user changes a login window setting. The 383 // "reopen windows" option is checked by default, so the plist would exist had 384 // the user unchecked it. 385 // https://developer.apple.com/library/mac/documentation/macosx/conceptual/bpsystemstartup/chapters/CustomLogin.html 386 if (!plist) 387 return true; 388 389 if (CFBooleanRef restore_state = base::mac::CFCast<CFBooleanRef>(plist)) 390 return CFBooleanGetValue(restore_state); 391 392 return false; 393 } 394 395 bool WasLaunchedAsHiddenLoginItem() { 396 if (!WasLaunchedAsLoginOrResumeItem()) 397 return false; 398 399 ScopedCFTypeRef<LSSharedFileListItemRef> item(GetLoginItemForApp()); 400 if (!item.get()) { 401 // Lion can launch items for the resume feature. So log an error only for 402 // Snow Leopard or earlier. 403 if (IsOSSnowLeopard()) 404 DLOG(ERROR) << 405 "Process launched at Login but can't access Login Item List."; 406 407 return false; 408 } 409 return IsHiddenLoginItem(item); 410 } 411 412 bool RemoveQuarantineAttribute(const FilePath& file_path) { 413 const char kQuarantineAttrName[] = "com.apple.quarantine"; 414 int status = removexattr(file_path.value().c_str(), kQuarantineAttrName, 0); 415 return status == 0 || errno == ENOATTR; 416 } 417 418 namespace { 419 420 // Returns the running system's Darwin major version. Don't call this, it's 421 // an implementation detail and its result is meant to be cached by 422 // MacOSXMinorVersion. 423 int DarwinMajorVersionInternal() { 424 // base::OperatingSystemVersionNumbers calls Gestalt, which is a 425 // higher-level operation than is needed. It might perform unnecessary 426 // operations. On 10.6, it was observed to be able to spawn threads (see 427 // http://crbug.com/53200). It might also read files or perform other 428 // blocking operations. Actually, nobody really knows for sure just what 429 // Gestalt might do, or what it might be taught to do in the future. 430 // 431 // uname, on the other hand, is implemented as a simple series of sysctl 432 // system calls to obtain the relevant data from the kernel. The data is 433 // compiled right into the kernel, so no threads or blocking or other 434 // funny business is necessary. 435 436 struct utsname uname_info; 437 if (uname(&uname_info) != 0) { 438 DPLOG(ERROR) << "uname"; 439 return 0; 440 } 441 442 if (strcmp(uname_info.sysname, "Darwin") != 0) { 443 DLOG(ERROR) << "unexpected uname sysname " << uname_info.sysname; 444 return 0; 445 } 446 447 int darwin_major_version = 0; 448 char* dot = strchr(uname_info.release, '.'); 449 if (dot) { 450 if (!base::StringToInt(base::StringPiece(uname_info.release, 451 dot - uname_info.release), 452 &darwin_major_version)) { 453 dot = NULL; 454 } 455 } 456 457 if (!dot) { 458 DLOG(ERROR) << "could not parse uname release " << uname_info.release; 459 return 0; 460 } 461 462 return darwin_major_version; 463 } 464 465 // Returns the running system's Mac OS X minor version. This is the |y| value 466 // in 10.y or 10.y.z. Don't call this, it's an implementation detail and the 467 // result is meant to be cached by MacOSXMinorVersion. 468 int MacOSXMinorVersionInternal() { 469 int darwin_major_version = DarwinMajorVersionInternal(); 470 471 // The Darwin major version is always 4 greater than the Mac OS X minor 472 // version for Darwin versions beginning with 6, corresponding to Mac OS X 473 // 10.2. Since this correspondence may change in the future, warn when 474 // encountering a version higher than anything seen before. Older Darwin 475 // versions, or versions that can't be determined, result in 476 // immediate death. 477 CHECK(darwin_major_version >= 6); 478 int mac_os_x_minor_version = darwin_major_version - 4; 479 DLOG_IF(WARNING, darwin_major_version > 14) << "Assuming Darwin " 480 << base::IntToString(darwin_major_version) << " is Mac OS X 10." 481 << base::IntToString(mac_os_x_minor_version); 482 483 return mac_os_x_minor_version; 484 } 485 486 // Returns the running system's Mac OS X minor version. This is the |y| value 487 // in 10.y or 10.y.z. 488 int MacOSXMinorVersion() { 489 static int mac_os_x_minor_version = MacOSXMinorVersionInternal(); 490 return mac_os_x_minor_version; 491 } 492 493 enum { 494 SNOW_LEOPARD_MINOR_VERSION = 6, 495 LION_MINOR_VERSION = 7, 496 MOUNTAIN_LION_MINOR_VERSION = 8, 497 MAVERICKS_MINOR_VERSION = 9, 498 YOSEMITE_MINOR_VERSION = 10, 499 }; 500 501 } // namespace 502 503 #if !defined(BASE_MAC_MAC_UTIL_H_INLINED_GE_10_7) 504 bool IsOSSnowLeopard() { 505 return MacOSXMinorVersion() == SNOW_LEOPARD_MINOR_VERSION; 506 } 507 #endif 508 509 #if !defined(BASE_MAC_MAC_UTIL_H_INLINED_GT_10_7) 510 bool IsOSLion() { 511 return MacOSXMinorVersion() == LION_MINOR_VERSION; 512 } 513 #endif 514 515 #if !defined(BASE_MAC_MAC_UTIL_H_INLINED_GE_10_7) 516 bool IsOSLionOrLater() { 517 return MacOSXMinorVersion() >= LION_MINOR_VERSION; 518 } 519 #endif 520 521 #if !defined(BASE_MAC_MAC_UTIL_H_INLINED_GT_10_8) 522 bool IsOSMountainLion() { 523 return MacOSXMinorVersion() == MOUNTAIN_LION_MINOR_VERSION; 524 } 525 #endif 526 527 #if !defined(BASE_MAC_MAC_UTIL_H_INLINED_GE_10_8) 528 bool IsOSMountainLionOrLater() { 529 return MacOSXMinorVersion() >= MOUNTAIN_LION_MINOR_VERSION; 530 } 531 #endif 532 533 #if !defined(BASE_MAC_MAC_UTIL_H_INLINED_GT_10_9) 534 bool IsOSMavericks() { 535 return MacOSXMinorVersion() == MAVERICKS_MINOR_VERSION; 536 } 537 #endif 538 539 #if !defined(BASE_MAC_MAC_UTIL_H_INLINED_GE_10_9) 540 bool IsOSMavericksOrLater() { 541 return MacOSXMinorVersion() >= MAVERICKS_MINOR_VERSION; 542 } 543 #endif 544 545 #if !defined(BASE_MAC_MAC_UTIL_H_INLINED_GT_10_10) 546 bool IsOSYosemite() { 547 return MacOSXMinorVersion() == YOSEMITE_MINOR_VERSION; 548 } 549 #endif 550 551 #if !defined(BASE_MAC_MAC_UTIL_H_INLINED_GE_10_10) 552 bool IsOSYosemiteOrLater() { 553 return MacOSXMinorVersion() >= YOSEMITE_MINOR_VERSION; 554 } 555 #endif 556 557 #if !defined(BASE_MAC_MAC_UTIL_H_INLINED_GT_10_10) 558 bool IsOSLaterThanYosemite_DontCallThis() { 559 return MacOSXMinorVersion() > YOSEMITE_MINOR_VERSION; 560 } 561 #endif 562 563 std::string GetModelIdentifier() { 564 std::string return_string; 565 ScopedIOObject<io_service_t> platform_expert( 566 IOServiceGetMatchingService(kIOMasterPortDefault, 567 IOServiceMatching("IOPlatformExpertDevice"))); 568 if (platform_expert) { 569 ScopedCFTypeRef<CFDataRef> model_data( 570 static_cast<CFDataRef>(IORegistryEntryCreateCFProperty( 571 platform_expert, 572 CFSTR("model"), 573 kCFAllocatorDefault, 574 0))); 575 if (model_data) { 576 return_string = 577 reinterpret_cast<const char*>(CFDataGetBytePtr(model_data)); 578 } 579 } 580 return return_string; 581 } 582 583 bool ParseModelIdentifier(const std::string& ident, 584 std::string* type, 585 int32* major, 586 int32* minor) { 587 size_t number_loc = ident.find_first_of("0123456789"); 588 if (number_loc == std::string::npos) 589 return false; 590 size_t comma_loc = ident.find(',', number_loc); 591 if (comma_loc == std::string::npos) 592 return false; 593 int32 major_tmp, minor_tmp; 594 std::string::const_iterator begin = ident.begin(); 595 if (!StringToInt( 596 StringPiece(begin + number_loc, begin + comma_loc), &major_tmp) || 597 !StringToInt( 598 StringPiece(begin + comma_loc + 1, ident.end()), &minor_tmp)) 599 return false; 600 *type = ident.substr(0, number_loc); 601 *major = major_tmp; 602 *minor = minor_tmp; 603 return true; 604 } 605 606 } // namespace mac 607 } // namespace base 608