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 65namespace { 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. 78const 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. 84const char* GetAndroidTempFolder() { 85 return "/sdcard"; 86} 87 88} // anonymous namespace 89#endif 90 91namespace talk_base { 92 93std::string UnixFilesystem::app_temp_path_; 94 95bool 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 124FileStream *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 134bool 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 150bool 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 160bool 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 171bool 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 204std::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 220bool 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 239bool 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 258bool 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 265bool 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 290bool 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 309bool 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 316bool 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 324bool 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 332bool 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 353bool 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 379bool 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 454bool 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 477bool 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 508Pathname 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