blob: c987691690fc290c6b7a2de1d80094de3fc24708 [file] [log] [blame]
henrike@webrtc.org28e20752013-07-10 00:45:36 +00001/*
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 <sys/stat.h>
34#include <unistd.h>
35
36#ifdef OSX
37#include <Carbon/Carbon.h>
38#include <IOKit/IOCFBundle.h>
39#include <sys/statvfs.h>
40#include "talk/base/macutils.h"
41#endif // OSX
42
43#if defined(POSIX) && !defined(OSX)
44#include <sys/types.h>
wu@webrtc.orgf6d6ed02014-01-03 22:08:47 +000045#if defined(ANDROID)
henrike@webrtc.org28e20752013-07-10 00:45:36 +000046#include <sys/statfs.h>
wu@webrtc.orgf6d6ed02014-01-03 22:08:47 +000047#elif !defined(__native_client__)
henrike@webrtc.org28e20752013-07-10 00:45:36 +000048#include <sys/statvfs.h>
wu@webrtc.orgf6d6ed02014-01-03 22:08:47 +000049#endif // !defined(__native_client__)
50#include <limits.h>
henrike@webrtc.org28e20752013-07-10 00:45:36 +000051#include <pwd.h>
52#include <stdio.h>
henrike@webrtc.org28e20752013-07-10 00:45:36 +000053#endif // POSIX && !OSX
54
wu@webrtc.orgf6d6ed02014-01-03 22:08:47 +000055#if defined(LINUX)
henrike@webrtc.org28e20752013-07-10 00:45:36 +000056#include <ctype.h>
57#include <algorithm>
58#endif
59
wu@webrtc.orgf6d6ed02014-01-03 22:08:47 +000060#if defined(__native_client__) && !defined(__GLIBC__)
61#include <sys/syslimits.h>
62#endif
63
henrike@webrtc.org28e20752013-07-10 00:45:36 +000064#include "talk/base/fileutils.h"
65#include "talk/base/pathutils.h"
66#include "talk/base/stream.h"
67#include "talk/base/stringutils.h"
68
69namespace talk_base {
70
71#if !defined(ANDROID) && !defined(IOS)
72char* UnixFilesystem::app_temp_path_ = NULL;
73#else
74char* UnixFilesystem::provided_app_data_folder_ = NULL;
75char* UnixFilesystem::provided_app_temp_folder_ = NULL;
76
77void UnixFilesystem::SetAppDataFolder(const std::string& folder) {
78 delete [] provided_app_data_folder_;
79 provided_app_data_folder_ = CopyString(folder);
80}
81
82void UnixFilesystem::SetAppTempFolder(const std::string& folder) {
83 delete [] provided_app_temp_folder_;
84 provided_app_temp_folder_ = CopyString(folder);
85}
86#endif
87
88bool UnixFilesystem::CreateFolder(const Pathname &path, mode_t mode) {
89 std::string pathname(path.pathname());
90 int len = pathname.length();
91 if ((len == 0) || (pathname[len - 1] != '/'))
92 return false;
93
94 struct stat st;
95 int res = ::stat(pathname.c_str(), &st);
96 if (res == 0) {
97 // Something exists at this location, check if it is a directory
98 return S_ISDIR(st.st_mode) != 0;
99 } else if (errno != ENOENT) {
100 // Unexpected error
101 return false;
102 }
103
104 // Directory doesn't exist, look up one directory level
105 do {
106 --len;
107 } while ((len > 0) && (pathname[len - 1] != '/'));
108
109 if (!CreateFolder(Pathname(pathname.substr(0, len)), mode)) {
110 return false;
111 }
112
113 LOG(LS_INFO) << "Creating folder: " << pathname;
114 return (0 == ::mkdir(pathname.c_str(), mode));
115}
116
117bool UnixFilesystem::CreateFolder(const Pathname &path) {
118 return CreateFolder(path, 0755);
119}
120
121FileStream *UnixFilesystem::OpenFile(const Pathname &filename,
122 const std::string &mode) {
123 FileStream *fs = new FileStream();
124 if (fs && !fs->Open(filename.pathname().c_str(), mode.c_str(), NULL)) {
125 delete fs;
126 fs = NULL;
127 }
128 return fs;
129}
130
131bool UnixFilesystem::CreatePrivateFile(const Pathname &filename) {
132 int fd = open(filename.pathname().c_str(),
133 O_RDWR | O_CREAT | O_EXCL,
134 S_IRUSR | S_IWUSR);
135 if (fd < 0) {
136 LOG_ERR(LS_ERROR) << "open() failed.";
137 return false;
138 }
139 // Don't need to keep the file descriptor.
140 if (close(fd) < 0) {
141 LOG_ERR(LS_ERROR) << "close() failed.";
142 // Continue.
143 }
144 return true;
145}
146
147bool UnixFilesystem::DeleteFile(const Pathname &filename) {
148 LOG(LS_INFO) << "Deleting file:" << filename.pathname();
149
150 if (!IsFile(filename)) {
151 ASSERT(IsFile(filename));
152 return false;
153 }
154 return ::unlink(filename.pathname().c_str()) == 0;
155}
156
157bool UnixFilesystem::DeleteEmptyFolder(const Pathname &folder) {
158 LOG(LS_INFO) << "Deleting folder" << folder.pathname();
159
160 if (!IsFolder(folder)) {
161 ASSERT(IsFolder(folder));
162 return false;
163 }
164 std::string no_slash(folder.pathname(), 0, folder.pathname().length()-1);
165 return ::rmdir(no_slash.c_str()) == 0;
166}
167
168bool UnixFilesystem::GetTemporaryFolder(Pathname &pathname, bool create,
169 const std::string *append) {
170#ifdef OSX
171 FSRef fr;
172 if (0 != FSFindFolder(kOnAppropriateDisk, kTemporaryFolderType,
173 kCreateFolder, &fr))
174 return false;
175 unsigned char buffer[NAME_MAX+1];
176 if (0 != FSRefMakePath(&fr, buffer, ARRAY_SIZE(buffer)))
177 return false;
178 pathname.SetPathname(reinterpret_cast<char*>(buffer), "");
179#elif defined(ANDROID) || defined(IOS)
180 ASSERT(provided_app_temp_folder_ != NULL);
181 pathname.SetPathname(provided_app_temp_folder_, "");
182#else // !OSX && !ANDROID
183 if (const char* tmpdir = getenv("TMPDIR")) {
184 pathname.SetPathname(tmpdir, "");
185 } else if (const char* tmp = getenv("TMP")) {
186 pathname.SetPathname(tmp, "");
187 } else {
188#ifdef P_tmpdir
189 pathname.SetPathname(P_tmpdir, "");
190#else // !P_tmpdir
191 pathname.SetPathname("/tmp/", "");
192#endif // !P_tmpdir
193 }
194#endif // !OSX && !ANDROID
195 if (append) {
196 ASSERT(!append->empty());
197 pathname.AppendFolder(*append);
198 }
199 return !create || CreateFolder(pathname);
200}
201
202std::string UnixFilesystem::TempFilename(const Pathname &dir,
203 const std::string &prefix) {
204 int len = dir.pathname().size() + prefix.size() + 2 + 6;
205 char *tempname = new char[len];
206
207 snprintf(tempname, len, "%s/%sXXXXXX", dir.pathname().c_str(),
208 prefix.c_str());
209 int fd = ::mkstemp(tempname);
210 if (fd != -1)
211 ::close(fd);
212 std::string ret(tempname);
213 delete[] tempname;
214
215 return ret;
216}
217
218bool UnixFilesystem::MoveFile(const Pathname &old_path,
219 const Pathname &new_path) {
220 if (!IsFile(old_path)) {
221 ASSERT(IsFile(old_path));
222 return false;
223 }
224 LOG(LS_VERBOSE) << "Moving " << old_path.pathname()
225 << " to " << new_path.pathname();
226 if (rename(old_path.pathname().c_str(), new_path.pathname().c_str()) != 0) {
227 if (errno != EXDEV)
228 return false;
229 if (!CopyFile(old_path, new_path))
230 return false;
231 if (!DeleteFile(old_path))
232 return false;
233 }
234 return true;
235}
236
237bool UnixFilesystem::MoveFolder(const Pathname &old_path,
238 const Pathname &new_path) {
239 if (!IsFolder(old_path)) {
240 ASSERT(IsFolder(old_path));
241 return false;
242 }
243 LOG(LS_VERBOSE) << "Moving " << old_path.pathname()
244 << " to " << new_path.pathname();
245 if (rename(old_path.pathname().c_str(), new_path.pathname().c_str()) != 0) {
246 if (errno != EXDEV)
247 return false;
248 if (!CopyFolder(old_path, new_path))
249 return false;
250 if (!DeleteFolderAndContents(old_path))
251 return false;
252 }
253 return true;
254}
255
256bool UnixFilesystem::IsFolder(const Pathname &path) {
257 struct stat st;
258 if (stat(path.pathname().c_str(), &st) < 0)
259 return false;
260 return S_ISDIR(st.st_mode);
261}
262
263bool UnixFilesystem::CopyFile(const Pathname &old_path,
264 const Pathname &new_path) {
265 LOG(LS_VERBOSE) << "Copying " << old_path.pathname()
266 << " to " << new_path.pathname();
267 char buf[256];
268 size_t len;
269
270 StreamInterface *source = OpenFile(old_path, "rb");
271 if (!source)
272 return false;
273
274 StreamInterface *dest = OpenFile(new_path, "wb");
275 if (!dest) {
276 delete source;
277 return false;
278 }
279
280 while (source->Read(buf, sizeof(buf), &len, NULL) == SR_SUCCESS)
281 dest->Write(buf, len, NULL, NULL);
282
283 delete source;
284 delete dest;
285 return true;
286}
287
288bool UnixFilesystem::IsTemporaryPath(const Pathname& pathname) {
289#if defined(ANDROID) || defined(IOS)
290 ASSERT(provided_app_temp_folder_ != NULL);
291#endif
292
293 const char* const kTempPrefixes[] = {
294#if defined(ANDROID) || defined(IOS)
295 provided_app_temp_folder_,
296#else
297 "/tmp/", "/var/tmp/",
298#ifdef OSX
299 "/private/tmp/", "/private/var/tmp/", "/private/var/folders/",
300#endif // OSX
301#endif // ANDROID || IOS
302 };
303 for (size_t i = 0; i < ARRAY_SIZE(kTempPrefixes); ++i) {
304 if (0 == strncmp(pathname.pathname().c_str(), kTempPrefixes[i],
305 strlen(kTempPrefixes[i])))
306 return true;
307 }
308 return false;
309}
310
311bool UnixFilesystem::IsFile(const Pathname& pathname) {
312 struct stat st;
313 int res = ::stat(pathname.pathname().c_str(), &st);
314 // Treat symlinks, named pipes, etc. all as files.
315 return res == 0 && !S_ISDIR(st.st_mode);
316}
317
318bool UnixFilesystem::IsAbsent(const Pathname& pathname) {
319 struct stat st;
320 int res = ::stat(pathname.pathname().c_str(), &st);
321 // Note: we specifically maintain ENOTDIR as an error, because that implies
322 // that you could not call CreateFolder(pathname).
323 return res != 0 && ENOENT == errno;
324}
325
326bool UnixFilesystem::GetFileSize(const Pathname& pathname, size_t *size) {
327 struct stat st;
328 if (::stat(pathname.pathname().c_str(), &st) != 0)
329 return false;
330 *size = st.st_size;
331 return true;
332}
333
334bool UnixFilesystem::GetFileTime(const Pathname& path, FileTimeType which,
335 time_t* time) {
336 struct stat st;
337 if (::stat(path.pathname().c_str(), &st) != 0)
338 return false;
339 switch (which) {
340 case FTT_CREATED:
341 *time = st.st_ctime;
342 break;
343 case FTT_MODIFIED:
344 *time = st.st_mtime;
345 break;
346 case FTT_ACCESSED:
347 *time = st.st_atime;
348 break;
349 default:
350 return false;
351 }
352 return true;
353}
354
355bool UnixFilesystem::GetAppPathname(Pathname* path) {
356#ifdef OSX
357 ProcessSerialNumber psn = { 0, kCurrentProcess };
358 CFDictionaryRef procinfo = ProcessInformationCopyDictionary(&psn,
359 kProcessDictionaryIncludeAllInformationMask);
360 if (NULL == procinfo)
361 return false;
362 CFStringRef cfpath = (CFStringRef) CFDictionaryGetValue(procinfo,
363 kIOBundleExecutableKey);
364 std::string path8;
365 bool success = ToUtf8(cfpath, &path8);
366 CFRelease(procinfo);
367 if (success)
368 path->SetPathname(path8);
369 return success;
henrika@webrtc.orgaebb1ad2014-01-14 10:00:58 +0000370#elif defined(__native_client__)
371 return false;
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000372#else // OSX
buildbot@webrtc.org504fc892014-04-23 03:23:19 +0000373 char buffer[PATH_MAX + 2];
374 ssize_t len = readlink("/proc/self/exe", buffer, ARRAY_SIZE(buffer) - 1);
375 if ((len <= 0) || (len == PATH_MAX + 1))
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000376 return false;
377 buffer[len] = '\0';
378 path->SetPathname(buffer);
379 return true;
380#endif // OSX
381}
382
383bool UnixFilesystem::GetAppDataFolder(Pathname* path, bool per_user) {
384 ASSERT(!organization_name_.empty());
385 ASSERT(!application_name_.empty());
386
387 // First get the base directory for app data.
388#ifdef OSX
389 if (per_user) {
390 // Use ~/Library/Application Support/<orgname>/<appname>/
391 FSRef fr;
392 if (0 != FSFindFolder(kUserDomain, kApplicationSupportFolderType,
393 kCreateFolder, &fr))
394 return false;
395 unsigned char buffer[NAME_MAX+1];
396 if (0 != FSRefMakePath(&fr, buffer, ARRAY_SIZE(buffer)))
397 return false;
398 path->SetPathname(reinterpret_cast<char*>(buffer), "");
399 } else {
400 // TODO
401 return false;
402 }
403#elif defined(ANDROID) || defined(IOS) // && !OSX
404 ASSERT(provided_app_data_folder_ != NULL);
405 path->SetPathname(provided_app_data_folder_, "");
406#elif defined(LINUX) // && !OSX && !defined(ANDROID) && !defined(IOS)
407 if (per_user) {
408 // We follow the recommendations in
409 // http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
410 // It specifies separate directories for data and config files, but
411 // GetAppDataFolder() does not distinguish. We just return the config dir
412 // path.
413 const char* xdg_config_home = getenv("XDG_CONFIG_HOME");
414 if (xdg_config_home) {
415 path->SetPathname(xdg_config_home, "");
416 } else {
417 // XDG says to default to $HOME/.config. We also support falling back to
418 // other synonyms for HOME if for some reason it is not defined.
419 const char* homedir;
420 if (const char* home = getenv("HOME")) {
421 homedir = home;
422 } else if (const char* dotdir = getenv("DOTDIR")) {
423 homedir = dotdir;
424 } else if (passwd* pw = getpwuid(geteuid())) {
425 homedir = pw->pw_dir;
426 } else {
427 return false;
428 }
429 path->SetPathname(homedir, "");
430 path->AppendFolder(".config");
431 }
432 } else {
433 // XDG does not define a standard directory for writable global data. Let's
434 // just use this.
435 path->SetPathname("/var/cache/", "");
436 }
437#endif // !OSX && !defined(ANDROID) && !defined(LINUX)
438
439 // Now add on a sub-path for our app.
440#if defined(OSX) || defined(ANDROID) || defined(IOS)
441 path->AppendFolder(organization_name_);
442 path->AppendFolder(application_name_);
443#elif defined(LINUX)
444 // XDG says to use a single directory level, so we concatenate the org and app
445 // name with a hyphen. We also do the Linuxy thing and convert to all
446 // lowercase with no spaces.
447 std::string subdir(organization_name_);
448 subdir.append("-");
449 subdir.append(application_name_);
450 replace_substrs(" ", 1, "", 0, &subdir);
451 std::transform(subdir.begin(), subdir.end(), subdir.begin(), ::tolower);
452 path->AppendFolder(subdir);
453#endif
454 if (!CreateFolder(*path, 0700)) {
455 return false;
456 }
henrika@webrtc.orgaebb1ad2014-01-14 10:00:58 +0000457#if !defined(__native_client__)
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000458 // If the folder already exists, it may have the wrong mode or be owned by
459 // someone else, both of which are security problems. Setting the mode
460 // avoids both issues since it will fail if the path is not owned by us.
461 if (0 != ::chmod(path->pathname().c_str(), 0700)) {
462 LOG_ERR(LS_ERROR) << "Can't set mode on " << path;
463 return false;
464 }
henrika@webrtc.orgaebb1ad2014-01-14 10:00:58 +0000465#endif
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000466 return true;
467}
468
469bool UnixFilesystem::GetAppTempFolder(Pathname* path) {
470#if defined(ANDROID) || defined(IOS)
471 ASSERT(provided_app_temp_folder_ != NULL);
472 path->SetPathname(provided_app_temp_folder_);
473 return true;
474#else
475 ASSERT(!application_name_.empty());
476 // TODO: Consider whether we are worried about thread safety.
477 if (app_temp_path_ != NULL && strlen(app_temp_path_) > 0) {
478 path->SetPathname(app_temp_path_);
479 return true;
480 }
481
482 // Create a random directory as /tmp/<appname>-<pid>-<timestamp>
483 char buffer[128];
484 sprintfn(buffer, ARRAY_SIZE(buffer), "-%d-%d",
485 static_cast<int>(getpid()),
486 static_cast<int>(time(0)));
487 std::string folder(application_name_);
488 folder.append(buffer);
489 if (!GetTemporaryFolder(*path, true, &folder))
490 return false;
491
492 delete [] app_temp_path_;
493 app_temp_path_ = CopyString(path->pathname());
494 // TODO: atexit(DeleteFolderAndContents(app_temp_path_));
495 return true;
496#endif
497}
498
499bool UnixFilesystem::GetDiskFreeSpace(const Pathname& path, int64 *freebytes) {
wu@webrtc.orgf6d6ed02014-01-03 22:08:47 +0000500#ifdef __native_client__
501 return false;
502#else // __native_client__
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000503 ASSERT(NULL != freebytes);
504 // TODO: Consider making relative paths absolute using cwd.
505 // TODO: When popping off a symlink, push back on the components of the
506 // symlink, so we don't jump out of the target disk inadvertently.
507 Pathname existing_path(path.folder(), "");
508 while (!existing_path.folder().empty() && IsAbsent(existing_path)) {
509 existing_path.SetFolder(existing_path.parent_folder());
510 }
511#ifdef ANDROID
512 struct statfs vfs;
513 memset(&vfs, 0, sizeof(vfs));
514 if (0 != statfs(existing_path.pathname().c_str(), &vfs))
515 return false;
516#else
517 struct statvfs vfs;
518 memset(&vfs, 0, sizeof(vfs));
519 if (0 != statvfs(existing_path.pathname().c_str(), &vfs))
520 return false;
521#endif // ANDROID
522#if defined(LINUX) || defined(ANDROID)
523 *freebytes = static_cast<int64>(vfs.f_bsize) * vfs.f_bavail;
524#elif defined(OSX)
525 *freebytes = static_cast<int64>(vfs.f_frsize) * vfs.f_bavail;
526#endif
527
528 return true;
wu@webrtc.orgf6d6ed02014-01-03 22:08:47 +0000529#endif // !__native_client__
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000530}
531
532Pathname UnixFilesystem::GetCurrentDirectory() {
533 Pathname cwd;
534 char buffer[PATH_MAX];
535 char *path = getcwd(buffer, PATH_MAX);
536
537 if (!path) {
538 LOG_ERR(LS_ERROR) << "getcwd() failed";
539 return cwd; // returns empty pathname
540 }
541 cwd.SetFolder(std::string(path));
542
543 return cwd;
544}
545
546char* UnixFilesystem::CopyString(const std::string& str) {
547 size_t size = str.length() + 1;
548
549 char* buf = new char[size];
550 if (!buf) {
551 return NULL;
552 }
553
554 strcpyn(buf, size, str.c_str());
555 return buf;
556}
557
558} // namespace talk_base
henrika@webrtc.orgaebb1ad2014-01-14 10:00:58 +0000559
560#if defined(__native_client__)
561extern "C" int __attribute__((weak))
562link(const char* oldpath, const char* newpath) {
563 errno = EACCES;
564 return -1;
565}
566#endif