1 // Copyright (c) 2011 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/foundation_util.h" 6 7 #include "base/file_path.h" 8 #include "base/logging.h" 9 #include "base/mac/scoped_cftyperef.h" 10 #include "base/sys_string_conversions.h" 11 12 namespace base { 13 namespace mac { 14 15 static bool g_override_am_i_bundled = false; 16 static bool g_override_am_i_bundled_value = false; 17 18 // Adapted from http://developer.apple.com/carbon/tipsandtricks.html#AmIBundled 19 static bool UncachedAmIBundled() { 20 if (g_override_am_i_bundled) 21 return g_override_am_i_bundled_value; 22 23 ProcessSerialNumber psn = {0, kCurrentProcess}; 24 25 FSRef fsref; 26 OSStatus pbErr; 27 if ((pbErr = GetProcessBundleLocation(&psn, &fsref)) != noErr) { 28 LOG(ERROR) << "GetProcessBundleLocation failed: error " << pbErr; 29 return false; 30 } 31 32 FSCatalogInfo info; 33 OSErr fsErr; 34 if ((fsErr = FSGetCatalogInfo(&fsref, kFSCatInfoNodeFlags, &info, 35 NULL, NULL, NULL)) != noErr) { 36 LOG(ERROR) << "FSGetCatalogInfo failed: error " << fsErr; 37 return false; 38 } 39 40 return info.nodeFlags & kFSNodeIsDirectoryMask; 41 } 42 43 bool AmIBundled() { 44 // If the return value is not cached, this function will return different 45 // values depending on when it's called. This confuses some client code, see 46 // http://crbug.com/63183 . 47 static bool result = UncachedAmIBundled(); 48 DCHECK_EQ(result, UncachedAmIBundled()) 49 << "The return value of AmIBundled() changed. This will confuse tests. " 50 << "Call SetAmIBundled() override manually if your test binary " 51 << "delay-loads the framework."; 52 return result; 53 } 54 55 void SetOverrideAmIBundled(bool value) { 56 g_override_am_i_bundled = true; 57 g_override_am_i_bundled_value = value; 58 } 59 60 bool IsBackgroundOnlyProcess() { 61 // This function really does want to examine NSBundle's idea of the main 62 // bundle dictionary, and not the overriden MainAppBundle. It needs to look 63 // at the actual running .app's Info.plist to access its LSUIElement 64 // property. 65 NSDictionary* info_dictionary = [[NSBundle mainBundle] infoDictionary]; 66 return [[info_dictionary objectForKey:@"LSUIElement"] boolValue] != NO; 67 } 68 69 // No threading worries since NSBundle isn't thread safe. 70 static NSBundle* g_override_app_bundle = nil; 71 72 NSBundle* MainAppBundle() { 73 if (g_override_app_bundle) 74 return g_override_app_bundle; 75 return [NSBundle mainBundle]; 76 } 77 78 FilePath MainAppBundlePath() { 79 NSBundle* bundle = MainAppBundle(); 80 return FilePath([[bundle bundlePath] fileSystemRepresentation]); 81 } 82 83 FilePath PathForMainAppBundleResource(CFStringRef resourceName) { 84 NSBundle* bundle = MainAppBundle(); 85 NSString* resourcePath = [bundle pathForResource:(NSString*)resourceName 86 ofType:nil]; 87 if (!resourcePath) 88 return FilePath(); 89 return FilePath([resourcePath fileSystemRepresentation]); 90 } 91 92 void SetOverrideAppBundle(NSBundle* bundle) { 93 if (bundle != g_override_app_bundle) { 94 [g_override_app_bundle release]; 95 g_override_app_bundle = [bundle retain]; 96 } 97 } 98 99 void SetOverrideAppBundlePath(const FilePath& file_path) { 100 NSString* path = base::SysUTF8ToNSString(file_path.value()); 101 NSBundle* bundle = [NSBundle bundleWithPath:path]; 102 CHECK(bundle) << "Failed to load the bundle at " << file_path.value(); 103 104 SetOverrideAppBundle(bundle); 105 } 106 107 OSType CreatorCodeForCFBundleRef(CFBundleRef bundle) { 108 OSType creator = kUnknownType; 109 CFBundleGetPackageInfo(bundle, NULL, &creator); 110 return creator; 111 } 112 113 OSType CreatorCodeForApplication() { 114 CFBundleRef bundle = CFBundleGetMainBundle(); 115 if (!bundle) 116 return kUnknownType; 117 118 return CreatorCodeForCFBundleRef(bundle); 119 } 120 121 bool GetSearchPathDirectory(NSSearchPathDirectory directory, 122 NSSearchPathDomainMask domain_mask, 123 FilePath* result) { 124 DCHECK(result); 125 NSArray* dirs = 126 NSSearchPathForDirectoriesInDomains(directory, domain_mask, YES); 127 if ([dirs count] < 1) { 128 return false; 129 } 130 NSString* path = [dirs objectAtIndex:0]; 131 *result = FilePath([path fileSystemRepresentation]); 132 return true; 133 } 134 135 bool GetLocalDirectory(NSSearchPathDirectory directory, FilePath* result) { 136 return GetSearchPathDirectory(directory, NSLocalDomainMask, result); 137 } 138 139 bool GetUserDirectory(NSSearchPathDirectory directory, FilePath* result) { 140 return GetSearchPathDirectory(directory, NSUserDomainMask, result); 141 } 142 143 FilePath GetUserLibraryPath() { 144 FilePath user_library_path; 145 if (!GetUserDirectory(NSLibraryDirectory, &user_library_path)) { 146 LOG(WARNING) << "Could not get user library path"; 147 } 148 return user_library_path; 149 } 150 151 // Takes a path to an (executable) binary and tries to provide the path to an 152 // application bundle containing it. It takes the outermost bundle that it can 153 // find (so for "/Foo/Bar.app/.../Baz.app/..." it produces "/Foo/Bar.app"). 154 // |exec_name| - path to the binary 155 // returns - path to the application bundle, or empty on error 156 FilePath GetAppBundlePath(const FilePath& exec_name) { 157 const char kExt[] = ".app"; 158 const size_t kExtLength = arraysize(kExt) - 1; 159 160 // Split the path into components. 161 std::vector<std::string> components; 162 exec_name.GetComponents(&components); 163 164 // It's an error if we don't get any components. 165 if (!components.size()) 166 return FilePath(); 167 168 // Don't prepend '/' to the first component. 169 std::vector<std::string>::const_iterator it = components.begin(); 170 std::string bundle_name = *it; 171 DCHECK_GT(it->length(), 0U); 172 // If the first component ends in ".app", we're already done. 173 if (it->length() > kExtLength && 174 !it->compare(it->length() - kExtLength, kExtLength, kExt, kExtLength)) 175 return FilePath(bundle_name); 176 177 // The first component may be "/" or "//", etc. Only append '/' if it doesn't 178 // already end in '/'. 179 if (bundle_name[bundle_name.length() - 1] != '/') 180 bundle_name += '/'; 181 182 // Go through the remaining components. 183 for (++it; it != components.end(); ++it) { 184 DCHECK_GT(it->length(), 0U); 185 186 bundle_name += *it; 187 188 // If the current component ends in ".app", we're done. 189 if (it->length() > kExtLength && 190 !it->compare(it->length() - kExtLength, kExtLength, kExt, kExtLength)) 191 return FilePath(bundle_name); 192 193 // Separate this component from the next one. 194 bundle_name += '/'; 195 } 196 197 return FilePath(); 198 } 199 200 CFTypeRef GetValueFromDictionary(CFDictionaryRef dict, 201 CFStringRef key, 202 CFTypeID expected_type) { 203 CFTypeRef value = CFDictionaryGetValue(dict, key); 204 if (!value) 205 return value; 206 207 if (CFGetTypeID(value) != expected_type) { 208 ScopedCFTypeRef<CFStringRef> expected_type_ref( 209 CFCopyTypeIDDescription(expected_type)); 210 ScopedCFTypeRef<CFStringRef> actual_type_ref( 211 CFCopyTypeIDDescription(CFGetTypeID(value))); 212 LOG(WARNING) << "Expected value for key " 213 << base::SysCFStringRefToUTF8(key) 214 << " to be " 215 << base::SysCFStringRefToUTF8(expected_type_ref) 216 << " but it was " 217 << base::SysCFStringRefToUTF8(actual_type_ref) 218 << " instead"; 219 return NULL; 220 } 221 222 return value; 223 } 224 225 void NSObjectRetain(void* obj) { 226 id<NSObject> nsobj = static_cast<id<NSObject> >(obj); 227 [nsobj retain]; 228 } 229 230 void NSObjectRelease(void* obj) { 231 id<NSObject> nsobj = static_cast<id<NSObject> >(obj); 232 [nsobj release]; 233 } 234 235 } // namespace mac 236 } // namespace base 237