Files
tubestation/xpcom/io/nsLocalFileUnix.cpp
Jan Grulich a2c79faafd Bug 1882079 - Display real path when choosing download directory over portal r=settings-reviewers,Gijs,emilio
Use the new API addition to Document portal allowing clients to get real
path to the exported document. This allows to still use the same path as
provided by the document portal, but display the path as exists on the
host side.

Differential Revision: https://phabricator.services.mozilla.com/D202717
2024-06-28 14:33:04 +00:00

3092 lines
80 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 "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 "mozilla/dom/Promise.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"
# ifdef MOZ_ENABLE_DBUS
# include "mozilla/widget/AsyncDBus.h"
# include "mozilla/WidgetUtilsGtk.h"
# include <map>
# endif
#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)
// Prefix for files exported through document portal when we are
// in a sandboxed environment (Flatpak).
static const nsCString& GetDocumentStorePath() {
static const nsDependentCString sDocumentStorePath = [] {
nsCString storePath = nsPrintfCString("/run/user/%d/doc/", getuid());
// Intentionally put into a ToNewCString copy, rather than just making a
// static nsCString to avoid leakchecking errors, since we really want to
// leak this string.
return nsDependentCString(ToNewCString(storePath), storePath.Length());
}();
return sDocumentStorePath;
}
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);
}
NS_IMETHODIMP
nsLocalFile::HostPath(JSContext* aCx, dom::Promise** aPromise) {
MOZ_ASSERT(aCx);
MOZ_ASSERT(aPromise);
nsIGlobalObject* globalObject = xpc::CurrentNativeGlobal(aCx);
if (NS_WARN_IF(!globalObject)) {
return NS_ERROR_FAILURE;
}
ErrorResult result;
RefPtr<dom::Promise> retPromise = dom::Promise::Create(globalObject, result);
if (NS_WARN_IF(result.Failed())) {
return result.StealNSResult();
}
#if defined(MOZ_ENABLE_DBUS) && defined(MOZ_WIDGET_GTK)
if (!widget::IsRunningUnderFlatpak() ||
!StringBeginsWith(mPath, GetDocumentStorePath())) {
retPromise->MaybeResolve(mPath);
retPromise.forget(aPromise);
return NS_OK;
}
nsCString docId = [this] {
auto subPath = Substring(mPath, GetDocumentStorePath().Length());
if (auto idx = subPath.Find("/"); idx > 0) {
subPath.Truncate(idx);
}
return nsCString(subPath);
}();
const char kServiceName[] = "org.freedesktop.portal.Documents";
const char kDBusPath[] = "/org/freedesktop/portal/documents";
const char kInterfaceName[] = "org.freedesktop.portal.Documents";
widget::CreateDBusProxyForBus(G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE,
/* aInterfaceInfo = */ nullptr, kServiceName,
kDBusPath, kInterfaceName)
->Then(
GetCurrentSerialEventTarget(), __func__,
[this, self = RefPtr(this), docId,
retPromise](RefPtr<GDBusProxy>&& aProxy) {
RefPtr<GVariant> version = dont_AddRef(
g_dbus_proxy_get_cached_property(aProxy, "version"));
if (!version ||
!g_variant_is_of_type(version, G_VARIANT_TYPE_UINT32)) {
g_printerr(
"nsIFile: failed to get host path for %s\n: Invalid value.",
mPath.get());
retPromise->MaybeReject(NS_ERROR_FAILURE);
return;
}
if (g_variant_get_uint32(version) < 5) {
g_printerr(
"nsIFile: failed to get host path for %s\n: Document "
"portal in version 5 is required.",
mPath.get());
retPromise->MaybeReject(NS_ERROR_NOT_AVAILABLE);
return;
}
GVariantBuilder builder;
g_variant_builder_init(&builder, G_VARIANT_TYPE("(as)"));
g_variant_builder_open(&builder, G_VARIANT_TYPE("as"));
g_variant_builder_add(&builder, "s", docId.get());
g_variant_builder_close(&builder);
RefPtr<GVariant> args = dont_AddRef(
g_variant_ref_sink(g_variant_builder_end(&builder)));
if (!args) {
g_printerr(
"nsIFile: failed to get host path for %s\n: "
"Invalid value.",
mPath.get());
retPromise->MaybeReject(NS_ERROR_FAILURE);
return;
}
widget::DBusProxyCall(aProxy, "GetHostPaths", args,
G_DBUS_CALL_FLAGS_NONE, -1,
/* cancellable */ nullptr)
->Then(
GetCurrentSerialEventTarget(), __func__,
[this, self = RefPtr(this), docId,
retPromise](RefPtr<GVariant>&& aResult) {
RefPtr<GVariant> result = dont_AddRef(
g_variant_get_child_value(aResult.get(), 0));
if (!g_variant_is_of_type(result,
G_VARIANT_TYPE("a{say}"))) {
g_printerr(
"nsIFile: failed to get host path for %s\n: "
"Invalid value.",
mPath.get());
retPromise->MaybeReject(NS_ERROR_FAILURE);
return;
}
const gchar* key = nullptr;
const gchar* path = nullptr;
GVariantIter* iter = g_variant_iter_new(result);
while (
g_variant_iter_loop(iter, "{&s^&ay}", &key, &path)) {
if (g_strcmp0(key, docId.get()) == 0) {
retPromise->MaybeResolve(nsDependentCString(path));
g_variant_iter_free(iter);
return;
}
}
g_variant_iter_free(iter);
g_printerr(
"nsIFile: failed to get host path for %s\n: "
"Invalid value.",
mPath.get());
retPromise->MaybeReject(NS_ERROR_FAILURE);
},
[this, self = RefPtr(this),
retPromise](GUniquePtr<GError>&& aError) {
g_printerr(
"nsIFile: failed to get host path for %s\n: %s.",
mPath.get(), aError->message);
retPromise->MaybeReject(NS_ERROR_FAILURE);
});
},
[this, self = RefPtr(this), retPromise](GUniquePtr<GError>&& aError) {
g_printerr("nsIFile: failed to get host path for %s\n: %s.",
mPath.get(), aError->message);
retPromise->MaybeReject(NS_ERROR_NOT_AVAILABLE);
});
#else
retPromise->MaybeResolve(mPath);
#endif
retPromise.forget(aPromise);
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;
(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