Vault
4.1
|
00001 /* 00002 Copyright c1997-2014 Trygve Isaacson. All rights reserved. 00003 This file is part of the Code Vault version 4.1 00004 http://www.bombaydigital.com/ 00005 License: MIT. See LICENSE.md in the Vault top level directory. 00006 */ 00007 00010 #include "vfsnode.h" 00011 #include "vtypes_internal.h" 00012 00013 #include "vexception.h" 00014 #include "vthread.h" 00015 #include <dirent.h> 00016 00017 #ifdef VPLATFORM_MAC 00018 #ifndef VPLATFORM_MAC_IOS 00019 // There is a conflict between standard and OpenTransport definitions such as TCP_NODELAY. We don't use OT nor want it. This 00020 // only cropped up when turning on precompiled headers. Specifying MAC_OS_X_VERSION_MIN_REQUIRED to MAC_OS_X_VERSION_10_4 00021 // should suppress the OT includes at the end of OSServices.h, but I can't make that work. These three 00022 // defines have the same effect. CoreServices.h is the thing that includes OSServices.h which conditionally 00023 // includes the three OpenTransport*.h files. We need CoreServices.h to use FSFindFolder et al in our directory operations here. 00024 // We also need Carbon.h for GetCurrentProcess and GetProcessBundleLocation in _platform_getExecutable. 00025 #define __OPENTRANSPORT__ 00026 #define __OPENTRANSPORTPROVIDERS__ 00027 #define __OPENTRANSPORTPROTOCOL__ 00028 00029 // Workaround problem on iOS SDK. Prevent inclusion of WebServicesCore, which presumes inclusion of CFXMLNode.h which does not exist. 00030 #define __WEBSERVICESCORE__ 00031 00032 #include <CoreServices/CoreServices.h> 00033 #endif /* not VPLATFORM_MAC_IOS */ 00034 #endif /* VPLATFORM_MAC */ 00035 00036 // static 00037 VString VFSNode::_platform_normalizePath(const VString& path) { 00038 // Paths are already in our "normalized" form on Unix. 00039 return path; 00040 } 00041 00042 // static 00043 VString VFSNode::_platform_denormalizePath(const VString& path) { 00044 // Paths are already in our "normalized" form on Unix. 00045 return path; 00046 } 00047 00048 #ifdef VPLATFORM_MAC 00049 00050 extern VString _V_NSHomeDirectory(); // Implemented in private vtypes_platform.mm Objective-C++ code. 00051 00052 // static 00053 VFSNode VFSNode::_platform_getKnownDirectoryNode(KnownDirectoryIdentifier id, const VString& companyName, const VString& appName) { 00054 if (id == CURRENT_WORKING_DIRECTORY) { 00055 return VFSNode(VPlatformAPI::getcwd()); 00056 } 00057 00058 if (id == EXECUTABLE_DIRECTORY) { 00059 /* 00060 This depends on the structure of the application or tool. 00061 If it's an iOS application, it's a bundle where we have: 00062 /...../wanted-dir/AppName.app/executable 00063 (2 levels up, wanted-dir is a randomized serial number at some path) 00064 If it's built as a Mac OS X application bundle we have: 00065 /...../wanted-dir/AppName.app/Contents/MacOS/executable 00066 (4 levels up, typically wanted-dir is /Applications if installed, but doesn't have to be) 00067 If it's built as a simple Unix-y tool we have: 00068 /...../wanted-dir/executable 00069 (1 level up, wanted-dir is wherever the tool has been placed) 00070 */ 00071 #ifdef VPLATFORM_MAC_IOS 00072 const int NUM_LEVELS_UP = 2; 00073 #else 00074 #ifdef VAULT_MACOSX_APP_IS_BUNDLE 00075 const int NUM_LEVELS_UP = 4; 00076 #else 00077 const int NUM_LEVELS_UP = 1; 00078 #endif 00079 #endif 00080 VFSNode node = VFSNode::getExecutable(); 00081 for (int i = 0; i < NUM_LEVELS_UP; ++i) { 00082 VFSNode parentNode; 00083 node.getParentNode(parentNode); 00084 node = parentNode; 00085 } 00086 00087 return node; 00088 } 00089 00090 VFSNode currentUserFolder(_V_NSHomeDirectory()); 00091 00092 if (id == USER_HOME_DIRECTORY) { 00093 return currentUserFolder; 00094 } 00095 00096 VFSNode libraryFolder; 00097 currentUserFolder.getChildNode("Library", libraryFolder); 00098 libraryFolder.mkdir(); 00099 00100 VFSNode subFolder; 00101 00102 switch (id) { 00103 case USER_HOME_DIRECTORY: 00104 // handled earlier; we returned above 00105 break; 00106 00107 case LOG_FILES_DIRECTORY: 00108 libraryFolder.getChildNode("Logs", subFolder); 00109 break; 00110 00111 case USER_PREFERENCES_DIRECTORY: 00112 libraryFolder.getChildNode("Preferences", subFolder); 00113 break; 00114 00115 case CACHED_DATA_DIRECTORY: 00116 libraryFolder.getChildNode("Caches", subFolder); 00117 break; 00118 00119 case APPLICATION_DATA_DIRECTORY: 00120 subFolder = libraryFolder; 00121 break; 00122 00123 case CURRENT_WORKING_DIRECTORY: 00124 // handled earlier; we returned above 00125 break; 00126 00127 case EXECUTABLE_DIRECTORY: 00128 // handled earlier; we returned above 00129 break; 00130 00131 default: 00132 throw VStackTraceException(VSTRING_FORMAT("VFSNode::_platform_getKnownDirectoryNode: Requested invalid directory ID %d.", (int) id)); 00133 break; 00134 } 00135 00136 subFolder.mkdir(); 00137 00138 VFSNode companyFolder; 00139 if (companyName.isEmpty()) { 00140 companyFolder = subFolder; 00141 } else { 00142 subFolder.getChildNode(companyName, companyFolder); 00143 companyFolder.mkdir(); 00144 } 00145 00146 VFSNode resultNode; 00147 if (appName.isEmpty()) { 00148 resultNode = companyFolder; 00149 } else { 00150 companyFolder.getChildNode(appName, resultNode); 00151 resultNode.mkdir(); 00152 } 00153 00154 return resultNode; 00155 } 00156 00157 #include <mach-o/dyld.h> // for _NSGetExecutablePath() 00158 00159 // static 00160 VFSNode VFSNode::_platform_getExecutable() { 00161 uint32_t bufsize = 3000; // can in theory be bigger than MAXPATH=1024 00162 VString executablePath; 00163 executablePath.preflight((int) bufsize); 00164 char* buffer = executablePath.buffer(); 00165 int result = _NSGetExecutablePath(buffer, &bufsize); 00166 if (result == -1) { 00167 throw VStackTraceException(VSTRING_FORMAT("VFSNode::_platform_getExecutable: Failed to get path. _NSGetExecutablePath returned %d.", result)); 00168 } 00169 00170 executablePath.postflight((int) ::strlen(buffer)); 00171 00172 // todo: could then convert to a "real path" in case returned path has sym links 00173 return VFSNode(VFSNode::normalizePath(executablePath)); // must supply normalized form to VFSNode below 00174 } 00175 00176 #else /* end of Mac OS X implementation of VFSNode::_platform_getKnownDirectoryNode and VFSNode::_platform_getExecutable */ 00177 00178 /* The generic Unix implementation of VFSNode::_platform_getKnownDirectoryNode and VFSNode::_platform_getExecutable follows */ 00179 00180 #include <pwd.h> 00181 00182 // static 00183 VFSNode VFSNode::_platform_getKnownDirectoryNode(KnownDirectoryIdentifier id, const VString& companyName, const VString& appName) { 00184 if (id == CURRENT_WORKING_DIRECTORY) { 00185 return VFSNode(VSystemAPI::getcwd()); 00186 } 00187 00188 if (id == EXECUTABLE_DIRECTORY) { 00189 VFSNode executable = VFSNode::getExecutable(); 00190 VFSNode executableDirectory; 00191 executable.getParentNode(executableDirectory); 00192 return executableDirectory; 00193 } 00194 00195 struct passwd* pwInfo = ::getpwuid(::getuid()); // Get info about the current user. 00196 if (pwInfo == NULL) { 00197 throw VStackTraceException( 00198 // Oddity: errno 0 can occur and means "no such user". 00199 (errno == 0 ? VSystemError(0, "No such user") : VSystemError()), 00200 "VFSNode::_platform_getKnownDirectoryNode failed to get current user info from getpwuid()." 00201 ); 00202 } 00203 00204 const VString homePath(pwInfo->pw_dir); 00205 00206 if (id == USER_HOME_DIRECTORY) { 00207 return VFSNode(homePath); 00208 } 00209 00210 VString basePath; 00211 VString companyFolderName(companyName); 00212 00213 switch (id) { 00214 case USER_HOME_DIRECTORY: 00215 // handled earlier; we returned above 00216 break; 00217 00218 case LOG_FILES_DIRECTORY: 00219 basePath = homePath + "/log"; 00220 break; 00221 00222 case USER_PREFERENCES_DIRECTORY: 00223 basePath = homePath; 00224 if (companyName.isNotEmpty()) { 00225 companyFolderName.format(".%s", companyName.chars()); 00226 } 00227 break; 00228 00229 case CACHED_DATA_DIRECTORY: 00230 basePath = homePath + "/cache"; 00231 break; 00232 00233 case APPLICATION_DATA_DIRECTORY: 00234 basePath = homePath + "/data"; 00235 break; 00236 00237 case CURRENT_WORKING_DIRECTORY: 00238 // handled earlier; we returned above 00239 break; 00240 00241 case EXECUTABLE_DIRECTORY: 00242 // handled earlier; we returned above 00243 break; 00244 00245 default: 00246 throw VStackTraceException(VSTRING_FORMAT("VFSNode::_platform_getKnownDirectoryNode: Requested invalid directory ID %d.", (int) id)); 00247 break; 00248 } 00249 00250 VFSNode baseDir(basePath); 00251 baseDir.mkdir(); 00252 00253 VFSNode companyFolder; 00254 if (companyFolderName.isEmpty()) { 00255 companyFolder = baseDir; 00256 } else { 00257 baseDir.getChildNode(companyFolderName, companyFolder); 00258 companyFolder.mkdir(); 00259 } 00260 00261 VFSNode resultNode; 00262 if (appName.isEmpty()) { 00263 resultNode = companyFolder; 00264 } else { 00265 companyFolder.getChildNode(appName, resultNode); 00266 resultNode.mkdir(); 00267 } 00268 00269 return resultNode; 00270 } 00271 00272 // Assume Linux; conditionalize others: 00273 #ifdef VPLATFORM_UNIX_BSD 00274 static const VString PROCESS_LINKPATH("/proc/curproc/file"); 00275 #else 00276 static const VString PROCESS_LINKPATH("/proc/self/exe"); 00277 #endif 00278 00279 // static 00280 VFSNode VFSNode::_platform_getExecutable() { 00281 const int PATH_BUFFER_SIZE = 1024; 00282 VString executablePath; 00283 executablePath.preflight(PATH_BUFFER_SIZE); 00284 ssize_t len = ::readlink(PROCESS_LINKPATH, executablePath.buffer(), PATH_BUFFER_SIZE - 1); 00285 if (len == -1) { 00286 throw VStackTraceException(VSystemError(), "VFSNode::_platform_getExecutable: Unable to determine executable path."); 00287 } 00288 00289 executablePath.postflight(len); 00290 return VFSNode(VFSNode::normalizePath(executablePath)); // must supply normalized form to VFSNode below 00291 } 00292 00293 #endif /* end of generic Unix implementation of VFSNode::_platform_getKnownDirectoryNode and VFSNode::_platform_getExecutable */ 00294 00295 bool VFSNode::_platform_getNodeInfo(VFSNodeInfo& info) const { 00296 struct stat statData; 00297 int result = VFileSystem::stat(mPath, &statData); 00298 00299 if (result >= 0) { 00300 info.mCreationDate = CONST_S64(1000) * static_cast<Vs64>(statData.st_ctime); 00301 info.mModificationDate = CONST_S64(1000) * static_cast<Vs64>(statData.st_mtime); 00302 info.mFileSize = statData.st_size; 00303 info.mIsFile = (! S_ISDIR(statData.st_mode)) && (! S_ISLNK(statData.st_mode)); 00304 info.mIsDirectory = (S_ISDIR(statData.st_mode)) || (S_ISLNK(statData.st_mode)); 00305 info.mErrNo = 0; 00306 } else { 00307 info.mErrNo = errno; 00308 } 00309 00310 return (result >= 0); 00311 } 00312 00313 void VFSNode::_platform_createDirectory() const { 00314 int result = VFileSystem::mkdir(mPath, (S_IFDIR | S_IRWXO | S_IRWXG | S_IRWXU)); 00315 00316 if (result != 0) 00317 throw VException(VSystemError(), VSTRING_FORMAT("VFSNode::_platform_createDirectory failed with result %d for '%s'.", result, mPath.chars())); 00318 } 00319 00320 bool VFSNode::_platform_removeDirectory() const { 00321 int result = VFileSystem::rmdir(mPath); 00322 return (result == 0); 00323 } 00324 00325 bool VFSNode::_platform_removeFile() const { 00326 int result = VFileSystem::unlink(mPath); 00327 return (result == 0); 00328 } 00329 00330 void VFSNode::_platform_renameNode(const VString& newPath) const { 00331 int result = VFileSystem::rename(mPath, newPath); 00332 00333 if (result != 0) 00334 throw VException(VSystemError(), VSTRING_FORMAT("VFSNode::_platform_renameNode failed with result %d renaming '%s' to '%s'.", result, mPath.chars(), newPath.chars())); 00335 } 00336 00337 // This is the Unix implementation of directory iteration using 00338 // opendir(), readdir(), closedir() functions. 00339 00340 void VFSNode::_platform_directoryIterate(VDirectoryIterationCallback& callback) const { 00341 VString nodeName; 00342 00343 DIR* dir = ::opendir(mPath); 00344 00345 if (dir == NULL) { 00346 throw VException(VSTRING_FORMAT("VFSNode::_platform_getDirectoryList failed for directory '%s'.", mPath.chars())); 00347 } 00348 00349 try { 00350 bool keepGoing = true; 00351 struct dirent* entry = ::readdir(dir); 00352 00353 while (keepGoing && (entry != NULL)) { 00354 VThread::yield(); // be nice if we're iterating over a huge directory 00355 00356 nodeName.copyFromCString(entry->d_name); 00357 00358 // Skip current and parent pseudo-entries. Otherwise client must 00359 // know too much detail in order to avoid traversal problems. 00360 if ((nodeName != ".") && 00361 (nodeName != "..")) { 00362 VFSNode childNode; 00363 this->getChildNode(nodeName, childNode); 00364 keepGoing = callback.handleNextNode(childNode); 00365 } 00366 00367 entry = ::readdir(dir); 00368 } 00369 } catch (...) { 00370 ::closedir(dir); 00371 throw; 00372 } 00373 00374 ::closedir(dir); 00375 } 00376