forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			2691 lines
		
	
	
	
		
			67 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			2691 lines
		
	
	
	
		
			67 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | |
| /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| /**
 | |
|  * Implementation of nsIFile for "unixy" systems.
 | |
|  */
 | |
| 
 | |
| #include "mozilla/ArrayUtils.h"
 | |
| #include "mozilla/Attributes.h"
 | |
| #include "mozilla/DebugOnly.h"
 | |
| #include "mozilla/Sprintf.h"
 | |
| #include "mozilla/FilePreferences.h"
 | |
| #include "prtime.h"
 | |
| 
 | |
| #include <sys/fcntl.h>
 | |
| #include <sys/select.h>
 | |
| #include <sys/stat.h>
 | |
| #include <sys/time.h>
 | |
| #include <sys/types.h>
 | |
| #include <unistd.h>
 | |
| #include <fcntl.h>
 | |
| #include <errno.h>
 | |
| #include <utime.h>
 | |
| #include <dirent.h>
 | |
| #include <ctype.h>
 | |
| #include <locale.h>
 | |
| 
 | |
| #if defined(XP_MACOSX)
 | |
| #  include <sys/xattr.h>
 | |
| #endif
 | |
| 
 | |
| #if defined(HAVE_SYS_QUOTA_H) && defined(HAVE_LINUX_QUOTA_H)
 | |
| #  define USE_LINUX_QUOTACTL
 | |
| #  include <sys/mount.h>
 | |
| #  include <sys/quota.h>
 | |
| #  include <sys/sysmacros.h>
 | |
| #  ifndef BLOCK_SIZE
 | |
| #    define BLOCK_SIZE 1024 /* kernel block size */
 | |
| #  endif
 | |
| #endif
 | |
| 
 | |
| #include "nsDirectoryServiceDefs.h"
 | |
| #include "nsCRT.h"
 | |
| #include "nsCOMPtr.h"
 | |
| #include "nsMemory.h"
 | |
| #include "nsIFile.h"
 | |
| #include "nsString.h"
 | |
| #include "nsReadableUtils.h"
 | |
| #include "nsLocalFile.h"
 | |
| #include "prproces.h"
 | |
| #include "nsIDirectoryEnumerator.h"
 | |
| #include "nsSimpleEnumerator.h"
 | |
| #include "private/pprio.h"
 | |
| #include "prlink.h"
 | |
| 
 | |
| #ifdef MOZ_WIDGET_GTK
 | |
| #  include "nsIGIOService.h"
 | |
| #endif
 | |
| 
 | |
| #ifdef MOZ_WIDGET_COCOA
 | |
| #  include <Carbon/Carbon.h>
 | |
| #  include "CocoaFileUtils.h"
 | |
| #  include "prmem.h"
 | |
| #  include "plbase64.h"
 | |
| 
 | |
| static nsresult MacErrorMapper(OSErr inErr);
 | |
| #endif
 | |
| 
 | |
| #ifdef MOZ_WIDGET_ANDROID
 | |
| #  include "mozilla/java/GeckoAppShellWrappers.h"
 | |
| #  include "nsIMIMEService.h"
 | |
| #  include <linux/magic.h>
 | |
| #endif
 | |
| 
 | |
| #include "nsNativeCharsetUtils.h"
 | |
| #include "nsTraceRefcnt.h"
 | |
| #include "nsHashKeys.h"
 | |
| 
 | |
| /**
 | |
|  *  we need these for statfs()
 | |
|  */
 | |
| #ifdef HAVE_SYS_STATVFS_H
 | |
| #  if defined(__osf__) && defined(__DECCXX)
 | |
| extern "C" int statvfs(const char*, struct statvfs*);
 | |
| #  endif
 | |
| #  include <sys/statvfs.h>
 | |
| #endif
 | |
| 
 | |
| #ifdef HAVE_SYS_STATFS_H
 | |
| #  include <sys/statfs.h>
 | |
| #endif
 | |
| 
 | |
| #ifdef HAVE_SYS_VFS_H
 | |
| #  include <sys/vfs.h>
 | |
| #endif
 | |
| 
 | |
| #ifdef HAVE_SYS_MOUNT_H
 | |
| #  include <sys/param.h>
 | |
| #  include <sys/mount.h>
 | |
| #endif
 | |
| 
 | |
| #if defined(HAVE_STATVFS64) && (!defined(LINUX) && !defined(__osf__))
 | |
| #  define STATFS statvfs64
 | |
| #  define F_BSIZE f_frsize
 | |
| #elif defined(HAVE_STATVFS) && (!defined(LINUX) && !defined(__osf__))
 | |
| #  define STATFS statvfs
 | |
| #  define F_BSIZE f_frsize
 | |
| #elif defined(HAVE_STATFS64)
 | |
| #  define STATFS statfs64
 | |
| #  define F_BSIZE f_bsize
 | |
| #elif defined(HAVE_STATFS)
 | |
| #  define STATFS statfs
 | |
| #  define F_BSIZE f_bsize
 | |
| #endif
 | |
| 
 | |
| using namespace mozilla;
 | |
| 
 | |
| #define ENSURE_STAT_CACHE()                            \
 | |
|   do {                                                 \
 | |
|     if (!FillStatCache()) return NSRESULT_FOR_ERRNO(); \
 | |
|   } while (0)
 | |
| 
 | |
| #define CHECK_mPath()                                     \
 | |
|   do {                                                    \
 | |
|     if (mPath.IsEmpty()) return NS_ERROR_NOT_INITIALIZED; \
 | |
|     if (!FilePreferences::IsAllowedPath(mPath))           \
 | |
|       return NS_ERROR_FILE_ACCESS_DENIED;                 \
 | |
|   } while (0)
 | |
| 
 | |
| static PRTime TimespecToMillis(const struct timespec& aTimeSpec) {
 | |
|   return PRTime(aTimeSpec.tv_sec) * PR_MSEC_PER_SEC +
 | |
|          PRTime(aTimeSpec.tv_nsec) / PR_NSEC_PER_MSEC;
 | |
| }
 | |
| 
 | |
| /* directory enumerator */
 | |
| class nsDirEnumeratorUnix final : public nsSimpleEnumerator,
 | |
|                                   public nsIDirectoryEnumerator {
 | |
|  public:
 | |
|   nsDirEnumeratorUnix();
 | |
| 
 | |
|   // nsISupports interface
 | |
|   NS_DECL_ISUPPORTS_INHERITED
 | |
| 
 | |
|   // nsISimpleEnumerator interface
 | |
|   NS_DECL_NSISIMPLEENUMERATOR
 | |
| 
 | |
|   // nsIDirectoryEnumerator interface
 | |
|   NS_DECL_NSIDIRECTORYENUMERATOR
 | |
| 
 | |
|   NS_IMETHOD Init(nsLocalFile* aParent, bool aIgnored);
 | |
| 
 | |
|   NS_FORWARD_NSISIMPLEENUMERATORBASE(nsSimpleEnumerator::)
 | |
| 
 | |
|   const nsID& DefaultInterface() override { return NS_GET_IID(nsIFile); }
 | |
| 
 | |
|  private:
 | |
|   ~nsDirEnumeratorUnix() override;
 | |
| 
 | |
|  protected:
 | |
|   NS_IMETHOD GetNextEntry();
 | |
| 
 | |
|   DIR* mDir;
 | |
|   struct dirent* mEntry;
 | |
|   nsCString mParentPath;
 | |
| };
 | |
| 
 | |
| nsDirEnumeratorUnix::nsDirEnumeratorUnix() : mDir(nullptr), mEntry(nullptr) {}
 | |
| 
 | |
| nsDirEnumeratorUnix::~nsDirEnumeratorUnix() { Close(); }
 | |
| 
 | |
