1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 * Implementation of nsIFile for "unixy" systems.
11 #include "nsLocalFile.h"
13 #include "mozilla/ArrayUtils.h"
14 #include "mozilla/Attributes.h"
15 #include "mozilla/CheckedInt.h"
16 #include "mozilla/DebugOnly.h"
17 #include "mozilla/Sprintf.h"
18 #include "mozilla/FilePreferences.h"
19 #include "mozilla/dom/Promise.h"
22 #include <sys/select.h>
25 #include <sys/types.h>
31 #if defined(XP_MACOSX)
32 # include <sys/xattr.h>
35 #if defined(USE_LINUX_QUOTACTL)
36 # include <sys/mount.h>
37 # include <sys/quota.h>
38 # include <sys/sysmacros.h>
40 # define BLOCK_SIZE 1024 /* kernel block size */
44 #include "nsDirectoryServiceDefs.h"
48 #include "nsIDirectoryEnumerator.h"
49 #include "nsSimpleEnumerator.h"
50 #include "private/pprio.h"
54 # include "nsIGIOService.h"
55 # ifdef MOZ_ENABLE_DBUS
56 # include "mozilla/widget/AsyncDBus.h"
57 # include "mozilla/WidgetUtilsGtk.h"
62 #ifdef MOZ_WIDGET_COCOA
63 # include <Carbon/Carbon.h>
64 # include "CocoaFileUtils.h"
66 # include "plbase64.h"
68 static nsresult
MacErrorMapper(OSErr inErr
);
71 #ifdef MOZ_WIDGET_ANDROID
72 # include "mozilla/java/GeckoAppShellWrappers.h"
73 # include "nsIMIMEService.h"
74 # include <linux/magic.h>
77 #include "nsNativeCharsetUtils.h"
78 #include "nsTraceRefcnt.h"
81 * we need these for statfs()
83 #ifdef HAVE_SYS_STATVFS_H
84 # if defined(__osf__) && defined(__DECCXX)
85 extern "C" int statvfs(const char*, struct statvfs
*);
87 # include <sys/statvfs.h>
90 #ifdef HAVE_SYS_STATFS_H
91 # include <sys/statfs.h>
98 #if defined(HAVE_STATVFS64) && (!defined(LINUX) && !defined(__osf__))
99 # define STATFS statvfs64
100 # define F_BSIZE f_frsize
101 #elif defined(HAVE_STATVFS) && (!defined(LINUX) && !defined(__osf__))
102 # define STATFS statvfs
103 # define F_BSIZE f_frsize
104 #elif defined(HAVE_STATFS64)
105 # define STATFS statfs64
106 # define F_BSIZE f_bsize
107 #elif defined(HAVE_STATFS)
108 # define STATFS statfs
109 # define F_BSIZE f_bsize
112 using namespace mozilla
;
114 #define ENSURE_STAT_CACHE() \
116 if (!FillStatCache()) return NSRESULT_FOR_ERRNO(); \
119 #define CHECK_mPath() \
121 if (mPath.IsEmpty()) return NS_ERROR_NOT_INITIALIZED; \
122 if (!FilePreferences::IsAllowedPath(mPath)) \
123 return NS_ERROR_FILE_ACCESS_DENIED; \
126 #if defined(MOZ_ENABLE_DBUS) && defined(MOZ_WIDGET_GTK)
127 // Prefix for files exported through document portal when we are
128 // in a sandboxed environment (Flatpak).
129 static const nsCString
& GetDocumentStorePath() {
130 static const nsDependentCString sDocumentStorePath
= [] {
131 nsCString storePath
= nsPrintfCString("/run/user/%d/doc/", getuid());
132 // Intentionally put into a ToNewCString copy, rather than just making a
133 // static nsCString to avoid leakchecking errors, since we really want to
135 return nsDependentCString(ToNewCString(storePath
), storePath
.Length());
137 return sDocumentStorePath
;
141 static PRTime
TimespecToMillis(const struct timespec
& aTimeSpec
) {
142 return PRTime(aTimeSpec
.tv_sec
) * PR_MSEC_PER_SEC
+
143 PRTime(aTimeSpec
.tv_nsec
) / PR_NSEC_PER_MSEC
;
146 /* directory enumerator */
147 class nsDirEnumeratorUnix final
: public nsSimpleEnumerator
,
148 public nsIDirectoryEnumerator
{
150 nsDirEnumeratorUnix();
152 // nsISupports interface
153 NS_DECL_ISUPPORTS_INHERITED
155 // nsISimpleEnumerator interface
156 NS_DECL_NSISIMPLEENUMERATOR
158 // nsIDirectoryEnumerator interface
159 NS_DECL_NSIDIRECTORYENUMERATOR
161 NS_IMETHOD
Init(nsLocalFile
* aParent
, bool aIgnored
);
163 NS_FORWARD_NSISIMPLEENUMERATORBASE(nsSimpleEnumerator::)
165 const nsID
& DefaultInterface() override
{ return NS_GET_IID(nsIFile
); }
168 ~nsDirEnumeratorUnix() override
;
171 NS_IMETHOD
GetNextEntry();
174 struct dirent
* mEntry
;
175 nsCString mParentPath
;
178 nsDirEnumeratorUnix::nsDirEnumeratorUnix() : mDir(nullptr), mEntry(nullptr) {}
180 nsDirEnumeratorUnix::~nsDirEnumeratorUnix() { Close(); }
182 NS_IMPL_ISUPPORTS_INHERITED(nsDirEnumeratorUnix
, nsSimpleEnumerator
,
183 nsIDirectoryEnumerator
)
186 nsDirEnumeratorUnix::Init(nsLocalFile
* aParent
,
187 bool aResolveSymlinks
/*ignored*/) {
188 nsAutoCString dirPath
;
189 if (NS_FAILED(aParent
->GetNativePath(dirPath
)) || dirPath
.IsEmpty()) {
190 return NS_ERROR_FILE_INVALID_PATH
;
193 // When enumerating the directory, the paths must have a slash at the end.
194 nsAutoCString
dirPathWithSlash(dirPath
);
195 dirPathWithSlash
.Append('/');
196 if (!FilePreferences::IsAllowedPath(dirPathWithSlash
)) {
197 return NS_ERROR_FILE_ACCESS_DENIED
;
200 if (NS_FAILED(aParent
->GetNativePath(mParentPath
))) {
201 return NS_ERROR_FAILURE
;
204 mDir
= opendir(dirPath
.get());
206 return NSRESULT_FOR_ERRNO();
208 return GetNextEntry();
212 nsDirEnumeratorUnix::HasMoreElements(bool* aResult
) {
213 *aResult
= mDir
&& mEntry
;
221 nsDirEnumeratorUnix::GetNext(nsISupports
** aResult
) {
222 nsCOMPtr
<nsIFile
> file
;
223 nsresult rv
= GetNextFile(getter_AddRefs(file
));
228 return NS_ERROR_FAILURE
;
230 file
.forget(aResult
);
235 nsDirEnumeratorUnix::GetNextEntry() {
238 mEntry
= readdir(mDir
);
240 // end of dir or error
242 return NSRESULT_FOR_ERRNO();
245 // keep going past "." and ".."
246 } while (mEntry
->d_name
[0] == '.' &&
247 (mEntry
->d_name
[1] == '\0' || // .\0
248 (mEntry
->d_name
[1] == '.' && mEntry
->d_name
[2] == '\0'))); // ..\0
253 nsDirEnumeratorUnix::GetNextFile(nsIFile
** aResult
) {
255 if (!mDir
|| !mEntry
) {
260 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
262 if (NS_FAILED(rv
= file
->InitWithNativePath(mParentPath
)) ||
263 NS_FAILED(rv
= file
->AppendNative(nsDependentCString(mEntry
->d_name
)))) {
267 file
.forget(aResult
);
268 return GetNextEntry();
272 nsDirEnumeratorUnix::Close() {
280 nsLocalFile::nsLocalFile() : mCachedStat() {}
282 nsLocalFile::nsLocalFile(const nsACString
& aFilePath
) : mCachedStat() {
283 InitWithNativePath(aFilePath
);
286 nsLocalFile::nsLocalFile(const nsLocalFile
& aOther
) : mPath(aOther
.mPath
) {}
288 #ifdef MOZ_WIDGET_COCOA
289 NS_IMPL_ISUPPORTS(nsLocalFile
, nsILocalFileMac
, nsIFile
)
291 NS_IMPL_ISUPPORTS(nsLocalFile
, nsIFile
)
294 nsresult
nsLocalFile::nsLocalFileConstructor(const nsIID
& aIID
,
295 void** aInstancePtr
) {
296 if (NS_WARN_IF(!aInstancePtr
)) {
297 return NS_ERROR_INVALID_ARG
;
300 *aInstancePtr
= nullptr;
302 nsCOMPtr
<nsIFile
> inst
= new nsLocalFile();
303 return inst
->QueryInterface(aIID
, aInstancePtr
);
306 bool nsLocalFile::FillStatCache() {
307 if (!FilePreferences::IsAllowedPath(mPath
)) {
312 if (STAT(mPath
.get(), &mCachedStat
) == -1) {
313 // try lstat it may be a symlink
314 if (LSTAT(mPath
.get(), &mCachedStat
) == -1) {
322 nsLocalFile::Clone(nsIFile
** aFile
) {
323 // Just copy-construct ourselves
324 RefPtr
<nsLocalFile
> copy
= new nsLocalFile(*this);
330 nsLocalFile::InitWithNativePath(const nsACString
& aFilePath
) {
331 if (!aFilePath
.IsEmpty() && aFilePath
.First() == '~') {
332 if (aFilePath
.Length() == 1 || aFilePath
.CharAt(1) == '/') {
333 // Home dir for the current user
335 nsCOMPtr
<nsIFile
> homeDir
;
336 nsAutoCString homePath
;
337 if (NS_FAILED(NS_GetSpecialDirectory(NS_OS_HOME_DIR
,
338 getter_AddRefs(homeDir
))) ||
339 NS_FAILED(homeDir
->GetNativePath(homePath
))) {
340 return NS_ERROR_FAILURE
;
344 if (aFilePath
.Length() > 2) {
345 mPath
.Append(Substring(aFilePath
, 1));
348 // Home dir for an arbitrary user e.g. `~foo/bar` -> `/home/foo/bar`
349 // (`/Users/foo/bar` on Mac). The accurate way to get this directory
350 // is with `getpwnam`, but we would like to avoid doing blocking
351 // filesystem I/O while creating an `nsIFile`.
359 + Substring(aFilePath
, 1);
362 if (aFilePath
.IsEmpty() || aFilePath
.First() != '/') {
363 return NS_ERROR_FILE_UNRECOGNIZED_PATH
;
368 if (!FilePreferences::IsAllowedPath(mPath
)) {
370 return NS_ERROR_FILE_ACCESS_DENIED
;
373 // trim off trailing slashes
374 ssize_t len
= mPath
.Length();
375 while ((len
> 1) && (mPath
[len
- 1] == '/')) {
378 mPath
.SetLength(len
);
384 nsLocalFile::CreateAllAncestors(uint32_t aPermissions
) {
385 if (!FilePreferences::IsAllowedPath(mPath
)) {
386 return NS_ERROR_FILE_ACCESS_DENIED
;
389 // <jband> I promise to play nice
390 char* buffer
= mPath
.BeginWriting();
391 char* slashp
= buffer
;
392 int mkdir_result
= 0;
396 fprintf(stderr
, "nsIFile: before: %s\n", buffer
);
399 while ((slashp
= strchr(slashp
+ 1, '/'))) {
401 * Sequences of '/' are equivalent to a single '/'.
403 if (slashp
[1] == '/') {
408 * If the path has a trailing slash, don't make the last component,
409 * because we'll get EEXIST in Create when we try to build the final
410 * component again, and it's easier to condition the logic here than
413 if (slashp
[1] == '\0') {
417 /* Temporarily NUL-terminate here */
420 fprintf(stderr
, "nsIFile: mkdir(\"%s\")\n", buffer
);
422 mkdir_result
= mkdir(buffer
, aPermissions
);
423 if (mkdir_result
== -1) {
426 * Always set |errno| to EEXIST if the dir already exists
427 * (we have to do this here since the errno value is not consistent
428 * in all cases - various reasons like different platform,
429 * automounter-controlled dir, etc. can affect it (see bug 125489
432 if (mkdir_errno
!= EEXIST
&& access(buffer
, F_OK
) == 0) {
433 mkdir_errno
= EEXIST
;
436 fprintf(stderr
, "nsIFile: errno: %d\n", mkdir_errno
);
445 * We could get EEXIST for an existing file -- not directory --
446 * but that's OK: we'll get ENOTDIR when we try to make the final
447 * component of the path back in Create and error out appropriately.
449 if (mkdir_result
== -1 && mkdir_errno
!= EEXIST
) {
450 return NS_ERROR_FAILURE
;
457 nsLocalFile::OpenNSPRFileDesc(int32_t aFlags
, int32_t aMode
,
458 PRFileDesc
** aResult
) {
459 if (!FilePreferences::IsAllowedPath(mPath
)) {
460 return NS_ERROR_FILE_ACCESS_DENIED
;
462 *aResult
= PR_Open(mPath
.get(), aFlags
, aMode
);
464 return NS_ErrorAccordingToNSPR();
467 if (aFlags
& DELETE_ON_CLOSE
) {
468 PR_Delete(mPath
.get());
471 #if defined(HAVE_POSIX_FADVISE)
472 if (aFlags
& OS_READAHEAD
) {
473 posix_fadvise(PR_FileDesc2NativeHandle(*aResult
), 0, 0,
474 POSIX_FADV_SEQUENTIAL
);
481 nsLocalFile::OpenANSIFileDesc(const char* aMode
, FILE** aResult
) {
482 if (!FilePreferences::IsAllowedPath(mPath
)) {
483 return NS_ERROR_FILE_ACCESS_DENIED
;
485 *aResult
= fopen(mPath
.get(), aMode
);
487 return NS_ERROR_FAILURE
;
493 static int do_create(const char* aPath
, int aFlags
, mode_t aMode
,
494 PRFileDesc
** aResult
) {
495 *aResult
= PR_Open(aPath
, aFlags
, aMode
);
496 return *aResult
? 0 : -1;
499 static int do_mkdir(const char* aPath
, int aFlags
, mode_t aMode
,
500 PRFileDesc
** aResult
) {
502 return mkdir(aPath
, aMode
);
505 nsresult
nsLocalFile::CreateAndKeepOpen(uint32_t aType
, int aFlags
,
506 uint32_t aPermissions
,
508 PRFileDesc
** aResult
) {
509 if (!FilePreferences::IsAllowedPath(mPath
)) {
510 return NS_ERROR_FILE_ACCESS_DENIED
;
513 if (aType
!= NORMAL_FILE_TYPE
&& aType
!= DIRECTORY_TYPE
) {
514 return NS_ERROR_FILE_UNKNOWN_TYPE
;
517 int (*createFunc
)(const char*, int, mode_t
, PRFileDesc
**) =
518 (aType
== NORMAL_FILE_TYPE
) ? do_create
: do_mkdir
;
520 int result
= createFunc(mPath
.get(), aFlags
, aPermissions
, aResult
);
521 if (result
== -1 && errno
== ENOENT
&& !aSkipAncestors
) {
523 * If we failed because of missing ancestor components, try to create
524 * them and then retry the original creation.
526 * Ancestor directories get the same permissions as the file we're
527 * creating, with the X bit set for each of (user,group,other) with
528 * an R bit in the original permissions. If you want to do anything
529 * fancy like setgid or sticky bits, do it by hand.
531 int dirperm
= aPermissions
;
532 if (aPermissions
& S_IRUSR
) {
535 if (aPermissions
& S_IRGRP
) {
538 if (aPermissions
& S_IROTH
) {
543 fprintf(stderr
, "nsIFile: perm = %o, dirperm = %o\n", aPermissions
,
547 if (NS_FAILED(CreateAllAncestors(dirperm
))) {
548 return NS_ERROR_FAILURE
;
552 fprintf(stderr
, "nsIFile: Create(\"%s\") again\n", mPath
.get());
554 result
= createFunc(mPath
.get(), aFlags
, aPermissions
, aResult
);
556 return NSRESULT_FOR_RETURN(result
);
560 nsLocalFile::Create(uint32_t aType
, uint32_t aPermissions
,
561 bool aSkipAncestors
) {
562 if (!FilePreferences::IsAllowedPath(mPath
)) {
563 return NS_ERROR_FILE_ACCESS_DENIED
;
566 PRFileDesc
* junk
= nullptr;
567 nsresult rv
= CreateAndKeepOpen(
568 aType
, PR_WRONLY
| PR_CREATE_FILE
| PR_TRUNCATE
| PR_EXCL
, aPermissions
,
569 aSkipAncestors
, &junk
);
577 nsLocalFile::AppendNative(const nsACString
& aFragment
) {
578 if (aFragment
.IsEmpty()) {
582 // only one component of path can be appended and cannot append ".."
583 nsACString::const_iterator begin
, end
;
584 if (aFragment
.EqualsASCII("..") ||
585 FindCharInReadable('/', aFragment
.BeginReading(begin
),
586 aFragment
.EndReading(end
))) {
587 return NS_ERROR_FILE_UNRECOGNIZED_PATH
;
590 return AppendRelativeNativePath(aFragment
);
594 nsLocalFile::AppendRelativeNativePath(const nsACString
& aFragment
) {
595 if (aFragment
.IsEmpty()) {
599 // No leading '/' and cannot be ".."
600 if (aFragment
.First() == '/' || aFragment
.EqualsASCII("..")) {
601 return NS_ERROR_FILE_UNRECOGNIZED_PATH
;
604 if (aFragment
.Contains('/')) {
605 // can't contain .. as a path component. Ensure that the valid components
606 // "foo..foo", "..foo", and "foo.." are not falsely detected,
607 // but the invalid paths "../", "foo/..", "foo/../foo",
608 // "../foo", etc are.
609 constexpr auto doubleDot
= "/.."_ns
;
610 nsACString::const_iterator start
, end
, offset
;
611 aFragment
.BeginReading(start
);
612 aFragment
.EndReading(end
);
614 while (FindInReadable(doubleDot
, start
, offset
)) {
615 if (offset
== end
|| *offset
== '/') {
616 return NS_ERROR_FILE_UNRECOGNIZED_PATH
;
622 // catches the remaining cases of prefixes
623 if (StringBeginsWith(aFragment
, "../"_ns
)) {
624 return NS_ERROR_FILE_UNRECOGNIZED_PATH
;
628 if (!mPath
.EqualsLiteral("/")) {
631 mPath
.Append(aFragment
);
637 nsLocalFile::Normalize() {
638 char resolved_path
[PATH_MAX
] = "";
639 char* resolved_path_ptr
= nullptr;
641 if (!FilePreferences::IsAllowedPath(mPath
)) {
642 return NS_ERROR_FILE_ACCESS_DENIED
;
645 resolved_path_ptr
= realpath(mPath
.get(), resolved_path
);
647 // if there is an error, the return is null.
648 if (!resolved_path_ptr
) {
649 return NSRESULT_FOR_ERRNO();
652 mPath
= resolved_path
;
656 void nsLocalFile::LocateNativeLeafName(nsACString::const_iterator
& aBegin
,
657 nsACString::const_iterator
& aEnd
) {
658 // XXX perhaps we should cache this??
660 mPath
.BeginReading(aBegin
);
661 mPath
.EndReading(aEnd
);
663 nsACString::const_iterator it
= aEnd
;
664 nsACString::const_iterator stop
= aBegin
;
666 while (--it
!= stop
) {
672 // else, the entire path is the leaf name (which means this
673 // isn't an absolute path... unexpected??)
677 nsLocalFile::GetNativeLeafName(nsACString
& aLeafName
) {
678 nsACString::const_iterator begin
, end
;
679 LocateNativeLeafName(begin
, end
);
680 aLeafName
= Substring(begin
, end
);
685 nsLocalFile::SetNativeLeafName(const nsACString
& aLeafName
) {
686 nsACString::const_iterator begin
, end
;
687 LocateNativeLeafName(begin
, end
);
688 mPath
.Replace(begin
.get() - mPath
.get(), Distance(begin
, end
), aLeafName
);
693 nsLocalFile::GetDisplayName(nsAString
& aLeafName
) {
694 return GetLeafName(aLeafName
);
698 nsLocalFile::HostPath(JSContext
* aCx
, dom::Promise
** aPromise
) {
700 MOZ_ASSERT(aPromise
);
702 nsIGlobalObject
* globalObject
= xpc::CurrentNativeGlobal(aCx
);
703 if (NS_WARN_IF(!globalObject
)) {
704 return NS_ERROR_FAILURE
;
708 RefPtr
<dom::Promise
> retPromise
= dom::Promise::Create(globalObject
, result
);
709 if (NS_WARN_IF(result
.Failed())) {
710 return result
.StealNSResult();
713 #if defined(MOZ_ENABLE_DBUS) && defined(MOZ_WIDGET_GTK)
714 if (!widget::IsRunningUnderFlatpak() ||
715 !StringBeginsWith(mPath
, GetDocumentStorePath())) {
716 retPromise
->MaybeResolve(mPath
);
717 retPromise
.forget(aPromise
);
721 nsCString docId
= [this] {
722 auto subPath
= Substring(mPath
, GetDocumentStorePath().Length());
723 if (auto idx
= subPath
.Find("/"); idx
> 0) {
724 subPath
.Truncate(idx
);
726 return nsCString(subPath
);
729 const char kServiceName
[] = "org.freedesktop.portal.Documents";
730 const char kDBusPath
[] = "/org/freedesktop/portal/documents";
731 const char kInterfaceName
[] = "org.freedesktop.portal.Documents";
733 widget::CreateDBusProxyForBus(G_BUS_TYPE_SESSION
, G_DBUS_PROXY_FLAGS_NONE
,
734 /* aInterfaceInfo = */ nullptr, kServiceName
,
735 kDBusPath
, kInterfaceName
)
737 GetCurrentSerialEventTarget(), __func__
,
738 [this, self
= RefPtr(this), docId
,
739 retPromise
](RefPtr
<GDBusProxy
>&& aProxy
) {
740 RefPtr
<GVariant
> version
= dont_AddRef(
741 g_dbus_proxy_get_cached_property(aProxy
, "version"));
743 !g_variant_is_of_type(version
, G_VARIANT_TYPE_UINT32
)) {
745 "nsIFile: failed to get host path for %s\n: Invalid value.",
747 retPromise
->MaybeReject(NS_ERROR_FAILURE
);
751 if (g_variant_get_uint32(version
) < 5) {
753 "nsIFile: failed to get host path for %s\n: Document "
754 "portal in version 5 is required.",
756 retPromise
->MaybeReject(NS_ERROR_NOT_AVAILABLE
);
760 GVariantBuilder builder
;
761 g_variant_builder_init(&builder
, G_VARIANT_TYPE("(as)"));
762 g_variant_builder_open(&builder
, G_VARIANT_TYPE("as"));
763 g_variant_builder_add(&builder
, "s", docId
.get());
764 g_variant_builder_close(&builder
);
766 RefPtr
<GVariant
> args
= dont_AddRef(
767 g_variant_ref_sink(g_variant_builder_end(&builder
)));
771 "nsIFile: failed to get host path for %s\n: "
774 retPromise
->MaybeReject(NS_ERROR_FAILURE
);
778 widget::DBusProxyCall(aProxy
, "GetHostPaths", args
,
779 G_DBUS_CALL_FLAGS_NONE
, -1,
780 /* cancellable */ nullptr)
782 GetCurrentSerialEventTarget(), __func__
,
783 [this, self
= RefPtr(this), docId
,
784 retPromise
](RefPtr
<GVariant
>&& aResult
) {
785 RefPtr
<GVariant
> result
= dont_AddRef(
786 g_variant_get_child_value(aResult
.get(), 0));
787 if (!g_variant_is_of_type(result
,
788 G_VARIANT_TYPE("a{say}"))) {
790 "nsIFile: failed to get host path for %s\n: "
793 retPromise
->MaybeReject(NS_ERROR_FAILURE
);
797 const gchar
* key
= nullptr;
798 const gchar
* path
= nullptr;
799 GVariantIter
* iter
= g_variant_iter_new(result
);
802 g_variant_iter_loop(iter
, "{&s^&ay}", &key
, &path
)) {
803 if (g_strcmp0(key
, docId
.get()) == 0) {
804 retPromise
->MaybeResolve(nsDependentCString(path
));
805 g_variant_iter_free(iter
);
810 g_variant_iter_free(iter
);
812 "nsIFile: failed to get host path for %s\n: "
815 retPromise
->MaybeReject(NS_ERROR_FAILURE
);
817 [this, self
= RefPtr(this),
818 retPromise
](GUniquePtr
<GError
>&& aError
) {
820 "nsIFile: failed to get host path for %s\n: %s.",
821 mPath
.get(), aError
->message
);
822 retPromise
->MaybeReject(NS_ERROR_FAILURE
);
825 [this, self
= RefPtr(this), retPromise
](GUniquePtr
<GError
>&& aError
) {
826 g_printerr("nsIFile: failed to get host path for %s\n: %s.",
827 mPath
.get(), aError
->message
);
828 retPromise
->MaybeReject(NS_ERROR_NOT_AVAILABLE
);
831 retPromise
->MaybeResolve(mPath
);
833 retPromise
.forget(aPromise
);
837 nsCString
nsLocalFile::NativePath() { return mPath
; }
839 nsresult
nsIFile::GetNativePath(nsACString
& aResult
) {
840 aResult
= NativePath();
844 nsCString
nsIFile::HumanReadablePath() {
846 DebugOnly
<nsresult
> rv
= GetNativePath(path
);
847 MOZ_ASSERT(NS_SUCCEEDED(rv
));
851 nsresult
nsLocalFile::GetNativeTargetPathName(nsIFile
* aNewParent
,
852 const nsACString
& aNewName
,
853 nsACString
& aResult
) {
855 nsCOMPtr
<nsIFile
> oldParent
;
858 if (NS_FAILED(rv
= GetParent(getter_AddRefs(oldParent
)))) {
861 aNewParent
= oldParent
.get();
863 // check to see if our target directory exists
865 if (NS_FAILED(rv
= aNewParent
->Exists(&targetExists
))) {
870 // XXX create the new directory with some permissions
871 rv
= aNewParent
->Create(DIRECTORY_TYPE
, 0755);
876 // make sure that the target is actually a directory
877 bool targetIsDirectory
;
878 if (NS_FAILED(rv
= aNewParent
->IsDirectory(&targetIsDirectory
))) {
881 if (!targetIsDirectory
) {
882 return NS_ERROR_FILE_DESTINATION_NOT_DIR
;
887 nsACString::const_iterator nameBegin
, nameEnd
;
888 if (!aNewName
.IsEmpty()) {
889 aNewName
.BeginReading(nameBegin
);
890 aNewName
.EndReading(nameEnd
);
892 LocateNativeLeafName(nameBegin
, nameEnd
);
895 nsAutoCString dirName
;
896 if (NS_FAILED(rv
= aNewParent
->GetNativePath(dirName
))) {
900 aResult
= dirName
+ "/"_ns
+ Substring(nameBegin
, nameEnd
);
904 nsresult
nsLocalFile::CopyDirectoryTo(nsIFile
* aNewParent
) {
907 * dirCheck is used for various boolean test results such as from Equals,
908 * Exists, isDir, etc.
910 bool dirCheck
, isSymlink
;
913 if (NS_FAILED(rv
= IsDirectory(&dirCheck
))) {
917 return CopyToNative(aNewParent
, ""_ns
);
920 if (NS_FAILED(rv
= Equals(aNewParent
, &dirCheck
))) {
924 // can't copy dir to itself
925 return NS_ERROR_INVALID_ARG
;
928 if (NS_FAILED(rv
= aNewParent
->Exists(&dirCheck
))) {
931 // get the dirs old permissions
932 if (NS_FAILED(rv
= GetPermissions(&oldPerms
))) {
936 if (NS_FAILED(rv
= aNewParent
->Create(DIRECTORY_TYPE
, oldPerms
))) {
939 } else { // dir exists lets try to use leaf
940 nsAutoCString leafName
;
941 if (NS_FAILED(rv
= GetNativeLeafName(leafName
))) {
944 if (NS_FAILED(rv
= aNewParent
->AppendNative(leafName
))) {
947 if (NS_FAILED(rv
= aNewParent
->Exists(&dirCheck
))) {
951 return NS_ERROR_FILE_ALREADY_EXISTS
; // dest exists
953 if (NS_FAILED(rv
= aNewParent
->Create(DIRECTORY_TYPE
, oldPerms
))) {
958 nsCOMPtr
<nsIDirectoryEnumerator
> dirIterator
;
959 if (NS_FAILED(rv
= GetDirectoryEntries(getter_AddRefs(dirIterator
)))) {
963 nsCOMPtr
<nsIFile
> entry
;
964 while (NS_SUCCEEDED(dirIterator
->GetNextFile(getter_AddRefs(entry
))) &&
966 if (NS_FAILED(rv
= entry
->IsSymlink(&isSymlink
))) {
969 if (NS_FAILED(rv
= entry
->IsDirectory(&dirCheck
))) {
972 if (dirCheck
&& !isSymlink
) {
973 nsCOMPtr
<nsIFile
> destClone
;
974 rv
= aNewParent
->Clone(getter_AddRefs(destClone
));
975 if (NS_SUCCEEDED(rv
)) {
976 if (NS_FAILED(rv
= entry
->CopyToNative(destClone
, ""_ns
))) {
979 nsAutoCString pathName
;
980 if (NS_FAILED(rv2
= entry
->GetNativePath(pathName
))) {
983 printf("Operation not supported: %s\n", pathName
.get());
985 if (rv
== NS_ERROR_OUT_OF_MEMORY
) {
992 if (NS_FAILED(rv
= entry
->CopyToNative(aNewParent
, ""_ns
))) {
995 nsAutoCString pathName
;
996 if (NS_FAILED(rv2
= entry
->GetNativePath(pathName
))) {
999 printf("Operation not supported: %s\n", pathName
.get());
1001 if (rv
== NS_ERROR_OUT_OF_MEMORY
) {
1012 nsLocalFile::CopyToNative(nsIFile
* aNewParent
, const nsACString
& aNewName
) {
1014 // check to make sure that this has been initialized properly
1017 // we copy the parent here so 'aNewParent' remains immutable
1018 nsCOMPtr
<nsIFile
> workParent
;
1020 if (NS_FAILED(rv
= aNewParent
->Clone(getter_AddRefs(workParent
)))) {
1024 if (NS_FAILED(rv
= GetParent(getter_AddRefs(workParent
)))) {
1029 // check to see if we are a directory or if we are a file
1031 if (NS_FAILED(rv
= IsDirectory(&isDirectory
))) {
1035 nsAutoCString newPathName
;
1037 if (!aNewName
.IsEmpty()) {
1038 if (NS_FAILED(rv
= workParent
->AppendNative(aNewName
))) {
1042 if (NS_FAILED(rv
= GetNativeLeafName(newPathName
))) {
1045 if (NS_FAILED(rv
= workParent
->AppendNative(newPathName
))) {
1049 if (NS_FAILED(rv
= CopyDirectoryTo(workParent
))) {
1053 rv
= GetNativeTargetPathName(workParent
, aNewName
, newPathName
);
1054 if (NS_FAILED(rv
)) {
1058 #ifdef DEBUG_blizzard
1059 printf("nsLocalFile::CopyTo() %s -> %s\n", mPath
.get(), newPathName
.get());
1062 // actually create the file.
1063 auto* newFile
= new nsLocalFile();
1064 nsCOMPtr
<nsIFile
> fileRef(newFile
); // release on exit
1066 rv
= newFile
->InitWithNativePath(newPathName
);
1067 if (NS_FAILED(rv
)) {
1071 // get the old permissions
1072 uint32_t myPerms
= 0;
1073 rv
= GetPermissions(&myPerms
);
1074 if (NS_FAILED(rv
)) {
1078 // Create the new file with the old file's permissions, even if write
1079 // permission is missing. We can't create with write permission and
1080 // then change back to myPerm on all filesystems (FAT on Linux, e.g.).
1081 // But we can write to a read-only file on all Unix filesystems if we
1082 // open it successfully for writing.
1085 rv
= newFile
->CreateAndKeepOpen(
1086 NORMAL_FILE_TYPE
, PR_WRONLY
| PR_CREATE_FILE
| PR_TRUNCATE
, myPerms
,
1087 /* aSkipAncestors = */ false, &newFD
);
1088 if (NS_FAILED(rv
)) {
1092 // open the old file, too
1094 if (NS_FAILED(rv
= IsSpecial(&specialFile
))) {
1100 printf("Operation not supported: %s\n", mPath
.get());
1102 // make sure to clean up properly
1107 #if defined(XP_MACOSX)
1108 bool quarantined
= true;
1109 (void)HasXAttr("com.apple.quarantine"_ns
, &quarantined
);
1113 rv
= OpenNSPRFileDesc(PR_RDONLY
, myPerms
, &oldFD
);
1114 if (NS_FAILED(rv
)) {
1115 // make sure to clean up properly
1120 #ifdef DEBUG_blizzard
1121 int32_t totalRead
= 0;
1122 int32_t totalWritten
= 0;
1127 // record PR_Write() error for better error message later.
1128 nsresult saved_write_error
= NS_OK
;
1129 nsresult saved_read_error
= NS_OK
;
1130 nsresult saved_read_close_error
= NS_OK
;
1131 nsresult saved_write_close_error
= NS_OK
;
1133 // DONE: Does PR_Read() return bytesRead < 0 for error?
1134 // Yes., The errors from PR_Read are not so common and
1135 // the value may not have correspondence in NS_ERROR_*, but
1136 // we do catch it still, immediately after while() loop.
1137 // We can differentiate errors pf PR_Read and PR_Write by
1138 // looking at saved_write_error value. If PR_Write error occurs (and not
1139 // PR_Read() error), save_write_error is not NS_OK.
1141 while ((bytesRead
= PR_Read(oldFD
, buf
, BUFSIZ
)) > 0) {
1142 #ifdef DEBUG_blizzard
1143 totalRead
+= bytesRead
;
1146 // PR_Write promises never to do a short write
1147 int32_t bytesWritten
= PR_Write(newFD
, buf
, bytesRead
);
1148 if (bytesWritten
< 0) {
1149 saved_write_error
= NSRESULT_FOR_ERRNO();
1153 NS_ASSERTION(bytesWritten
== bytesRead
, "short PR_Write?");
1155 #ifdef DEBUG_blizzard
1156 totalWritten
+= bytesWritten
;
1160 // TODO/FIXME: If CIFS (and NFS?) may force read/write to return EINTR,
1161 // we are better off to prepare for retrying. But we need confirmation if
1162 // EINTR is returned.
1164 // Record error if PR_Read() failed.
1165 // Must be done before any other I/O which may reset errno.
1166 if (bytesRead
< 0 && saved_write_error
== NS_OK
) {
1167 saved_read_error
= NSRESULT_FOR_ERRNO();
1170 #ifdef DEBUG_blizzard
1171 printf("read %d bytes, wrote %d bytes\n", totalRead
, totalWritten
);
1174 // DONE: Errors of close can occur. Read man page of
1176 // This is likely to happen if the file system is remote file
1177 // system (NFS, CIFS, etc.) and network outage occurs.
1178 // At least, we should tell the user that filesystem/disk is
1179 // hosed (possibly due to network error, hard disk failure,
1180 // etc.) so that users can take remedial action.
1183 if (PR_Close(newFD
) < 0) {
1184 saved_write_close_error
= NSRESULT_FOR_ERRNO();
1186 // This error merits printing.
1187 fprintf(stderr
, "ERROR: PR_Close(newFD) returned error. errno = %d\n",
1191 #if defined(XP_MACOSX)
1192 else if (!quarantined
) {
1193 // If the original file was not in quarantine, lift the quarantine that
1194 // file creation added because of LSFileQuarantineEnabled.
1195 (void)newFile
->DelXAttr("com.apple.quarantine"_ns
);
1197 #endif // defined(XP_MACOSX)
1199 if (PR_Close(oldFD
) < 0) {
1200 saved_read_close_error
= NSRESULT_FOR_ERRNO();
1202 fprintf(stderr
, "ERROR: PR_Close(oldFD) returned error. errno = %d\n",
1207 // Let us report the failure to write and read.
1208 // check for write/read error after cleaning up
1209 if (bytesRead
< 0) {
1210 if (saved_write_error
!= NS_OK
) {
1211 return saved_write_error
;
1213 if (saved_read_error
!= NS_OK
) {
1214 return saved_read_error
;
1221 if (saved_write_close_error
!= NS_OK
) {
1222 return saved_write_close_error
;
1224 if (saved_read_close_error
!= NS_OK
) {
1225 return saved_read_close_error
;
1232 nsLocalFile::CopyToFollowingLinksNative(nsIFile
* aNewParent
,
1233 const nsACString
& aNewName
) {
1234 return CopyToNative(aNewParent
, aNewName
);
1238 nsLocalFile::MoveToNative(nsIFile
* aNewParent
, const nsACString
& aNewName
) {
1241 // check to make sure that this has been initialized properly
1244 // check to make sure that we have a new parent
1245 nsAutoCString newPathName
;
1246 rv
= GetNativeTargetPathName(aNewParent
, aNewName
, newPathName
);
1247 if (NS_FAILED(rv
)) {
1251 if (!FilePreferences::IsAllowedPath(newPathName
)) {
1252 return NS_ERROR_FILE_ACCESS_DENIED
;
1255 // try for atomic rename, falling back to copy/delete
1256 if (rename(mPath
.get(), newPathName
.get()) < 0) {
1257 if (errno
== EXDEV
) {
1258 rv
= CopyToNative(aNewParent
, aNewName
);
1259 if (NS_SUCCEEDED(rv
)) {
1263 rv
= NSRESULT_FOR_ERRNO();
1267 if (NS_SUCCEEDED(rv
)) {
1269 mPath
= newPathName
;
1275 nsLocalFile::MoveToFollowingLinksNative(nsIFile
* aNewParent
,
1276 const nsACString
& aNewName
) {
1277 return MoveToNative(aNewParent
, aNewName
);
1281 nsLocalFile::Remove(bool aRecursive
, uint32_t* aRemoveCount
) {
1283 ENSURE_STAT_CACHE();
1287 nsresult rv
= IsSymlink(&isSymLink
);
1288 if (NS_FAILED(rv
)) {
1292 if (isSymLink
|| !S_ISDIR(mCachedStat
.st_mode
)) {
1293 rv
= NSRESULT_FOR_RETURN(unlink(mPath
.get()));
1294 if (NS_SUCCEEDED(rv
) && aRemoveCount
) {
1301 auto* dir
= new nsDirEnumeratorUnix();
1303 RefPtr
<nsSimpleEnumerator
> dirRef(dir
); // release on exit
1305 rv
= dir
->Init(this, false);
1306 if (NS_FAILED(rv
)) {
1311 while (NS_SUCCEEDED(dir
->HasMoreElements(&more
)) && more
) {
1312 nsCOMPtr
<nsISupports
> item
;
1313 rv
= dir
->GetNext(getter_AddRefs(item
));
1314 if (NS_FAILED(rv
)) {
1315 return NS_ERROR_FAILURE
;
1318 nsCOMPtr
<nsIFile
> file
= do_QueryInterface(item
, &rv
);
1319 if (NS_FAILED(rv
)) {
1320 return NS_ERROR_FAILURE
;
1322 // XXX: We care the result of the removal here while
1323 // nsLocalFileWin does not. We should align the behavior. (bug 1779696)
1324 rv
= file
->Remove(aRecursive
, aRemoveCount
);
1327 // See bug 580434 - Bionic gives us just deleted files
1328 if (rv
== NS_ERROR_FILE_NOT_FOUND
) {
1332 if (NS_FAILED(rv
)) {
1338 rv
= NSRESULT_FOR_RETURN(rmdir(mPath
.get()));
1339 if (NS_SUCCEEDED(rv
) && aRemoveCount
) {
1345 nsresult
nsLocalFile::GetTimeImpl(PRTime
* aTime
,
1346 nsLocalFile::TimeField aTimeField
,
1347 bool aFollowLinks
) {
1349 if (NS_WARN_IF(!aTime
)) {
1350 return NS_ERROR_INVALID_ARG
;
1353 using StatFn
= int (*)(const char*, struct STAT
*);
1354 StatFn statFn
= aFollowLinks
? &STAT
: &LSTAT
;
1356 struct STAT fileStats
{};
1357 if (statFn(mPath
.get(), &fileStats
) < 0) {
1358 return NSRESULT_FOR_ERRNO();
1361 struct timespec
* timespec
;
1362 switch (aTimeField
) {
1363 case TimeField::AccessedTime
:
1364 #if (defined(__APPLE__) && defined(__MACH__))
1365 timespec
= &fileStats
.st_atimespec
;
1367 timespec
= &fileStats
.st_atim
;
1371 case TimeField::ModifiedTime
:
1372 #if (defined(__APPLE__) && defined(__MACH__))
1373 timespec
= &fileStats
.st_mtimespec
;
1375 timespec
= &fileStats
.st_mtim
;
1380 MOZ_CRASH("Unknown TimeField");
1383 *aTime
= TimespecToMillis(*timespec
);
1388 nsresult
nsLocalFile::SetTimeImpl(PRTime aTime
,
1389 nsLocalFile::TimeField aTimeField
,
1390 bool aFollowLinks
) {
1393 using UtimesFn
= int (*)(const char*, const timeval
*);
1394 UtimesFn utimesFn
= &utimes
;
1397 if (!aFollowLinks
) {
1398 utimesFn
= &lutimes
;
1402 ENSURE_STAT_CACHE();
1408 // We only want to write to a single field (accessed time or modified time),
1409 // but utimes() doesn't let you omit one. If you do, it will set that field to
1410 // the current time, which is not what we want.
1412 // So what we do is write to both fields, but copy one of the fields from our
1413 // cached stat structure.
1415 // If we are writing to the accessed time field, then we want to copy the
1416 // modified time and vice versa.
1420 const size_t writeIndex
= aTimeField
== TimeField::AccessedTime
? 0 : 1;
1421 const size_t copyIndex
= aTimeField
== TimeField::AccessedTime
? 1 : 0;
1423 #if (defined(__APPLE__) && defined(__MACH__))
1424 auto* copyFrom
= aTimeField
== TimeField::AccessedTime
1425 ? &mCachedStat
.st_mtimespec
1426 : &mCachedStat
.st_atimespec
;
1428 auto* copyFrom
= aTimeField
== TimeField::AccessedTime
? &mCachedStat
.st_mtim
1429 : &mCachedStat
.st_atim
;
1432 times
[copyIndex
].tv_sec
= copyFrom
->tv_sec
;
1433 times
[copyIndex
].tv_usec
= copyFrom
->tv_nsec
/ 1000;
1435 times
[writeIndex
].tv_sec
= aTime
/ PR_MSEC_PER_SEC
;
1436 times
[writeIndex
].tv_usec
= (aTime
% PR_MSEC_PER_SEC
) * PR_USEC_PER_MSEC
;
1438 int result
= utimesFn(mPath
.get(), times
);
1439 return NSRESULT_FOR_RETURN(result
);
1443 nsLocalFile::GetLastAccessedTime(PRTime
* aLastAccessedTime
) {
1444 return GetTimeImpl(aLastAccessedTime
, TimeField::AccessedTime
,
1445 /* follow links? */ true);
1449 nsLocalFile::SetLastAccessedTime(PRTime aLastAccessedTime
) {
1450 return SetTimeImpl(aLastAccessedTime
, TimeField::AccessedTime
,
1451 /* follow links? */ true);
1455 nsLocalFile::GetLastAccessedTimeOfLink(PRTime
* aLastAccessedTime
) {
1456 return GetTimeImpl(aLastAccessedTime
, TimeField::AccessedTime
,
1457 /* follow links? */ false);
1461 nsLocalFile::SetLastAccessedTimeOfLink(PRTime aLastAccessedTime
) {
1462 return SetTimeImpl(aLastAccessedTime
, TimeField::AccessedTime
,
1463 /* follow links? */ false);
1467 nsLocalFile::GetLastModifiedTime(PRTime
* aLastModTime
) {
1468 return GetTimeImpl(aLastModTime
, TimeField::ModifiedTime
,
1469 /* follow links? */ true);
1473 nsLocalFile::SetLastModifiedTime(PRTime aLastModTime
) {
1474 return SetTimeImpl(aLastModTime
, TimeField::ModifiedTime
,
1475 /* follow links ? */ true);
1479 nsLocalFile::GetLastModifiedTimeOfLink(PRTime
* aLastModTimeOfLink
) {
1480 return GetTimeImpl(aLastModTimeOfLink
, TimeField::ModifiedTime
,
1481 /* follow link? */ false);
1485 nsLocalFile::SetLastModifiedTimeOfLink(PRTime aLastModTimeOfLink
) {
1486 return SetTimeImpl(aLastModTimeOfLink
, TimeField::ModifiedTime
,
1487 /* follow links? */ false);
1491 nsLocalFile::GetCreationTime(PRTime
* aCreationTime
) {
1492 return GetCreationTimeImpl(aCreationTime
, false);
1496 nsLocalFile::GetCreationTimeOfLink(PRTime
* aCreationTimeOfLink
) {
1497 return GetCreationTimeImpl(aCreationTimeOfLink
, /* aFollowLinks = */ true);
1500 nsresult
nsLocalFile::GetCreationTimeImpl(PRTime
* aCreationTime
,
1501 bool aFollowLinks
) {
1503 if (NS_WARN_IF(!aCreationTime
)) {
1504 return NS_ERROR_INVALID_ARG
;
1507 #if defined(_DARWIN_FEATURE_64_BIT_INODE)
1508 using StatFn
= int (*)(const char*, struct STAT
*);
1509 StatFn statFn
= aFollowLinks
? &STAT
: &LSTAT
;
1511 struct STAT fileStats
{};
1512 if (statFn(mPath
.get(), &fileStats
) < 0) {
1513 return NSRESULT_FOR_ERRNO();
1516 *aCreationTime
= TimespecToMillis(fileStats
.st_birthtimespec
);
1519 return NS_ERROR_NOT_IMPLEMENTED
;
1524 * Only send back permissions bits: maybe we want to send back the whole
1525 * mode_t to permit checks against other file types?
1528 #define NORMALIZE_PERMS(mode) ((mode) & (S_IRWXU | S_IRWXG | S_IRWXO))
1531 nsLocalFile::GetPermissions(uint32_t* aPermissions
) {
1532 if (NS_WARN_IF(!aPermissions
)) {
1533 return NS_ERROR_INVALID_ARG
;
1535 ENSURE_STAT_CACHE();
1536 *aPermissions
= NORMALIZE_PERMS(mCachedStat
.st_mode
);
1541 nsLocalFile::GetPermissionsOfLink(uint32_t* aPermissionsOfLink
) {
1543 if (NS_WARN_IF(!aPermissionsOfLink
)) {
1544 return NS_ERROR_INVALID_ARG
;
1548 if (LSTAT(mPath
.get(), &sbuf
) == -1) {
1549 return NSRESULT_FOR_ERRNO();
1551 *aPermissionsOfLink
= NORMALIZE_PERMS(sbuf
.st_mode
);
1556 nsLocalFile::SetPermissions(uint32_t aPermissions
) {
1560 * Race condition here: we should use fchmod instead, there's no way to
1561 * guarantee the name still refers to the same file.
1563 if (chmod(mPath
.get(), aPermissions
) >= 0) {
1566 #if defined(ANDROID) && defined(STATFS)
1567 // For the time being, this is restricted for use by Android, but we
1568 // will figure out what to do for all platforms in bug 638503
1570 if (STATFS(mPath
.get(), &sfs
) < 0) {
1571 return NSRESULT_FOR_ERRNO();
1574 // if this is a FAT file system we can't set file permissions
1575 if (sfs
.f_type
== MSDOS_SUPER_MAGIC
) {
1579 return NSRESULT_FOR_ERRNO();
1583 nsLocalFile::SetPermissionsOfLink(uint32_t aPermissions
) {
1584 // There isn't a consistent mechanism for doing this on UNIX platforms. We
1585 // might want to carefully implement this in the future though.
1586 return NS_ERROR_NOT_IMPLEMENTED
;
1590 nsLocalFile::GetFileSize(int64_t* aFileSize
) {
1591 if (NS_WARN_IF(!aFileSize
)) {
1592 return NS_ERROR_INVALID_ARG
;
1595 ENSURE_STAT_CACHE();
1597 if (!S_ISDIR(mCachedStat
.st_mode
)) {
1598 *aFileSize
= (int64_t)mCachedStat
.st_size
;
1604 nsLocalFile::SetFileSize(int64_t aFileSize
) {
1607 #if defined(ANDROID)
1608 /* no truncate on bionic */
1609 int fd
= open(mPath
.get(), O_WRONLY
);
1611 return NSRESULT_FOR_ERRNO();
1614 int ret
= ftruncate(fd
, (off_t
)aFileSize
);
1618 return NSRESULT_FOR_ERRNO();
1620 #elif defined(HAVE_TRUNCATE64)
1621 if (truncate64(mPath
.get(), (off64_t
)aFileSize
) == -1) {
1622 return NSRESULT_FOR_ERRNO();
1625 off_t size
= (off_t
)aFileSize
;
1626 if (truncate(mPath
.get(), size
) == -1) {
1627 return NSRESULT_FOR_ERRNO();
1634 nsLocalFile::GetFileSizeOfLink(int64_t* aFileSize
) {
1636 if (NS_WARN_IF(!aFileSize
)) {
1637 return NS_ERROR_INVALID_ARG
;
1641 if (LSTAT(mPath
.get(), &sbuf
) == -1) {
1642 return NSRESULT_FOR_ERRNO();
1645 *aFileSize
= (int64_t)sbuf
.st_size
;
1649 #if defined(USE_LINUX_QUOTACTL)
1651 * Searches /proc/self/mountinfo for given device (Major:Minor),
1652 * returns exported name from /dev
1654 * Fails when /proc/self/mountinfo or diven device don't exist.
1656 static bool GetDeviceName(unsigned int aDeviceMajor
, unsigned int aDeviceMinor
,
1657 nsACString
& aDeviceName
) {
1660 const int kMountInfoLineLength
= 200;
1661 const int kMountInfoDevPosition
= 6;
1663 char mountinfoLine
[kMountInfoLineLength
];
1664 char deviceNum
[kMountInfoLineLength
];
1666 SprintfLiteral(deviceNum
, "%u:%u", aDeviceMajor
, aDeviceMinor
);
1668 FILE* f
= fopen("/proc/self/mountinfo", "rt");
1673 // Expects /proc/self/mountinfo in format:
1674 // 'ID ID major:minor root mountpoint flags - type devicename flags'
1675 while (fgets(mountinfoLine
, kMountInfoLineLength
, f
)) {
1676 char* p_dev
= strstr(mountinfoLine
, deviceNum
);
1678 for (int i
= 0; i
< kMountInfoDevPosition
&& p_dev
; ++i
) {
1679 p_dev
= strchr(p_dev
, ' ');
1686 char* p_dev_end
= strchr(p_dev
, ' ');
1689 aDeviceName
.Assign(p_dev
);
1701 #if defined(USE_LINUX_QUOTACTL)
1702 template <typename StatInfoFunc
, typename QuotaInfoFunc
>
1703 nsresult
nsLocalFile::GetDiskInfo(StatInfoFunc
&& aStatInfoFunc
,
1704 QuotaInfoFunc
&& aQuotaInfoFunc
,
1707 template <typename StatInfoFunc
>
1708 nsresult
nsLocalFile::GetDiskInfo(StatInfoFunc
&& aStatInfoFunc
,
1712 if (NS_WARN_IF(!aResult
)) {
1713 return NS_ERROR_INVALID_ARG
;
1716 // These systems have the operations necessary to check disk space.
1720 // check to make sure that mPath is properly initialized
1723 struct STATFS fs_buf
;
1726 * Members of the STATFS struct that you should know about:
1727 * F_BSIZE = block size on disk.
1728 * f_bavail = number of free blocks available to a non-superuser.
1729 * f_bfree = number of total free blocks in file system.
1730 * f_blocks = number of total used or free blocks in file system.
1733 if (STATFS(mPath
.get(), &fs_buf
) < 0) {
1734 // The call to STATFS failed.
1736 printf("ERROR: GetDiskInfo: STATFS call FAILED. \n");
1738 return NS_ERROR_FAILURE
;
1741 CheckedInt64 statfsResult
= std::forward
<StatInfoFunc
>(aStatInfoFunc
)(fs_buf
);
1742 if (!statfsResult
.isValid()) {
1743 return NS_ERROR_CANNOT_CONVERT_DATA
;
1746 // Assign statfsResult to *aResult in case one of the quota calls fails.
1747 *aResult
= statfsResult
.value();
1749 # if defined(USE_LINUX_QUOTACTL)
1751 if (!FillStatCache()) {
1752 // Returns info from statfs
1756 nsAutoCString deviceName
;
1757 if (!GetDeviceName(major(mCachedStat
.st_dev
), minor(mCachedStat
.st_dev
),
1759 // Returns info from statfs
1764 if (!quotactl(QCMD(Q_GETQUOTA
, USRQUOTA
), deviceName
.get(), getuid(),
1767 && dq
.dqb_valid
& QIF_BLIMITS
1769 && dq
.dqb_bhardlimit
) {
1770 CheckedInt64 quotaResult
= std::forward
<QuotaInfoFunc
>(aQuotaInfoFunc
)(dq
);
1771 if (!quotaResult
.isValid()) {
1772 // Returns info from statfs
1776 if (quotaResult
.value() < *aResult
) {
1777 *aResult
= quotaResult
.value();
1780 # endif // defined(USE_LINUX_QUOTACTL)
1782 # ifdef DEBUG_DISK_SPACE
1783 printf("DiskInfo: %lu bytes\n", *aResult
);
1790 * This platform doesn't have statfs or statvfs. I'm sure that there's
1791 * a way to check for free disk space and disk capacity on platforms that
1792 * don't have statfs (I'm SURE they have df, for example).
1794 * Until we figure out how to do that, lets be honest and say that this
1795 * command isn't implemented properly for these platforms yet.
1798 printf("ERROR: GetDiskInfo: Not implemented for plaforms without statfs.\n");
1800 return NS_ERROR_NOT_IMPLEMENTED
;
1806 nsLocalFile::GetDiskSpaceAvailable(int64_t* aDiskSpaceAvailable
) {
1808 [](const struct STATFS
& aStatInfo
) {
1809 return aStatInfo
.f_bavail
* static_cast<uint64_t>(aStatInfo
.F_BSIZE
);
1811 #if defined(USE_LINUX_QUOTACTL)
1812 [](const struct dqblk
& aQuotaInfo
) -> uint64_t {
1813 // dqb_bhardlimit is count of BLOCK_SIZE blocks, dqb_curspace is bytes
1814 const uint64_t hardlimit
= aQuotaInfo
.dqb_bhardlimit
* BLOCK_SIZE
;
1815 if (hardlimit
> aQuotaInfo
.dqb_curspace
) {
1816 return hardlimit
- aQuotaInfo
.dqb_curspace
;
1821 aDiskSpaceAvailable
);
1825 nsLocalFile::GetDiskCapacity(int64_t* aDiskCapacity
) {
1827 [](const struct STATFS
& aStatInfo
) {
1828 return aStatInfo
.f_blocks
* static_cast<uint64_t>(aStatInfo
.F_BSIZE
);
1830 #if defined(USE_LINUX_QUOTACTL)
1831 [](const struct dqblk
& aQuotaInfo
) {
1832 // dqb_bhardlimit is count of BLOCK_SIZE blocks
1833 return aQuotaInfo
.dqb_bhardlimit
* BLOCK_SIZE
;
1840 nsLocalFile::GetParent(nsIFile
** aParent
) {
1842 if (NS_WARN_IF(!aParent
)) {
1843 return NS_ERROR_INVALID_ARG
;
1847 // if '/' we are at the top of the volume, return null
1848 if (mPath
.EqualsLiteral("/")) {
1852 // <brendan, after jband> I promise to play nice
1853 char* buffer
= mPath
.BeginWriting();
1854 // find the last significant slash in buffer
1855 char* slashp
= strrchr(buffer
, '/');
1856 NS_ASSERTION(slashp
, "non-canonical path?");
1858 return NS_ERROR_FILE_INVALID_PATH
;
1861 // for the case where we are at '/'
1862 if (slashp
== buffer
) {
1866 // temporarily terminate buffer at the last significant slash
1870 nsCOMPtr
<nsIFile
> localFile
;
1871 nsresult rv
= NS_NewNativeLocalFile(nsDependentCString(buffer
),
1872 getter_AddRefs(localFile
));
1874 // make buffer whole again
1877 if (NS_FAILED(rv
)) {
1881 localFile
.forget(aParent
);
1886 * The results of Exists, isWritable and isReadable are not cached.
1890 nsLocalFile::Exists(bool* aResult
) {
1892 if (NS_WARN_IF(!aResult
)) {
1893 return NS_ERROR_INVALID_ARG
;
1896 *aResult
= (access(mPath
.get(), F_OK
) == 0);
1901 nsLocalFile::IsWritable(bool* aResult
) {
1903 if (NS_WARN_IF(!aResult
)) {
1904 return NS_ERROR_INVALID_ARG
;
1907 *aResult
= (access(mPath
.get(), W_OK
) == 0);
1908 if (*aResult
|| errno
== EACCES
) {
1911 return NSRESULT_FOR_ERRNO();
1915 nsLocalFile::IsReadable(bool* aResult
) {
1917 if (NS_WARN_IF(!aResult
)) {
1918 return NS_ERROR_INVALID_ARG
;
1921 *aResult
= (access(mPath
.get(), R_OK
) == 0);
1922 if (*aResult
|| errno
== EACCES
) {
1925 return NSRESULT_FOR_ERRNO();
1929 nsLocalFile::IsExecutable(bool* aResult
) {
1931 if (NS_WARN_IF(!aResult
)) {
1932 return NS_ERROR_INVALID_ARG
;
1935 // Check extension (bug 663899). On certain platforms, the file
1936 // extension may cause the OS to treat it as executable regardless of
1937 // the execute bit, such as .jar on Mac OS X. We borrow the code from
1938 // nsLocalFileWin, slightly modified.
1940 // Don't be fooled by symlinks.
1942 nsresult rv
= IsSymlink(&symLink
);
1943 if (NS_FAILED(rv
)) {
1954 int32_t dotIdx
= path
.RFindChar(char16_t('.'));
1955 if (dotIdx
!= kNotFound
) {
1956 // Convert extension to lower case.
1957 char16_t
* p
= path
.BeginWriting();
1958 for (p
+= dotIdx
+ 1; *p
; ++p
) {
1959 *p
+= (*p
>= L
'A' && *p
<= L
'Z') ? 'a' - 'A' : 0;
1962 // Search for any of the set of executable extensions.
1963 static const char* const executableExts
[] = {
1964 #ifdef MOZ_WIDGET_COCOA
1965 "afploc", // Can point to other files.
1967 "air", // Adobe AIR installer
1968 #ifdef MOZ_WIDGET_COCOA
1969 "atloc", // Can point to other files.
1970 "fileloc", // File location files can be used to point to other
1972 "ftploc", // Can point to other files.
1973 "inetloc", // Shouldn't be able to do the same, but can, due to
1974 // macOS vulnerabilities.
1976 "jar" // java application bundle
1978 nsDependentSubstring ext
= Substring(path
, dotIdx
+ 1);
1979 for (auto executableExt
: executableExts
) {
1980 if (ext
.EqualsASCII(executableExt
)) {
1981 // Found a match. Set result and quit.
1988 // On OS X, then query Launch Services.
1989 #ifdef MOZ_WIDGET_COCOA
1990 // Certain Mac applications, such as Classic applications, which
1991 // run under Rosetta, might not have the +x mode bit but are still
1992 // considered to be executable by Launch Services (bug 646748).
1994 if (NS_FAILED(GetCFURL(&url
))) {
1995 return NS_ERROR_FAILURE
;
1998 LSRequestedInfo theInfoRequest
= kLSRequestAllInfo
;
1999 LSItemInfoRecord theInfo
;
2000 OSStatus result
= ::LSCopyItemInfoForURL(url
, theInfoRequest
, &theInfo
);
2002 if (result
== noErr
) {
2003 if ((theInfo
.flags
& kLSItemInfoIsApplication
) != 0) {
2010 // Then check the execute bit.
2011 *aResult
= (access(mPath
.get(), X_OK
) == 0);
2013 // On Solaris, access will always return 0 for root user, however
2014 // the file is only executable if S_IXUSR | S_IXGRP | S_IXOTH is set.
2015 // See bug 351950, https://bugzilla.mozilla.org/show_bug.cgi?id=351950
2019 *aResult
= (STAT(mPath
.get(), &buf
) == 0);
2020 if (*aResult
|| errno
== EACCES
) {
2021 *aResult
= *aResult
&& (buf
.st_mode
& (S_IXUSR
| S_IXGRP
| S_IXOTH
));
2025 return NSRESULT_FOR_ERRNO();
2028 if (*aResult
|| errno
== EACCES
) {
2031 return NSRESULT_FOR_ERRNO();
2035 nsLocalFile::IsDirectory(bool* aResult
) {
2036 if (NS_WARN_IF(!aResult
)) {
2037 return NS_ERROR_INVALID_ARG
;
2040 ENSURE_STAT_CACHE();
2041 *aResult
= S_ISDIR(mCachedStat
.st_mode
);
2046 nsLocalFile::IsFile(bool* aResult
) {
2047 if (NS_WARN_IF(!aResult
)) {
2048 return NS_ERROR_INVALID_ARG
;
2051 ENSURE_STAT_CACHE();
2052 *aResult
= S_ISREG(mCachedStat
.st_mode
);
2057 nsLocalFile::IsHidden(bool* aResult
) {
2058 if (NS_WARN_IF(!aResult
)) {
2059 return NS_ERROR_INVALID_ARG
;
2061 nsACString::const_iterator begin
, end
;
2062 LocateNativeLeafName(begin
, end
);
2063 *aResult
= (*begin
== '.');
2068 nsLocalFile::IsSymlink(bool* aResult
) {
2069 if (NS_WARN_IF(!aResult
)) {
2070 return NS_ERROR_INVALID_ARG
;
2074 struct STAT symStat
;
2075 if (LSTAT(mPath
.get(), &symStat
) == -1) {
2076 return NSRESULT_FOR_ERRNO();
2078 *aResult
= S_ISLNK(symStat
.st_mode
);
2083 nsLocalFile::IsSpecial(bool* aResult
) {
2084 if (NS_WARN_IF(!aResult
)) {
2085 return NS_ERROR_INVALID_ARG
;
2087 ENSURE_STAT_CACHE();
2088 *aResult
= S_ISCHR(mCachedStat
.st_mode
) || S_ISBLK(mCachedStat
.st_mode
) ||
2090 S_ISSOCK(mCachedStat
.st_mode
) ||
2092 S_ISFIFO(mCachedStat
.st_mode
);
2098 nsLocalFile::Equals(nsIFile
* aInFile
, bool* aResult
) {
2099 if (NS_WARN_IF(!aInFile
)) {
2100 return NS_ERROR_INVALID_ARG
;
2102 if (NS_WARN_IF(!aResult
)) {
2103 return NS_ERROR_INVALID_ARG
;
2107 nsAutoCString inPath
;
2108 nsresult rv
= aInFile
->GetNativePath(inPath
);
2109 if (NS_FAILED(rv
)) {
2113 // We don't need to worry about "/foo/" vs. "/foo" here
2114 // because trailing slashes are stripped on init.
2115 *aResult
= !strcmp(inPath
.get(), mPath
.get());
2120 nsLocalFile::Contains(nsIFile
* aInFile
, bool* aResult
) {
2122 if (NS_WARN_IF(!aInFile
)) {
2123 return NS_ERROR_INVALID_ARG
;
2125 if (NS_WARN_IF(!aResult
)) {
2126 return NS_ERROR_INVALID_ARG
;
2129 nsAutoCString inPath
;
2132 if (NS_FAILED(rv
= aInFile
->GetNativePath(inPath
))) {
2138 ssize_t len
= mPath
.Length();
2139 if (strncmp(mPath
.get(), inPath
.get(), len
) == 0) {
2140 // Now make sure that the |aInFile|'s path has a separator at len,
2141 // which implies that it has more components after len.
2142 if (inPath
[len
] == '/') {
2150 static nsresult
ReadLinkSafe(const nsCString
& aTarget
, int32_t aExpectedSize
,
2151 nsACString
& aOutBuffer
) {
2152 // If we call readlink with a buffer size S it returns S, then we cannot tell
2153 // if the buffer was big enough to hold the entire path. We allocate an
2154 // additional byte so we can check if the buffer was large enough.
2155 const auto allocSize
= CheckedInt
<size_t>(aExpectedSize
) + 1;
2156 if (!allocSize
.isValid()) {
2157 return NS_ERROR_OUT_OF_MEMORY
;
2160 auto result
= aOutBuffer
.BulkWrite(allocSize
.value(), 0, false);
2161 if (result
.isErr()) {
2162 return result
.unwrapErr();
2165 auto handle
= result
.unwrap();
2168 ssize_t bytesWritten
=
2169 readlink(aTarget
.get(), handle
.Elements(), handle
.Length());
2170 if (bytesWritten
< 0) {
2171 return NSRESULT_FOR_ERRNO();
2174 // written >= 0 so it is safe to cast to size_t.
2175 if ((size_t)bytesWritten
< handle
.Length()) {
2176 // Target might have changed since the lstat call, or lstat might lie, see
2178 handle
.Finish(bytesWritten
, false);
2182 // The buffer was not large enough, so double it and try again.
2183 auto restartResult
= handle
.RestartBulkWrite(handle
.Length() * 2, 0, false);
2184 if (restartResult
.isErr()) {
2185 return restartResult
.unwrapErr();
2191 nsLocalFile::GetNativeTarget(nsACString
& aResult
) {
2195 struct STAT symStat
;
2196 if (LSTAT(mPath
.get(), &symStat
) == -1) {
2197 return NSRESULT_FOR_ERRNO();
2200 if (!S_ISLNK(symStat
.st_mode
)) {
2201 return NS_ERROR_FILE_INVALID_PATH
;
2204 nsAutoCString target
;
2205 nsresult rv
= ReadLinkSafe(mPath
, symStat
.st_size
, target
);
2206 if (NS_FAILED(rv
)) {
2210 nsCOMPtr
<nsIFile
> self(this);
2211 int32_t maxLinks
= 40;
2213 if (maxLinks
-- == 0) {
2214 rv
= NS_ERROR_FILE_UNRESOLVABLE_SYMLINK
;
2218 if (target
[0] != '/') {
2219 nsCOMPtr
<nsIFile
> parent
;
2220 if (NS_FAILED(rv
= self
->GetParent(getter_AddRefs(parent
)))) {
2223 if (NS_FAILED(rv
= parent
->AppendRelativeNativePath(target
))) {
2226 if (NS_FAILED(rv
= parent
->GetNativePath(aResult
))) {
2234 const nsPromiseFlatCString
& flatRetval
= PromiseFlatCString(aResult
);
2236 // Any failure in testing the current target we'll just interpret
2237 // as having reached our destiny.
2238 if (LSTAT(flatRetval
.get(), &symStat
) == -1) {
2242 // And of course we're done if it isn't a symlink.
2243 if (!S_ISLNK(symStat
.st_mode
)) {
2247 nsAutoCString newTarget
;
2248 rv
= ReadLinkSafe(flatRetval
, symStat
.st_size
, newTarget
);
2249 if (NS_FAILED(rv
)) {
2256 if (NS_FAILED(rv
)) {
2263 nsLocalFile::GetDirectoryEntriesImpl(nsIDirectoryEnumerator
** aEntries
) {
2264 RefPtr
<nsDirEnumeratorUnix
> dir
= new nsDirEnumeratorUnix();
2266 nsresult rv
= dir
->Init(this, false);
2267 if (NS_FAILED(rv
)) {
2268 *aEntries
= nullptr;
2270 dir
.forget(aEntries
);
2277 nsLocalFile::Load(PRLibrary
** aResult
) {
2279 if (NS_WARN_IF(!aResult
)) {
2280 return NS_ERROR_INVALID_ARG
;
2283 #ifdef NS_BUILD_REFCNT_LOGGING
2284 nsTraceRefcnt::SetActivityIsLegal(false);
2287 *aResult
= PR_LoadLibrary(mPath
.get());
2289 #ifdef NS_BUILD_REFCNT_LOGGING
2290 nsTraceRefcnt::SetActivityIsLegal(true);
2294 return NS_ERROR_FAILURE
;
2300 nsLocalFile::GetPersistentDescriptor(nsACString
& aPersistentDescriptor
) {
2301 return GetNativePath(aPersistentDescriptor
);
2305 nsLocalFile::SetPersistentDescriptor(const nsACString
& aPersistentDescriptor
) {
2306 #ifdef MOZ_WIDGET_COCOA
2307 if (aPersistentDescriptor
.IsEmpty()) {
2308 return NS_ERROR_INVALID_ARG
;
2311 // Support pathnames as user-supplied descriptors if they begin with '/'
2312 // or '~'. These characters do not collide with the base64 set used for
2313 // encoding alias records.
2314 char first
= aPersistentDescriptor
.First();
2315 if (first
== '/' || first
== '~') {
2316 return InitWithNativePath(aPersistentDescriptor
);
2319 uint32_t dataSize
= aPersistentDescriptor
.Length();
2320 char* decodedData
= PL_Base64Decode(
2321 PromiseFlatCString(aPersistentDescriptor
).get(), dataSize
, nullptr);
2323 NS_ERROR("SetPersistentDescriptor was given bad data");
2324 return NS_ERROR_FAILURE
;
2327 // Cast to an alias record and resolve.
2328 AliasRecord aliasHeader
= *(AliasPtr
)decodedData
;
2329 int32_t aliasSize
= ::GetAliasSizeFromPtr(&aliasHeader
);
2331 ((int32_t)dataSize
* 3) / 4) { // be paranoid about having too few data
2332 PR_Free(decodedData
); // PL_Base64Decode() uses PR_Malloc().
2333 return NS_ERROR_FAILURE
;
2336 nsresult rv
= NS_OK
;
2338 // Move the now-decoded data into the Handle.
2339 // The size of the decoded data is 3/4 the size of the encoded data. See
2341 Handle newHandle
= nullptr;
2342 if (::PtrToHand(decodedData
, &newHandle
, aliasSize
) != noErr
) {
2343 rv
= NS_ERROR_OUT_OF_MEMORY
;
2345 PR_Free(decodedData
); // PL_Base64Decode() uses PR_Malloc().
2346 if (NS_FAILED(rv
)) {
2351 FSRef resolvedFSRef
;
2352 OSErr err
= ::FSResolveAlias(nullptr, (AliasHandle
)newHandle
, &resolvedFSRef
,
2355 rv
= MacErrorMapper(err
);
2356 DisposeHandle(newHandle
);
2357 if (NS_FAILED(rv
)) {
2361 return InitWithFSRef(&resolvedFSRef
);
2363 return InitWithNativePath(aPersistentDescriptor
);
2368 nsLocalFile::Reveal() {
2369 if (!FilePreferences::IsAllowedPath(mPath
)) {
2370 return NS_ERROR_FILE_ACCESS_DENIED
;
2373 #ifdef MOZ_WIDGET_GTK
2374 nsCOMPtr
<nsIGIOService
> giovfs
= do_GetService(NS_GIOSERVICE_CONTRACTID
);
2376 return NS_ERROR_FAILURE
;
2378 return giovfs
->RevealFile(this);
2379 #elif defined(MOZ_WIDGET_COCOA)
2381 if (NS_SUCCEEDED(GetCFURL(&url
))) {
2382 nsresult rv
= CocoaFileUtils::RevealFileInFinder(url
);
2386 return NS_ERROR_FAILURE
;
2388 return NS_ERROR_FAILURE
;
2393 nsLocalFile::Launch() {
2394 if (!FilePreferences::IsAllowedPath(mPath
)) {
2395 return NS_ERROR_FILE_ACCESS_DENIED
;
2398 #ifdef MOZ_WIDGET_GTK
2399 nsCOMPtr
<nsIGIOService
> giovfs
= do_GetService(NS_GIOSERVICE_CONTRACTID
);
2401 return NS_ERROR_FAILURE
;
2404 return giovfs
->LaunchFile(mPath
);
2405 #elif defined(MOZ_WIDGET_ANDROID)
2406 // Not supported on GeckoView
2407 return NS_ERROR_NOT_IMPLEMENTED
;
2408 #elif defined(MOZ_WIDGET_COCOA)
2410 if (NS_SUCCEEDED(GetCFURL(&url
))) {
2411 nsresult rv
= CocoaFileUtils::OpenURL(url
);
2415 return NS_ERROR_FAILURE
;
2417 return NS_ERROR_FAILURE
;
2421 nsresult
NS_NewNativeLocalFile(const nsACString
& aPath
, nsIFile
** aResult
) {
2422 RefPtr
<nsLocalFile
> file
= new nsLocalFile();
2424 if (!aPath
.IsEmpty()) {
2425 nsresult rv
= file
->InitWithNativePath(aPath
);
2426 if (NS_FAILED(rv
)) {
2430 file
.forget(aResult
);
2434 //-----------------------------------------------------------------------------
2436 //-----------------------------------------------------------------------------
2438 #define SET_UCS(func, ucsArg) \
2440 nsAutoCString buf; \
2441 nsresult rv = NS_CopyUnicodeToNative(ucsArg, buf); \
2442 if (NS_FAILED(rv)) return rv; \
2443 return (func)(buf); \
2446 #define GET_UCS(func, ucsArg) \
2448 nsAutoCString buf; \
2449 nsresult rv = (func)(buf); \
2450 if (NS_FAILED(rv)) return rv; \
2451 return NS_CopyNativeToUnicode(buf, ucsArg); \
2454 #define SET_UCS_2ARGS_2(func, opaqueArg, ucsArg) \
2456 nsAutoCString buf; \
2457 nsresult rv = NS_CopyUnicodeToNative(ucsArg, buf); \
2458 if (NS_FAILED(rv)) return rv; \
2459 return (func)(opaqueArg, buf); \
2462 // Unicode interface Wrapper
2463 nsresult
nsLocalFile::InitWithPath(const nsAString
& aFilePath
) {
2464 SET_UCS(InitWithNativePath
, aFilePath
);
2466 nsresult
nsLocalFile::Append(const nsAString
& aNode
) {
2467 SET_UCS(AppendNative
, aNode
);
2469 nsresult
nsLocalFile::AppendRelativePath(const nsAString
& aNode
) {
2470 SET_UCS(AppendRelativeNativePath
, aNode
);
2472 nsresult
nsLocalFile::GetLeafName(nsAString
& aLeafName
) {
2473 GET_UCS(GetNativeLeafName
, aLeafName
);
2475 nsresult
nsLocalFile::SetLeafName(const nsAString
& aLeafName
) {
2476 SET_UCS(SetNativeLeafName
, aLeafName
);
2478 nsresult
nsLocalFile::GetPath(nsAString
& aResult
) {
2479 return NS_CopyNativeToUnicode(mPath
, aResult
);
2481 nsresult
nsLocalFile::CopyTo(nsIFile
* aNewParentDir
,
2482 const nsAString
& aNewName
) {
2483 SET_UCS_2ARGS_2(CopyToNative
, aNewParentDir
, aNewName
);
2485 nsresult
nsLocalFile::CopyToFollowingLinks(nsIFile
* aNewParentDir
,
2486 const nsAString
& aNewName
) {
2487 SET_UCS_2ARGS_2(CopyToFollowingLinksNative
, aNewParentDir
, aNewName
);
2489 nsresult
nsLocalFile::MoveTo(nsIFile
* aNewParentDir
,
2490 const nsAString
& aNewName
) {
2491 SET_UCS_2ARGS_2(MoveToNative
, aNewParentDir
, aNewName
);
2494 nsLocalFile::MoveToFollowingLinks(nsIFile
* aNewParentDir
,
2495 const nsAString
& aNewName
) {
2496 SET_UCS_2ARGS_2(MoveToFollowingLinksNative
, aNewParentDir
, aNewName
);
2500 nsLocalFile::RenameTo(nsIFile
* aNewParentDir
, const nsAString
& aNewName
) {
2501 SET_UCS_2ARGS_2(RenameToNative
, aNewParentDir
, aNewName
);
2505 nsLocalFile::RenameToNative(nsIFile
* aNewParentDir
,
2506 const nsACString
& aNewName
) {
2509 // check to make sure that this has been initialized properly
2512 // check to make sure that we have a new parent
2513 nsAutoCString newPathName
;
2514 rv
= GetNativeTargetPathName(aNewParentDir
, aNewName
, newPathName
);
2515 if (NS_FAILED(rv
)) {
2519 if (!FilePreferences::IsAllowedPath(newPathName
)) {
2520 return NS_ERROR_FILE_ACCESS_DENIED
;
2523 // try for atomic rename
2524 if (rename(mPath
.get(), newPathName
.get()) < 0) {
2525 if (errno
== EXDEV
) {
2526 rv
= NS_ERROR_FILE_ACCESS_DENIED
;
2528 rv
= NSRESULT_FOR_ERRNO();
2535 nsresult
nsLocalFile::GetTarget(nsAString
& aResult
) {
2536 GET_UCS(GetNativeTarget
, aResult
);
2539 nsresult
NS_NewLocalFile(const nsAString
& aPath
, nsIFile
** aResult
) {
2541 nsresult rv
= NS_CopyUnicodeToNative(aPath
, buf
);
2542 if (NS_FAILED(rv
)) {
2545 return NS_NewNativeLocalFile(buf
, aResult
);
2550 #ifdef MOZ_WIDGET_COCOA
2553 nsLocalFile::HasXAttr(const nsACString
& aAttrName
, bool* aHasAttr
) {
2554 NS_ENSURE_ARG_POINTER(aHasAttr
);
2556 nsAutoCString attrName
{aAttrName
};
2558 ssize_t size
= getxattr(mPath
.get(), attrName
.get(), nullptr, 0, 0, 0);
2560 if (errno
== ENOATTR
) {
2563 return NSRESULT_FOR_ERRNO();
2573 nsLocalFile::GetXAttr(const nsACString
& aAttrName
,
2574 nsTArray
<uint8_t>& aAttrValue
) {
2577 nsAutoCString attrName
{aAttrName
};
2579 ssize_t size
= getxattr(mPath
.get(), attrName
.get(), nullptr, 0, 0, 0);
2582 return NSRESULT_FOR_ERRNO();
2586 aAttrValue
.SetCapacity(size
);
2588 // The attribute can change between our first call and this call, so we need
2589 // to re-check the size and possibly call with a larger buffer.
2590 ssize_t newSize
= getxattr(mPath
.get(), attrName
.get(),
2591 aAttrValue
.Elements(), size
, 0, 0);
2592 if (newSize
== -1) {
2593 return NSRESULT_FOR_ERRNO();
2596 if (newSize
<= size
) {
2597 aAttrValue
.SetLength(newSize
);
2608 nsLocalFile::SetXAttr(const nsACString
& aAttrName
,
2609 const nsTArray
<uint8_t>& aAttrValue
) {
2610 nsAutoCString attrName
{aAttrName
};
2612 if (setxattr(mPath
.get(), attrName
.get(), aAttrValue
.Elements(),
2613 aAttrValue
.Length(), 0, 0) == -1) {
2614 return NSRESULT_FOR_ERRNO();
2621 nsLocalFile::DelXAttr(const nsACString
& aAttrName
) {
2622 nsAutoCString attrName
{aAttrName
};
2624 // Ignore removing an attribute that does not exist.
2625 if (removexattr(mPath
.get(), attrName
.get(), 0) == -1) {
2626 return NSRESULT_FOR_ERRNO();
2632 static nsresult
MacErrorMapper(OSErr inErr
) {
2641 case afpObjectNotFound
:
2642 case afpDirNotFound
:
2643 outErr
= NS_ERROR_FILE_NOT_FOUND
;
2647 case afpObjectExists
:
2648 outErr
= NS_ERROR_FILE_ALREADY_EXISTS
;
2653 outErr
= NS_ERROR_FILE_NO_DEVICE_SPACE
;
2658 outErr
= NS_ERROR_FILE_IS_LOCKED
;
2661 case afpAccessDenied
:
2662 outErr
= NS_ERROR_FILE_ACCESS_DENIED
;
2665 case afpDirNotEmpty
:
2666 outErr
= NS_ERROR_FILE_DIR_NOT_EMPTY
;
2669 // Can't find good map for some
2671 outErr
= NS_ERROR_FAILURE
;
2675 outErr
= NS_ERROR_FAILURE
;
2682 static nsresult
CFStringReftoUTF8(CFStringRef aInStrRef
, nsACString
& aOutStr
) {
2683 // first see if the conversion would succeed and find the length of the
2685 CFIndex usedBufLen
, inStrLen
= ::CFStringGetLength(aInStrRef
);
2686 CFIndex charsConverted
= ::CFStringGetBytes(
2687 aInStrRef
, CFRangeMake(0, inStrLen
), kCFStringEncodingUTF8
, 0, false,
2688 nullptr, 0, &usedBufLen
);
2689 if (charsConverted
== inStrLen
) {
2690 // all characters converted, do the actual conversion
2691 aOutStr
.SetLength(usedBufLen
);
2692 if (aOutStr
.Length() != (unsigned int)usedBufLen
) {
2693 return NS_ERROR_OUT_OF_MEMORY
;
2695 UInt8
* buffer
= (UInt8
*)aOutStr
.BeginWriting();
2696 ::CFStringGetBytes(aInStrRef
, CFRangeMake(0, inStrLen
),
2697 kCFStringEncodingUTF8
, 0, false, buffer
, usedBufLen
,
2702 return NS_ERROR_FAILURE
;
2706 nsLocalFile::InitWithCFURL(CFURLRef aCFURL
) {
2707 UInt8 path
[PATH_MAX
];
2708 if (::CFURLGetFileSystemRepresentation(aCFURL
, true, path
, PATH_MAX
)) {
2709 nsDependentCString
nativePath((char*)path
);
2710 return InitWithNativePath(nativePath
);
2713 return NS_ERROR_FAILURE
;
2717 nsLocalFile::InitWithFSRef(const FSRef
* aFSRef
) {
2718 if (NS_WARN_IF(!aFSRef
)) {
2719 return NS_ERROR_INVALID_ARG
;
2722 CFURLRef newURLRef
= ::CFURLCreateFromFSRef(kCFAllocatorDefault
, aFSRef
);
2724 nsresult rv
= InitWithCFURL(newURLRef
);
2725 ::CFRelease(newURLRef
);
2729 return NS_ERROR_FAILURE
;
2733 nsLocalFile::GetCFURL(CFURLRef
* aResult
) {
2737 IsDirectory(&isDir
);
2738 *aResult
= ::CFURLCreateFromFileSystemRepresentation(
2739 kCFAllocatorDefault
, (UInt8
*)mPath
.get(), mPath
.Length(), isDir
);
2741 return (*aResult
? NS_OK
: NS_ERROR_FAILURE
);
2745 nsLocalFile::GetFSRef(FSRef
* aResult
) {
2746 if (NS_WARN_IF(!aResult
)) {
2747 return NS_ERROR_INVALID_ARG
;
2750 nsresult rv
= NS_ERROR_FAILURE
;
2752 CFURLRef url
= nullptr;
2753 if (NS_SUCCEEDED(GetCFURL(&url
))) {
2754 if (::CFURLGetFSRef(url
, aResult
)) {
2764 nsLocalFile::GetFSSpec(FSSpec
* aResult
) {
2765 if (NS_WARN_IF(!aResult
)) {
2766 return NS_ERROR_INVALID_ARG
;
2770 nsresult rv
= GetFSRef(&fsRef
);
2771 if (NS_SUCCEEDED(rv
)) {
2772 OSErr err
= ::FSGetCatalogInfo(&fsRef
, kFSCatInfoNone
, nullptr, nullptr,
2774 return MacErrorMapper(err
);
2781 nsLocalFile::GetFileSizeWithResFork(int64_t* aFileSizeWithResFork
) {
2782 if (NS_WARN_IF(!aFileSizeWithResFork
)) {
2783 return NS_ERROR_INVALID_ARG
;
2787 nsresult rv
= GetFSRef(&fsRef
);
2788 if (NS_FAILED(rv
)) {
2792 FSCatalogInfo catalogInfo
;
2794 ::FSGetCatalogInfo(&fsRef
, kFSCatInfoDataSizes
+ kFSCatInfoRsrcSizes
,
2795 &catalogInfo
, nullptr, nullptr, nullptr);
2797 return MacErrorMapper(err
);
2800 *aFileSizeWithResFork
=
2801 catalogInfo
.dataLogicalSize
+ catalogInfo
.rsrcLogicalSize
;
2806 nsLocalFile::GetFileType(OSType
* aFileType
) {
2808 if (NS_SUCCEEDED(GetCFURL(&url
))) {
2809 nsresult rv
= CocoaFileUtils::GetFileTypeCode(url
, aFileType
);
2813 return NS_ERROR_FAILURE
;
2817 nsLocalFile::SetFileType(OSType aFileType
) {
2819 if (NS_SUCCEEDED(GetCFURL(&url
))) {
2820 nsresult rv
= CocoaFileUtils::SetFileTypeCode(url
, aFileType
);
2824 return NS_ERROR_FAILURE
;
2828 nsLocalFile::GetFileCreator(OSType
* aFileCreator
) {
2830 if (NS_SUCCEEDED(GetCFURL(&url
))) {
2831 nsresult rv
= CocoaFileUtils::GetFileCreatorCode(url
, aFileCreator
);
2835 return NS_ERROR_FAILURE
;
2839 nsLocalFile::SetFileCreator(OSType aFileCreator
) {
2841 if (NS_SUCCEEDED(GetCFURL(&url
))) {
2842 nsresult rv
= CocoaFileUtils::SetFileCreatorCode(url
, aFileCreator
);
2846 return NS_ERROR_FAILURE
;
2850 nsLocalFile::LaunchWithDoc(nsIFile
* aDocToLoad
, bool aLaunchInBackground
) {
2852 nsresult rv
= IsExecutable(&isExecutable
);
2853 if (NS_FAILED(rv
)) {
2856 if (!isExecutable
) {
2857 return NS_ERROR_FILE_EXECUTION_FAILED
;
2860 FSRef appFSRef
, docFSRef
;
2861 rv
= GetFSRef(&appFSRef
);
2862 if (NS_FAILED(rv
)) {
2867 nsCOMPtr
<nsILocalFileMac
> macDoc
= do_QueryInterface(aDocToLoad
);
2868 rv
= macDoc
->GetFSRef(&docFSRef
);
2869 if (NS_FAILED(rv
)) {
2874 LSLaunchFlags theLaunchFlags
= kLSLaunchDefaults
;
2875 LSLaunchFSRefSpec thelaunchSpec
;
2877 if (aLaunchInBackground
) {
2878 theLaunchFlags
|= kLSLaunchDontSwitch
;
2880 memset(&thelaunchSpec
, 0, sizeof(LSLaunchFSRefSpec
));
2882 thelaunchSpec
.appRef
= &appFSRef
;
2884 thelaunchSpec
.numDocs
= 1;
2885 thelaunchSpec
.itemRefs
= &docFSRef
;
2887 thelaunchSpec
.launchFlags
= theLaunchFlags
;
2889 OSErr err
= ::LSOpenFromRefSpec(&thelaunchSpec
, nullptr);
2891 return MacErrorMapper(err
);
2898 nsLocalFile::OpenDocWithApp(nsIFile
* aAppToOpenWith
, bool aLaunchInBackground
) {
2900 nsresult rv
= GetFSRef(&docFSRef
);
2901 if (NS_FAILED(rv
)) {
2905 if (!aAppToOpenWith
) {
2906 OSErr err
= ::LSOpenFSRef(&docFSRef
, nullptr);
2907 return MacErrorMapper(err
);
2910 nsCOMPtr
<nsILocalFileMac
> appFileMac
= do_QueryInterface(aAppToOpenWith
, &rv
);
2916 rv
= appFileMac
->IsExecutable(&isExecutable
);
2917 if (NS_FAILED(rv
)) {
2920 if (!isExecutable
) {
2921 return NS_ERROR_FILE_EXECUTION_FAILED
;
2925 rv
= appFileMac
->GetFSRef(&appFSRef
);
2926 if (NS_FAILED(rv
)) {
2930 LSLaunchFlags theLaunchFlags
= kLSLaunchDefaults
;
2931 LSLaunchFSRefSpec thelaunchSpec
;
2933 if (aLaunchInBackground
) {
2934 theLaunchFlags
|= kLSLaunchDontSwitch
;
2936 memset(&thelaunchSpec
, 0, sizeof(LSLaunchFSRefSpec
));
2938 thelaunchSpec
.appRef
= &appFSRef
;
2939 thelaunchSpec
.numDocs
= 1;
2940 thelaunchSpec
.itemRefs
= &docFSRef
;
2941 thelaunchSpec
.launchFlags
= theLaunchFlags
;
2943 OSErr err
= ::LSOpenFromRefSpec(&thelaunchSpec
, nullptr);
2945 return MacErrorMapper(err
);
2952 nsLocalFile::IsPackage(bool* aResult
) {
2953 if (NS_WARN_IF(!aResult
)) {
2954 return NS_ERROR_INVALID_ARG
;
2959 nsresult rv
= GetCFURL(&url
);
2960 if (NS_FAILED(rv
)) {
2964 LSItemInfoRecord info
;
2966 ::LSCopyItemInfoForURL(url
, kLSRequestBasicFlagsOnly
, &info
);
2970 if (status
!= noErr
) {
2971 return NS_ERROR_FAILURE
;
2974 *aResult
= !!(info
.flags
& kLSItemInfoIsPackage
);
2980 nsLocalFile::GetBundleDisplayName(nsAString
& aOutBundleName
) {
2981 bool isPackage
= false;
2982 nsresult rv
= IsPackage(&isPackage
);
2983 if (NS_FAILED(rv
) || !isPackage
) {
2984 return NS_ERROR_FAILURE
;
2988 rv
= GetLeafName(name
);
2989 if (NS_FAILED(rv
)) {
2993 int32_t length
= name
.Length();
2994 if (Substring(name
, length
- 4, length
).EqualsLiteral(".app")) {
2995 // 4 characters in ".app"
2996 aOutBundleName
= Substring(name
, 0, length
- 4);
2998 aOutBundleName
= name
;
3005 nsLocalFile::GetBundleIdentifier(nsACString
& aOutBundleIdentifier
) {
3006 nsresult rv
= NS_ERROR_FAILURE
;
3009 if (NS_SUCCEEDED(GetCFURL(&urlRef
))) {
3010 CFBundleRef bundle
= ::CFBundleCreate(nullptr, urlRef
);
3012 CFStringRef bundleIdentifier
= ::CFBundleGetIdentifier(bundle
);
3013 if (bundleIdentifier
) {
3014 rv
= CFStringReftoUTF8(bundleIdentifier
, aOutBundleIdentifier
);
3016 ::CFRelease(bundle
);
3018 ::CFRelease(urlRef
);
3025 nsLocalFile::GetBundleContentsLastModifiedTime(int64_t* aLastModTime
) {
3027 if (NS_WARN_IF(!aLastModTime
)) {
3028 return NS_ERROR_INVALID_ARG
;
3031 bool isPackage
= false;
3032 nsresult rv
= IsPackage(&isPackage
);
3033 if (NS_FAILED(rv
) || !isPackage
) {
3034 return GetLastModifiedTime(aLastModTime
);
3037 nsAutoCString
infoPlistPath(mPath
);
3038 infoPlistPath
.AppendLiteral("/Contents/Info.plist");
3040 if (PR_GetFileInfo64(infoPlistPath
.get(), &info
) != PR_SUCCESS
) {
3041 return GetLastModifiedTime(aLastModTime
);
3043 int64_t modTime
= int64_t(info
.modifyTime
);
3047 *aLastModTime
= modTime
/ int64_t(PR_USEC_PER_MSEC
);
3053 NS_IMETHODIMP
nsLocalFile::InitWithFile(nsIFile
* aFile
) {
3054 if (NS_WARN_IF(!aFile
)) {
3055 return NS_ERROR_INVALID_ARG
;
3058 nsAutoCString nativePath
;
3059 nsresult rv
= aFile
->GetNativePath(nativePath
);
3060 if (NS_FAILED(rv
)) {
3064 return InitWithNativePath(nativePath
);
3067 nsresult
NS_NewLocalFileWithFSRef(const FSRef
* aFSRef
,
3068 nsILocalFileMac
** aResult
) {
3069 RefPtr
<nsLocalFile
> file
= new nsLocalFile();
3071 nsresult rv
= file
->InitWithFSRef(aFSRef
);
3072 if (NS_FAILED(rv
)) {
3075 file
.forget(aResult
);
3079 nsresult
NS_NewLocalFileWithCFURL(const CFURLRef aURL
,
3080 nsILocalFileMac
** aResult
) {
3081 RefPtr
<nsLocalFile
> file
= new nsLocalFile();
3083 nsresult rv
= file
->InitWithCFURL(aURL
);
3084 if (NS_FAILED(rv
)) {
3087 file
.forget(aResult
);