Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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)
#if defined(MOZ_ENABLE_DBUS) && defined(MOZ_WIDGET_GTK)
// 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;
}
#endif
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 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: Invalid value.\n",
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: Document "
"portal in version 5 is required.\n",
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: "
"Invalid value.\n",
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: "
"Invalid value.\n",
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: "
"Invalid value.\n",
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: %s.\n",
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: %s.\n",
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),
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.
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
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, 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;
}
nsresult NS_NewUTF8LocalFile(const nsACString& aPath, nsIFile** aResult) {
static_assert(NS_IsNativeUTF8());
return NS_NewNativeLocalFile(aPath, aResult);
}
nsresult NS_NewPathStringLocalFile(const PathSubstring& aPath,
nsIFile** aResult) {
return NS_NewNativeLocalFile(aPath, aResult);
}
//-----------------------------------------------------------------------------
// 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, nsIFile** aResult) {
nsAutoCString buf;
nsresult rv = NS_CopyUnicodeToNative(aPath, buf);
if (NS_FAILED(rv)) {
return rv;
}
return NS_NewNativeLocalFile(buf, 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,
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,
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