| NS_IMPL_ISUPPORTS_INHERITED(nsDirEnumeratorUnix, nsSimpleEnumerator,
 | |
|                             nsIDirectoryEnumerator)
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsDirEnumeratorUnix::Init(nsLocalFile* aParent,
 | |
|                           bool aResolveSymlinks /*ignored*/) {
 | |
|   nsAutoCString dirPath;
 | |
|   if (NS_FAILED(aParent->GetNativePath(dirPath)) || dirPath.IsEmpty()) {
 | |
|     return NS_ERROR_FILE_INVALID_PATH;
 | |
|   }
 | |
| 
 | |
|   // When enumerating the directory, the paths must have a slash at the end.
 | |
|   nsAutoCString dirPathWithSlash(dirPath);
 | |
|   dirPathWithSlash.Append('/');
 | |
|   if (!FilePreferences::IsAllowedPath(dirPathWithSlash)) {
 | |
|     return NS_ERROR_FILE_ACCESS_DENIED;
 | |
|   }
 | |
| 
 | |
|   if (NS_FAILED(aParent->GetNativePath(mParentPath))) {
 | |
|     return NS_ERROR_FAILURE;
 | |
|   }
 | |
| 
 | |
|   mDir = opendir(dirPath.get());
 | |
|   if (!mDir) {
 | |
|     return NSRESULT_FOR_ERRNO();
 | |
|   }
 | |
|   return GetNextEntry();
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsDirEnumeratorUnix::HasMoreElements(bool* aResult) {
 | |
|   *aResult = mDir && mEntry;
 | |
|   if (!*aResult) {
 | |
|     Close();
 | |
|   }
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsDirEnumeratorUnix::GetNext(nsISupports** aResult) {
 | |
|   nsCOMPtr<nsIFile> file;
 | |
|   nsresult rv = GetNextFile(getter_AddRefs(file));
 | |
|   if (NS_FAILED(rv)) {
 | |
|     return rv;
 | |
|   }
 | |
|   if (!file) {
 | |
|     return NS_ERROR_FAILURE;
 | |
|   }
 | |
|   file.forget(aResult);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsDirEnumeratorUnix::GetNextEntry() {
 | |
|   do {
 | |
|     errno = 0;
 | |
|     mEntry = readdir(mDir);
 | |
| 
 | |
|     // end of dir or error
 | |
|     if (!mEntry) {
 | |
|       return NSRESULT_FOR_ERRNO();
 | |
|     }
 | |
| 
 | |
|     // keep going past "." and ".."
 | |
|   } while (mEntry->d_name[0] == '.' &&
 | |
|            (mEntry->d_name[1] == '\0' ||                                // .\0
 | |
|             (mEntry->d_name[1] == '.' && mEntry->d_name[2] == '\0')));  // ..\0
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsDirEnumeratorUnix::GetNextFile(nsIFile** aResult) {
 | |
|   nsresult rv;
 | |
|   if (!mDir || !mEntry) {
 | |
|     *aResult = nullptr;
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIFile> file = new nsLocalFile();
 | |
| 
 | |
|   if (NS_FAILED(rv = file->InitWithNativePath(mParentPath)) ||
 | |
|       NS_FAILED(rv = file->AppendNative(nsDependentCString(mEntry->d_name)))) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   file.forget(aResult);
 | |
|   return GetNextEntry();
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsDirEnumeratorUnix::Close() {
 | |
|   if (mDir) {
 | |
|     closedir(mDir);
 | |
|     mDir = nullptr;
 | |
|   }
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsLocalFile::nsLocalFile() : mCachedStat() {}
 | |
| 
 | |
| nsLocalFile::nsLocalFile(const nsACString& aFilePath) : mCachedStat() {
 | |
|   InitWithNativePath(aFilePath);
 | |
| }
 | |
| 
 | |
| nsLocalFile::nsLocalFile(const nsLocalFile& aOther) : mPath(aOther.mPath) {}
 | |
| 
 | |
| #ifdef MOZ_WIDGET_COCOA
 | |
| NS_IMPL_ISUPPORTS(nsLocalFile, nsILocalFileMac, nsIFile)
 | |
| #else
 | |
| NS_IMPL_ISUPPORTS(nsLocalFile, nsIFile)
 | |
| #endif
 | |
| 
 | |
| nsresult nsLocalFile::nsLocalFileConstructor(nsISupports* aOuter,
 | |
|                                              const nsIID& aIID,
 | |
|                                              void** aInstancePtr) {
 | |
|   if (NS_WARN_IF(!aInstancePtr)) {
 | |
|     return NS_ERROR_INVALID_ARG;
 | |
|   }
 | |
|   if (NS_WARN_IF(aOuter)) {
 | |
|     return NS_ERROR_NO_AGGREGATION;
 | |
|   }
 | |
| 
 | |
|   *aInstancePtr = nullptr;
 | |
| 
 | |
|   nsCOMPtr<nsIFile> inst = new nsLocalFile();
 | |
|   return inst->QueryInterface(aIID, aInstancePtr);
 | |
| }
 | |
| 
 | |
| bool nsLocalFile::FillStatCache() {
 | |
|   if (!FilePreferences::IsAllowedPath(mPath)) {
 | |
|     errno = EACCES;
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   if (STAT(mPath.get(), &mCachedStat) == -1) {
 | |
|     // try lstat it may be a symlink
 | |
|     if (LSTAT(mPath.get(), &mCachedStat) == -1) {
 | |
|       return false;
 | |
|     }
 | |
|   }
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::Clone(nsIFile** aFile) {
 | |
|   // Just copy-construct ourselves
 | |
|   RefPtr<nsLocalFile> copy = new nsLocalFile(*this);
 | |
|   copy.forget(aFile);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::InitWithNativePath(const nsACString& aFilePath) {
 | |
|   if (aFilePath.EqualsLiteral("~") ||
 | |
|       Substring(aFilePath, 0, 2).EqualsLiteral("~/")) {
 | |
|     nsCOMPtr<nsIFile> homeDir;
 | |
|     nsAutoCString homePath;
 | |
|     if (NS_FAILED(
 | |
|             NS_GetSpecialDirectory(NS_OS_HOME_DIR, getter_AddRefs(homeDir))) ||
 | |
|         NS_FAILED(homeDir->GetNativePath(homePath))) {
 | |
|       return NS_ERROR_FAILURE;
 | |
|     }
 | |
| 
 | |
|     mPath = homePath;
 | |
|     if (aFilePath.Length() > 2) {
 | |
|       mPath.Append(Substring(aFilePath, 1, aFilePath.Length() - 1));
 | |
|     }
 | |
|   } else {
 | |
|     if (aFilePath.IsEmpty() || aFilePath.First() != '/') {
 | |
|       return NS_ERROR_FILE_UNRECOGNIZED_PATH;
 | |
|     }
 | |
|     mPath = aFilePath;
 | |
|   }
 | |
| 
 | |
|   if (!FilePreferences::IsAllowedPath(mPath)) {
 | |
|     mPath.Truncate();
 | |
|     return NS_ERROR_FILE_ACCESS_DENIED;
 | |
|   }
 | |
| 
 | |
|   // trim off trailing slashes
 | |
|   ssize_t len = mPath.Length();
 | |
|   while ((len > 1) && (mPath[len - 1] == '/')) {
 | |
|     --len;
 | |
|   }
 | |
|   mPath.SetLength(len);
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::CreateAllAncestors(uint32_t aPermissions) {
 | |
|   if (!FilePreferences::IsAllowedPath(mPath)) {
 | |
|     return NS_ERROR_FILE_ACCESS_DENIED;
 | |
|   }
 | |
| 
 | |
|   // <jband> I promise to play nice
 | |
|   char* buffer = mPath.BeginWriting();
 | |
|   char* slashp = buffer;
 | |
|   int mkdir_result = 0;
 | |
|   int mkdir_errno;
 | |
| 
 | |
| #ifdef DEBUG_NSIFILE
 | |
|   fprintf(stderr, "nsIFile: before: %s\n", buffer);
 | |
| #endif
 | |
| 
 | |
|   while ((slashp = strchr(slashp + 1, '/'))) {
 | |
|     /*
 | |
|      * Sequences of '/' are equivalent to a single '/'.
 | |
|      */
 | |
|     if (slashp[1] == '/') {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     /*
 | |
|      * If the path has a trailing slash, don't make the last component,
 | |
|      * because we'll get EEXIST in Create when we try to build the final
 | |
|      * component again, and it's easier to condition the logic here than
 | |
|      * there.
 | |
|      */
 | |
|     if (slashp[1] == '\0') {
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|     /* Temporarily NUL-terminate here */
 | |
|     *slashp = '\0';
 | |
| #ifdef DEBUG_NSIFILE
 | |
|     fprintf(stderr, "nsIFile: mkdir(\"%s\")\n", buffer);
 | |
| #endif
 | |
|     mkdir_result = mkdir(buffer, aPermissions);
 | |
|     if (mkdir_result == -1) {
 | |
|       mkdir_errno = errno;
 | |
|       /*
 | |
|        * Always set |errno| to EEXIST if the dir already exists
 | |
|        * (we have to do this here since the errno value is not consistent
 | |
|        * in all cases - various reasons like different platform,
 | |
|        * automounter-controlled dir, etc. can affect it (see bug 125489
 | |
|        * for details)).
 | |
|        */
 | |
|       if (mkdir_errno != EEXIST && access(buffer, F_OK) == 0) {
 | |
|         mkdir_errno = EEXIST;
 | |
|       }
 | |
| #ifdef DEBUG_NSIFILE
 | |
|       fprintf(stderr, "nsIFile: errno: %d\n", mkdir_errno);
 | |
| #endif
 | |
|     }
 | |
| 
 | |
|     /* Put the / back */
 | |
|     *slashp = '/';
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|    * We could get EEXIST for an existing file -- not directory --
 | |
|    * but that's OK: we'll get ENOTDIR when we try to make the final
 | |
|    * component of the path back in Create and error out appropriately.
 | |
|    */
 | |
|   if (mkdir_result == -1 && mkdir_errno != EEXIST) {
 | |
|     return NS_ERROR_FAILURE;
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::OpenNSPRFileDesc(int32_t aFlags, int32_t aMode,
 | |
|                               PRFileDesc** aResult) {
 | |
|   if (!FilePreferences::IsAllowedPath(mPath)) {
 | |
|     return NS_ERROR_FILE_ACCESS_DENIED;
 | |
|   }
 | |
|   *aResult = PR_Open(mPath.get(), aFlags, aMode);
 | |
|   if (!*aResult) {
 | |
|     return NS_ErrorAccordingToNSPR();
 | |
|   }
 | |
| 
 | |
|   if (aFlags & DELETE_ON_CLOSE) {
 | |
|     PR_Delete(mPath.get());
 | |
|   }
 | |
| 
 | |
| #if defined(HAVE_POSIX_FADVISE)
 | |
|   if (aFlags & OS_READAHEAD) {
 | |
|     posix_fadvise(PR_FileDesc2NativeHandle(*aResult), 0, 0,
 | |
|                   POSIX_FADV_SEQUENTIAL);
 | |
|   }
 | |
| #endif
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::OpenANSIFileDesc(const char* aMode, FILE** aResult) {
 | |
|   if (!FilePreferences::IsAllowedPath(mPath)) {
 | |
|     return NS_ERROR_FILE_ACCESS_DENIED;
 | |
|   }
 | |
|   *aResult = fopen(mPath.get(), aMode);
 | |
|   if (!*aResult) {
 | |
|     return NS_ERROR_FAILURE;
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| static int do_create(const char* aPath, int aFlags, mode_t aMode,
 | |
|                      PRFileDesc** aResult) {
 | |
|   *aResult = PR_Open(aPath, aFlags, aMode);
 | |
|   return *aResult ? 0 : -1;
 | |
| }
 | |
| 
 | |
| static int do_mkdir(const char* aPath, int aFlags, mode_t aMode,
 | |
|                     PRFileDesc** aResult) {
 | |
|   *aResult = nullptr;
 | |
|   return mkdir(aPath, aMode);
 | |
| }
 | |
| 
 | |
| nsresult nsLocalFile::CreateAndKeepOpen(uint32_t aType, int aFlags,
 | |
|                                         uint32_t aPermissions,
 | |
|                                         bool aSkipAncestors,
 | |
|                                         PRFileDesc** aResult) {
 | |
|   if (!FilePreferences::IsAllowedPath(mPath)) {
 | |
|     return NS_ERROR_FILE_ACCESS_DENIED;
 | |
|   }
 | |
| 
 | |
|   if (aType != NORMAL_FILE_TYPE && aType != DIRECTORY_TYPE) {
 | |
|     return NS_ERROR_FILE_UNKNOWN_TYPE;
 | |
|   }
 | |
| 
 | |
|   int (*createFunc)(const char*, int, mode_t, PRFileDesc**) =
 | |
|       (aType == NORMAL_FILE_TYPE) ? do_create : do_mkdir;
 | |
| 
 | |
|   int result = createFunc(mPath.get(), aFlags, aPermissions, aResult);
 | |
|   if (result == -1 && errno == ENOENT && !aSkipAncestors) {
 | |
|     /*
 | |
|      * If we failed because of missing ancestor components, try to create
 | |
|      * them and then retry the original creation.
 | |
|      *
 | |
|      * Ancestor directories get the same permissions as the file we're
 | |
|      * creating, with the X bit set for each of (user,group,other) with
 | |
|      * an R bit in the original permissions.    If you want to do anything
 | |
|      * fancy like setgid or sticky bits, do it by hand.
 | |
|      */
 | |
|     int dirperm = aPermissions;
 | |
|     if (aPermissions & S_IRUSR) {
 | |
|       dirperm |= S_IXUSR;
 | |
|     }
 | |
|     if (aPermissions & S_IRGRP) {
 | |
|       dirperm |= S_IXGRP;
 | |
|     }
 | |
|     if (aPermissions & S_IROTH) {
 | |
|       dirperm |= S_IXOTH;
 | |
|     }
 | |
| 
 | |
| #ifdef DEBUG_NSIFILE
 | |
|     fprintf(stderr, "nsIFile: perm = %o, dirperm = %o\n", aPermissions,
 | |
|             dirperm);
 | |
| #endif
 | |
| 
 | |
|     if (NS_FAILED(CreateAllAncestors(dirperm))) {
 | |
|       return NS_ERROR_FAILURE;
 | |
|     }
 | |
| 
 | |
| #ifdef DEBUG_NSIFILE
 | |
|     fprintf(stderr, "nsIFile: Create(\"%s\") again\n", mPath.get());
 | |
| #endif
 | |
|     result = createFunc(mPath.get(), aFlags, aPermissions, aResult);
 | |
|   }
 | |
|   return NSRESULT_FOR_RETURN(result);
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::Create(uint32_t aType, uint32_t aPermissions,
 | |
|                     bool aSkipAncestors) {
 | |
|   if (!FilePreferences::IsAllowedPath(mPath)) {
 | |
|     return NS_ERROR_FILE_ACCESS_DENIED;
 | |
|   }
 | |
| 
 | |
|   PRFileDesc* junk = nullptr;
 | |
|   nsresult rv = CreateAndKeepOpen(
 | |
|       aType, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE | PR_EXCL, aPermissions,
 | |
|       aSkipAncestors, &junk);
 | |
|   if (junk) {
 | |
|     PR_Close(junk);
 | |
|   }
 | |
|   return rv;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::AppendNative(const nsACString& aFragment) {
 | |
|   if (aFragment.IsEmpty()) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   // only one component of path can be appended
 | |
|   nsACString::const_iterator begin, end;
 | |
|   if (FindCharInReadable('/', aFragment.BeginReading(begin),
 | |
|                          aFragment.EndReading(end))) {
 | |
|     return NS_ERROR_FILE_UNRECOGNIZED_PATH;
 | |
|   }
 | |
| 
 | |
|   return AppendRelativeNativePath(aFragment);
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::AppendRelativeNativePath(const nsACString& aFragment) {
 | |
|   if (aFragment.IsEmpty()) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   // No leading '/'
 | |
|   if (aFragment.First() == '/') {
 | |
|     return NS_ERROR_FILE_UNRECOGNIZED_PATH;
 | |
|   }
 | |
| 
 | |
|   if (!mPath.EqualsLiteral("/")) {
 | |
|     mPath.Append('/');
 | |
|   }
 | |
|   mPath.Append(aFragment);
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::Normalize() {
 | |
|   char resolved_path[PATH_MAX] = "";
 | |
|   char* resolved_path_ptr = nullptr;
 | |
| 
 | |
|   if (!FilePreferences::IsAllowedPath(mPath)) {
 | |
|     return NS_ERROR_FILE_ACCESS_DENIED;
 | |
|   }
 | |
| 
 | |
|   resolved_path_ptr = realpath(mPath.get(), resolved_path);
 | |
| 
 | |
|   // if there is an error, the return is null.
 | |
|   if (!resolved_path_ptr) {
 | |
|     return NSRESULT_FOR_ERRNO();
 | |
|   }
 | |
| 
 | |
|   mPath = resolved_path;
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void nsLocalFile::LocateNativeLeafName(nsACString::const_iterator& aBegin,
 | |
|                                        nsACString::const_iterator& aEnd) {
 | |
|   // XXX perhaps we should cache this??
 | |
| 
 | |
|   mPath.BeginReading(aBegin);
 | |
|   mPath.EndReading(aEnd);
 | |
| 
 | |
|   nsACString::const_iterator it = aEnd;
 | |
|   nsACString::const_iterator stop = aBegin;
 | |
|   --stop;
 | |
|   while (--it != stop) {
 | |
|     if (*it == '/') {
 | |
|       aBegin = ++it;
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
|   // else, the entire path is the leaf name (which means this
 | |
|   // isn't an absolute path... unexpected??)
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::GetNativeLeafName(nsACString& aLeafName) {
 | |
|   nsACString::const_iterator begin, end;
 | |
|   LocateNativeLeafName(begin, end);
 | |
|   aLeafName = Substring(begin, end);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::SetNativeLeafName(const nsACString& aLeafName) {
 | |
|   nsACString::const_iterator begin, end;
 | |
|   LocateNativeLeafName(begin, end);
 | |
|   mPath.Replace(begin.get() - mPath.get(), Distance(begin, end), aLeafName);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsCString nsLocalFile::NativePath() { return mPath; }
 | |
| 
 | |
| nsresult nsIFile::GetNativePath(nsACString& aResult) {
 | |
|   aResult = NativePath();
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsCString nsIFile::HumanReadablePath() {
 | |
|   nsCString path;
 | |
|   DebugOnly<nsresult> rv = GetNativePath(path);
 | |
|   MOZ_ASSERT(NS_SUCCEEDED(rv));
 | |
|   return path;
 | |
| }
 | |
| 
 | |
| nsresult nsLocalFile::GetNativeTargetPathName(nsIFile* aNewParent,
 | |
|                                               const nsACString& aNewName,
 | |
|                                               nsACString& aResult) {
 | |
|   nsresult rv;
 | |
|   nsCOMPtr<nsIFile> oldParent;
 | |
| 
 | |
|   if (!aNewParent) {
 | |
|     if (NS_FAILED(rv = GetParent(getter_AddRefs(oldParent)))) {
 | |
|       return rv;
 | |
|     }
 | |
|     aNewParent = oldParent.get();
 | |
|   } else {
 | |
|     // check to see if our target directory exists
 | |
|     bool targetExists;
 | |
|     if (NS_FAILED(rv = aNewParent->Exists(&targetExists))) {
 | |
|       return rv;
 | |
|     }
 | |
| 
 | |
|     if (!targetExists) {
 | |
|       // XXX create the new directory with some permissions
 | |
|       rv = aNewParent->Create(DIRECTORY_TYPE, 0755);
 | |
|       if (NS_FAILED(rv)) {
 | |
|         return rv;
 | |
|       }
 | |
|     } else {
 | |
|       // make sure that the target is actually a directory
 | |
|       bool targetIsDirectory;
 | |
|       if (NS_FAILED(rv = aNewParent->IsDirectory(&targetIsDirectory))) {
 | |
|         return rv;
 | |
|       }
 | |
|       if (!targetIsDirectory) {
 | |
|         return NS_ERROR_FILE_DESTINATION_NOT_DIR;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   nsACString::const_iterator nameBegin, nameEnd;
 | |
|   if (!aNewName.IsEmpty()) {
 | |
|     aNewName.BeginReading(nameBegin);
 | |
|     aNewName.EndReading(nameEnd);
 | |
|   } else {
 | |
|     LocateNativeLeafName(nameBegin, nameEnd);
 | |
|   }
 | |
| 
 | |
|   nsAutoCString dirName;
 | |
|   if (NS_FAILED(rv = aNewParent->GetNativePath(dirName))) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   aResult = dirName + "/"_ns + Substring(nameBegin, nameEnd);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult nsLocalFile::CopyDirectoryTo(nsIFile* aNewParent) {
 | |
|   nsresult rv;
 | |
|   /*
 | |
|    * dirCheck is used for various boolean test results such as from Equals,
 | |
|    * Exists, isDir, etc.
 | |
|    */
 | |
|   bool dirCheck, isSymlink;
 | |
|   uint32_t oldPerms;
 | |
| 
 | |
|   if (NS_FAILED(rv = IsDirectory(&dirCheck))) {
 | |
|     return rv;
 | |
|   }
 | |
|   if (!dirCheck) {
 | |
|     return CopyToNative(aNewParent, ""_ns);
 | |
|   }
 | |
| 
 | |
|   if (NS_FAILED(rv = Equals(aNewParent, &dirCheck))) {
 | |
|     return rv;
 | |
|   }
 | |
|   if (dirCheck) {
 | |
|     // can't copy dir to itself
 | |
|     return NS_ERROR_INVALID_ARG;
 | |
|   }
 | |
| 
 | |
|   if (NS_FAILED(rv = aNewParent->Exists(&dirCheck))) {
 | |
|     return rv;
 | |
|   }
 | |
|   // get the dirs old permissions
 | |
|   if (NS_FAILED(rv = GetPermissions(&oldPerms))) {
 | |
|     return rv;
 | |
|   }
 | |
|   if (!dirCheck) {
 | |
|     if (NS_FAILED(rv = aNewParent->Create(DIRECTORY_TYPE, oldPerms))) {
 | |
|       return rv;
 | |
|     }
 | |
|   } else {  // dir exists lets try to use leaf
 | |
|     nsAutoCString leafName;
 | |
|     if (NS_FAILED(rv = GetNativeLeafName(leafName))) {
 | |
|       return rv;
 | |
|     }
 | |
|     if (NS_FAILED(rv = aNewParent->AppendNative(leafName))) {
 | |
|       return rv;
 | |
|     }
 | |
|     if (NS_FAILED(rv = aNewParent->Exists(&dirCheck))) {
 | |
|       return rv;
 | |
|     }
 | |
|     if (dirCheck) {
 | |
|       return NS_ERROR_FILE_ALREADY_EXISTS;  // dest exists
 | |
|     }
 | |
|     if (NS_FAILED(rv = aNewParent->Create(DIRECTORY_TYPE, oldPerms))) {
 | |
|       return rv;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIDirectoryEnumerator> dirIterator;
 | |
|   if (NS_FAILED(rv = GetDirectoryEntries(getter_AddRefs(dirIterator)))) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIFile> entry;
 | |
|   while (NS_SUCCEEDED(dirIterator->GetNextFile(getter_AddRefs(entry))) &&
 | |
|          entry) {
 | |
|     if (NS_FAILED(rv = entry->IsSymlink(&isSymlink))) {
 | |
|       return rv;
 | |
|     }
 | |
|     if (NS_FAILED(rv = entry->IsDirectory(&dirCheck))) {
 | |
|       return rv;
 | |
|     }
 | |
|     if (dirCheck && !isSymlink) {
 | |
|       nsCOMPtr<nsIFile> destClone;
 | |
|       rv = aNewParent->Clone(getter_AddRefs(destClone));
 | |
|       if (NS_SUCCEEDED(rv)) {
 | |
|         if (NS_FAILED(rv = entry->CopyToNative(destClone, ""_ns))) {
 | |
| #ifdef DEBUG
 | |
|           nsresult rv2;
 | |
|           nsAutoCString pathName;
 | |
|           if (NS_FAILED(rv2 = entry->GetNativePath(pathName))) {
 | |
|             return rv2;
 | |
|           }
 | |
|           printf("Operation not supported: %s\n", pathName.get());
 | |
| #endif
 | |
|           if (rv == NS_ERROR_OUT_OF_MEMORY) {
 | |
|             return rv;
 | |
|           }
 | |
|           continue;
 | |
|         }
 | |
|       }
 | |
|     } else {
 | |
|       if (NS_FAILED(rv = entry->CopyToNative(aNewParent, ""_ns))) {
 | |
| #ifdef DEBUG
 | |
|         nsresult rv2;
 | |
|         nsAutoCString pathName;
 | |
|         if (NS_FAILED(rv2 = entry->GetNativePath(pathName))) {
 | |
|           return rv2;
 | |
|         }
 | |
|         printf("Operation not supported: %s\n", pathName.get());
 | |
| #endif
 | |
|         if (rv == NS_ERROR_OUT_OF_MEMORY) {
 | |
|           return rv;
 | |
|         }
 | |
|         continue;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::CopyToNative(nsIFile* aNewParent, const nsACString& aNewName) {
 | |
|   nsresult rv;
 | |
|   // check to make sure that this has been initialized properly
 | |
|   CHECK_mPath();
 | |
| 
 | |
|   // we copy the parent here so 'aNewParent' remains immutable
 | |
|   nsCOMPtr<nsIFile> workParent;
 | |
|   if (aNewParent) {
 | |
|     if (NS_FAILED(rv = aNewParent->Clone(getter_AddRefs(workParent)))) {
 | |
|       return rv;
 | |
|     }
 | |
|   } else {
 | |
|     if (NS_FAILED(rv = GetParent(getter_AddRefs(workParent)))) {
 | |
|       return rv;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // check to see if we are a directory or if we are a file
 | |
|   bool isDirectory;
 | |
|   if (NS_FAILED(rv = IsDirectory(&isDirectory))) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   nsAutoCString newPathName;
 | |
|   if (isDirectory) {
 | |
|     if (!aNewName.IsEmpty()) {
 | |
|       if (NS_FAILED(rv = workParent->AppendNative(aNewName))) {
 | |
|         return rv;
 | |
|       }
 | |
|     } else {
 | |
|       if (NS_FAILED(rv = GetNativeLeafName(newPathName))) {
 | |
|         return rv;
 | |
|       }
 | |
|       if (NS_FAILED(rv = workParent->AppendNative(newPathName))) {
 | |
|         return rv;
 | |
|       }
 | |
|     }
 | |
|     if (NS_FAILED(rv = CopyDirectoryTo(workParent))) {
 | |
|       return rv;
 | |
|     }
 | |
|   } else {
 | |
|     rv = GetNativeTargetPathName(workParent, aNewName, newPathName);
 | |
|     if (NS_FAILED(rv)) {
 | |
|       return rv;
 | |
|     }
 | |
| 
 | |
| #ifdef DEBUG_blizzard
 | |
|     printf("nsLocalFile::CopyTo() %s -> %s\n", mPath.get(), newPathName.get());
 | |
| #endif
 | |
| 
 | |
|     // actually create the file.
 | |
|     auto* newFile = new nsLocalFile();
 | |
|     nsCOMPtr<nsIFile> fileRef(newFile);  // release on exit
 | |
| 
 | |
|     rv = newFile->InitWithNativePath(newPathName);
 | |
|     if (NS_FAILED(rv)) {
 | |
|       return rv;
 | |
|     }
 | |
| 
 | |
|     // get the old permissions
 | |
|     uint32_t myPerms = 0;
 | |
|     rv = GetPermissions(&myPerms);
 | |
|     if (NS_FAILED(rv)) {
 | |
|       return rv;
 | |
|     }
 | |
| 
 | |
|     // Create the new file with the old file's permissions, even if write
 | |
|     // permission is missing.  We can't create with write permission and
 | |
|     // then change back to myPerm on all filesystems (FAT on Linux, e.g.).
 | |
|     // But we can write to a read-only file on all Unix filesystems if we
 | |
|     // open it successfully for writing.
 | |
| 
 | |
|     PRFileDesc* newFD;
 | |
|     rv = newFile->CreateAndKeepOpen(
 | |
|         NORMAL_FILE_TYPE, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, myPerms,
 | |
|         /* aSkipAncestors = */ false, &newFD);
 | |
|     if (NS_FAILED(rv)) {
 | |
|       return rv;
 | |
|     }
 | |
| 
 | |
|     // open the old file, too
 | |
|     bool specialFile;
 | |
|     if (NS_FAILED(rv = IsSpecial(&specialFile))) {
 | |
|       PR_Close(newFD);
 | |
|       return rv;
 | |
|     }
 | |
|     if (specialFile) {
 | |
| #ifdef DEBUG
 | |
|       printf("Operation not supported: %s\n", mPath.get());
 | |
| #endif
 | |
|       // make sure to clean up properly
 | |
|       PR_Close(newFD);
 | |
|       return NS_OK;
 | |
|     }
 | |
| 
 | |
| #if defined(XP_MACOSX)
 | |
|     bool quarantined = true;
 | |
|     if (getxattr(mPath.get(), "com.apple.quarantine", nullptr, 0, 0, 0) == -1) {
 | |
|       if (errno == ENOATTR) {
 | |
|         quarantined = false;
 | |
|       }
 | |
|     }
 | |
| #endif
 | |
| 
 | |
|     PRFileDesc* oldFD;
 | |
|     rv = OpenNSPRFileDesc(PR_RDONLY, myPerms, &oldFD);
 | |
|     if (NS_FAILED(rv)) {
 | |
|       // make sure to clean up properly
 | |
|       PR_Close(newFD);
 | |
|       return rv;
 | |
|     }
 | |
| 
 | |
| #ifdef DEBUG_blizzard
 | |
|     int32_t totalRead = 0;
 | |
|     int32_t totalWritten = 0;
 | |
| #endif
 | |
|     char buf[BUFSIZ];
 | |
|     int32_t bytesRead;
 | |
| 
 | |
|     // record PR_Write() error for better error message later.
 | |
|     nsresult saved_write_error = NS_OK;
 | |
|     nsresult saved_read_error = NS_OK;
 | |
|     nsresult saved_read_close_error = NS_OK;
 | |
|     nsresult saved_write_close_error = NS_OK;
 | |
| 
 | |
|     // DONE: Does PR_Read() return bytesRead < 0 for error?
 | |
|     // Yes., The errors from PR_Read are not so common and
 | |
|     // the value may not have correspondence in NS_ERROR_*, but
 | |
|     // we do catch it still, immediately after while() loop.
 | |
|     // We can differentiate errors pf PR_Read and PR_Write by
 | |
|     // looking at saved_write_error value. If PR_Write error occurs (and not
 | |
|     // PR_Read() error), save_write_error is not NS_OK.
 | |
| 
 | |
|     while ((bytesRead = PR_Read(oldFD, buf, BUFSIZ)) > 0) {
 | |
| #ifdef DEBUG_blizzard
 | |
|       totalRead += bytesRead;
 | |
| #endif
 | |
| 
 | |
|       // PR_Write promises never to do a short write
 | |
|       int32_t bytesWritten = PR_Write(newFD, buf, bytesRead);
 | |
|       if (bytesWritten < 0) {
 | |
|         saved_write_error = NSRESULT_FOR_ERRNO();
 | |
|         bytesRead = -1;
 | |
|         break;
 | |
|       }
 | |
|       NS_ASSERTION(bytesWritten == bytesRead, "short PR_Write?");
 | |
| 
 | |
| #ifdef DEBUG_blizzard
 | |
|       totalWritten += bytesWritten;
 | |
| #endif
 | |
|     }
 | |
| 
 | |
|     // TODO/FIXME: If CIFS (and NFS?) may force read/write to return EINTR,
 | |
|     // we are better off to prepare for retrying. But we need confirmation if
 | |
|     // EINTR is returned.
 | |
| 
 | |
|     // Record error if PR_Read() failed.
 | |
|     // Must be done before any other I/O which may reset errno.
 | |
|     if (bytesRead < 0 && saved_write_error == NS_OK) {
 | |
|       saved_read_error = NSRESULT_FOR_ERRNO();
 | |
|     }
 | |
| 
 | |
| #ifdef DEBUG_blizzard
 | |
|     printf("read %d bytes, wrote %d bytes\n", totalRead, totalWritten);
 | |
| #endif
 | |
| 
 | |
|     // DONE: Errors of close can occur.  Read man page of
 | |
|     // close(2);
 | |
|     // This is likely to happen if the file system is remote file
 | |
|     // system (NFS, CIFS, etc.) and network outage occurs.
 | |
|     // At least, we should tell the user that filesystem/disk is
 | |
|     // hosed (possibly due to network error, hard disk failure,
 | |
|     // etc.) so that users can take remedial action.
 | |
| 
 | |
|     // close the files
 | |
|     if (PR_Close(newFD) < 0) {
 | |
|       saved_write_close_error = NSRESULT_FOR_ERRNO();
 | |
| #if DEBUG
 | |
|       // This error merits printing.
 | |
|       fprintf(stderr, "ERROR: PR_Close(newFD) returned error. errno = %d\n",
 | |
|               errno);
 | |
| #endif
 | |
|     }
 | |
| #if defined(XP_MACOSX)
 | |
|     else if (!quarantined) {
 | |
|       // If the original file was not in quarantine, lift the quarantine that
 | |
|       // file creation added because of LSFileQuarantineEnabled.
 | |
|       removexattr(newPathName.get(), "com.apple.quarantine", 0);
 | |
|     }
 | |
| #endif  // defined(XP_MACOSX)
 | |
| 
 | |
|     if (PR_Close(oldFD) < 0) {
 | |
|       saved_read_close_error = NSRESULT_FOR_ERRNO();
 | |
| #if DEBUG
 | |
|       fprintf(stderr, "ERROR: PR_Close(oldFD) returned error. errno = %d\n",
 | |
|               errno);
 | |
| #endif
 | |
|     }
 | |
| 
 | |
|     // Let us report the failure to write and read.
 | |
|     // check for write/read error after cleaning up
 | |
|     if (bytesRead < 0) {
 | |
|       if (saved_write_error != NS_OK) {
 | |
|         return saved_write_error;
 | |
|       }
 | |
|       if (saved_read_error != NS_OK) {
 | |
|         return saved_read_error;
 | |
|       }
 | |
| #if DEBUG
 | |
|       MOZ_ASSERT(0);
 | |
| #endif
 | |
|     }
 | |
| 
 | |
|     if (saved_write_close_error != NS_OK) {
 | |
|       return saved_write_close_error;
 | |
|     }
 | |
|     if (saved_read_close_error != NS_OK) {
 | |
|       return saved_read_close_error;
 | |
|     }
 | |
|   }
 | |
|   return rv;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::CopyToFollowingLinksNative(nsIFile* aNewParent,
 | |
|                                         const nsACString& aNewName) {
 | |
|   return CopyToNative(aNewParent, aNewName);
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::MoveToNative(nsIFile* aNewParent, const nsACString& aNewName) {
 | |
|   nsresult rv;
 | |
| 
 | |
|   // check to make sure that this has been initialized properly
 | |
|   CHECK_mPath();
 | |
| 
 | |
|   // check to make sure that we have a new parent
 | |
|   nsAutoCString newPathName;
 | |
|   rv = GetNativeTargetPathName(aNewParent, aNewName, newPathName);
 | |
|   if (NS_FAILED(rv)) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   if (!FilePreferences::IsAllowedPath(newPathName)) {
 | |
|     return NS_ERROR_FILE_ACCESS_DENIED;
 | |
|   }
 | |
| 
 | |
|   // try for atomic rename, falling back to copy/delete
 | |
|   if (rename(mPath.get(), newPathName.get()) < 0) {
 | |
|     if (errno == EXDEV) {
 | |
|       rv = CopyToNative(aNewParent, aNewName);
 | |
|       if (NS_SUCCEEDED(rv)) {
 | |
|         rv = Remove(true);
 | |
|       }
 | |
|     } else {
 | |
|       rv = NSRESULT_FOR_ERRNO();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (NS_SUCCEEDED(rv)) {
 | |
|     // Adjust this
 | |
|     mPath = newPathName;
 | |
|   }
 | |
|   return rv;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::MoveToFollowingLinksNative(nsIFile* aNewParent,
 | |
|                                         const nsACString& aNewName) {
 | |
|   return MoveToNative(aNewParent, aNewName);
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::Remove(bool aRecursive) {
 | |
|   CHECK_mPath();
 | |
|   ENSURE_STAT_CACHE();
 | |
| 
 | |
|   bool isSymLink;
 | |
| 
 | |
|   nsresult rv = IsSymlink(&isSymLink);
 | |
|   if (NS_FAILED(rv)) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   if (isSymLink || !S_ISDIR(mCachedStat.st_mode)) {
 | |
|     return NSRESULT_FOR_RETURN(unlink(mPath.get()));
 | |
|   }
 | |
| 
 | |
|   if (aRecursive) {
 | |
|     auto* dir = new nsDirEnumeratorUnix();
 | |
| 
 | |
|     RefPtr<nsSimpleEnumerator> dirRef(dir);  // release on exit
 | |
| 
 | |
|     rv = dir->Init(this, false);
 | |
|     if (NS_FAILED(rv)) {
 | |
|       return rv;
 | |
|     }
 | |
| 
 | |
|     bool more;
 | |
|     while (NS_SUCCEEDED(dir->HasMoreElements(&more)) && more) {
 | |
|       nsCOMPtr<nsISupports> item;
 | |
|       rv = dir->GetNext(getter_AddRefs(item));
 | |
|       if (NS_FAILED(rv)) {
 | |
|         return NS_ERROR_FAILURE;
 | |
|       }
 | |
| 
 | |
|       nsCOMPtr<nsIFile> file = do_QueryInterface(item, &rv);
 | |
|       if (NS_FAILED(rv)) {
 | |
|         return NS_ERROR_FAILURE;
 | |
|       }
 | |
|       rv = file->Remove(aRecursive);
 | |
| 
 | |
| #ifdef ANDROID
 | |
|       // See bug 580434 - Bionic gives us just deleted files
 | |
|       if (rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
 | |
|         continue;
 | |
|       }
 | |
| #endif
 | |
|       if (NS_FAILED(rv)) {
 | |
|         return rv;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return NSRESULT_FOR_RETURN(rmdir(mPath.get()));
 | |
| }
 | |
| 
 | |
| nsresult nsLocalFile::GetLastModifiedTimeImpl(PRTime* aLastModTime,
 | |
|                                               bool aFollowLinks) {
 | |
|   CHECK_mPath();
 | |
|   if (NS_WARN_IF(!aLastModTime)) {
 | |
|     return NS_ERROR_INVALID_ARG;
 | |
|   }
 | |
| 
 | |
|   using StatFn = int (*)(const char*, struct STAT*);
 | |
|   StatFn statFn = aFollowLinks ? &STAT : &LSTAT;
 | |
| 
 | |
|   struct STAT fileStats {};
 | |
|   if (statFn(mPath.get(), &fileStats) < 0) {
 | |
|     return NSRESULT_FOR_ERRNO();
 | |
|   }
 | |
| 
 | |
| #if (defined(__APPLE__) && defined(__MACH__))
 | |
|   *aLastModTime = TimespecToMillis(fileStats.st_mtimespec);
 | |
| #else
 | |
|   *aLastModTime = TimespecToMillis(fileStats.st_mtim);
 | |
| #endif
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult nsLocalFile::SetLastModifiedTimeImpl(PRTime aLastModTime,
 | |
|                                               bool aFollowLinks) {
 | |
|   CHECK_mPath();
 | |
| 
 | |
|   using UtimesFn = int (*)(const char*, const timeval*);
 | |
|   UtimesFn utimesFn = &utimes;
 | |
| 
 | |
| #if HAVE_LUTIMES
 | |
|   if (!aFollowLinks) {
 | |
|     utimesFn = &lutimes;
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   int result;
 | |
|   if (aLastModTime != 0) {
 | |
|     ENSURE_STAT_CACHE();
 | |
|     timeval access{};
 | |
| #if (defined(__APPLE__) && defined(__MACH__))
 | |
|     access.tv_sec = mCachedStat.st_atimespec.tv_sec;
 | |
|     access.tv_usec = mCachedStat.st_atimespec.tv_nsec / 1000;
 | |
| #else
 | |
|     access.tv_sec = mCachedStat.st_atim.tv_sec;
 | |
|     access.tv_usec = mCachedStat.st_atim.tv_nsec / 1000;
 | |
| #endif
 | |
|     timeval modification{};
 | |
|     modification.tv_sec = aLastModTime / PR_MSEC_PER_SEC;
 | |
|     modification.tv_usec = (aLastModTime % PR_MSEC_PER_SEC) * PR_USEC_PER_MSEC;
 | |
| 
 | |
|     timeval times[2];
 | |
|     times[0] = access;
 | |
|     times[1] = modification;
 | |
|     result = utimesFn(mPath.get(), times);
 | |
|   } else {
 | |
|     result = utimesFn(mPath.get(), nullptr);
 | |
|   }
 | |
|   return NSRESULT_FOR_RETURN(result);
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::GetLastModifiedTime(PRTime* aLastModTime) {
 | |
|   return GetLastModifiedTimeImpl(aLastModTime, /* follow links? */ true);
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::SetLastModifiedTime(PRTime aLastModTime) {
 | |
|   return SetLastModifiedTimeImpl(aLastModTime, /* follow links ? */ true);
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::GetLastModifiedTimeOfLink(PRTime* aLastModTimeOfLink) {
 | |
|   return GetLastModifiedTimeImpl(aLastModTimeOfLink, /* follow link? */ false);
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::SetLastModifiedTimeOfLink(PRTime aLastModTimeOfLink) {
 | |
|   return SetLastModifiedTimeImpl(aLastModTimeOfLink, /* follow links? */ false);
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::GetCreationTime(PRTime* aCreationTime) {
 | |
|   return GetCreationTimeImpl(aCreationTime, false);
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::GetCreationTimeOfLink(PRTime* aCreationTimeOfLink) {
 | |
|   return GetCreationTimeImpl(aCreationTimeOfLink, /* aFollowLinks = */ true);
 | |
| }
 | |
| 
 | |
| nsresult nsLocalFile::GetCreationTimeImpl(PRTime* aCreationTime,
 | |
|                                           bool aFollowLinks) {
 | |
|   CHECK_mPath();
 | |
|   if (NS_WARN_IF(!aCreationTime)) {
 | |
|     return NS_ERROR_INVALID_ARG;
 | |
|   }
 | |
| 
 | |
| #if defined(_DARWIN_FEATURE_64_BIT_INODE)
 | |
|   using StatFn = int (*)(const char*, struct STAT*);
 | |
|   StatFn statFn = aFollowLinks ? &STAT : &LSTAT;
 | |
| 
 | |
|   struct STAT fileStats {};
 | |
|   if (statFn(mPath.get(), &fileStats) < 0) {
 | |
|     return NSRESULT_FOR_ERRNO();
 | |
|   }
 | |
| 
 | |
|   *aCreationTime = TimespecToMillis(fileStats.st_birthtimespec);
 | |
|   return NS_OK;
 | |
| #else
 | |
|   return NS_ERROR_NOT_IMPLEMENTED;
 | |
| #endif
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Only send back permissions bits: maybe we want to send back the whole
 | |
|  * mode_t to permit checks against other file types?
 | |
|  */
 | |
| 
 | |
| #define NORMALIZE_PERMS(mode) ((mode) & (S_IRWXU | S_IRWXG | S_IRWXO))
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::GetPermissions(uint32_t* aPermissions) {
 | |
|   if (NS_WARN_IF(!aPermissions)) {
 | |
|     return NS_ERROR_INVALID_ARG;
 | |
|   }
 | |
|   ENSURE_STAT_CACHE();
 | |
|   *aPermissions = NORMALIZE_PERMS(mCachedStat.st_mode);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::GetPermissionsOfLink(uint32_t* aPermissionsOfLink) {
 | |
|   CHECK_mPath();
 | |
|   if (NS_WARN_IF(!aPermissionsOfLink)) {
 | |
|     return NS_ERROR_INVALID_ARG;
 | |
|   }
 | |
| 
 | |
|   struct STAT sbuf;
 | |
|   if (LSTAT(mPath.get(), &sbuf) == -1) {
 | |
|     return NSRESULT_FOR_ERRNO();
 | |
|   }
 | |
|   *aPermissionsOfLink = NORMALIZE_PERMS(sbuf.st_mode);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::SetPermissions(uint32_t aPermissions) {
 | |
|   CHECK_mPath();
 | |
| 
 | |
|   /*
 | |
|    * Race condition here: we should use fchmod instead, there's no way to
 | |
|    * guarantee the name still refers to the same file.
 | |
|    */
 | |
|   if (chmod(mPath.get(), aPermissions) >= 0) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| #if defined(ANDROID) && defined(STATFS)
 | |
|   // For the time being, this is restricted for use by Android, but we
 | |
|   // will figure out what to do for all platforms in bug 638503
 | |
|   struct STATFS sfs;
 | |
|   if (STATFS(mPath.get(), &sfs) < 0) {
 | |
|     return NSRESULT_FOR_ERRNO();
 | |
|   }
 | |
| 
 | |
|   // if this is a FAT file system we can't set file permissions
 | |
|   if (sfs.f_type == MSDOS_SUPER_MAGIC) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| #endif
 | |
|   return NSRESULT_FOR_ERRNO();
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::SetPermissionsOfLink(uint32_t aPermissions) {
 | |
|   // There isn't a consistent mechanism for doing this on UNIX platforms. We
 | |
|   // might want to carefully implement this in the future though.
 | |
|   return NS_ERROR_NOT_IMPLEMENTED;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::GetFileSize(int64_t* aFileSize) {
 | |
|   if (NS_WARN_IF(!aFileSize)) {
 | |
|     return NS_ERROR_INVALID_ARG;
 | |
|   }
 | |
|   *aFileSize = 0;
 | |
|   ENSURE_STAT_CACHE();
 | |
| 
 | |
|   if (!S_ISDIR(mCachedStat.st_mode)) {
 | |
|     *aFileSize = (int64_t)mCachedStat.st_size;
 | |
|   }
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::SetFileSize(int64_t aFileSize) {
 | |
|   CHECK_mPath();
 | |
| 
 | |
| #if defined(ANDROID)
 | |
|   /* no truncate on bionic */
 | |
|   int fd = open(mPath.get(), O_WRONLY);
 | |
|   if (fd == -1) {
 | |
|     return NSRESULT_FOR_ERRNO();
 | |
|   }
 | |
| 
 | |
|   int ret = ftruncate(fd, (off_t)aFileSize);
 | |
|   close(fd);
 | |
| 
 | |
|   if (ret == -1) {
 | |
|     return NSRESULT_FOR_ERRNO();
 | |
|   }
 | |
| #elif defined(HAVE_TRUNCATE64)
 | |
|   if (truncate64(mPath.get(), (off64_t)aFileSize) == -1) {
 | |
|     return NSRESULT_FOR_ERRNO();
 | |
|   }
 | |
| #else
 | |
|   off_t size = (off_t)aFileSize;
 | |
|   if (truncate(mPath.get(), size) == -1) {
 | |
|     return NSRESULT_FOR_ERRNO();
 | |
|   }
 | |
| #endif
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::GetFileSizeOfLink(int64_t* aFileSize) {
 | |
|   CHECK_mPath();
 | |
|   if (NS_WARN_IF(!aFileSize)) {
 | |
|     return NS_ERROR_INVALID_ARG;
 | |
|   }
 | |
| 
 | |
|   struct STAT sbuf;
 | |
|   if (LSTAT(mPath.get(), &sbuf) == -1) {
 | |
|     return NSRESULT_FOR_ERRNO();
 | |
|   }
 | |
| 
 | |
|   *aFileSize = (int64_t)sbuf.st_size;
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| #if defined(USE_LINUX_QUOTACTL)
 | |
| /*
 | |
|  * Searches /proc/self/mountinfo for given device (Major:Minor),
 | |
|  * returns exported name from /dev
 | |
|  *
 | |
|  * Fails when /proc/self/mountinfo or diven device don't exist.
 | |
|  */
 | |
| static bool GetDeviceName(unsigned int aDeviceMajor, unsigned int aDeviceMinor,
 | |
|                           nsACString& aDeviceName) {
 | |
|   bool ret = false;
 | |
| 
 | |
|   const int kMountInfoLineLength = 200;
 | |
|   const int kMountInfoDevPosition = 6;
 | |
| 
 | |
|   char mountinfoLine[kMountInfoLineLength];
 | |
|   char deviceNum[kMountInfoLineLength];
 | |
| 
 | |
|   SprintfLiteral(deviceNum, "%u:%u", aDeviceMajor, aDeviceMinor);
 | |
| 
 | |
|   FILE* f = fopen("/proc/self/mountinfo", "rt");
 | |
|   if (!f) {
 | |
|     return ret;
 | |
|   }
 | |
| 
 | |
|   // Expects /proc/self/mountinfo in format:
 | |
|   // 'ID ID major:minor root mountpoint flags - type devicename flags'
 | |
|   while (fgets(mountinfoLine, kMountInfoLineLength, f)) {
 | |
|     char* p_dev = strstr(mountinfoLine, deviceNum);
 | |
| 
 | |
|     for (int i = 0; i < kMountInfoDevPosition && p_dev; ++i) {
 | |
|       p_dev = strchr(p_dev, ' ');
 | |
|       if (p_dev) {
 | |
|         p_dev++;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (p_dev) {
 | |
|       char* p_dev_end = strchr(p_dev, ' ');
 | |
|       if (p_dev_end) {
 | |
|         *p_dev_end = '\0';
 | |
|         aDeviceName.Assign(p_dev);
 | |
|         ret = true;
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   fclose(f);
 | |
|   return ret;
 | |
| }
 | |
| #endif
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::GetDiskSpaceAvailable(int64_t* aDiskSpaceAvailable) {
 | |
|   if (NS_WARN_IF(!aDiskSpaceAvailable)) {
 | |
|     return NS_ERROR_INVALID_ARG;
 | |
|   }
 | |
| 
 | |
|   // These systems have the operations necessary to check disk space.
 | |
| 
 | |
| #ifdef STATFS
 | |
| 
 | |
|   // check to make sure that mPath is properly initialized
 | |
|   CHECK_mPath();
 | |
| 
 | |
|   struct STATFS fs_buf;
 | |
| 
 | |
|   /*
 | |
|    * Members of the STATFS struct that you should know about:
 | |
|    * F_BSIZE = block size on disk.
 | |
|    * f_bavail = number of free blocks available to a non-superuser.
 | |
|    * f_bfree = number of total free blocks in file system.
 | |
|    */
 | |
| 
 | |
|   if (STATFS(mPath.get(), &fs_buf) < 0) {
 | |
|     // The call to STATFS failed.
 | |
| #  ifdef DEBUG
 | |
|     printf("ERROR: GetDiskSpaceAvailable: STATFS call FAILED. \n");
 | |
| #  endif
 | |
|     return NS_ERROR_FAILURE;
 | |
|   }
 | |
| 
 | |
|   *aDiskSpaceAvailable = (int64_t)fs_buf.F_BSIZE * fs_buf.f_bavail;
 | |
| 
 | |
| #  ifdef DEBUG_DISK_SPACE
 | |
|   printf("DiskSpaceAvailable: %lu bytes\n", *aDiskSpaceAvailable);
 | |
| #  endif
 | |
| 
 | |
| #  if defined(USE_LINUX_QUOTACTL)
 | |
| 
 | |
|   if (!FillStatCache()) {
 | |
|     // Return available size from statfs
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   nsCString deviceName;
 | |
|   if (!GetDeviceName(major(mCachedStat.st_dev), minor(mCachedStat.st_dev),
 | |
|                      deviceName)) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   struct dqblk dq;
 | |
|   if (!quotactl(QCMD(Q_GETQUOTA, USRQUOTA), deviceName.get(), getuid(),
 | |
|                 (caddr_t)&dq)
 | |
| #    ifdef QIF_BLIMITS
 | |
|       && dq.dqb_valid & QIF_BLIMITS
 | |
| #    endif
 | |
|       && dq.dqb_bhardlimit) {
 | |
|     int64_t QuotaSpaceAvailable = 0;
 | |
|     // dqb_bhardlimit is count of BLOCK_SIZE blocks, dqb_curspace is bytes
 | |
|     if ((BLOCK_SIZE * dq.dqb_bhardlimit) > dq.dqb_curspace)
 | |
|       QuotaSpaceAvailable =
 | |
|           int64_t(BLOCK_SIZE * dq.dqb_bhardlimit - dq.dqb_curspace);
 | |
|     if (QuotaSpaceAvailable < *aDiskSpaceAvailable) {
 | |
|       *aDiskSpaceAvailable = QuotaSpaceAvailable;
 | |
|     }
 | |
|   }
 | |
| #  endif
 | |
| 
 | |
|   return NS_OK;
 | |
| 
 | |
| #else
 | |
|   /*
 | |
|    * This platform doesn't have statfs or statvfs.  I'm sure that there's
 | |
|    * a way to check for free disk space on platforms that don't have statfs
 | |
|    * (I'm SURE they have df, for example).
 | |
|    *
 | |
|    * Until we figure out how to do that, lets be honest and say that this
 | |
|    * command isn't implemented properly for these platforms yet.
 | |
|    */
 | |
| #  ifdef DEBUG
 | |
|   printf(
 | |
|       "ERROR: GetDiskSpaceAvailable: Not implemented for plaforms without "
 | |
|       "statfs.\n");
 | |
| #  endif
 | |
|   return NS_ERROR_NOT_IMPLEMENTED;
 | |
| 
 | |
| #endif /* STATFS */
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::GetParent(nsIFile** aParent) {
 | |
|   CHECK_mPath();
 | |
|   if (NS_WARN_IF(!aParent)) {
 | |
|     return NS_ERROR_INVALID_ARG;
 | |
|   }
 | |
|   *aParent = nullptr;
 | |
| 
 | |
|   // if '/' we are at the top of the volume, return null
 | |
|   if (mPath.EqualsLiteral("/")) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   // <brendan, after jband> I promise to play nice
 | |
|   char* buffer = mPath.BeginWriting();
 | |
|   // find the last significant slash in buffer
 | |
|   char* slashp = strrchr(buffer, '/');
 | |
|   NS_ASSERTION(slashp, "non-canonical path?");
 | |
|   if (!slashp) {
 | |
|     return NS_ERROR_FILE_INVALID_PATH;
 | |
|   }
 | |
| 
 | |
|   // for the case where we are at '/'
 | |
|   if (slashp == buffer) {
 | |
|     slashp++;
 | |
|   }
 | |
| 
 | |
|   // temporarily terminate buffer at the last significant slash
 | |
|   char c = *slashp;
 | |
|   *slashp = '\0';
 | |
| 
 | |
|   nsCOMPtr<nsIFile> localFile;
 | |
|   nsresult rv = NS_NewNativeLocalFile(nsDependentCString(buffer), true,
 | |
|                                       getter_AddRefs(localFile));
 | |
| 
 | |
|   // make buffer whole again
 | |
|   *slashp = c;
 | |
| 
 | |
|   if (NS_FAILED(rv)) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   localFile.forget(aParent);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * The results of Exists, isWritable and isReadable are not cached.
 | |
|  */
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::Exists(bool* aResult) {
 | |
|   CHECK_mPath();
 | |
|   if (NS_WARN_IF(!aResult)) {
 | |
|     return NS_ERROR_INVALID_ARG;
 | |
|   }
 | |
| 
 | |
|   *aResult = (access(mPath.get(), F_OK) == 0);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::IsWritable(bool* aResult) {
 | |
|   CHECK_mPath();
 | |
|   if (NS_WARN_IF(!aResult)) {
 | |
|     return NS_ERROR_INVALID_ARG;
 | |
|   }
 | |
| 
 | |
|   *aResult = (access(mPath.get(), W_OK) == 0);
 | |
|   if (*aResult || errno == EACCES) {
 | |
|     return NS_OK;
 | |
|   }
 | |
|   return NSRESULT_FOR_ERRNO();
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::IsReadable(bool* aResult) {
 | |
|   CHECK_mPath();
 | |
|   if (NS_WARN_IF(!aResult)) {
 | |
|     return NS_ERROR_INVALID_ARG;
 | |
|   }
 | |
| 
 | |
|   *aResult = (access(mPath.get(), R_OK) == 0);
 | |
|   if (*aResult || errno == EACCES) {
 | |
|     return NS_OK;
 | |
|   }
 | |
|   return NSRESULT_FOR_ERRNO();
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::IsExecutable(bool* aResult) {
 | |
|   CHECK_mPath();
 | |
|   if (NS_WARN_IF(!aResult)) {
 | |
|     return NS_ERROR_INVALID_ARG;
 | |
|   }
 | |
| 
 | |
|   // Check extension (bug 663899). On certain platforms, the file
 | |
|   // extension may cause the OS to treat it as executable regardless of
 | |
|   // the execute bit, such as .jar on Mac OS X. We borrow the code from
 | |
|   // nsLocalFileWin, slightly modified.
 | |
| 
 | |
|   // Don't be fooled by symlinks.
 | |
|   bool symLink;
 | |
|   nsresult rv = IsSymlink(&symLink);
 | |
|   if (NS_FAILED(rv)) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   nsAutoString path;
 | |
|   if (symLink) {
 | |
|     GetTarget(path);
 | |
|   } else {
 | |
|     GetPath(path);
 | |
|   }
 | |
| 
 | |
|   int32_t dotIdx = path.RFindChar(char16_t('.'));
 | |
|   if (dotIdx != kNotFound) {
 | |
|     // Convert extension to lower case.
 | |
|     char16_t* p = path.BeginWriting();
 | |
|     for (p += dotIdx + 1; *p; ++p) {
 | |
|       *p += (*p >= L'A' && *p <= L'Z') ? 'a' - 'A' : 0;
 | |
|     }
 | |
| 
 | |
|     // Search for any of the set of executable extensions.
 | |
|     static const char* const executableExts[] = {
 | |
|         "air",  // Adobe AIR installer
 | |
| #ifdef MOZ_WIDGET_COCOA
 | |
|         "fileloc",  // File location files can be used to point to other
 | |
|                     // files.
 | |
| #endif
 | |
|         "jar"  // java application bundle
 | |
|     };
 | |
|     nsDependentSubstring ext = Substring(path, dotIdx + 1);
 | |
|     for (auto executableExt : executableExts) {
 | |
|       if (ext.EqualsASCII(executableExt)) {
 | |
|         // Found a match.  Set result and quit.
 | |
|         *aResult = true;
 | |
|         return NS_OK;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // On OS X, then query Launch Services.
 | |
| #ifdef MOZ_WIDGET_COCOA
 | |
|   // Certain Mac applications, such as Classic applications, which
 | |
|   // run under Rosetta, might not have the +x mode bit but are still
 | |
|   // considered to be executable by Launch Services (bug 646748).
 | |
|   CFURLRef url;
 | |
|   if (NS_FAILED(GetCFURL(&url))) {
 | |
|     return NS_ERROR_FAILURE;
 | |
|   }
 | |
| 
 | |
|   LSRequestedInfo theInfoRequest = kLSRequestAllInfo;
 | |
|   LSItemInfoRecord theInfo;
 | |
|   OSStatus result = ::LSCopyItemInfoForURL(url, theInfoRequest, &theInfo);
 | |
|   ::CFRelease(url);
 | |
|   if (result == noErr) {
 | |
|     if ((theInfo.flags & kLSItemInfoIsApplication) != 0) {
 | |
|       *aResult = true;
 | |
|       return NS_OK;
 | |
|     }
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   // Then check the execute bit.
 | |
|   *aResult = (access(mPath.get(), X_OK) == 0);
 | |
| #ifdef SOLARIS
 | |
|   // On Solaris, access will always return 0 for root user, however
 | |
|   // the file is only executable if S_IXUSR | S_IXGRP | S_IXOTH is set.
 | |
|   // See bug 351950, https://bugzilla.mozilla.org/show_bug.cgi?id=351950
 | |
|   if (*aResult) {
 | |
|     struct STAT buf;
 | |
| 
 | |
|     *aResult = (STAT(mPath.get(), &buf) == 0);
 | |
|     if (*aResult || errno == EACCES) {
 | |
|       *aResult = *aResult && (buf.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH));
 | |
|       return NS_OK;
 | |
|     }
 | |
| 
 | |
|     return NSRESULT_FOR_ERRNO();
 | |
|   }
 | |
| #endif
 | |
|   if (*aResult || errno == EACCES) {
 | |
|     return NS_OK;
 | |
|   }
 | |
|   return NSRESULT_FOR_ERRNO();
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::IsDirectory(bool* aResult) {
 | |
|   if (NS_WARN_IF(!aResult)) {
 | |
|     return NS_ERROR_INVALID_ARG;
 | |
|   }
 | |
|   *aResult = false;
 | |
|   ENSURE_STAT_CACHE();
 | |
|   *aResult = S_ISDIR(mCachedStat.st_mode);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::IsFile(bool* aResult) {
 | |
|   if (NS_WARN_IF(!aResult)) {
 | |
|     return NS_ERROR_INVALID_ARG;
 | |
|   }
 | |
|   *aResult = false;
 | |
|   ENSURE_STAT_CACHE();
 | |
|   *aResult = S_ISREG(mCachedStat.st_mode);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::IsHidden(bool* aResult) {
 | |
|   if (NS_WARN_IF(!aResult)) {
 | |
|     return NS_ERROR_INVALID_ARG;
 | |
|   }
 | |
|   nsACString::const_iterator begin, end;
 | |
|   LocateNativeLeafName(begin, end);
 | |
|   *aResult = (*begin == '.');
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::IsSymlink(bool* aResult) {
 | |
|   if (NS_WARN_IF(!aResult)) {
 | |
|     return NS_ERROR_INVALID_ARG;
 | |
|   }
 | |
|   CHECK_mPath();
 | |
| 
 | |
|   struct STAT symStat;
 | |
|   if (LSTAT(mPath.get(), &symStat) == -1) {
 | |
|     return NSRESULT_FOR_ERRNO();
 | |
|   }
 | |
|   *aResult = S_ISLNK(symStat.st_mode);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::IsSpecial(bool* aResult) {
 | |
|   if (NS_WARN_IF(!aResult)) {
 | |
|     return NS_ERROR_INVALID_ARG;
 | |
|   }
 | |
|   ENSURE_STAT_CACHE();
 | |
|   *aResult = S_ISCHR(mCachedStat.st_mode) || S_ISBLK(mCachedStat.st_mode) ||
 | |
| #ifdef S_ISSOCK
 | |
|              S_ISSOCK(mCachedStat.st_mode) ||
 | |
| #endif
 | |
|              S_ISFIFO(mCachedStat.st_mode);
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::Equals(nsIFile* aInFile, bool* aResult) {
 | |
|   if (NS_WARN_IF(!aInFile)) {
 | |
|     return NS_ERROR_INVALID_ARG;
 | |
|   }
 | |
|   if (NS_WARN_IF(!aResult)) {
 | |
|     return NS_ERROR_INVALID_ARG;
 | |
|   }
 | |
|   *aResult = false;
 | |
| 
 | |
|   nsAutoCString inPath;
 | |
|   nsresult rv = aInFile->GetNativePath(inPath);
 | |
|   if (NS_FAILED(rv)) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   // We don't need to worry about "/foo/" vs. "/foo" here
 | |
|   // because trailing slashes are stripped on init.
 | |
|   *aResult = !strcmp(inPath.get(), mPath.get());
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::Contains(nsIFile* aInFile, bool* aResult) {
 | |
|   CHECK_mPath();
 | |
|   if (NS_WARN_IF(!aInFile)) {
 | |
|     return NS_ERROR_INVALID_ARG;
 | |
|   }
 | |
|   if (NS_WARN_IF(!aResult)) {
 | |
|     return NS_ERROR_INVALID_ARG;
 | |
|   }
 | |
| 
 | |
|   nsAutoCString inPath;
 | |
|   nsresult rv;
 | |
| 
 | |
|   if (NS_FAILED(rv = aInFile->GetNativePath(inPath))) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   *aResult = false;
 | |
| 
 | |
|   ssize_t len = mPath.Length();
 | |
|   if (strncmp(mPath.get(), inPath.get(), len) == 0) {
 | |
|     // Now make sure that the |aInFile|'s path has a separator at len,
 | |
|     // which implies that it has more components after len.
 | |
|     if (inPath[len] == '/') {
 | |
|       *aResult = true;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::GetNativeTarget(nsACString& aResult) {
 | |
|   CHECK_mPath();
 | |
|   aResult.Truncate();
 | |
| 
 | |
|   struct STAT symStat;
 | |
|   if (LSTAT(mPath.get(), &symStat) == -1) {
 | |
|     return NSRESULT_FOR_ERRNO();
 | |
|   }
 | |
| 
 | |
|   if (!S_ISLNK(symStat.st_mode)) {
 | |
|     return NS_ERROR_FILE_INVALID_PATH;
 | |
|   }
 | |
| 
 | |
|   int32_t size = (int32_t)symStat.st_size;
 | |
|   nsAutoCString target;
 | |
|   if (!target.SetLength(size, mozilla::fallible)) {
 | |
|     return NS_ERROR_OUT_OF_MEMORY;
 | |
|   }
 | |
| 
 | |
|   if (readlink(mPath.get(), target.BeginWriting(), (size_t)size) < 0) {
 | |
|     return NSRESULT_FOR_ERRNO();
 | |
|   }
 | |
| 
 | |
|   nsresult rv = NS_OK;
 | |
|   nsCOMPtr<nsIFile> self(this);
 | |
|   int32_t maxLinks = 40;
 | |
|   while (true) {
 | |
|     if (maxLinks-- == 0) {
 | |
|       rv = NS_ERROR_FILE_UNRESOLVABLE_SYMLINK;
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|     if (target[0] != '/') {
 | |
|       nsCOMPtr<nsIFile> parent;
 | |
|       if (NS_FAILED(rv = self->GetParent(getter_AddRefs(parent)))) {
 | |
|         break;
 | |
|       }
 | |
|       if (NS_FAILED(rv = parent->AppendRelativeNativePath(target))) {
 | |
|         break;
 | |
|       }
 | |
|       if (NS_FAILED(rv = parent->GetNativePath(aResult))) {
 | |
|         break;
 | |
|       }
 | |
|       self = parent;
 | |
|     } else {
 | |
|       aResult = target;
 | |
|     }
 | |
| 
 | |
|     const nsPromiseFlatCString& flatRetval = PromiseFlatCString(aResult);
 | |
| 
 | |
|     // Any failure in testing the current target we'll just interpret
 | |
|     // as having reached our destiny.
 | |
|     if (LSTAT(flatRetval.get(), &symStat) == -1) {
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|     // And of course we're done if it isn't a symlink.
 | |
|     if (!S_ISLNK(symStat.st_mode)) {
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|     int32_t newSize = (int32_t)symStat.st_size;
 | |
|     size = newSize;
 | |
|     nsAutoCString newTarget;
 | |
|     if (!newTarget.SetLength(size, mozilla::fallible)) {
 | |
|       rv = NS_ERROR_OUT_OF_MEMORY;
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|     int32_t linkLen =
 | |
|         readlink(flatRetval.get(), newTarget.BeginWriting(), size);
 | |
|     if (linkLen == -1) {
 | |
|       rv = NSRESULT_FOR_ERRNO();
 | |
|       break;
 | |
|     }
 | |
|     target = newTarget;
 | |
|   }
 | |
| 
 | |
|   if (NS_FAILED(rv)) {
 | |
|     aResult.Truncate();
 | |
|   }
 | |
|   return rv;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::GetDirectoryEntriesImpl(nsIDirectoryEnumerator** aEntries) {
 | |
|   RefPtr<nsDirEnumeratorUnix> dir = new nsDirEnumeratorUnix();
 | |
| 
 | |
|   nsresult rv = dir->Init(this, false);
 | |
|   if (NS_FAILED(rv)) {
 | |
|     *aEntries = nullptr;
 | |
|   } else {
 | |
|     dir.forget(aEntries);
 | |
|   }
 | |
| 
 | |
|   return rv;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::Load(PRLibrary** aResult) {
 | |
|   CHECK_mPath();
 | |
|   if (NS_WARN_IF(!aResult)) {
 | |
|     return NS_ERROR_INVALID_ARG;
 | |
|   }
 | |
| 
 | |
| #ifdef NS_BUILD_REFCNT_LOGGING
 | |
|   nsTraceRefcnt::SetActivityIsLegal(false);
 | |
| #endif
 | |
| 
 | |
|   *aResult = PR_LoadLibrary(mPath.get());
 | |
| 
 | |
| #ifdef NS_BUILD_REFCNT_LOGGING
 | |
|   nsTraceRefcnt::SetActivityIsLegal(true);
 | |
| #endif
 | |
| 
 | |
|   if (!*aResult) {
 | |
|     return NS_ERROR_FAILURE;
 | |
|   }
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::GetPersistentDescriptor(nsACString& aPersistentDescriptor) {
 | |
|   return GetNativePath(aPersistentDescriptor);
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::SetPersistentDescriptor(const nsACString& aPersistentDescriptor) {
 | |
| #ifdef MOZ_WIDGET_COCOA
 | |
|   if (aPersistentDescriptor.IsEmpty()) {
 | |
|     return NS_ERROR_INVALID_ARG;
 | |
|   }
 | |
| 
 | |
|   // Support pathnames as user-supplied descriptors if they begin with '/'
 | |
|   // or '~'.  These characters do not collide with the base64 set used for
 | |
|   // encoding alias records.
 | |
|   char first = aPersistentDescriptor.First();
 | |
|   if (first == '/' || first == '~') {
 | |
|     return InitWithNativePath(aPersistentDescriptor);
 | |
|   }
 | |
| 
 | |
|   uint32_t dataSize = aPersistentDescriptor.Length();
 | |
|   char* decodedData = PL_Base64Decode(
 | |
|       PromiseFlatCString(aPersistentDescriptor).get(), dataSize, nullptr);
 | |
|   if (!decodedData) {
 | |
|     NS_ERROR("SetPersistentDescriptor was given bad data");
 | |
|     return NS_ERROR_FAILURE;
 | |
|   }
 | |
| 
 | |
|   // Cast to an alias record and resolve.
 | |
|   AliasRecord aliasHeader = *(AliasPtr)decodedData;
 | |
|   int32_t aliasSize = ::GetAliasSizeFromPtr(&aliasHeader);
 | |
|   if (aliasSize >
 | |
|       ((int32_t)dataSize * 3) / 4) {  // be paranoid about having too few data
 | |
|     PR_Free(decodedData);             // PL_Base64Decode() uses PR_Malloc().
 | |
|     return NS_ERROR_FAILURE;
 | |
|   }
 | |
| 
 | |
|   nsresult rv = NS_OK;
 | |
| 
 | |
|   // Move the now-decoded data into the Handle.
 | |
|   // The size of the decoded data is 3/4 the size of the encoded data. See
 | |
|   // plbase64.h
 | |
|   Handle newHandle = nullptr;
 | |
|   if (::PtrToHand(decodedData, &newHandle, aliasSize) != noErr) {
 | |
|     rv = NS_ERROR_OUT_OF_MEMORY;
 | |
|   }
 | |
|   PR_Free(decodedData);  // PL_Base64Decode() uses PR_Malloc().
 | |
|   if (NS_FAILED(rv)) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   Boolean changed;
 | |
|   FSRef resolvedFSRef;
 | |
|   OSErr err = ::FSResolveAlias(nullptr, (AliasHandle)newHandle, &resolvedFSRef,
 | |
|                                &changed);
 | |
| 
 | |
|   rv = MacErrorMapper(err);
 | |
|   DisposeHandle(newHandle);
 | |
|   if (NS_FAILED(rv)) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   return InitWithFSRef(&resolvedFSRef);
 | |
| #else
 | |
|   return InitWithNativePath(aPersistentDescriptor);
 | |
| #endif
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::Reveal() {
 | |
|   if (!FilePreferences::IsAllowedPath(mPath)) {
 | |
|     return NS_ERROR_FILE_ACCESS_DENIED;
 | |
|   }
 | |
| 
 | |
| #ifdef MOZ_WIDGET_GTK
 | |
|   nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
 | |
|   if (!giovfs) {
 | |
|     return NS_ERROR_FAILURE;
 | |
|   }
 | |
| 
 | |
|   bool isDirectory;
 | |
|   if (NS_FAILED(IsDirectory(&isDirectory))) {
 | |
|     return NS_ERROR_FAILURE;
 | |
|   }
 | |
| 
 | |
|   if (isDirectory) {
 | |
|     return giovfs->ShowURIForInput(mPath);
 | |
|   }
 | |
|   if (NS_SUCCEEDED(giovfs->OrgFreedesktopFileManager1ShowItems(mPath))) {
 | |
|     return NS_OK;
 | |
|   }
 | |
|   nsCOMPtr<nsIFile> parentDir;
 | |
|   nsAutoCString dirPath;
 | |
|   if (NS_FAILED(GetParent(getter_AddRefs(parentDir)))) {
 | |
|     return NS_ERROR_FAILURE;
 | |
|   }
 | |
|   if (NS_FAILED(parentDir->GetNativePath(dirPath))) {
 | |
|     return NS_ERROR_FAILURE;
 | |
|   }
 | |
| 
 | |
|   return giovfs->ShowURIForInput(dirPath);
 | |
| #elif defined(MOZ_WIDGET_COCOA)
 | |
|   CFURLRef url;
 | |
|   if (NS_SUCCEEDED(GetCFURL(&url))) {
 | |
|     nsresult rv = CocoaFileUtils::RevealFileInFinder(url);
 | |
|     ::CFRelease(url);
 | |
|     return rv;
 | |
|   }
 | |
|   return NS_ERROR_FAILURE;
 | |
| #else
 | |
|   return NS_ERROR_FAILURE;
 | |
| #endif
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::Launch() {
 | |
|   if (!FilePreferences::IsAllowedPath(mPath)) {
 | |
|     return NS_ERROR_FILE_ACCESS_DENIED;
 | |
|   }
 | |
| 
 | |
| #ifdef MOZ_WIDGET_GTK
 | |
|   nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
 | |
|   if (!giovfs) {
 | |
|     return NS_ERROR_FAILURE;
 | |
|   }
 | |
| 
 | |
|   return giovfs->ShowURIForInput(mPath);
 | |
| #elif defined(MOZ_WIDGET_ANDROID)
 | |
|   // Not supported on GeckoView
 | |
|   return NS_ERROR_NOT_IMPLEMENTED;
 | |
| #elif defined(MOZ_WIDGET_COCOA)
 | |
|   CFURLRef url;
 | |
|   if (NS_SUCCEEDED(GetCFURL(&url))) {
 | |
|     nsresult rv = CocoaFileUtils::OpenURL(url);
 | |
|     ::CFRelease(url);
 | |
|     return rv;
 | |
|   }
 | |
|   return NS_ERROR_FAILURE;
 | |
| #else
 | |
|   return NS_ERROR_FAILURE;
 | |
| #endif
 | |
| }
 | |
| 
 | |
| nsresult NS_NewNativeLocalFile(const nsACString& aPath, bool aFollowSymlinks,
 | |
|                                nsIFile** aResult) {
 | |
|   RefPtr<nsLocalFile> file = new nsLocalFile();
 | |
| 
 | |
|   if (!aPath.IsEmpty()) {
 | |
|     nsresult rv = file->InitWithNativePath(aPath);
 | |
|     if (NS_FAILED(rv)) {
 | |
|       return rv;
 | |
|     }
 | |
|   }
 | |
|   file.forget(aResult);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| //-----------------------------------------------------------------------------
 | |
| // unicode support
 | |
| //-----------------------------------------------------------------------------
 | |
| 
 | |
| #define SET_UCS(func, ucsArg)                          \
 | |
|   {                                                    \
 | |
|     nsAutoCString buf;                                 \
 | |
|     nsresult rv = NS_CopyUnicodeToNative(ucsArg, buf); \
 | |
|     if (NS_FAILED(rv)) return rv;                      \
 | |
|     return (func)(buf);                                \
 | |
|   }
 | |
| 
 | |
| #define GET_UCS(func, ucsArg)                   \
 | |
|   {                                             \
 | |
|     nsAutoCString buf;                          \
 | |
|     nsresult rv = (func)(buf);                  \
 | |
|     if (NS_FAILED(rv)) return rv;               \
 | |
|     return NS_CopyNativeToUnicode(buf, ucsArg); \
 | |
|   }
 | |
| 
 | |
| #define SET_UCS_2ARGS_2(func, opaqueArg, ucsArg)       \
 | |
|   {                                                    \
 | |
|     nsAutoCString buf;                                 \
 | |
|     nsresult rv = NS_CopyUnicodeToNative(ucsArg, buf); \
 | |
|     if (NS_FAILED(rv)) return rv;                      \
 | |
|     return (func)(opaqueArg, buf);                     \
 | |
|   }
 | |
| 
 | |
| // Unicode interface Wrapper
 | |
| nsresult nsLocalFile::InitWithPath(const nsAString& aFilePath) {
 | |
|   SET_UCS(InitWithNativePath, aFilePath);
 | |
| }
 | |
| nsresult nsLocalFile::Append(const nsAString& aNode) {
 | |
|   SET_UCS(AppendNative, aNode);
 | |
| }
 | |
| nsresult nsLocalFile::AppendRelativePath(const nsAString& aNode) {
 | |
|   SET_UCS(AppendRelativeNativePath, aNode);
 | |
| }
 | |
| nsresult nsLocalFile::GetLeafName(nsAString& aLeafName) {
 | |
|   GET_UCS(GetNativeLeafName, aLeafName);
 | |
| }
 | |
| nsresult nsLocalFile::SetLeafName(const nsAString& aLeafName) {
 | |
|   SET_UCS(SetNativeLeafName, aLeafName);
 | |
| }
 | |
| nsresult nsLocalFile::GetPath(nsAString& aResult) {
 | |
|   return NS_CopyNativeToUnicode(mPath, aResult);
 | |
| }
 | |
| nsresult nsLocalFile::CopyTo(nsIFile* aNewParentDir,
 | |
|                              const nsAString& aNewName) {
 | |
|   SET_UCS_2ARGS_2(CopyToNative, aNewParentDir, aNewName);
 | |
| }
 | |
| nsresult nsLocalFile::CopyToFollowingLinks(nsIFile* aNewParentDir,
 | |
|                                            const nsAString& aNewName) {
 | |
|   SET_UCS_2ARGS_2(CopyToFollowingLinksNative, aNewParentDir, aNewName);
 | |
| }
 | |
| nsresult nsLocalFile::MoveTo(nsIFile* aNewParentDir,
 | |
|                              const nsAString& aNewName) {
 | |
|   SET_UCS_2ARGS_2(MoveToNative, aNewParentDir, aNewName);
 | |
| }
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::MoveToFollowingLinks(nsIFile* aNewParentDir,
 | |
|                                   const nsAString& aNewName) {
 | |
|   SET_UCS_2ARGS_2(MoveToFollowingLinksNative, aNewParentDir, aNewName);
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::RenameTo(nsIFile* aNewParentDir, const nsAString& aNewName) {
 | |
|   SET_UCS_2ARGS_2(RenameToNative, aNewParentDir, aNewName);
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::RenameToNative(nsIFile* aNewParentDir,
 | |
|                             const nsACString& aNewName) {
 | |
|   nsresult rv;
 | |
| 
 | |
|   // check to make sure that this has been initialized properly
 | |
|   CHECK_mPath();
 | |
| 
 | |
|   // check to make sure that we have a new parent
 | |
|   nsAutoCString newPathName;
 | |
|   rv = GetNativeTargetPathName(aNewParentDir, aNewName, newPathName);
 | |
|   if (NS_FAILED(rv)) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   if (!FilePreferences::IsAllowedPath(newPathName)) {
 | |
|     return NS_ERROR_FILE_ACCESS_DENIED;
 | |
|   }
 | |
| 
 | |
|   // try for atomic rename
 | |
|   if (rename(mPath.get(), newPathName.get()) < 0) {
 | |
|     if (errno == EXDEV) {
 | |
|       rv = NS_ERROR_FILE_ACCESS_DENIED;
 | |
|     } else {
 | |
|       rv = NSRESULT_FOR_ERRNO();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return rv;
 | |
| }
 | |
| 
 | |
| nsresult nsLocalFile::GetTarget(nsAString& aResult) {
 | |
|   GET_UCS(GetNativeTarget, aResult);
 | |
| }
 | |
| 
 | |
| nsresult NS_NewLocalFile(const nsAString& aPath, bool aFollowLinks,
 | |
|                          nsIFile** aResult) {
 | |
|   nsAutoCString buf;
 | |
|   nsresult rv = NS_CopyUnicodeToNative(aPath, buf);
 | |
|   if (NS_FAILED(rv)) {
 | |
|     return rv;
 | |
|   }
 | |
|   return NS_NewNativeLocalFile(buf, aFollowLinks, aResult);
 | |
| }
 | |
| 
 | |
| // nsILocalFileMac
 | |
| 
 | |
| #ifdef MOZ_WIDGET_COCOA
 | |
| 
 | |
| static nsresult MacErrorMapper(OSErr inErr) {
 | |
|   nsresult outErr;
 | |
| 
 | |
|   switch (inErr) {
 | |
|     case noErr:
 | |
|       outErr = NS_OK;
 | |
|       break;
 | |
| 
 | |
|     case fnfErr:
 | |
|     case afpObjectNotFound:
 | |
|     case afpDirNotFound:
 | |
|       outErr = NS_ERROR_FILE_NOT_FOUND;
 | |
|       break;
 | |
| 
 | |
|     case dupFNErr:
 | |
|     case afpObjectExists:
 | |
|       outErr = NS_ERROR_FILE_ALREADY_EXISTS;
 | |
|       break;
 | |
| 
 | |
|     case dskFulErr:
 | |
|     case afpDiskFull:
 | |
|       outErr = NS_ERROR_FILE_NO_DEVICE_SPACE;
 | |
|       break;
 | |
| 
 | |
|     case fLckdErr:
 | |
|     case afpVolLocked:
 | |
|       outErr = NS_ERROR_FILE_IS_LOCKED;
 | |
|       break;
 | |
| 
 | |
|     case afpAccessDenied:
 | |
|       outErr = NS_ERROR_FILE_ACCESS_DENIED;
 | |
|       break;
 | |
| 
 | |
|     case afpDirNotEmpty:
 | |
|       outErr = NS_ERROR_FILE_DIR_NOT_EMPTY;
 | |
|       break;
 | |
| 
 | |
|     // Can't find good map for some
 | |
|     case bdNamErr:
 | |
|       outErr = NS_ERROR_FAILURE;
 | |
|       break;
 | |
| 
 | |
|     default:
 | |
|       outErr = NS_ERROR_FAILURE;
 | |
|       break;
 | |
|   }
 | |
| 
 | |
|   return outErr;
 | |
| }
 | |
| 
 | |
| static nsresult CFStringReftoUTF8(CFStringRef aInStrRef, nsACString& aOutStr) {
 | |
|   // first see if the conversion would succeed and find the length of the
 | |
|   // result
 | |
|   CFIndex usedBufLen, inStrLen = ::CFStringGetLength(aInStrRef);
 | |
|   CFIndex charsConverted = ::CFStringGetBytes(
 | |
|       aInStrRef, CFRangeMake(0, inStrLen), kCFStringEncodingUTF8, 0, false,
 | |
|       nullptr, 0, &usedBufLen);
 | |
|   if (charsConverted == inStrLen) {
 | |
|     // all characters converted, do the actual conversion
 | |
|     aOutStr.SetLength(usedBufLen);
 | |
|     if (aOutStr.Length() != (unsigned int)usedBufLen) {
 | |
|       return NS_ERROR_OUT_OF_MEMORY;
 | |
|     }
 | |
|     UInt8* buffer = (UInt8*)aOutStr.BeginWriting();
 | |
|     ::CFStringGetBytes(aInStrRef, CFRangeMake(0, inStrLen),
 | |
|                        kCFStringEncodingUTF8, 0, false, buffer, usedBufLen,
 | |
|                        &usedBufLen);
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   return NS_ERROR_FAILURE;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::InitWithCFURL(CFURLRef aCFURL) {
 | |
|   UInt8 path[PATH_MAX];
 | |
|   if (::CFURLGetFileSystemRepresentation(aCFURL, true, path, PATH_MAX)) {
 | |
|     nsDependentCString nativePath((char*)path);
 | |
|     return InitWithNativePath(nativePath);
 | |
|   }
 | |
| 
 | |
|   return NS_ERROR_FAILURE;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::InitWithFSRef(const FSRef* aFSRef) {
 | |
|   if (NS_WARN_IF(!aFSRef)) {
 | |
|     return NS_ERROR_INVALID_ARG;
 | |
|   }
 | |
| 
 | |
|   CFURLRef newURLRef = ::CFURLCreateFromFSRef(kCFAllocatorDefault, aFSRef);
 | |
|   if (newURLRef) {
 | |
|     nsresult rv = InitWithCFURL(newURLRef);
 | |
|     ::CFRelease(newURLRef);
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   return NS_ERROR_FAILURE;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::GetCFURL(CFURLRef* aResult) {
 | |
|   CHECK_mPath();
 | |
| 
 | |
|   bool isDir;
 | |
|   IsDirectory(&isDir);
 | |
|   *aResult = ::CFURLCreateFromFileSystemRepresentation(
 | |
|       kCFAllocatorDefault, (UInt8*)mPath.get(), mPath.Length(), isDir);
 | |
| 
 | |
|   return (*aResult ? NS_OK : NS_ERROR_FAILURE);
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::GetFSRef(FSRef* aResult) {
 | |
|   if (NS_WARN_IF(!aResult)) {
 | |
|     return NS_ERROR_INVALID_ARG;
 | |
|   }
 | |
| 
 | |
|   nsresult rv = NS_ERROR_FAILURE;
 | |
| 
 | |
|   CFURLRef url = nullptr;
 | |
|   if (NS_SUCCEEDED(GetCFURL(&url))) {
 | |
|     if (::CFURLGetFSRef(url, aResult)) {
 | |
|       rv = NS_OK;
 | |
|     }
 | |
|     ::CFRelease(url);
 | |
|   }
 | |
| 
 | |
|   return rv;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::GetFSSpec(FSSpec* aResult) {
 | |
|   if (NS_WARN_IF(!aResult)) {
 | |
|     return NS_ERROR_INVALID_ARG;
 | |
|   }
 | |
| 
 | |
|   FSRef fsRef;
 | |
|   nsresult rv = GetFSRef(&fsRef);
 | |
|   if (NS_SUCCEEDED(rv)) {
 | |
|     OSErr err = ::FSGetCatalogInfo(&fsRef, kFSCatInfoNone, nullptr, nullptr,
 | |
|                                    aResult, nullptr);
 | |
|     return MacErrorMapper(err);
 | |
|   }
 | |
| 
 | |
|   return rv;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::GetFileSizeWithResFork(int64_t* aFileSizeWithResFork) {
 | |
|   if (NS_WARN_IF(!aFileSizeWithResFork)) {
 | |
|     return NS_ERROR_INVALID_ARG;
 | |
|   }
 | |
| 
 | |
|   FSRef fsRef;
 | |
|   nsresult rv = GetFSRef(&fsRef);
 | |
|   if (NS_FAILED(rv)) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   FSCatalogInfo catalogInfo;
 | |
|   OSErr err =
 | |
|       ::FSGetCatalogInfo(&fsRef, kFSCatInfoDataSizes + kFSCatInfoRsrcSizes,
 | |
|                          &catalogInfo, nullptr, nullptr, nullptr);
 | |
|   if (err != noErr) {
 | |
|     return MacErrorMapper(err);
 | |
|   }
 | |
| 
 | |
|   *aFileSizeWithResFork =
 | |
|       catalogInfo.dataLogicalSize + catalogInfo.rsrcLogicalSize;
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::GetFileType(OSType* aFileType) {
 | |
|   CFURLRef url;
 | |
|   if (NS_SUCCEEDED(GetCFURL(&url))) {
 | |
|     nsresult rv = CocoaFileUtils::GetFileTypeCode(url, aFileType);
 | |
|     ::CFRelease(url);
 | |
|     return rv;
 | |
|   }
 | |
|   return NS_ERROR_FAILURE;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::SetFileType(OSType aFileType) {
 | |
|   CFURLRef url;
 | |
|   if (NS_SUCCEEDED(GetCFURL(&url))) {
 | |
|     nsresult rv = CocoaFileUtils::SetFileTypeCode(url, aFileType);
 | |
|     ::CFRelease(url);
 | |
|     return rv;
 | |
|   }
 | |
|   return NS_ERROR_FAILURE;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::GetFileCreator(OSType* aFileCreator) {
 | |
|   CFURLRef url;
 | |
|   if (NS_SUCCEEDED(GetCFURL(&url))) {
 | |
|     nsresult rv = CocoaFileUtils::GetFileCreatorCode(url, aFileCreator);
 | |
|     ::CFRelease(url);
 | |
|     return rv;
 | |
|   }
 | |
|   return NS_ERROR_FAILURE;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::SetFileCreator(OSType aFileCreator) {
 | |
|   CFURLRef url;
 | |
|   if (NS_SUCCEEDED(GetCFURL(&url))) {
 | |
|     nsresult rv = CocoaFileUtils::SetFileCreatorCode(url, aFileCreator);
 | |
|     ::CFRelease(url);
 | |
|     return rv;
 | |
|   }
 | |
|   return NS_ERROR_FAILURE;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::LaunchWithDoc(nsIFile* aDocToLoad, bool aLaunchInBackground) {
 | |
|   bool isExecutable;
 | |
|   nsresult rv = IsExecutable(&isExecutable);
 | |
|   if (NS_FAILED(rv)) {
 | |
|     return rv;
 | |
|   }
 | |
|   if (!isExecutable) {
 | |
|     return NS_ERROR_FILE_EXECUTION_FAILED;
 | |
|   }
 | |
| 
 | |
|   FSRef appFSRef, docFSRef;
 | |
|   rv = GetFSRef(&appFSRef);
 | |
|   if (NS_FAILED(rv)) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   if (aDocToLoad) {
 | |
|     nsCOMPtr<nsILocalFileMac> macDoc = do_QueryInterface(aDocToLoad);
 | |
|     rv = macDoc->GetFSRef(&docFSRef);
 | |
|     if (NS_FAILED(rv)) {
 | |
|       return rv;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   LSLaunchFlags theLaunchFlags = kLSLaunchDefaults;
 | |
|   LSLaunchFSRefSpec thelaunchSpec;
 | |
| 
 | |
|   if (aLaunchInBackground) {
 | |
|     theLaunchFlags |= kLSLaunchDontSwitch;
 | |
|   }
 | |
|   memset(&thelaunchSpec, 0, sizeof(LSLaunchFSRefSpec));
 | |
| 
 | |
|   thelaunchSpec.appRef = &appFSRef;
 | |
|   if (aDocToLoad) {
 | |
|     thelaunchSpec.numDocs = 1;
 | |
|     thelaunchSpec.itemRefs = &docFSRef;
 | |
|   }
 | |
|   thelaunchSpec.launchFlags = theLaunchFlags;
 | |
| 
 | |
|   OSErr err = ::LSOpenFromRefSpec(&thelaunchSpec, nullptr);
 | |
|   if (err != noErr) {
 | |
|     return MacErrorMapper(err);
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::OpenDocWithApp(nsIFile* aAppToOpenWith, bool aLaunchInBackground) {
 | |
|   FSRef docFSRef;
 | |
|   nsresult rv = GetFSRef(&docFSRef);
 | |
|   if (NS_FAILED(rv)) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   if (!aAppToOpenWith) {
 | |
|     OSErr err = ::LSOpenFSRef(&docFSRef, nullptr);
 | |
|     return MacErrorMapper(err);
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsILocalFileMac> appFileMac = do_QueryInterface(aAppToOpenWith, &rv);
 | |
|   if (!appFileMac) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   bool isExecutable;
 | |
|   rv = appFileMac->IsExecutable(&isExecutable);
 | |
|   if (NS_FAILED(rv)) {
 | |
|     return rv;
 | |
|   }
 | |
|   if (!isExecutable) {
 | |
|     return NS_ERROR_FILE_EXECUTION_FAILED;
 | |
|   }
 | |
| 
 | |
|   FSRef appFSRef;
 | |
|   rv = appFileMac->GetFSRef(&appFSRef);
 | |
|   if (NS_FAILED(rv)) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   LSLaunchFlags theLaunchFlags = kLSLaunchDefaults;
 | |
|   LSLaunchFSRefSpec thelaunchSpec;
 | |
| 
 | |
|   if (aLaunchInBackground) {
 | |
|     theLaunchFlags |= kLSLaunchDontSwitch;
 | |
|   }
 | |
|   memset(&thelaunchSpec, 0, sizeof(LSLaunchFSRefSpec));
 | |
| 
 | |
|   thelaunchSpec.appRef = &appFSRef;
 | |
|   thelaunchSpec.numDocs = 1;
 | |
|   thelaunchSpec.itemRefs = &docFSRef;
 | |
|   thelaunchSpec.launchFlags = theLaunchFlags;
 | |
| 
 | |
|   OSErr err = ::LSOpenFromRefSpec(&thelaunchSpec, nullptr);
 | |
|   if (err != noErr) {
 | |
|     return MacErrorMapper(err);
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::IsPackage(bool* aResult) {
 | |
|   if (NS_WARN_IF(!aResult)) {
 | |
|     return NS_ERROR_INVALID_ARG;
 | |
|   }
 | |
|   *aResult = false;
 | |
| 
 | |
|   CFURLRef url;
 | |
|   nsresult rv = GetCFURL(&url);
 | |
|   if (NS_FAILED(rv)) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   LSItemInfoRecord info;
 | |
|   OSStatus status =
 | |
|       ::LSCopyItemInfoForURL(url, kLSRequestBasicFlagsOnly, &info);
 | |
| 
 | |
|   ::CFRelease(url);
 | |
| 
 | |
|   if (status != noErr) {
 | |
|     return NS_ERROR_FAILURE;
 | |
|   }
 | |
| 
 | |
|   *aResult = !!(info.flags & kLSItemInfoIsPackage);
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::GetBundleDisplayName(nsAString& aOutBundleName) {
 | |
|   bool isPackage = false;
 | |
|   nsresult rv = IsPackage(&isPackage);
 | |
|   if (NS_FAILED(rv) || !isPackage) {
 | |
|     return NS_ERROR_FAILURE;
 | |
|   }
 | |
| 
 | |
|   nsAutoString name;
 | |
|   rv = GetLeafName(name);
 | |
|   if (NS_FAILED(rv)) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   int32_t length = name.Length();
 | |
|   if (Substring(name, length - 4, length).EqualsLiteral(".app")) {
 | |
|     // 4 characters in ".app"
 | |
|     aOutBundleName = Substring(name, 0, length - 4);
 | |
|   } else {
 | |
|     aOutBundleName = name;
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::GetBundleIdentifier(nsACString& aOutBundleIdentifier) {
 | |
|   nsresult rv = NS_ERROR_FAILURE;
 | |
| 
 | |
|   CFURLRef urlRef;
 | |
|   if (NS_SUCCEEDED(GetCFURL(&urlRef))) {
 | |
|     CFBundleRef bundle = ::CFBundleCreate(nullptr, urlRef);
 | |
|     if (bundle) {
 | |
|       CFStringRef bundleIdentifier = ::CFBundleGetIdentifier(bundle);
 | |
|       if (bundleIdentifier) {
 | |
|         rv = CFStringReftoUTF8(bundleIdentifier, aOutBundleIdentifier);
 | |
|       }
 | |
|       ::CFRelease(bundle);
 | |
|     }
 | |
|     ::CFRelease(urlRef);
 | |
|   }
 | |
| 
 | |
|   return rv;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsLocalFile::GetBundleContentsLastModifiedTime(int64_t* aLastModTime) {
 | |
|   CHECK_mPath();
 | |
|   if (NS_WARN_IF(!aLastModTime)) {
 | |
|     return NS_ERROR_INVALID_ARG;
 | |
|   }
 | |
| 
 | |
|   bool isPackage = false;
 | |
|   nsresult rv = IsPackage(&isPackage);
 | |
|   if (NS_FAILED(rv) || !isPackage) {
 | |
|     return GetLastModifiedTime(aLastModTime);
 | |
|   }
 | |
| 
 | |
|   nsAutoCString infoPlistPath(mPath);
 | |
|   infoPlistPath.AppendLiteral("/Contents/Info.plist");
 | |
|   PRFileInfo64 info;
 | |
|   if (PR_GetFileInfo64(infoPlistPath.get(), &info) != PR_SUCCESS) {
 | |
|     return GetLastModifiedTime(aLastModTime);
 | |
|   }
 | |
|   int64_t modTime = int64_t(info.modifyTime);
 | |
|   if (modTime == 0) {
 | |
|     *aLastModTime = 0;
 | |
|   } else {
 | |
|     *aLastModTime = modTime / int64_t(PR_USEC_PER_MSEC);
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP nsLocalFile::InitWithFile(nsIFile* aFile) {
 | |
|   if (NS_WARN_IF(!aFile)) {
 | |
|     return NS_ERROR_INVALID_ARG;
 | |
|   }
 | |
| 
 | |
|   nsAutoCString nativePath;
 | |
|   nsresult rv = aFile->GetNativePath(nativePath);
 | |
|   if (NS_FAILED(rv)) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   return InitWithNativePath(nativePath);
 | |
| }
 | |
| 
 | |
| nsresult NS_NewLocalFileWithFSRef(const FSRef* aFSRef, bool aFollowLinks,
 | |
|                                   nsILocalFileMac** aResult) {
 | |
|   RefPtr<nsLocalFile> file = new nsLocalFile();
 | |
| 
 | |
|   nsresult rv = file->InitWithFSRef(aFSRef);
 | |
|   if (NS_FAILED(rv)) {
 | |
|     return rv;
 | |
|   }
 | |
|   file.forget(aResult);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult NS_NewLocalFileWithCFURL(const CFURLRef aURL, bool aFollowLinks,
 | |
|                                   nsILocalFileMac** aResult) {
 | |
|   RefPtr<nsLocalFile> file = new nsLocalFile();
 | |
| 
 | |
|   nsresult rv = file->InitWithCFURL(aURL);
 | |
|   if (NS_FAILED(rv)) {
 | |
|     return rv;
 | |
|   }
 | |
|   file.forget(aResult);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| #endif
 | 
