1 /* 2 * libjingle 3 * Copyright 2004--2006, Google Inc. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright notice, 9 * this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright notice, 11 * this list of conditions and the following disclaimer in the documentation 12 * and/or other materials provided with the distribution. 13 * 3. The name of the author may not be used to endorse or promote products 14 * derived from this software without specific prior written permission. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED 17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO 19 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 */ 27 28 #include "talk/base/unixfilesystem.h" 29 30 #include <errno.h> 31 #include <fcntl.h> 32 #include <stdlib.h> 33 #include <unistd.h> 34 35 #ifdef OSX 36 #include <Carbon/Carbon.h> 37 #include <IOKit/IOCFBundle.h> 38 #include <sys/statvfs.h> 39 #include "talk/base/macutils.h" 40 #endif // OSX 41 42 #if defined(POSIX) && !defined(OSX) 43 #include <sys/types.h> 44 #ifdef ANDROID 45 #include <sys/statfs.h> 46 #else 47 #include <sys/statvfs.h> 48 #endif // ANDROID 49 #include <pwd.h> 50 #include <stdio.h> 51 #include <unistd.h> 52 #endif // POSIX && !OSX 53 54 #ifdef LINUX 55 #include <ctype.h> 56 #include <algorithm> 57 #endif 58 59 #include "talk/base/fileutils.h" 60 #include "talk/base/pathutils.h" 61 #include "talk/base/stream.h" 62 #include "talk/base/stringutils.h" 63 64 #ifdef ANDROID 65 namespace { 66 // Android does not have a concept of a single temp dir shared by all 67 // because resource are scarse on a phone. Instead each app gets some 68 // space on the sdcard under a path that is given at runtime by the 69 // system. 70 // The disk allocation feature is still work in progress so currently 71 // we return a hardcoded a path on the sdcard. In the future, we 72 // should do a JNI call to get that info from the context. 73 // TODO: Replace hardcoded path with a query to the Context 74 // object to get the equivalents of '/tmp' and '~/.' 75 76 // @return the folder for libjingle. Some extra path (typically 77 // Google/<app name>) will be added. 78 const char* GetAndroidAppDataFolder() { 79 return "/sdcard"; 80 } 81 82 // @return the tmp folder to be used. Some extra path will be added to 83 // that base folder. 84 const char* GetAndroidTempFolder() { 85 return "/sdcard"; 86 } 87 88 } // anonymous namespace 89 #endif 90 91 namespace talk_base { 92 93 std::string UnixFilesystem::app_temp_path_; 94 95 bool UnixFilesystem::CreateFolder(const Pathname &path) { 96 std::string pathname(path.pathname()); 97 int len = pathname.length(); 98 if ((len == 0) || (pathname[len - 1] != '/')) 99 return false; 100 101 struct stat st; 102 int res = ::stat(pathname.c_str(), &st); 103 if (res == 0) { 104 // Something exists at this location, check if it is a directory 105 return S_ISDIR(st.st_mode) != 0; 106 } else if (errno != ENOENT) { 107 // Unexpected error 108 return false; 109 } 110 111 // Directory doesn't exist, look up one directory level 112 do { 113 --len; 114 } while ((len > 0) && (pathname[len - 1] != '/')); 115 116 if (!CreateFolder(Pathname(pathname.substr(0, len)))) { 117 return false; 118 } 119 120 LOG(LS_INFO) << "Creating folder: " << pathname; 121 return (0 == ::mkdir(pathname.c_str(), 0755)); 122 } 123 124 FileStream *UnixFilesystem::OpenFile(const Pathname &filename, 125 const std::string &mode) { 126 FileStream *fs = new FileStream(); 127 if (fs && !fs->Open(filename.pathname().c_str(), mode.c_str())) { 128 delete fs; 129 fs = NULL; 130 } 131 return fs; 132 } 133 134 bool UnixFilesystem::CreatePrivateFile(const Pathname &filename) { 135 int fd = open(filename.pathname().c_str(), 136 O_RDWR | O_CREAT | O_EXCL, 137 S_IRUSR | S_IWUSR); 138 if (fd < 0) { 139 LOG_ERR(LS_ERROR) << "open() failed."; 140 return false; 141 } 142 // Don't need to keep the file descriptor. 143 if (close(fd) < 0) { 144 LOG_ERR(LS_ERROR) << "close() failed."; 145 // Continue. 146 } 147 return true; 148 } 149 150 bool UnixFilesystem::DeleteFile(const Pathname &filename) { 151 LOG(LS_INFO) << "Deleting file:" << filename.pathname(); 152 153 if (!IsFile(filename)) { 154 ASSERT(IsFile(filename)); 155 return false; 156 } 157 return ::unlink(filename.pathname().c_str()) == 0; 158 } 159 160 bool UnixFilesystem::DeleteEmptyFolder(const Pathname &folder) { 161 LOG(LS_INFO) << "Deleting folder" << folder.pathname(); 162 163 if (!IsFolder(folder)) { 164 ASSERT(IsFolder(folder)); 165 return false; 166 } 167 std::string no_slash(folder.pathname(), 0, folder.pathname().length()-1); 168 return ::rmdir(no_slash.c_str()) == 0; 169 } 170 171 bool UnixFilesystem::GetTemporaryFolder(Pathname &pathname, bool create, 172 const std::string *append) { 173 #ifdef OSX 174 FSRef fr; 175 if (0 != FSFindFolder(kOnAppropriateDisk, kTemporaryFolderType, 176 kCreateFolder, &fr)) 177 return false; 178 unsigned char buffer[NAME_MAX+1]; 179 if (0 != FSRefMakePath(&fr, buffer, ARRAY_SIZE(buffer))) 180 return false; 181 pathname.SetPathname(reinterpret_cast<char*>(buffer), ""); 182 #elif defined(ANDROID) 183 pathname.SetPathname(GetAndroidTempFolder(), ""); 184 #else // !OSX && !ANDROID 185 if (const char* tmpdir = getenv("TMPDIR")) { 186 pathname.SetPathname(tmpdir, ""); 187 } else if (const char* tmp = getenv("TMP")) { 188 pathname.SetPathname(tmp, ""); 189 } else { 190 #ifdef P_tmpdir 191 pathname.SetPathname(P_tmpdir, ""); 192 #else // !P_tmpdir 193 pathname.SetPathname("/tmp/", ""); 194 #endif // !P_tmpdir 195 } 196 #endif // !OSX && !ANDROID 197 if (append) { 198 ASSERT(!append->empty()); 199 pathname.AppendFolder(*append); 200 } 201 return !create || CreateFolder(pathname); 202 } 203 204 std::string UnixFilesystem::TempFilename(const Pathname &dir, 205 const std::string &prefix) { 206 int len = dir.pathname().size() + prefix.size() + 2 + 6; 207 char *tempname = new char[len]; 208 209 snprintf(tempname, len, "%s/%sXXXXXX", dir.pathname().c_str(), 210 prefix.c_str()); 211 int fd = ::mkstemp(tempname); 212 if (fd != -1) 213 ::close(fd); 214 std::string ret(tempname); 215 delete[] tempname; 216 217 return ret; 218 } 219 220 bool UnixFilesystem::MoveFile(const Pathname &old_path, 221 const Pathname &new_path) { 222 if (!IsFile(old_path)) { 223 ASSERT(IsFile(old_path)); 224 return false; 225 } 226 LOG(LS_VERBOSE) << "Moving " << old_path.pathname() 227 << " to " << new_path.pathname(); 228 if (rename(old_path.pathname().c_str(), new_path.pathname().c_str()) != 0) { 229 if (errno != EXDEV) 230 return false; 231 if (!CopyFile(old_path, new_path)) 232 return false; 233 if (!DeleteFile(old_path)) 234 return false; 235 } 236 return true; 237 } 238 239 bool UnixFilesystem::MoveFolder(const Pathname &old_path, 240 const Pathname &new_path) { 241 if (!IsFolder(old_path)) { 242 ASSERT(IsFolder(old_path)); 243 return false; 244 } 245 LOG(LS_VERBOSE) << "Moving " << old_path.pathname() 246 << " to " << new_path.pathname(); 247 if (rename(old_path.pathname().c_str(), new_path.pathname().c_str()) != 0) { 248 if (errno != EXDEV) 249 return false; 250 if (!CopyFolder(old_path, new_path)) 251 return false; 252 if (!DeleteFolderAndContents(old_path)) 253 return false; 254 } 255 return true; 256 } 257 258 bool UnixFilesystem::IsFolder(const Pathname &path) { 259 struct stat st; 260 if (stat(path.pathname().c_str(), &st) < 0) 261 return false; 262 return S_ISDIR(st.st_mode); 263 } 264 265 bool UnixFilesystem::CopyFile(const Pathname &old_path, 266 const Pathname &new_path) { 267 LOG(LS_VERBOSE) << "Copying " << old_path.pathname() 268 << " to " << new_path.pathname(); 269 char buf[256]; 270 size_t len; 271 272 StreamInterface *source = OpenFile(old_path, "rb"); 273 if (!source) 274 return false; 275 276 StreamInterface *dest = OpenFile(new_path, "wb"); 277 if (!dest) { 278 delete source; 279 return false; 280 } 281 282 while (source->Read(buf, sizeof(buf), &len, NULL) == SR_SUCCESS) 283 dest->Write(buf, len, NULL, NULL); 284 285 delete source; 286 delete dest; 287 return true; 288 } 289 290 bool UnixFilesystem::IsTemporaryPath(const Pathname& pathname) { 291 const char* const kTempPrefixes[] = { 292 #ifdef ANDROID 293 GetAndroidTempFolder() 294 #else 295 "/tmp/", "/var/tmp/", 296 #ifdef OSX 297 "/private/tmp/", "/private/var/tmp/", "/private/var/folders/", 298 #endif // OSX 299 #endif // ANDROID 300 }; 301 for (size_t i = 0; i < ARRAY_SIZE(kTempPrefixes); ++i) { 302 if (0 == strncmp(pathname.pathname().c_str(), kTempPrefixes[i], 303 strlen(kTempPrefixes[i]))) 304 return true; 305 } 306 return false; 307 } 308 309 bool UnixFilesystem::IsFile(const Pathname& pathname) { 310 struct stat st; 311 int res = ::stat(pathname.pathname().c_str(), &st); 312 // Treat symlinks, named pipes, etc. all as files. 313 return res == 0 && !S_ISDIR(st.st_mode); 314 } 315 316 bool UnixFilesystem::IsAbsent(const Pathname& pathname) { 317 struct stat st; 318 int res = ::stat(pathname.pathname().c_str(), &st); 319 // Note: we specifically maintain ENOTDIR as an error, because that implies 320 // that you could not call CreateFolder(pathname). 321 return res != 0 && ENOENT == errno; 322 } 323 324 bool UnixFilesystem::GetFileSize(const Pathname& pathname, size_t *size) { 325 struct stat st; 326 if (::stat(pathname.pathname().c_str(), &st) != 0) 327 return false; 328 *size = st.st_size; 329 return true; 330 } 331 332 bool UnixFilesystem::GetFileTime(const Pathname& path, FileTimeType which, 333 time_t* time) { 334 struct stat st; 335 if (::stat(path.pathname().c_str(), &st) != 0) 336 return false; 337 switch (which) { 338 case FTT_CREATED: 339 *time = st.st_ctime; 340 break; 341 case FTT_MODIFIED: 342 *time = st.st_mtime; 343 break; 344 case FTT_ACCESSED: 345 *time = st.st_atime; 346 break; 347 default: 348 return false; 349 } 350 return true; 351 } 352 353 bool UnixFilesystem::GetAppPathname(Pathname* path) { 354 #ifdef OSX 355 ProcessSerialNumber psn = { 0, kCurrentProcess }; 356 CFDictionaryRef procinfo = ProcessInformationCopyDictionary(&psn, 357 kProcessDictionaryIncludeAllInformationMask); 358 if (NULL == procinfo) 359 return false; 360 CFStringRef cfpath = (CFStringRef) CFDictionaryGetValue(procinfo, 361 kIOBundleExecutableKey); 362 std::string path8; 363 bool success = ToUtf8(cfpath, &path8); 364 CFRelease(procinfo); 365 if (success) 366 path->SetPathname(path8); 367 return success; 368 #else // OSX 369 char buffer[NAME_MAX+1]; 370 size_t len = readlink("/proc/self/exe", buffer, ARRAY_SIZE(buffer) - 1); 371 if (len <= 0) 372 return false; 373 buffer[len] = '\0'; 374 path->SetPathname(buffer); 375 return true; 376 #endif // OSX 377 } 378 379 bool UnixFilesystem::GetAppDataFolder(Pathname* path, bool per_user) { 380 ASSERT(!organization_name_.empty()); 381 ASSERT(!application_name_.empty()); 382 383 // First get the base directory for app data. 384 #ifdef OSX 385 if (per_user) { 386 // Use ~/Library/Application Support/<orgname>/<appname>/ 387 FSRef fr; 388 if (0 != FSFindFolder(kUserDomain, kApplicationSupportFolderType, 389 kCreateFolder, &fr)) 390 return false; 391 unsigned char buffer[NAME_MAX+1]; 392 if (0 != FSRefMakePath(&fr, buffer, ARRAY_SIZE(buffer))) 393 return false; 394 path->SetPathname(reinterpret_cast<char*>(buffer), ""); 395 } else { 396 // TODO 397 return false; 398 } 399 #elif defined(ANDROID) // && !OSX 400 // TODO: Check if the new disk allocation mechanism works 401 // per-user and we don't have the per_user distinction. 402 path->SetPathname(GetAndroidAppDataFolder(), ""); 403 #elif defined(LINUX) // && !OSX && !defined(ANDROID) 404 if (per_user) { 405 // We follow the recommendations in 406 // http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html 407 // It specifies separate directories for data and config files, but 408 // GetAppDataFolder() does not distinguish. We just return the config dir 409 // path. 410 const char* xdg_config_home = getenv("XDG_CONFIG_HOME"); 411 if (xdg_config_home) { 412 path->SetPathname(xdg_config_home, ""); 413 } else { 414 // XDG says to default to $HOME/.config. We also support falling back to 415 // other synonyms for HOME if for some reason it is not defined. 416 const char* homedir; 417 if (const char* home = getenv("HOME")) { 418 homedir = home; 419 } else if (const char* dotdir = getenv("DOTDIR")) { 420 homedir = dotdir; 421 } else if (passwd* pw = getpwuid(geteuid())) { 422 homedir = pw->pw_dir; 423 } else { 424 return false; 425 } 426 path->SetPathname(homedir, ""); 427 path->AppendFolder(".config"); 428 } 429 } else { 430 // XDG does not define a standard directory for writable global data. Let's 431 // just use this. 432 path->SetPathname("/var/cache/", ""); 433 } 434 #endif // !OSX && !defined(ANDROID) && !defined(LINUX) 435 436 // Now add on a sub-path for our app. 437 #if defined(OSX) || defined(ANDROID) 438 path->AppendFolder(organization_name_); 439 path->AppendFolder(application_name_); 440 #elif defined(LINUX) 441 // XDG says to use a single directory level, so we concatenate the org and app 442 // name with a hyphen. We also do the Linuxy thing and convert to all 443 // lowercase with no spaces. 444 std::string subdir(organization_name_); 445 subdir.append("-"); 446 subdir.append(application_name_); 447 replace_substrs(" ", 1, "", 0, &subdir); 448 std::transform(subdir.begin(), subdir.end(), subdir.begin(), ::tolower); 449 path->AppendFolder(subdir); 450 #endif 451 return CreateFolder(*path); 452 } 453 454 bool UnixFilesystem::GetAppTempFolder(Pathname* path) { 455 ASSERT(!application_name_.empty()); 456 // TODO: Consider whether we are worried about thread safety. 457 if (!app_temp_path_.empty()) { 458 path->SetPathname(app_temp_path_); 459 return true; 460 } 461 462 // Create a random directory as /tmp/<appname>-<pid>-<timestamp> 463 char buffer[128]; 464 sprintfn(buffer, ARRAY_SIZE(buffer), "-%d-%d", 465 static_cast<int>(getpid()), 466 static_cast<int>(time(0))); 467 std::string folder(application_name_); 468 folder.append(buffer); 469 if (!GetTemporaryFolder(*path, true, &folder)) 470 return false; 471 472 app_temp_path_ = path->pathname(); 473 // TODO: atexit(DeleteFolderAndContents(app_temp_path_)); 474 return true; 475 } 476 477 bool UnixFilesystem::GetDiskFreeSpace(const Pathname& path, int64 *freebytes) { 478 ASSERT(NULL != freebytes); 479 // TODO: Consider making relative paths absolute using cwd. 480 // TODO: When popping off a symlink, push back on the components of the 481 // symlink, so we don't jump out of the target disk inadvertently. 482 Pathname existing_path(path.folder(), ""); 483 while (!existing_path.folder().empty() && IsAbsent(existing_path)) { 484 existing_path.SetFolder(existing_path.parent_folder()); 485 } 486 #ifdef ANDROID 487 struct statfs fs; 488 memset(&fs, 0, sizeof(fs)); 489 if (0 != statfs(existing_path.pathname().c_str(), &fs)) 490 return false; 491 #else 492 struct statvfs vfs; 493 memset(&vfs, 0, sizeof(vfs)); 494 if (0 != statvfs(existing_path.pathname().c_str(), &vfs)) 495 return false; 496 #endif // ANDROID 497 #ifdef LINUX 498 *freebytes = static_cast<int64>(vfs.f_bsize) * vfs.f_bavail; 499 #elif defined(OSX) 500 *freebytes = static_cast<int64>(vfs.f_frsize) * vfs.f_bavail; 501 #elif defined(ANDROID) 502 *freebytes = static_cast<int64>(fs.f_bsize) * fs.f_bavail; 503 #endif 504 505 return true; 506 } 507 508 Pathname UnixFilesystem::GetCurrentDirectory() { 509 Pathname cwd; 510 char buffer[PATH_MAX]; 511 char *path = getcwd(buffer, PATH_MAX); 512 513 if (!path) { 514 LOG_ERR(LS_ERROR) << "getcwd() failed"; 515 return cwd; // returns empty pathname 516 } 517 cwd.SetFolder(std::string(path)); 518 519 return cwd; 520 } 521 522 } // namespace talk_base 523