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