1/*
2 *  Copyright 2004 The WebRTC Project Authors. All rights reserved.
3 *
4 *  Use of this source code is governed by a BSD-style license
5 *  that can be found in the LICENSE file in the root of the source
6 *  tree. An additional intellectual property rights grant can be found
7 *  in the file PATENTS.  All contributing project authors may
8 *  be found in the AUTHORS file in the root of the source tree.
9 */
10
11#include "webrtc/base/unixfilesystem.h"
12
13#include <errno.h>
14#include <fcntl.h>
15#include <stdlib.h>
16#include <sys/stat.h>
17#include <unistd.h>
18
19#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS)
20#include <Carbon/Carbon.h>
21#include <IOKit/IOCFBundle.h>
22#include <sys/statvfs.h>
23#include "webrtc/base/macutils.h"
24#endif  // WEBRTC_MAC && !defined(WEBRTC_IOS)
25
26#if defined(WEBRTC_POSIX) && !defined(WEBRTC_MAC) || defined(WEBRTC_IOS)
27#include <sys/types.h>
28#if defined(WEBRTC_ANDROID)
29#include <sys/statfs.h>
30#elif !defined(__native_client__)
31#include <sys/statvfs.h>
32#endif  //  !defined(__native_client__)
33#include <limits.h>
34#include <pwd.h>
35#include <stdio.h>
36#endif  // WEBRTC_POSIX && !WEBRTC_MAC || WEBRTC_IOS
37
38#if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID)
39#include <ctype.h>
40#include <algorithm>
41#endif
42
43#if defined(__native_client__) && !defined(__GLIBC__)
44#include <sys/syslimits.h>
45#endif
46
47#include "webrtc/base/fileutils.h"
48#include "webrtc/base/pathutils.h"
49#include "webrtc/base/stream.h"
50#include "webrtc/base/stringutils.h"
51
52#if defined(WEBRTC_IOS)
53// Defined in iosfilesystem.mm.  No header file to discourage use
54// elsewhere; other places should use GetApp{Data,Temp}Folder() in
55// this file.  Don't copy/paste.  I mean it.
56char* IOSDataDirectory();
57char* IOSTempDirectory();
58void IOSAppName(rtc::Pathname* path);
59#endif
60
61namespace rtc {
62
63#if !defined(WEBRTC_ANDROID) && !defined(WEBRTC_IOS)
64char* UnixFilesystem::app_temp_path_ = NULL;
65#else
66char* UnixFilesystem::provided_app_data_folder_ = NULL;
67char* UnixFilesystem::provided_app_temp_folder_ = NULL;
68
69void UnixFilesystem::SetAppDataFolder(const std::string& folder) {
70  delete [] provided_app_data_folder_;
71  provided_app_data_folder_ = CopyString(folder);
72}
73
74void UnixFilesystem::SetAppTempFolder(const std::string& folder) {
75  delete [] provided_app_temp_folder_;
76  provided_app_temp_folder_ = CopyString(folder);
77}
78#endif
79
80UnixFilesystem::UnixFilesystem() {
81#if defined(WEBRTC_IOS)
82  if (!provided_app_data_folder_)
83    provided_app_data_folder_ = IOSDataDirectory();
84  if (!provided_app_temp_folder_)
85    provided_app_temp_folder_ = IOSTempDirectory();
86#endif
87}
88
89UnixFilesystem::~UnixFilesystem() {}
90
91bool UnixFilesystem::CreateFolder(const Pathname &path, mode_t mode) {
92  std::string pathname(path.pathname());
93  int len = pathname.length();
94  if ((len == 0) || (pathname[len - 1] != '/'))
95    return false;
96
97  struct stat st;
98  int res = ::stat(pathname.c_str(), &st);
99  if (res == 0) {
100    // Something exists at this location, check if it is a directory
101    return S_ISDIR(st.st_mode) != 0;
102  } else if (errno != ENOENT) {
103    // Unexpected error
104    return false;
105  }
106
107  // Directory doesn't exist, look up one directory level
108  do {
109    --len;
110  } while ((len > 0) && (pathname[len - 1] != '/'));
111
112  if (!CreateFolder(Pathname(pathname.substr(0, len)), mode)) {
113    return false;
114  }
115
116  LOG(LS_INFO) << "Creating folder: " << pathname;
117  return (0 == ::mkdir(pathname.c_str(), mode));
118}
119
120bool UnixFilesystem::CreateFolder(const Pathname &path) {
121  return CreateFolder(path, 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(), NULL)) {
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#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS)
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(WEBRTC_ANDROID) || defined(WEBRTC_IOS)
183  ASSERT(provided_app_temp_folder_ != NULL);
184  pathname.SetPathname(provided_app_temp_folder_, "");
185#else  // !WEBRTC_MAC || WEBRTC_IOS && !WEBRTC_ANDROID
186  if (const char* tmpdir = getenv("TMPDIR")) {
187    pathname.SetPathname(tmpdir, "");
188  } else if (const char* tmp = getenv("TMP")) {
189    pathname.SetPathname(tmp, "");
190  } else {
191#ifdef P_tmpdir
192    pathname.SetPathname(P_tmpdir, "");
193#else  // !P_tmpdir
194    pathname.SetPathname("/tmp/", "");
195#endif  // !P_tmpdir
196  }
197#endif  // !WEBRTC_MAC || WEBRTC_IOS && !WEBRTC_ANDROID
198  if (append) {
199    ASSERT(!append->empty());
200    pathname.AppendFolder(*append);
201  }
202  return !create || CreateFolder(pathname);
203}
204
205std::string UnixFilesystem::TempFilename(const Pathname &dir,
206                                         const std::string &prefix) {
207  int len = dir.pathname().size() + prefix.size() + 2 + 6;
208  char *tempname = new char[len];
209
210  snprintf(tempname, len, "%s/%sXXXXXX", dir.pathname().c_str(),
211           prefix.c_str());
212  int fd = ::mkstemp(tempname);
213  if (fd != -1)
214    ::close(fd);
215  std::string ret(tempname);
216  delete[] tempname;
217
218  return ret;
219}
220
221bool UnixFilesystem::MoveFile(const Pathname &old_path,
222                              const Pathname &new_path) {
223  if (!IsFile(old_path)) {
224    ASSERT(IsFile(old_path));
225    return false;
226  }
227  LOG(LS_VERBOSE) << "Moving " << old_path.pathname()
228                  << " to " << new_path.pathname();
229  if (rename(old_path.pathname().c_str(), new_path.pathname().c_str()) != 0) {
230    if (errno != EXDEV)
231      return false;
232    if (!CopyFile(old_path, new_path))
233      return false;
234    if (!DeleteFile(old_path))
235      return false;
236  }
237  return true;
238}
239
240bool UnixFilesystem::MoveFolder(const Pathname &old_path,
241                                const Pathname &new_path) {
242  if (!IsFolder(old_path)) {
243    ASSERT(IsFolder(old_path));
244    return false;
245  }
246  LOG(LS_VERBOSE) << "Moving " << old_path.pathname()
247                  << " to " << new_path.pathname();
248  if (rename(old_path.pathname().c_str(), new_path.pathname().c_str()) != 0) {
249    if (errno != EXDEV)
250      return false;
251    if (!CopyFolder(old_path, new_path))
252      return false;
253    if (!DeleteFolderAndContents(old_path))
254      return false;
255  }
256  return true;
257}
258
259bool UnixFilesystem::IsFolder(const Pathname &path) {
260  struct stat st;
261  if (stat(path.pathname().c_str(), &st) < 0)
262    return false;
263  return S_ISDIR(st.st_mode);
264}
265
266bool UnixFilesystem::CopyFile(const Pathname &old_path,
267                              const Pathname &new_path) {
268  LOG(LS_VERBOSE) << "Copying " << old_path.pathname()
269                  << " to " << new_path.pathname();
270  char buf[256];
271  size_t len;
272
273  StreamInterface *source = OpenFile(old_path, "rb");
274  if (!source)
275    return false;
276
277  StreamInterface *dest = OpenFile(new_path, "wb");
278  if (!dest) {
279    delete source;
280    return false;
281  }
282
283  while (source->Read(buf, sizeof(buf), &len, NULL) == SR_SUCCESS)
284    dest->Write(buf, len, NULL, NULL);
285
286  delete source;
287  delete dest;
288  return true;
289}
290
291bool UnixFilesystem::IsTemporaryPath(const Pathname& pathname) {
292#if defined(WEBRTC_ANDROID) || defined(WEBRTC_IOS)
293  ASSERT(provided_app_temp_folder_ != NULL);
294#endif
295
296  const char* const kTempPrefixes[] = {
297#if defined(WEBRTC_ANDROID) || defined(WEBRTC_IOS)
298    provided_app_temp_folder_,
299#else
300    "/tmp/", "/var/tmp/",
301#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS)
302    "/private/tmp/", "/private/var/tmp/", "/private/var/folders/",
303#endif  // WEBRTC_MAC && !defined(WEBRTC_IOS)
304#endif  // WEBRTC_ANDROID || WEBRTC_IOS
305  };
306  for (size_t i = 0; i < ARRAY_SIZE(kTempPrefixes); ++i) {
307    if (0 == strncmp(pathname.pathname().c_str(), kTempPrefixes[i],
308                     strlen(kTempPrefixes[i])))
309      return true;
310  }
311  return false;
312}
313
314bool UnixFilesystem::IsFile(const Pathname& pathname) {
315  struct stat st;
316  int res = ::stat(pathname.pathname().c_str(), &st);
317  // Treat symlinks, named pipes, etc. all as files.
318  return res == 0 && !S_ISDIR(st.st_mode);
319}
320
321bool UnixFilesystem::IsAbsent(const Pathname& pathname) {
322  struct stat st;
323  int res = ::stat(pathname.pathname().c_str(), &st);
324  // Note: we specifically maintain ENOTDIR as an error, because that implies
325  // that you could not call CreateFolder(pathname).
326  return res != 0 && ENOENT == errno;
327}
328
329bool UnixFilesystem::GetFileSize(const Pathname& pathname, size_t *size) {
330  struct stat st;
331  if (::stat(pathname.pathname().c_str(), &st) != 0)
332    return false;
333  *size = st.st_size;
334  return true;
335}
336
337bool UnixFilesystem::GetFileTime(const Pathname& path, FileTimeType which,
338                                 time_t* time) {
339  struct stat st;
340  if (::stat(path.pathname().c_str(), &st) != 0)
341    return false;
342  switch (which) {
343  case FTT_CREATED:
344    *time = st.st_ctime;
345    break;
346  case FTT_MODIFIED:
347    *time = st.st_mtime;
348    break;
349  case FTT_ACCESSED:
350    *time = st.st_atime;
351    break;
352  default:
353    return false;
354  }
355  return true;
356}
357
358bool UnixFilesystem::GetAppPathname(Pathname* path) {
359#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS)
360  ProcessSerialNumber psn = { 0, kCurrentProcess };
361  CFDictionaryRef procinfo = ProcessInformationCopyDictionary(&psn,
362      kProcessDictionaryIncludeAllInformationMask);
363  if (NULL == procinfo)
364    return false;
365  CFStringRef cfpath = (CFStringRef) CFDictionaryGetValue(procinfo,
366      kIOBundleExecutableKey);
367  std::string path8;
368  bool success = ToUtf8(cfpath, &path8);
369  CFRelease(procinfo);
370  if (success)
371    path->SetPathname(path8);
372  return success;
373#elif defined(__native_client__)
374  return false;
375#elif IOS
376  IOSAppName(path);
377  return true;
378#else  // WEBRTC_MAC && !defined(WEBRTC_IOS)
379  char buffer[PATH_MAX + 2];
380  ssize_t len = readlink("/proc/self/exe", buffer, ARRAY_SIZE(buffer) - 1);
381  if ((len <= 0) || (len == PATH_MAX + 1))
382    return false;
383  buffer[len] = '\0';
384  path->SetPathname(buffer);
385  return true;
386#endif  // WEBRTC_MAC && !defined(WEBRTC_IOS)
387}
388
389bool UnixFilesystem::GetAppDataFolder(Pathname* path, bool per_user) {
390  ASSERT(!organization_name_.empty());
391  ASSERT(!application_name_.empty());
392
393  // First get the base directory for app data.
394#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS)
395  if (per_user) {
396    // Use ~/Library/Application Support/<orgname>/<appname>/
397    FSRef fr;
398    if (0 != FSFindFolder(kUserDomain, kApplicationSupportFolderType,
399                          kCreateFolder, &fr))
400      return false;
401    unsigned char buffer[NAME_MAX+1];
402    if (0 != FSRefMakePath(&fr, buffer, ARRAY_SIZE(buffer)))
403      return false;
404    path->SetPathname(reinterpret_cast<char*>(buffer), "");
405  } else {
406    // TODO
407    return false;
408  }
409#elif defined(WEBRTC_ANDROID) || defined(WEBRTC_IOS)  // && !WEBRTC_MAC || WEBRTC_IOS
410  ASSERT(provided_app_data_folder_ != NULL);
411  path->SetPathname(provided_app_data_folder_, "");
412#elif defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID)  // && !WEBRTC_MAC && !WEBRTC_IOS && !WEBRTC_ANDROID
413  if (per_user) {
414    // We follow the recommendations in
415    // http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
416    // It specifies separate directories for data and config files, but
417    // GetAppDataFolder() does not distinguish. We just return the config dir
418    // path.
419    const char* xdg_config_home = getenv("XDG_CONFIG_HOME");
420    if (xdg_config_home) {
421      path->SetPathname(xdg_config_home, "");
422    } else {
423      // XDG says to default to $HOME/.config. We also support falling back to
424      // other synonyms for HOME if for some reason it is not defined.
425      const char* homedir;
426      if (const char* home = getenv("HOME")) {
427        homedir = home;
428      } else if (const char* dotdir = getenv("DOTDIR")) {
429        homedir = dotdir;
430      } else if (passwd* pw = getpwuid(geteuid())) {
431        homedir = pw->pw_dir;
432      } else {
433        return false;
434      }
435      path->SetPathname(homedir, "");
436      path->AppendFolder(".config");
437    }
438  } else {
439    // XDG does not define a standard directory for writable global data. Let's
440    // just use this.
441    path->SetPathname("/var/cache/", "");
442  }
443#endif  // !WEBRTC_MAC && !WEBRTC_LINUX
444
445  // Now add on a sub-path for our app.
446#if defined(WEBRTC_MAC) || defined(WEBRTC_ANDROID)
447  path->AppendFolder(organization_name_);
448  path->AppendFolder(application_name_);
449#elif defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID)
450  // XDG says to use a single directory level, so we concatenate the org and app
451  // name with a hyphen. We also do the Linuxy thing and convert to all
452  // lowercase with no spaces.
453  std::string subdir(organization_name_);
454  subdir.append("-");
455  subdir.append(application_name_);
456  replace_substrs(" ", 1, "", 0, &subdir);
457  std::transform(subdir.begin(), subdir.end(), subdir.begin(), ::tolower);
458  path->AppendFolder(subdir);
459#endif
460  if (!CreateFolder(*path, 0700)) {
461    return false;
462  }
463#if !defined(__native_client__)
464  // If the folder already exists, it may have the wrong mode or be owned by
465  // someone else, both of which are security problems. Setting the mode
466  // avoids both issues since it will fail if the path is not owned by us.
467  if (0 != ::chmod(path->pathname().c_str(), 0700)) {
468    LOG_ERR(LS_ERROR) << "Can't set mode on " << path;
469    return false;
470  }
471#endif
472  return true;
473}
474
475bool UnixFilesystem::GetAppTempFolder(Pathname* path) {
476#if defined(WEBRTC_ANDROID) || defined(WEBRTC_IOS)
477  ASSERT(provided_app_temp_folder_ != NULL);
478  path->SetPathname(provided_app_temp_folder_);
479  return true;
480#else
481  ASSERT(!application_name_.empty());
482  // TODO: Consider whether we are worried about thread safety.
483  if (app_temp_path_ != NULL && strlen(app_temp_path_) > 0) {
484    path->SetPathname(app_temp_path_);
485    return true;
486  }
487
488  // Create a random directory as /tmp/<appname>-<pid>-<timestamp>
489  char buffer[128];
490  sprintfn(buffer, ARRAY_SIZE(buffer), "-%d-%d",
491           static_cast<int>(getpid()),
492           static_cast<int>(time(0)));
493  std::string folder(application_name_);
494  folder.append(buffer);
495  if (!GetTemporaryFolder(*path, true, &folder))
496    return false;
497
498  delete [] app_temp_path_;
499  app_temp_path_ = CopyString(path->pathname());
500  // TODO: atexit(DeleteFolderAndContents(app_temp_path_));
501  return true;
502#endif
503}
504
505bool UnixFilesystem::GetDiskFreeSpace(const Pathname& path, int64 *freebytes) {
506#ifdef __native_client__
507  return false;
508#else  // __native_client__
509  ASSERT(NULL != freebytes);
510  // TODO: Consider making relative paths absolute using cwd.
511  // TODO: When popping off a symlink, push back on the components of the
512  // symlink, so we don't jump out of the target disk inadvertently.
513  Pathname existing_path(path.folder(), "");
514  while (!existing_path.folder().empty() && IsAbsent(existing_path)) {
515    existing_path.SetFolder(existing_path.parent_folder());
516  }
517#if defined(WEBRTC_ANDROID)
518  struct statfs vfs;
519  memset(&vfs, 0, sizeof(vfs));
520  if (0 != statfs(existing_path.pathname().c_str(), &vfs))
521    return false;
522#else
523  struct statvfs vfs;
524  memset(&vfs, 0, sizeof(vfs));
525  if (0 != statvfs(existing_path.pathname().c_str(), &vfs))
526    return false;
527#endif  // WEBRTC_ANDROID
528#if defined(WEBRTC_LINUX)
529  *freebytes = static_cast<int64>(vfs.f_bsize) * vfs.f_bavail;
530#elif defined(WEBRTC_MAC)
531  *freebytes = static_cast<int64>(vfs.f_frsize) * vfs.f_bavail;
532#endif
533
534  return true;
535#endif  // !__native_client__
536}
537
538Pathname UnixFilesystem::GetCurrentDirectory() {
539  Pathname cwd;
540  char buffer[PATH_MAX];
541  char *path = getcwd(buffer, PATH_MAX);
542
543  if (!path) {
544    LOG_ERR(LS_ERROR) << "getcwd() failed";
545    return cwd;  // returns empty pathname
546  }
547  cwd.SetFolder(std::string(path));
548
549  return cwd;
550}
551
552char* UnixFilesystem::CopyString(const std::string& str) {
553  size_t size = str.length() + 1;
554
555  char* buf = new char[size];
556  if (!buf) {
557    return NULL;
558  }
559
560  strcpyn(buf, size, str.c_str());
561  return buf;
562}
563
564}  // namespace rtc
565
566#if defined(__native_client__)
567extern "C" int __attribute__((weak))
568link(const char* oldpath, const char* newpath) {
569  errno = EACCES;
570  return -1;
571}
572#endif
573