diff options
Diffstat (limited to 'xpcom/io/nsLocalFileUnix.cpp')
-rw-r--r-- | xpcom/io/nsLocalFileUnix.cpp | 2932 |
1 files changed, 2932 insertions, 0 deletions
diff --git a/xpcom/io/nsLocalFileUnix.cpp b/xpcom/io/nsLocalFileUnix.cpp new file mode 100644 index 0000000000..ae501f4041 --- /dev/null +++ b/xpcom/io/nsLocalFileUnix.cpp @@ -0,0 +1,2932 @@ +/* -*- 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 "nsLocalFile.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/Attributes.h" +#include "mozilla/CheckedInt.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/Sprintf.h" +#include "mozilla/FilePreferences.h" +#include "prtime.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 <dirent.h> + +#if defined(XP_MACOSX) +# include <sys/xattr.h> +#endif + +#if defined(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 "nsCOMPtr.h" +#include "nsIFile.h" +#include "nsString.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" + +/** + * 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 + +#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(const nsIID& aIID, + void** aInstancePtr) { + if (NS_WARN_IF(!aInstancePtr)) { + return NS_ERROR_INVALID_ARG; + } + + *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.IsEmpty() && aFilePath.First() == '~') { + if (aFilePath.Length() == 1 || aFilePath.CharAt(1) == '/') { + // Home dir for the current user + + 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)); + } + } else { + // Home dir for an arbitrary user e.g. `~foo/bar` -> `/home/foo/bar` + // (`/Users/foo/bar` on Mac). The accurate way to get this directory + // is with `getpwnam`, but we would like to avoid doing blocking + // filesystem I/O while creating an `nsIFile`. + + mPath = +#ifdef XP_MACOSX + "/Users/"_ns +#else + "/home/"_ns +#endif + + Substring(aFilePath, 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 and cannot append ".." + nsACString::const_iterator begin, end; + if (aFragment.EqualsASCII("..") || + 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 '/' and cannot be ".." + if (aFragment.First() == '/' || aFragment.EqualsASCII("..")) { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + + if (aFragment.Contains('/')) { + // can't contain .. as a path component. Ensure that the valid components + // "foo..foo", "..foo", and "foo.." are not falsely detected, + // but the invalid paths "../", "foo/..", "foo/../foo", + // "../foo", etc are. + constexpr auto doubleDot = "/.."_ns; + nsACString::const_iterator start, end, offset; + aFragment.BeginReading(start); + aFragment.EndReading(end); + offset = end; + while (FindInReadable(doubleDot, start, offset)) { + if (offset == end || *offset == '/') { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + start = offset; + offset = end; + } + + // catches the remaining cases of prefixes + if (StringBeginsWith(aFragment, "../"_ns)) { + 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; +} + +NS_IMETHODIMP +nsLocalFile::GetDisplayName(nsAString& aLeafName) { + return GetLeafName(aLeafName); +} + +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; + (void)HasXAttr("com.apple.quarantine"_ns, &quarantined); +#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. + (void)newFile->DelXAttr("com.apple.quarantine"_ns); + } +#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, uint32_t* aRemoveCount) { + CHECK_mPath(); + ENSURE_STAT_CACHE(); + + bool isSymLink; + + nsresult rv = IsSymlink(&isSymLink); + if (NS_FAILED(rv)) { + return rv; + } + + if (isSymLink || !S_ISDIR(mCachedStat.st_mode)) { + rv = NSRESULT_FOR_RETURN(unlink(mPath.get())); + if (NS_SUCCEEDED(rv) && aRemoveCount) { + *aRemoveCount += 1; + } + return rv; + } + + 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; + } + // XXX: We care the result of the removal here while + // nsLocalFileWin does not. We should align the behavior. (bug 1779696) + rv = file->Remove(aRecursive, aRemoveCount); + +#ifdef ANDROID + // See bug 580434 - Bionic gives us just deleted files + if (rv == NS_ERROR_FILE_NOT_FOUND) { + continue; + } +#endif + if (NS_FAILED(rv)) { + return rv; + } + } + } + + rv = NSRESULT_FOR_RETURN(rmdir(mPath.get())); + if (NS_SUCCEEDED(rv) && aRemoveCount) { + *aRemoveCount += 1; + } + return rv; +} + +nsresult nsLocalFile::GetTimeImpl(PRTime* aTime, + nsLocalFile::TimeField aTimeField, + bool aFollowLinks) { + CHECK_mPath(); + if (NS_WARN_IF(!aTime)) { + 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(); + } + + struct timespec* timespec; + switch (aTimeField) { + case TimeField::AccessedTime: +#if (defined(__APPLE__) && defined(__MACH__)) + timespec = &fileStats.st_atimespec; +#else + timespec = &fileStats.st_atim; +#endif + break; + + case TimeField::ModifiedTime: +#if (defined(__APPLE__) && defined(__MACH__)) + timespec = &fileStats.st_mtimespec; +#else + timespec = &fileStats.st_mtim; +#endif + break; + + default: + MOZ_CRASH("Unknown TimeField"); + } + + *aTime = TimespecToMillis(*timespec); + + return NS_OK; +} + +nsresult nsLocalFile::SetTimeImpl(PRTime aTime, + nsLocalFile::TimeField aTimeField, + bool aFollowLinks) { + CHECK_mPath(); + + using UtimesFn = int (*)(const char*, const timeval*); + UtimesFn utimesFn = &utimes; + +#if HAVE_LUTIMES + if (!aFollowLinks) { + utimesFn = &lutimes; + } +#endif + + ENSURE_STAT_CACHE(); + + if (aTime == 0) { + aTime = PR_Now(); + } + + // We only want to write to a single field (accessed time or modified time), + // but utimes() doesn't let you omit one. If you do, it will set that field to + // the current time, which is not what we want. + // + // So what we do is write to both fields, but copy one of the fields from our + // cached stat structure. + // + // If we are writing to the accessed time field, then we want to copy the + // modified time and vice versa. + + timeval times[2]; + + const size_t writeIndex = aTimeField == TimeField::AccessedTime ? 0 : 1; + const size_t copyIndex = aTimeField == TimeField::AccessedTime ? 1 : 0; + +#if (defined(__APPLE__) && defined(__MACH__)) + auto* copyFrom = aTimeField == TimeField::AccessedTime + ? &mCachedStat.st_mtimespec + : &mCachedStat.st_atimespec; +#else + auto* copyFrom = aTimeField == TimeField::AccessedTime ? &mCachedStat.st_mtim + : &mCachedStat.st_atim; +#endif + + times[copyIndex].tv_sec = copyFrom->tv_sec; + times[copyIndex].tv_usec = copyFrom->tv_nsec / 1000; + + times[writeIndex].tv_sec = aTime / PR_MSEC_PER_SEC; + times[writeIndex].tv_usec = (aTime % PR_MSEC_PER_SEC) * PR_USEC_PER_MSEC; + + int result = utimesFn(mPath.get(), times); + return NSRESULT_FOR_RETURN(result); +} + +NS_IMETHODIMP +nsLocalFile::GetLastAccessedTime(PRTime* aLastAccessedTime) { + return GetTimeImpl(aLastAccessedTime, TimeField::AccessedTime, + /* follow links? */ true); +} + +NS_IMETHODIMP +nsLocalFile::SetLastAccessedTime(PRTime aLastAccessedTime) { + return SetTimeImpl(aLastAccessedTime, TimeField::AccessedTime, + /* follow links? */ true); +} + +NS_IMETHODIMP +nsLocalFile::GetLastAccessedTimeOfLink(PRTime* aLastAccessedTime) { + return GetTimeImpl(aLastAccessedTime, TimeField::AccessedTime, + /* follow links? */ false); +} + +NS_IMETHODIMP +nsLocalFile::SetLastAccessedTimeOfLink(PRTime aLastAccessedTime) { + return SetTimeImpl(aLastAccessedTime, TimeField::AccessedTime, + /* follow links? */ false); +} + +NS_IMETHODIMP +nsLocalFile::GetLastModifiedTime(PRTime* aLastModTime) { + return GetTimeImpl(aLastModTime, TimeField::ModifiedTime, + /* follow links? */ true); +} + +NS_IMETHODIMP +nsLocalFile::SetLastModifiedTime(PRTime aLastModTime) { + return SetTimeImpl(aLastModTime, TimeField::ModifiedTime, + /* follow links ? */ true); +} + +NS_IMETHODIMP +nsLocalFile::GetLastModifiedTimeOfLink(PRTime* aLastModTimeOfLink) { + return GetTimeImpl(aLastModTimeOfLink, TimeField::ModifiedTime, + /* follow link? */ false); +} + +NS_IMETHODIMP +nsLocalFile::SetLastModifiedTimeOfLink(PRTime aLastModTimeOfLink) { + return SetTimeImpl(aLastModTimeOfLink, TimeField::ModifiedTime, + /* 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 + +#if defined(USE_LINUX_QUOTACTL) +template <typename StatInfoFunc, typename QuotaInfoFunc> +nsresult nsLocalFile::GetDiskInfo(StatInfoFunc&& aStatInfoFunc, + QuotaInfoFunc&& aQuotaInfoFunc, + int64_t* aResult) +#else +template <typename StatInfoFunc> +nsresult nsLocalFile::GetDiskInfo(StatInfoFunc&& aStatInfoFunc, + int64_t* aResult) +#endif +{ + if (NS_WARN_IF(!aResult)) { + 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. + * f_blocks = number of total used or free blocks in file system. + */ + + if (STATFS(mPath.get(), &fs_buf) < 0) { + // The call to STATFS failed. +# ifdef DEBUG + printf("ERROR: GetDiskInfo: STATFS call FAILED. \n"); +# endif + return NS_ERROR_FAILURE; + } + + CheckedInt64 statfsResult = std::forward<StatInfoFunc>(aStatInfoFunc)(fs_buf); + if (!statfsResult.isValid()) { + return NS_ERROR_CANNOT_CONVERT_DATA; + } + + // Assign statfsResult to *aResult in case one of the quota calls fails. + *aResult = statfsResult.value(); + +# if defined(USE_LINUX_QUOTACTL) + + if (!FillStatCache()) { + // Returns info from statfs + return NS_OK; + } + + nsAutoCString deviceName; + if (!GetDeviceName(major(mCachedStat.st_dev), minor(mCachedStat.st_dev), + deviceName)) { + // Returns info from statfs + 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) { + CheckedInt64 quotaResult = std::forward<QuotaInfoFunc>(aQuotaInfoFunc)(dq); + if (!quotaResult.isValid()) { + // Returns info from statfs + return NS_OK; + } + + if (quotaResult.value() < *aResult) { + *aResult = quotaResult.value(); + } + } +# endif // defined(USE_LINUX_QUOTACTL) + +# ifdef DEBUG_DISK_SPACE + printf("DiskInfo: %lu bytes\n", *aResult); +# endif + + return NS_OK; + +#else // STATFS + /* + * This platform doesn't have statfs or statvfs. I'm sure that there's + * a way to check for free disk space and disk capacity 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: GetDiskInfo: Not implemented for plaforms without statfs.\n"); +# endif + return NS_ERROR_NOT_IMPLEMENTED; + +#endif // STATFS +} + +NS_IMETHODIMP +nsLocalFile::GetDiskSpaceAvailable(int64_t* aDiskSpaceAvailable) { + return GetDiskInfo( + [](const struct STATFS& aStatInfo) { + return aStatInfo.f_bavail * static_cast<uint64_t>(aStatInfo.F_BSIZE); + }, +#if defined(USE_LINUX_QUOTACTL) + [](const struct dqblk& aQuotaInfo) -> uint64_t { + // dqb_bhardlimit is count of BLOCK_SIZE blocks, dqb_curspace is bytes + const uint64_t hardlimit = aQuotaInfo.dqb_bhardlimit * BLOCK_SIZE; + if (hardlimit > aQuotaInfo.dqb_curspace) { + return hardlimit - aQuotaInfo.dqb_curspace; + } + return 0; + }, +#endif + aDiskSpaceAvailable); +} + +NS_IMETHODIMP +nsLocalFile::GetDiskCapacity(int64_t* aDiskCapacity) { + return GetDiskInfo( + [](const struct STATFS& aStatInfo) { + return aStatInfo.f_blocks * static_cast<uint64_t>(aStatInfo.F_BSIZE); + }, +#if defined(USE_LINUX_QUOTACTL) + [](const struct dqblk& aQuotaInfo) { + // dqb_bhardlimit is count of BLOCK_SIZE blocks + return aQuotaInfo.dqb_bhardlimit * BLOCK_SIZE; + }, +#endif + aDiskCapacity); +} + +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[] = { +#ifdef MOZ_WIDGET_COCOA + "afploc", // Can point to other files. +#endif + "air", // Adobe AIR installer +#ifdef MOZ_WIDGET_COCOA + "atloc", // Can point to other files. + "fileloc", // File location files can be used to point to other + // files. + "ftploc", // Can point to other files. + "inetloc", // Shouldn't be able to do the same, but can, due to + // macOS vulnerabilities. +#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; +} + +static nsresult ReadLinkSafe(const nsCString& aTarget, int32_t aExpectedSize, + nsACString& aOutBuffer) { + // If we call readlink with a buffer size S it returns S, then we cannot tell + // if the buffer was big enough to hold the entire path. We allocate an + // additional byte so we can check if the buffer was large enough. + const auto allocSize = CheckedInt<size_t>(aExpectedSize) + 1; + if (!allocSize.isValid()) { + return NS_ERROR_OUT_OF_MEMORY; + } + + auto result = aOutBuffer.BulkWrite(allocSize.value(), 0, false); + if (result.isErr()) { + return result.unwrapErr(); + } + + auto handle = result.unwrap(); + + while (true) { + ssize_t bytesWritten = + readlink(aTarget.get(), handle.Elements(), handle.Length()); + if (bytesWritten < 0) { + return NSRESULT_FOR_ERRNO(); + } + + // written >= 0 so it is safe to cast to size_t. + if ((size_t)bytesWritten < handle.Length()) { + // Target might have changed since the lstat call, or lstat might lie, see + // bug 1791029. + handle.Finish(bytesWritten, false); + return NS_OK; + } + + // The buffer was not large enough, so double it and try again. + auto restartResult = handle.RestartBulkWrite(handle.Length() * 2, 0, false); + if (restartResult.isErr()) { + return restartResult.unwrapErr(); + } + } +} + +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; + } + + nsAutoCString target; + nsresult rv = ReadLinkSafe(mPath, symStat.st_size, target); + if (NS_FAILED(rv)) { + return rv; + } + + 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; + } + + nsAutoCString newTarget; + rv = ReadLinkSafe(flatRetval, symStat.st_size, newTarget); + if (NS_FAILED(rv)) { + 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; + } + return giovfs->RevealFile(this); +#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->LaunchFile(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 + +NS_IMETHODIMP +nsLocalFile::HasXAttr(const nsACString& aAttrName, bool* aHasAttr) { + NS_ENSURE_ARG_POINTER(aHasAttr); + + nsAutoCString attrName{aAttrName}; + + ssize_t size = getxattr(mPath.get(), attrName.get(), nullptr, 0, 0, 0); + if (size == -1) { + if (errno == ENOATTR) { + *aHasAttr = false; + } else { + return NSRESULT_FOR_ERRNO(); + } + } else { + *aHasAttr = true; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetXAttr(const nsACString& aAttrName, + nsTArray<uint8_t>& aAttrValue) { + aAttrValue.Clear(); + + nsAutoCString attrName{aAttrName}; + + ssize_t size = getxattr(mPath.get(), attrName.get(), nullptr, 0, 0, 0); + + if (size == -1) { + return NSRESULT_FOR_ERRNO(); + } + + for (;;) { + aAttrValue.SetCapacity(size); + + // The attribute can change between our first call and this call, so we need + // to re-check the size and possibly call with a larger buffer. + ssize_t newSize = getxattr(mPath.get(), attrName.get(), + aAttrValue.Elements(), size, 0, 0); + if (newSize == -1) { + return NSRESULT_FOR_ERRNO(); + } + + if (newSize <= size) { + aAttrValue.SetLength(newSize); + break; + } else { + size = newSize; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::SetXAttr(const nsACString& aAttrName, + const nsTArray<uint8_t>& aAttrValue) { + nsAutoCString attrName{aAttrName}; + + if (setxattr(mPath.get(), attrName.get(), aAttrValue.Elements(), + aAttrValue.Length(), 0, 0) == -1) { + return NSRESULT_FOR_ERRNO(); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::DelXAttr(const nsACString& aAttrName) { + nsAutoCString attrName{aAttrName}; + + // Ignore removing an attribute that does not exist. + if (removexattr(mPath.get(), attrName.get(), 0) == -1) { + return NSRESULT_FOR_ERRNO(); + } + + return NS_OK; +} + +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 |