No Bug, mozilla-central repo-update HSTS HPKP remote-settings tld-suffixes ct-logs...
[gecko.git] / xpcom / io / nsLocalFileUnix.cpp
blobe46c91b2b7fac85ebfb4122fbfb17de9b6ec0ca1
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/. */
7 /**
8 * Implementation of nsIFile for "unixy" systems.
9 */
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"
20 #include "prtime.h"
22 #include <sys/select.h>
23 #include <sys/stat.h>
24 #include <sys/time.h>
25 #include <sys/types.h>
26 #include <unistd.h>
27 #include <fcntl.h>
28 #include <errno.h>
29 #include <dirent.h>
31 #if defined(XP_MACOSX)
32 # include <sys/xattr.h>
33 #endif
35 #if defined(USE_LINUX_QUOTACTL)
36 # include <sys/mount.h>
37 # include <sys/quota.h>
38 # include <sys/sysmacros.h>
39 # ifndef BLOCK_SIZE
40 # define BLOCK_SIZE 1024 /* kernel block size */
41 # endif
42 #endif
44 #include "nsDirectoryServiceDefs.h"
45 #include "nsCOMPtr.h"
46 #include "nsIFile.h"
47 #include "nsString.h"
48 #include "nsIDirectoryEnumerator.h"
49 #include "nsSimpleEnumerator.h"
50 #include "private/pprio.h"
51 #include "prlink.h"
53 #ifdef MOZ_WIDGET_GTK
54 # include "nsIGIOService.h"
55 # ifdef MOZ_ENABLE_DBUS
56 # include "mozilla/widget/AsyncDBus.h"
57 # include "mozilla/WidgetUtilsGtk.h"
58 # include <map>
59 # endif
60 #endif
62 #ifdef MOZ_WIDGET_COCOA
63 # include <Carbon/Carbon.h>
64 # include "CocoaFileUtils.h"
65 # include "prmem.h"
66 # include "plbase64.h"
68 static nsresult MacErrorMapper(OSErr inErr);
69 #endif
71 #ifdef MOZ_WIDGET_ANDROID
72 # include "mozilla/java/GeckoAppShellWrappers.h"
73 # include "nsIMIMEService.h"
74 # include <linux/magic.h>
75 #endif
77 #include "nsNativeCharsetUtils.h"
78 #include "nsTraceRefcnt.h"
80 /**
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*);
86 # endif
87 # include <sys/statvfs.h>
88 #endif
90 #ifdef HAVE_SYS_STATFS_H
91 # include <sys/statfs.h>
92 #endif
94 #ifdef HAVE_SYS_VFS_H
95 # include <sys/vfs.h>
96 #endif
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
110 #endif
112 using namespace mozilla;
114 #define ENSURE_STAT_CACHE() \
115 do { \
116 if (!FillStatCache()) return NSRESULT_FOR_ERRNO(); \
117 } while (0)
119 #define CHECK_mPath() \
120 do { \
121 if (mPath.IsEmpty()) return NS_ERROR_NOT_INITIALIZED; \
122 if (!FilePreferences::IsAllowedPath(mPath)) \
123 return NS_ERROR_FILE_ACCESS_DENIED; \
124 } while (0)
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
134 // leak this string.
135 return nsDependentCString(ToNewCString(storePath), storePath.Length());
136 }();
137 return sDocumentStorePath;
139 #endif
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 {
149 public:
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); }
167 private:
168 ~nsDirEnumeratorUnix() override;
170 protected:
171 NS_IMETHOD GetNextEntry();
173 DIR* mDir;
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)
185 NS_IMETHODIMP
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());
205 if (!mDir) {
206 return NSRESULT_FOR_ERRNO();
208 return GetNextEntry();
211 NS_IMETHODIMP
212 nsDirEnumeratorUnix::HasMoreElements(bool* aResult) {
213 *aResult = mDir && mEntry;
214 if (!*aResult) {
215 Close();
217 return NS_OK;
220 NS_IMETHODIMP
221 nsDirEnumeratorUnix::GetNext(nsISupports** aResult) {
222 nsCOMPtr<nsIFile> file;
223 nsresult rv = GetNextFile(getter_AddRefs(file));
224 if (NS_FAILED(rv)) {
225 return rv;
227 if (!file) {
228 return NS_ERROR_FAILURE;
230 file.forget(aResult);
231 return NS_OK;
234 NS_IMETHODIMP
235 nsDirEnumeratorUnix::GetNextEntry() {
236 do {
237 errno = 0;
238 mEntry = readdir(mDir);
240 // end of dir or error
241 if (!mEntry) {
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
249 return NS_OK;
252 NS_IMETHODIMP
253 nsDirEnumeratorUnix::GetNextFile(nsIFile** aResult) {
254 nsresult rv;
255 if (!mDir || !mEntry) {
256 *aResult = nullptr;
257 return NS_OK;
260 nsCOMPtr<nsIFile> file = new nsLocalFile();
262 if (NS_FAILED(rv = file->InitWithNativePath(mParentPath)) ||
263 NS_FAILED(rv = file->AppendNative(nsDependentCString(mEntry->d_name)))) {
264 return rv;
267 file.forget(aResult);
268 return GetNextEntry();
271 NS_IMETHODIMP
272 nsDirEnumeratorUnix::Close() {
273 if (mDir) {
274 closedir(mDir);
275 mDir = nullptr;
277 return NS_OK;
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)
290 #else
291 NS_IMPL_ISUPPORTS(nsLocalFile, nsIFile)
292 #endif
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)) {
308 errno = EACCES;
309 return false;
312 if (STAT(mPath.get(), &mCachedStat) == -1) {
313 // try lstat it may be a symlink
314 if (LSTAT(mPath.get(), &mCachedStat) == -1) {
315 return false;
318 return true;
321 NS_IMETHODIMP
322 nsLocalFile::Clone(nsIFile** aFile) {
323 // Just copy-construct ourselves
324 RefPtr<nsLocalFile> copy = new nsLocalFile(*this);
325 copy.forget(aFile);
326 return NS_OK;
329 NS_IMETHODIMP
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;
343 mPath = homePath;
344 if (aFilePath.Length() > 2) {
345 mPath.Append(Substring(aFilePath, 1));
347 } else {
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`.
353 mPath =
354 #ifdef XP_MACOSX
355 "/Users/"_ns
356 #else
357 "/home/"_ns
358 #endif
359 + Substring(aFilePath, 1);
361 } else {
362 if (aFilePath.IsEmpty() || aFilePath.First() != '/') {
363 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
365 mPath = aFilePath;
368 if (!FilePreferences::IsAllowedPath(mPath)) {
369 mPath.Truncate();
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] == '/')) {
376 --len;
378 mPath.SetLength(len);
380 return NS_OK;
383 NS_IMETHODIMP
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;
393 int mkdir_errno;
395 #ifdef DEBUG_NSIFILE
396 fprintf(stderr, "nsIFile: before: %s\n", buffer);
397 #endif
399 while ((slashp = strchr(slashp + 1, '/'))) {
401 * Sequences of '/' are equivalent to a single '/'.
403 if (slashp[1] == '/') {
404 continue;
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
411 * there.
413 if (slashp[1] == '\0') {
414 break;
417 /* Temporarily NUL-terminate here */
418 *slashp = '\0';
419 #ifdef DEBUG_NSIFILE
420 fprintf(stderr, "nsIFile: mkdir(\"%s\")\n", buffer);
421 #endif
422 mkdir_result = mkdir(buffer, aPermissions);
423 if (mkdir_result == -1) {
424 mkdir_errno = errno;
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
430 * for details)).
432 if (mkdir_errno != EEXIST && access(buffer, F_OK) == 0) {
433 mkdir_errno = EEXIST;
435 #ifdef DEBUG_NSIFILE
436 fprintf(stderr, "nsIFile: errno: %d\n", mkdir_errno);
437 #endif
440 /* Put the / back */
441 *slashp = '/';
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;
453 return NS_OK;
456 NS_IMETHODIMP
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);
463 if (!*aResult) {
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);
476 #endif
477 return NS_OK;
480 NS_IMETHODIMP
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);
486 if (!*aResult) {
487 return NS_ERROR_FAILURE;
490 return NS_OK;
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) {
501 *aResult = nullptr;
502 return mkdir(aPath, aMode);
505 nsresult nsLocalFile::CreateAndKeepOpen(uint32_t aType, int aFlags,
506 uint32_t aPermissions,
507 bool aSkipAncestors,
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) {
533 dirperm |= S_IXUSR;
535 if (aPermissions & S_IRGRP) {
536 dirperm |= S_IXGRP;
538 if (aPermissions & S_IROTH) {
539 dirperm |= S_IXOTH;
542 #ifdef DEBUG_NSIFILE
543 fprintf(stderr, "nsIFile: perm = %o, dirperm = %o\n", aPermissions,
544 dirperm);
545 #endif
547 if (NS_FAILED(CreateAllAncestors(dirperm))) {
548 return NS_ERROR_FAILURE;
551 #ifdef DEBUG_NSIFILE
552 fprintf(stderr, "nsIFile: Create(\"%s\") again\n", mPath.get());
553 #endif
554 result = createFunc(mPath.get(), aFlags, aPermissions, aResult);
556 return NSRESULT_FOR_RETURN(result);
559 NS_IMETHODIMP
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);
570 if (junk) {
571 PR_Close(junk);
573 return rv;
576 NS_IMETHODIMP
577 nsLocalFile::AppendNative(const nsACString& aFragment) {
578 if (aFragment.IsEmpty()) {
579 return NS_OK;
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);
593 NS_IMETHODIMP
594 nsLocalFile::AppendRelativeNativePath(const nsACString& aFragment) {
595 if (aFragment.IsEmpty()) {
596 return NS_OK;
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);
613 offset = end;
614 while (FindInReadable(doubleDot, start, offset)) {
615 if (offset == end || *offset == '/') {
616 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
618 start = offset;
619 offset = end;
622 // catches the remaining cases of prefixes
623 if (StringBeginsWith(aFragment, "../"_ns)) {
624 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
628 if (!mPath.EqualsLiteral("/")) {
629 mPath.Append('/');
631 mPath.Append(aFragment);
633 return NS_OK;
636 NS_IMETHODIMP
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;
653 return NS_OK;
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;
665 --stop;
666 while (--it != stop) {
667 if (*it == '/') {
668 aBegin = ++it;
669 return;
672 // else, the entire path is the leaf name (which means this
673 // isn't an absolute path... unexpected??)
676 NS_IMETHODIMP
677 nsLocalFile::GetNativeLeafName(nsACString& aLeafName) {
678 nsACString::const_iterator begin, end;
679 LocateNativeLeafName(begin, end);
680 aLeafName = Substring(begin, end);
681 return NS_OK;
684 NS_IMETHODIMP
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);
689 return NS_OK;
692 NS_IMETHODIMP
693 nsLocalFile::GetDisplayName(nsAString& aLeafName) {
694 return GetLeafName(aLeafName);
697 NS_IMETHODIMP
698 nsLocalFile::HostPath(JSContext* aCx, dom::Promise** aPromise) {
699 MOZ_ASSERT(aCx);
700 MOZ_ASSERT(aPromise);
702 nsIGlobalObject* globalObject = xpc::CurrentNativeGlobal(aCx);
703 if (NS_WARN_IF(!globalObject)) {
704 return NS_ERROR_FAILURE;
707 ErrorResult result;
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);
718 return NS_OK;
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);
727 }();
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)
736 ->Then(
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"));
742 if (!version ||
743 !g_variant_is_of_type(version, G_VARIANT_TYPE_UINT32)) {
744 g_printerr(
745 "nsIFile: failed to get host path for %s\n: Invalid value.",
746 mPath.get());
747 retPromise->MaybeReject(NS_ERROR_FAILURE);
748 return;
751 if (g_variant_get_uint32(version) < 5) {
752 g_printerr(
753 "nsIFile: failed to get host path for %s\n: Document "
754 "portal in version 5 is required.",
755 mPath.get());
756 retPromise->MaybeReject(NS_ERROR_NOT_AVAILABLE);
757 return;
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)));
769 if (!args) {
770 g_printerr(
771 "nsIFile: failed to get host path for %s\n: "
772 "Invalid value.",
773 mPath.get());
774 retPromise->MaybeReject(NS_ERROR_FAILURE);
775 return;
778 widget::DBusProxyCall(aProxy, "GetHostPaths", args,
779 G_DBUS_CALL_FLAGS_NONE, -1,
780 /* cancellable */ nullptr)
781 ->Then(
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}"))) {
789 g_printerr(
790 "nsIFile: failed to get host path for %s\n: "
791 "Invalid value.",
792 mPath.get());
793 retPromise->MaybeReject(NS_ERROR_FAILURE);
794 return;
797 const gchar* key = nullptr;
798 const gchar* path = nullptr;
799 GVariantIter* iter = g_variant_iter_new(result);
801 while (
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);
806 return;
810 g_variant_iter_free(iter);
811 g_printerr(
812 "nsIFile: failed to get host path for %s\n: "
813 "Invalid value.",
814 mPath.get());
815 retPromise->MaybeReject(NS_ERROR_FAILURE);
817 [this, self = RefPtr(this),
818 retPromise](GUniquePtr<GError>&& aError) {
819 g_printerr(
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);
830 #else
831 retPromise->MaybeResolve(mPath);
832 #endif
833 retPromise.forget(aPromise);
834 return NS_OK;
837 nsCString nsLocalFile::NativePath() { return mPath; }
839 nsresult nsIFile::GetNativePath(nsACString& aResult) {
840 aResult = NativePath();
841 return NS_OK;
844 nsCString nsIFile::HumanReadablePath() {
845 nsCString path;
846 DebugOnly<nsresult> rv = GetNativePath(path);
847 MOZ_ASSERT(NS_SUCCEEDED(rv));
848 return path;
851 nsresult nsLocalFile::GetNativeTargetPathName(nsIFile* aNewParent,
852 const nsACString& aNewName,
853 nsACString& aResult) {
854 nsresult rv;
855 nsCOMPtr<nsIFile> oldParent;
857 if (!aNewParent) {
858 if (NS_FAILED(rv = GetParent(getter_AddRefs(oldParent)))) {
859 return rv;
861 aNewParent = oldParent.get();
862 } else {
863 // check to see if our target directory exists
864 bool targetExists;
865 if (NS_FAILED(rv = aNewParent->Exists(&targetExists))) {
866 return rv;
869 if (!targetExists) {
870 // XXX create the new directory with some permissions
871 rv = aNewParent->Create(DIRECTORY_TYPE, 0755);
872 if (NS_FAILED(rv)) {
873 return rv;
875 } else {
876 // make sure that the target is actually a directory
877 bool targetIsDirectory;
878 if (NS_FAILED(rv = aNewParent->IsDirectory(&targetIsDirectory))) {
879 return rv;
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);
891 } else {
892 LocateNativeLeafName(nameBegin, nameEnd);
895 nsAutoCString dirName;
896 if (NS_FAILED(rv = aNewParent->GetNativePath(dirName))) {
897 return rv;
900 aResult = dirName + "/"_ns + Substring(nameBegin, nameEnd);
901 return NS_OK;
904 nsresult nsLocalFile::CopyDirectoryTo(nsIFile* aNewParent) {
905 nsresult rv;
907 * dirCheck is used for various boolean test results such as from Equals,
908 * Exists, isDir, etc.
910 bool dirCheck, isSymlink;
911 uint32_t oldPerms;
913 if (NS_FAILED(rv = IsDirectory(&dirCheck))) {
914 return rv;
916 if (!dirCheck) {
917 return CopyToNative(aNewParent, ""_ns);
920 if (NS_FAILED(rv = Equals(aNewParent, &dirCheck))) {
921 return rv;
923 if (dirCheck) {
924 // can't copy dir to itself
925 return NS_ERROR_INVALID_ARG;
928 if (NS_FAILED(rv = aNewParent->Exists(&dirCheck))) {
929 return rv;
931 // get the dirs old permissions
932 if (NS_FAILED(rv = GetPermissions(&oldPerms))) {
933 return rv;
935 if (!dirCheck) {
936 if (NS_FAILED(rv = aNewParent->Create(DIRECTORY_TYPE, oldPerms))) {
937 return rv;
939 } else { // dir exists lets try to use leaf
940 nsAutoCString leafName;
941 if (NS_FAILED(rv = GetNativeLeafName(leafName))) {
942 return rv;
944 if (NS_FAILED(rv = aNewParent->AppendNative(leafName))) {
945 return rv;
947 if (NS_FAILED(rv = aNewParent->Exists(&dirCheck))) {
948 return rv;
950 if (dirCheck) {
951 return NS_ERROR_FILE_ALREADY_EXISTS; // dest exists
953 if (NS_FAILED(rv = aNewParent->Create(DIRECTORY_TYPE, oldPerms))) {
954 return rv;
958 nsCOMPtr<nsIDirectoryEnumerator> dirIterator;
959 if (NS_FAILED(rv = GetDirectoryEntries(getter_AddRefs(dirIterator)))) {
960 return rv;
963 nsCOMPtr<nsIFile> entry;
964 while (NS_SUCCEEDED(dirIterator->GetNextFile(getter_AddRefs(entry))) &&
965 entry) {
966 if (NS_FAILED(rv = entry->IsSymlink(&isSymlink))) {
967 return rv;
969 if (NS_FAILED(rv = entry->IsDirectory(&dirCheck))) {
970 return rv;
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))) {
977 #ifdef DEBUG
978 nsresult rv2;
979 nsAutoCString pathName;
980 if (NS_FAILED(rv2 = entry->GetNativePath(pathName))) {
981 return rv2;
983 printf("Operation not supported: %s\n", pathName.get());
984 #endif
985 if (rv == NS_ERROR_OUT_OF_MEMORY) {
986 return rv;
988 continue;
991 } else {
992 if (NS_FAILED(rv = entry->CopyToNative(aNewParent, ""_ns))) {
993 #ifdef DEBUG
994 nsresult rv2;
995 nsAutoCString pathName;
996 if (NS_FAILED(rv2 = entry->GetNativePath(pathName))) {
997 return rv2;
999 printf("Operation not supported: %s\n", pathName.get());
1000 #endif
1001 if (rv == NS_ERROR_OUT_OF_MEMORY) {
1002 return rv;
1004 continue;
1008 return NS_OK;
1011 NS_IMETHODIMP
1012 nsLocalFile::CopyToNative(nsIFile* aNewParent, const nsACString& aNewName) {
1013 nsresult rv;
1014 // check to make sure that this has been initialized properly
1015 CHECK_mPath();
1017 // we copy the parent here so 'aNewParent' remains immutable
1018 nsCOMPtr<nsIFile> workParent;
1019 if (aNewParent) {
1020 if (NS_FAILED(rv = aNewParent->Clone(getter_AddRefs(workParent)))) {
1021 return rv;
1023 } else {
1024 if (NS_FAILED(rv = GetParent(getter_AddRefs(workParent)))) {
1025 return rv;
1029 // check to see if we are a directory or if we are a file
1030 bool isDirectory;
1031 if (NS_FAILED(rv = IsDirectory(&isDirectory))) {
1032 return rv;
1035 nsAutoCString newPathName;
1036 if (isDirectory) {
1037 if (!aNewName.IsEmpty()) {
1038 if (NS_FAILED(rv = workParent->AppendNative(aNewName))) {
1039 return rv;
1041 } else {
1042 if (NS_FAILED(rv = GetNativeLeafName(newPathName))) {
1043 return rv;
1045 if (NS_FAILED(rv = workParent->AppendNative(newPathName))) {
1046 return rv;
1049 if (NS_FAILED(rv = CopyDirectoryTo(workParent))) {
1050 return rv;
1052 } else {
1053 rv = GetNativeTargetPathName(workParent, aNewName, newPathName);
1054 if (NS_FAILED(rv)) {
1055 return rv;
1058 #ifdef DEBUG_blizzard
1059 printf("nsLocalFile::CopyTo() %s -> %s\n", mPath.get(), newPathName.get());
1060 #endif
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)) {
1068 return rv;
1071 // get the old permissions
1072 uint32_t myPerms = 0;
1073 rv = GetPermissions(&myPerms);
1074 if (NS_FAILED(rv)) {
1075 return 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.
1084 PRFileDesc* newFD;
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)) {
1089 return rv;
1092 // open the old file, too
1093 bool specialFile;
1094 if (NS_FAILED(rv = IsSpecial(&specialFile))) {
1095 PR_Close(newFD);
1096 return rv;
1098 if (specialFile) {
1099 #ifdef DEBUG
1100 printf("Operation not supported: %s\n", mPath.get());
1101 #endif
1102 // make sure to clean up properly
1103 PR_Close(newFD);
1104 return NS_OK;
1107 #if defined(XP_MACOSX)
1108 bool quarantined = true;
1109 (void)HasXAttr("com.apple.quarantine"_ns, &quarantined);
1110 #endif
1112 PRFileDesc* oldFD;
1113 rv = OpenNSPRFileDesc(PR_RDONLY, myPerms, &oldFD);
1114 if (NS_FAILED(rv)) {
1115 // make sure to clean up properly
1116 PR_Close(newFD);
1117 return rv;
1120 #ifdef DEBUG_blizzard
1121 int32_t totalRead = 0;
1122 int32_t totalWritten = 0;
1123 #endif
1124 char buf[BUFSIZ];
1125 int32_t bytesRead;
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;
1144 #endif
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();
1150 bytesRead = -1;
1151 break;
1153 NS_ASSERTION(bytesWritten == bytesRead, "short PR_Write?");
1155 #ifdef DEBUG_blizzard
1156 totalWritten += bytesWritten;
1157 #endif
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);
1172 #endif
1174 // DONE: Errors of close can occur. Read man page of
1175 // close(2);
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.
1182 // close the files
1183 if (PR_Close(newFD) < 0) {
1184 saved_write_close_error = NSRESULT_FOR_ERRNO();
1185 #if DEBUG
1186 // This error merits printing.
1187 fprintf(stderr, "ERROR: PR_Close(newFD) returned error. errno = %d\n",
1188 errno);
1189 #endif
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();
1201 #if DEBUG
1202 fprintf(stderr, "ERROR: PR_Close(oldFD) returned error. errno = %d\n",
1203 errno);
1204 #endif
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;
1216 #if DEBUG
1217 MOZ_ASSERT(0);
1218 #endif
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;
1228 return rv;
1231 NS_IMETHODIMP
1232 nsLocalFile::CopyToFollowingLinksNative(nsIFile* aNewParent,
1233 const nsACString& aNewName) {
1234 return CopyToNative(aNewParent, aNewName);
1237 NS_IMETHODIMP
1238 nsLocalFile::MoveToNative(nsIFile* aNewParent, const nsACString& aNewName) {
1239 nsresult rv;
1241 // check to make sure that this has been initialized properly
1242 CHECK_mPath();
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)) {
1248 return 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)) {
1260 rv = Remove(true);
1262 } else {
1263 rv = NSRESULT_FOR_ERRNO();
1267 if (NS_SUCCEEDED(rv)) {
1268 // Adjust this
1269 mPath = newPathName;
1271 return rv;
1274 NS_IMETHODIMP
1275 nsLocalFile::MoveToFollowingLinksNative(nsIFile* aNewParent,
1276 const nsACString& aNewName) {
1277 return MoveToNative(aNewParent, aNewName);
1280 NS_IMETHODIMP
1281 nsLocalFile::Remove(bool aRecursive, uint32_t* aRemoveCount) {
1282 CHECK_mPath();
1283 ENSURE_STAT_CACHE();
1285 bool isSymLink;
1287 nsresult rv = IsSymlink(&isSymLink);
1288 if (NS_FAILED(rv)) {
1289 return rv;
1292 if (isSymLink || !S_ISDIR(mCachedStat.st_mode)) {
1293 rv = NSRESULT_FOR_RETURN(unlink(mPath.get()));
1294 if (NS_SUCCEEDED(rv) && aRemoveCount) {
1295 *aRemoveCount += 1;
1297 return rv;
1300 if (aRecursive) {
1301 auto* dir = new nsDirEnumeratorUnix();
1303 RefPtr<nsSimpleEnumerator> dirRef(dir); // release on exit
1305 rv = dir->Init(this, false);
1306 if (NS_FAILED(rv)) {
1307 return rv;
1310 bool more;
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);
1326 #ifdef ANDROID
1327 // See bug 580434 - Bionic gives us just deleted files
1328 if (rv == NS_ERROR_FILE_NOT_FOUND) {
1329 continue;
1331 #endif
1332 if (NS_FAILED(rv)) {
1333 return rv;
1338 rv = NSRESULT_FOR_RETURN(rmdir(mPath.get()));
1339 if (NS_SUCCEEDED(rv) && aRemoveCount) {
1340 *aRemoveCount += 1;
1342 return rv;
1345 nsresult nsLocalFile::GetTimeImpl(PRTime* aTime,
1346 nsLocalFile::TimeField aTimeField,
1347 bool aFollowLinks) {
1348 CHECK_mPath();
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;
1366 #else
1367 timespec = &fileStats.st_atim;
1368 #endif
1369 break;
1371 case TimeField::ModifiedTime:
1372 #if (defined(__APPLE__) && defined(__MACH__))
1373 timespec = &fileStats.st_mtimespec;
1374 #else
1375 timespec = &fileStats.st_mtim;
1376 #endif
1377 break;
1379 default:
1380 MOZ_CRASH("Unknown TimeField");
1383 *aTime = TimespecToMillis(*timespec);
1385 return NS_OK;
1388 nsresult nsLocalFile::SetTimeImpl(PRTime aTime,
1389 nsLocalFile::TimeField aTimeField,
1390 bool aFollowLinks) {
1391 CHECK_mPath();
1393 using UtimesFn = int (*)(const char*, const timeval*);
1394 UtimesFn utimesFn = &utimes;
1396 #if HAVE_LUTIMES
1397 if (!aFollowLinks) {
1398 utimesFn = &lutimes;
1400 #endif
1402 ENSURE_STAT_CACHE();
1404 if (aTime == 0) {
1405 aTime = PR_Now();
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.
1418 timeval times[2];
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;
1427 #else
1428 auto* copyFrom = aTimeField == TimeField::AccessedTime ? &mCachedStat.st_mtim
1429 : &mCachedStat.st_atim;
1430 #endif
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);
1442 NS_IMETHODIMP
1443 nsLocalFile::GetLastAccessedTime(PRTime* aLastAccessedTime) {
1444 return GetTimeImpl(aLastAccessedTime, TimeField::AccessedTime,
1445 /* follow links? */ true);
1448 NS_IMETHODIMP
1449 nsLocalFile::SetLastAccessedTime(PRTime aLastAccessedTime) {
1450 return SetTimeImpl(aLastAccessedTime, TimeField::AccessedTime,
1451 /* follow links? */ true);
1454 NS_IMETHODIMP
1455 nsLocalFile::GetLastAccessedTimeOfLink(PRTime* aLastAccessedTime) {
1456 return GetTimeImpl(aLastAccessedTime, TimeField::AccessedTime,
1457 /* follow links? */ false);
1460 NS_IMETHODIMP
1461 nsLocalFile::SetLastAccessedTimeOfLink(PRTime aLastAccessedTime) {
1462 return SetTimeImpl(aLastAccessedTime, TimeField::AccessedTime,
1463 /* follow links? */ false);
1466 NS_IMETHODIMP
1467 nsLocalFile::GetLastModifiedTime(PRTime* aLastModTime) {
1468 return GetTimeImpl(aLastModTime, TimeField::ModifiedTime,
1469 /* follow links? */ true);
1472 NS_IMETHODIMP
1473 nsLocalFile::SetLastModifiedTime(PRTime aLastModTime) {
1474 return SetTimeImpl(aLastModTime, TimeField::ModifiedTime,
1475 /* follow links ? */ true);
1478 NS_IMETHODIMP
1479 nsLocalFile::GetLastModifiedTimeOfLink(PRTime* aLastModTimeOfLink) {
1480 return GetTimeImpl(aLastModTimeOfLink, TimeField::ModifiedTime,
1481 /* follow link? */ false);
1484 NS_IMETHODIMP
1485 nsLocalFile::SetLastModifiedTimeOfLink(PRTime aLastModTimeOfLink) {
1486 return SetTimeImpl(aLastModTimeOfLink, TimeField::ModifiedTime,
1487 /* follow links? */ false);
1490 NS_IMETHODIMP
1491 nsLocalFile::GetCreationTime(PRTime* aCreationTime) {
1492 return GetCreationTimeImpl(aCreationTime, false);
1495 NS_IMETHODIMP
1496 nsLocalFile::GetCreationTimeOfLink(PRTime* aCreationTimeOfLink) {
1497 return GetCreationTimeImpl(aCreationTimeOfLink, /* aFollowLinks = */ true);
1500 nsresult nsLocalFile::GetCreationTimeImpl(PRTime* aCreationTime,
1501 bool aFollowLinks) {
1502 CHECK_mPath();
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);
1517 return NS_OK;
1518 #else
1519 return NS_ERROR_NOT_IMPLEMENTED;
1520 #endif
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))
1530 NS_IMETHODIMP
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);
1537 return NS_OK;
1540 NS_IMETHODIMP
1541 nsLocalFile::GetPermissionsOfLink(uint32_t* aPermissionsOfLink) {
1542 CHECK_mPath();
1543 if (NS_WARN_IF(!aPermissionsOfLink)) {
1544 return NS_ERROR_INVALID_ARG;
1547 struct STAT sbuf;
1548 if (LSTAT(mPath.get(), &sbuf) == -1) {
1549 return NSRESULT_FOR_ERRNO();
1551 *aPermissionsOfLink = NORMALIZE_PERMS(sbuf.st_mode);
1552 return NS_OK;
1555 NS_IMETHODIMP
1556 nsLocalFile::SetPermissions(uint32_t aPermissions) {
1557 CHECK_mPath();
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) {
1564 return NS_OK;
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
1569 struct STATFS sfs;
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) {
1576 return NS_OK;
1578 #endif
1579 return NSRESULT_FOR_ERRNO();
1582 NS_IMETHODIMP
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;
1589 NS_IMETHODIMP
1590 nsLocalFile::GetFileSize(int64_t* aFileSize) {
1591 if (NS_WARN_IF(!aFileSize)) {
1592 return NS_ERROR_INVALID_ARG;
1594 *aFileSize = 0;
1595 ENSURE_STAT_CACHE();
1597 if (!S_ISDIR(mCachedStat.st_mode)) {
1598 *aFileSize = (int64_t)mCachedStat.st_size;
1600 return NS_OK;
1603 NS_IMETHODIMP
1604 nsLocalFile::SetFileSize(int64_t aFileSize) {
1605 CHECK_mPath();
1607 #if defined(ANDROID)
1608 /* no truncate on bionic */
1609 int fd = open(mPath.get(), O_WRONLY);
1610 if (fd == -1) {
1611 return NSRESULT_FOR_ERRNO();
1614 int ret = ftruncate(fd, (off_t)aFileSize);
1615 close(fd);
1617 if (ret == -1) {
1618 return NSRESULT_FOR_ERRNO();
1620 #elif defined(HAVE_TRUNCATE64)
1621 if (truncate64(mPath.get(), (off64_t)aFileSize) == -1) {
1622 return NSRESULT_FOR_ERRNO();
1624 #else
1625 off_t size = (off_t)aFileSize;
1626 if (truncate(mPath.get(), size) == -1) {
1627 return NSRESULT_FOR_ERRNO();
1629 #endif
1630 return NS_OK;
1633 NS_IMETHODIMP
1634 nsLocalFile::GetFileSizeOfLink(int64_t* aFileSize) {
1635 CHECK_mPath();
1636 if (NS_WARN_IF(!aFileSize)) {
1637 return NS_ERROR_INVALID_ARG;
1640 struct STAT sbuf;
1641 if (LSTAT(mPath.get(), &sbuf) == -1) {
1642 return NSRESULT_FOR_ERRNO();
1645 *aFileSize = (int64_t)sbuf.st_size;
1646 return NS_OK;
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) {
1658 bool ret = false;
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");
1669 if (!f) {
1670 return ret;
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, ' ');
1680 if (p_dev) {
1681 p_dev++;
1685 if (p_dev) {
1686 char* p_dev_end = strchr(p_dev, ' ');
1687 if (p_dev_end) {
1688 *p_dev_end = '\0';
1689 aDeviceName.Assign(p_dev);
1690 ret = true;
1691 break;
1696 fclose(f);
1697 return ret;
1699 #endif
1701 #if defined(USE_LINUX_QUOTACTL)
1702 template <typename StatInfoFunc, typename QuotaInfoFunc>
1703 nsresult nsLocalFile::GetDiskInfo(StatInfoFunc&& aStatInfoFunc,
1704 QuotaInfoFunc&& aQuotaInfoFunc,
1705 int64_t* aResult)
1706 #else
1707 template <typename StatInfoFunc>
1708 nsresult nsLocalFile::GetDiskInfo(StatInfoFunc&& aStatInfoFunc,
1709 int64_t* aResult)
1710 #endif
1712 if (NS_WARN_IF(!aResult)) {
1713 return NS_ERROR_INVALID_ARG;
1716 // These systems have the operations necessary to check disk space.
1718 #ifdef STATFS
1720 // check to make sure that mPath is properly initialized
1721 CHECK_mPath();
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.
1735 # ifdef DEBUG
1736 printf("ERROR: GetDiskInfo: STATFS call FAILED. \n");
1737 # endif
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
1753 return NS_OK;
1756 nsAutoCString deviceName;
1757 if (!GetDeviceName(major(mCachedStat.st_dev), minor(mCachedStat.st_dev),
1758 deviceName)) {
1759 // Returns info from statfs
1760 return NS_OK;
1763 struct dqblk dq;
1764 if (!quotactl(QCMD(Q_GETQUOTA, USRQUOTA), deviceName.get(), getuid(),
1765 (caddr_t)&dq)
1766 # ifdef QIF_BLIMITS
1767 && dq.dqb_valid & QIF_BLIMITS
1768 # endif
1769 && dq.dqb_bhardlimit) {
1770 CheckedInt64 quotaResult = std::forward<QuotaInfoFunc>(aQuotaInfoFunc)(dq);
1771 if (!quotaResult.isValid()) {
1772 // Returns info from statfs
1773 return NS_OK;
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);
1784 # endif
1786 return NS_OK;
1788 #else // STATFS
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.
1797 # ifdef DEBUG
1798 printf("ERROR: GetDiskInfo: Not implemented for plaforms without statfs.\n");
1799 # endif
1800 return NS_ERROR_NOT_IMPLEMENTED;
1802 #endif // STATFS
1805 NS_IMETHODIMP
1806 nsLocalFile::GetDiskSpaceAvailable(int64_t* aDiskSpaceAvailable) {
1807 return GetDiskInfo(
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;
1818 return 0;
1820 #endif
1821 aDiskSpaceAvailable);
1824 NS_IMETHODIMP
1825 nsLocalFile::GetDiskCapacity(int64_t* aDiskCapacity) {
1826 return GetDiskInfo(
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;
1835 #endif
1836 aDiskCapacity);
1839 NS_IMETHODIMP
1840 nsLocalFile::GetParent(nsIFile** aParent) {
1841 CHECK_mPath();
1842 if (NS_WARN_IF(!aParent)) {
1843 return NS_ERROR_INVALID_ARG;
1845 *aParent = nullptr;
1847 // if '/' we are at the top of the volume, return null
1848 if (mPath.EqualsLiteral("/")) {
1849 return NS_OK;
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?");
1857 if (!slashp) {
1858 return NS_ERROR_FILE_INVALID_PATH;
1861 // for the case where we are at '/'
1862 if (slashp == buffer) {
1863 slashp++;
1866 // temporarily terminate buffer at the last significant slash
1867 char c = *slashp;
1868 *slashp = '\0';
1870 nsCOMPtr<nsIFile> localFile;
1871 nsresult rv = NS_NewNativeLocalFile(nsDependentCString(buffer),
1872 getter_AddRefs(localFile));
1874 // make buffer whole again
1875 *slashp = c;
1877 if (NS_FAILED(rv)) {
1878 return rv;
1881 localFile.forget(aParent);
1882 return NS_OK;
1886 * The results of Exists, isWritable and isReadable are not cached.
1889 NS_IMETHODIMP
1890 nsLocalFile::Exists(bool* aResult) {
1891 CHECK_mPath();
1892 if (NS_WARN_IF(!aResult)) {
1893 return NS_ERROR_INVALID_ARG;
1896 *aResult = (access(mPath.get(), F_OK) == 0);
1897 return NS_OK;
1900 NS_IMETHODIMP
1901 nsLocalFile::IsWritable(bool* aResult) {
1902 CHECK_mPath();
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) {
1909 return NS_OK;
1911 return NSRESULT_FOR_ERRNO();
1914 NS_IMETHODIMP
1915 nsLocalFile::IsReadable(bool* aResult) {
1916 CHECK_mPath();
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) {
1923 return NS_OK;
1925 return NSRESULT_FOR_ERRNO();
1928 NS_IMETHODIMP
1929 nsLocalFile::IsExecutable(bool* aResult) {
1930 CHECK_mPath();
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.
1941 bool symLink;
1942 nsresult rv = IsSymlink(&symLink);
1943 if (NS_FAILED(rv)) {
1944 return rv;
1947 nsAutoString path;
1948 if (symLink) {
1949 GetTarget(path);
1950 } else {
1951 GetPath(path);
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.
1966 #endif
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
1971 // files.
1972 "ftploc", // Can point to other files.
1973 "inetloc", // Shouldn't be able to do the same, but can, due to
1974 // macOS vulnerabilities.
1975 #endif
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.
1982 *aResult = true;
1983 return NS_OK;
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).
1993 CFURLRef url;
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);
2001 ::CFRelease(url);
2002 if (result == noErr) {
2003 if ((theInfo.flags & kLSItemInfoIsApplication) != 0) {
2004 *aResult = true;
2005 return NS_OK;
2008 #endif
2010 // Then check the execute bit.
2011 *aResult = (access(mPath.get(), X_OK) == 0);
2012 #ifdef SOLARIS
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
2016 if (*aResult) {
2017 struct STAT buf;
2019 *aResult = (STAT(mPath.get(), &buf) == 0);
2020 if (*aResult || errno == EACCES) {
2021 *aResult = *aResult && (buf.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH));
2022 return NS_OK;
2025 return NSRESULT_FOR_ERRNO();
2027 #endif
2028 if (*aResult || errno == EACCES) {
2029 return NS_OK;
2031 return NSRESULT_FOR_ERRNO();
2034 NS_IMETHODIMP
2035 nsLocalFile::IsDirectory(bool* aResult) {
2036 if (NS_WARN_IF(!aResult)) {
2037 return NS_ERROR_INVALID_ARG;
2039 *aResult = false;
2040 ENSURE_STAT_CACHE();
2041 *aResult = S_ISDIR(mCachedStat.st_mode);
2042 return NS_OK;
2045 NS_IMETHODIMP
2046 nsLocalFile::IsFile(bool* aResult) {
2047 if (NS_WARN_IF(!aResult)) {
2048 return NS_ERROR_INVALID_ARG;
2050 *aResult = false;
2051 ENSURE_STAT_CACHE();
2052 *aResult = S_ISREG(mCachedStat.st_mode);
2053 return NS_OK;
2056 NS_IMETHODIMP
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 == '.');
2064 return NS_OK;
2067 NS_IMETHODIMP
2068 nsLocalFile::IsSymlink(bool* aResult) {
2069 if (NS_WARN_IF(!aResult)) {
2070 return NS_ERROR_INVALID_ARG;
2072 CHECK_mPath();
2074 struct STAT symStat;
2075 if (LSTAT(mPath.get(), &symStat) == -1) {
2076 return NSRESULT_FOR_ERRNO();
2078 *aResult = S_ISLNK(symStat.st_mode);
2079 return NS_OK;
2082 NS_IMETHODIMP
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) ||
2089 #ifdef S_ISSOCK
2090 S_ISSOCK(mCachedStat.st_mode) ||
2091 #endif
2092 S_ISFIFO(mCachedStat.st_mode);
2094 return NS_OK;
2097 NS_IMETHODIMP
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;
2105 *aResult = false;
2107 nsAutoCString inPath;
2108 nsresult rv = aInFile->GetNativePath(inPath);
2109 if (NS_FAILED(rv)) {
2110 return 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());
2116 return NS_OK;
2119 NS_IMETHODIMP
2120 nsLocalFile::Contains(nsIFile* aInFile, bool* aResult) {
2121 CHECK_mPath();
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;
2130 nsresult rv;
2132 if (NS_FAILED(rv = aInFile->GetNativePath(inPath))) {
2133 return rv;
2136 *aResult = false;
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] == '/') {
2143 *aResult = true;
2147 return NS_OK;
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();
2167 while (true) {
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
2177 // bug 1791029.
2178 handle.Finish(bytesWritten, false);
2179 return NS_OK;
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();
2190 NS_IMETHODIMP
2191 nsLocalFile::GetNativeTarget(nsACString& aResult) {
2192 CHECK_mPath();
2193 aResult.Truncate();
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)) {
2207 return rv;
2210 nsCOMPtr<nsIFile> self(this);
2211 int32_t maxLinks = 40;
2212 while (true) {
2213 if (maxLinks-- == 0) {
2214 rv = NS_ERROR_FILE_UNRESOLVABLE_SYMLINK;
2215 break;
2218 if (target[0] != '/') {
2219 nsCOMPtr<nsIFile> parent;
2220 if (NS_FAILED(rv = self->GetParent(getter_AddRefs(parent)))) {
2221 break;
2223 if (NS_FAILED(rv = parent->AppendRelativeNativePath(target))) {
2224 break;
2226 if (NS_FAILED(rv = parent->GetNativePath(aResult))) {
2227 break;
2229 self = parent;
2230 } else {
2231 aResult = target;
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) {
2239 break;
2242 // And of course we're done if it isn't a symlink.
2243 if (!S_ISLNK(symStat.st_mode)) {
2244 break;
2247 nsAutoCString newTarget;
2248 rv = ReadLinkSafe(flatRetval, symStat.st_size, newTarget);
2249 if (NS_FAILED(rv)) {
2250 break;
2253 target = newTarget;
2256 if (NS_FAILED(rv)) {
2257 aResult.Truncate();
2259 return rv;
2262 NS_IMETHODIMP
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;
2269 } else {
2270 dir.forget(aEntries);
2273 return rv;
2276 NS_IMETHODIMP
2277 nsLocalFile::Load(PRLibrary** aResult) {
2278 CHECK_mPath();
2279 if (NS_WARN_IF(!aResult)) {
2280 return NS_ERROR_INVALID_ARG;
2283 #ifdef NS_BUILD_REFCNT_LOGGING
2284 nsTraceRefcnt::SetActivityIsLegal(false);
2285 #endif
2287 *aResult = PR_LoadLibrary(mPath.get());
2289 #ifdef NS_BUILD_REFCNT_LOGGING
2290 nsTraceRefcnt::SetActivityIsLegal(true);
2291 #endif
2293 if (!*aResult) {
2294 return NS_ERROR_FAILURE;
2296 return NS_OK;
2299 NS_IMETHODIMP
2300 nsLocalFile::GetPersistentDescriptor(nsACString& aPersistentDescriptor) {
2301 return GetNativePath(aPersistentDescriptor);
2304 NS_IMETHODIMP
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);
2322 if (!decodedData) {
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);
2330 if (aliasSize >
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
2340 // plbase64.h
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)) {
2347 return rv;
2350 Boolean changed;
2351 FSRef resolvedFSRef;
2352 OSErr err = ::FSResolveAlias(nullptr, (AliasHandle)newHandle, &resolvedFSRef,
2353 &changed);
2355 rv = MacErrorMapper(err);
2356 DisposeHandle(newHandle);
2357 if (NS_FAILED(rv)) {
2358 return rv;
2361 return InitWithFSRef(&resolvedFSRef);
2362 #else
2363 return InitWithNativePath(aPersistentDescriptor);
2364 #endif
2367 NS_IMETHODIMP
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);
2375 if (!giovfs) {
2376 return NS_ERROR_FAILURE;
2378 return giovfs->RevealFile(this);
2379 #elif defined(MOZ_WIDGET_COCOA)
2380 CFURLRef url;
2381 if (NS_SUCCEEDED(GetCFURL(&url))) {
2382 nsresult rv = CocoaFileUtils::RevealFileInFinder(url);
2383 ::CFRelease(url);
2384 return rv;
2386 return NS_ERROR_FAILURE;
2387 #else
2388 return NS_ERROR_FAILURE;
2389 #endif
2392 NS_IMETHODIMP
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);
2400 if (!giovfs) {
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)
2409 CFURLRef url;
2410 if (NS_SUCCEEDED(GetCFURL(&url))) {
2411 nsresult rv = CocoaFileUtils::OpenURL(url);
2412 ::CFRelease(url);
2413 return rv;
2415 return NS_ERROR_FAILURE;
2416 #else
2417 return NS_ERROR_FAILURE;
2418 #endif
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)) {
2427 return rv;
2430 file.forget(aResult);
2431 return NS_OK;
2434 //-----------------------------------------------------------------------------
2435 // unicode support
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);
2493 NS_IMETHODIMP
2494 nsLocalFile::MoveToFollowingLinks(nsIFile* aNewParentDir,
2495 const nsAString& aNewName) {
2496 SET_UCS_2ARGS_2(MoveToFollowingLinksNative, aNewParentDir, aNewName);
2499 NS_IMETHODIMP
2500 nsLocalFile::RenameTo(nsIFile* aNewParentDir, const nsAString& aNewName) {
2501 SET_UCS_2ARGS_2(RenameToNative, aNewParentDir, aNewName);
2504 NS_IMETHODIMP
2505 nsLocalFile::RenameToNative(nsIFile* aNewParentDir,
2506 const nsACString& aNewName) {
2507 nsresult rv;
2509 // check to make sure that this has been initialized properly
2510 CHECK_mPath();
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)) {
2516 return 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;
2527 } else {
2528 rv = NSRESULT_FOR_ERRNO();
2532 return rv;
2535 nsresult nsLocalFile::GetTarget(nsAString& aResult) {
2536 GET_UCS(GetNativeTarget, aResult);
2539 nsresult NS_NewLocalFile(const nsAString& aPath, nsIFile** aResult) {
2540 nsAutoCString buf;
2541 nsresult rv = NS_CopyUnicodeToNative(aPath, buf);
2542 if (NS_FAILED(rv)) {
2543 return rv;
2545 return NS_NewNativeLocalFile(buf, aResult);
2548 // nsILocalFileMac
2550 #ifdef MOZ_WIDGET_COCOA
2552 NS_IMETHODIMP
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);
2559 if (size == -1) {
2560 if (errno == ENOATTR) {
2561 *aHasAttr = false;
2562 } else {
2563 return NSRESULT_FOR_ERRNO();
2565 } else {
2566 *aHasAttr = true;
2569 return NS_OK;
2572 NS_IMETHODIMP
2573 nsLocalFile::GetXAttr(const nsACString& aAttrName,
2574 nsTArray<uint8_t>& aAttrValue) {
2575 aAttrValue.Clear();
2577 nsAutoCString attrName{aAttrName};
2579 ssize_t size = getxattr(mPath.get(), attrName.get(), nullptr, 0, 0, 0);
2581 if (size == -1) {
2582 return NSRESULT_FOR_ERRNO();
2585 for (;;) {
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);
2598 break;
2599 } else {
2600 size = newSize;
2604 return NS_OK;
2607 NS_IMETHODIMP
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();
2617 return NS_OK;
2620 NS_IMETHODIMP
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();
2629 return NS_OK;
2632 static nsresult MacErrorMapper(OSErr inErr) {
2633 nsresult outErr;
2635 switch (inErr) {
2636 case noErr:
2637 outErr = NS_OK;
2638 break;
2640 case fnfErr:
2641 case afpObjectNotFound:
2642 case afpDirNotFound:
2643 outErr = NS_ERROR_FILE_NOT_FOUND;
2644 break;
2646 case dupFNErr:
2647 case afpObjectExists:
2648 outErr = NS_ERROR_FILE_ALREADY_EXISTS;
2649 break;
2651 case dskFulErr:
2652 case afpDiskFull:
2653 outErr = NS_ERROR_FILE_NO_DEVICE_SPACE;
2654 break;
2656 case fLckdErr:
2657 case afpVolLocked:
2658 outErr = NS_ERROR_FILE_IS_LOCKED;
2659 break;
2661 case afpAccessDenied:
2662 outErr = NS_ERROR_FILE_ACCESS_DENIED;
2663 break;
2665 case afpDirNotEmpty:
2666 outErr = NS_ERROR_FILE_DIR_NOT_EMPTY;
2667 break;
2669 // Can't find good map for some
2670 case bdNamErr:
2671 outErr = NS_ERROR_FAILURE;
2672 break;
2674 default:
2675 outErr = NS_ERROR_FAILURE;
2676 break;
2679 return outErr;
2682 static nsresult CFStringReftoUTF8(CFStringRef aInStrRef, nsACString& aOutStr) {
2683 // first see if the conversion would succeed and find the length of the
2684 // result
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,
2698 &usedBufLen);
2699 return NS_OK;
2702 return NS_ERROR_FAILURE;
2705 NS_IMETHODIMP
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;
2716 NS_IMETHODIMP
2717 nsLocalFile::InitWithFSRef(const FSRef* aFSRef) {
2718 if (NS_WARN_IF(!aFSRef)) {
2719 return NS_ERROR_INVALID_ARG;
2722 CFURLRef newURLRef = ::CFURLCreateFromFSRef(kCFAllocatorDefault, aFSRef);
2723 if (newURLRef) {
2724 nsresult rv = InitWithCFURL(newURLRef);
2725 ::CFRelease(newURLRef);
2726 return rv;
2729 return NS_ERROR_FAILURE;
2732 NS_IMETHODIMP
2733 nsLocalFile::GetCFURL(CFURLRef* aResult) {
2734 CHECK_mPath();
2736 bool isDir;
2737 IsDirectory(&isDir);
2738 *aResult = ::CFURLCreateFromFileSystemRepresentation(
2739 kCFAllocatorDefault, (UInt8*)mPath.get(), mPath.Length(), isDir);
2741 return (*aResult ? NS_OK : NS_ERROR_FAILURE);
2744 NS_IMETHODIMP
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)) {
2755 rv = NS_OK;
2757 ::CFRelease(url);
2760 return rv;
2763 NS_IMETHODIMP
2764 nsLocalFile::GetFSSpec(FSSpec* aResult) {
2765 if (NS_WARN_IF(!aResult)) {
2766 return NS_ERROR_INVALID_ARG;
2769 FSRef fsRef;
2770 nsresult rv = GetFSRef(&fsRef);
2771 if (NS_SUCCEEDED(rv)) {
2772 OSErr err = ::FSGetCatalogInfo(&fsRef, kFSCatInfoNone, nullptr, nullptr,
2773 aResult, nullptr);
2774 return MacErrorMapper(err);
2777 return rv;
2780 NS_IMETHODIMP
2781 nsLocalFile::GetFileSizeWithResFork(int64_t* aFileSizeWithResFork) {
2782 if (NS_WARN_IF(!aFileSizeWithResFork)) {
2783 return NS_ERROR_INVALID_ARG;
2786 FSRef fsRef;
2787 nsresult rv = GetFSRef(&fsRef);
2788 if (NS_FAILED(rv)) {
2789 return rv;
2792 FSCatalogInfo catalogInfo;
2793 OSErr err =
2794 ::FSGetCatalogInfo(&fsRef, kFSCatInfoDataSizes + kFSCatInfoRsrcSizes,
2795 &catalogInfo, nullptr, nullptr, nullptr);
2796 if (err != noErr) {
2797 return MacErrorMapper(err);
2800 *aFileSizeWithResFork =
2801 catalogInfo.dataLogicalSize + catalogInfo.rsrcLogicalSize;
2802 return NS_OK;
2805 NS_IMETHODIMP
2806 nsLocalFile::GetFileType(OSType* aFileType) {
2807 CFURLRef url;
2808 if (NS_SUCCEEDED(GetCFURL(&url))) {
2809 nsresult rv = CocoaFileUtils::GetFileTypeCode(url, aFileType);
2810 ::CFRelease(url);
2811 return rv;
2813 return NS_ERROR_FAILURE;
2816 NS_IMETHODIMP
2817 nsLocalFile::SetFileType(OSType aFileType) {
2818 CFURLRef url;
2819 if (NS_SUCCEEDED(GetCFURL(&url))) {
2820 nsresult rv = CocoaFileUtils::SetFileTypeCode(url, aFileType);
2821 ::CFRelease(url);
2822 return rv;
2824 return NS_ERROR_FAILURE;
2827 NS_IMETHODIMP
2828 nsLocalFile::GetFileCreator(OSType* aFileCreator) {
2829 CFURLRef url;
2830 if (NS_SUCCEEDED(GetCFURL(&url))) {
2831 nsresult rv = CocoaFileUtils::GetFileCreatorCode(url, aFileCreator);
2832 ::CFRelease(url);
2833 return rv;
2835 return NS_ERROR_FAILURE;
2838 NS_IMETHODIMP
2839 nsLocalFile::SetFileCreator(OSType aFileCreator) {
2840 CFURLRef url;
2841 if (NS_SUCCEEDED(GetCFURL(&url))) {
2842 nsresult rv = CocoaFileUtils::SetFileCreatorCode(url, aFileCreator);
2843 ::CFRelease(url);
2844 return rv;
2846 return NS_ERROR_FAILURE;
2849 NS_IMETHODIMP
2850 nsLocalFile::LaunchWithDoc(nsIFile* aDocToLoad, bool aLaunchInBackground) {
2851 bool isExecutable;
2852 nsresult rv = IsExecutable(&isExecutable);
2853 if (NS_FAILED(rv)) {
2854 return rv;
2856 if (!isExecutable) {
2857 return NS_ERROR_FILE_EXECUTION_FAILED;
2860 FSRef appFSRef, docFSRef;
2861 rv = GetFSRef(&appFSRef);
2862 if (NS_FAILED(rv)) {
2863 return rv;
2866 if (aDocToLoad) {
2867 nsCOMPtr<nsILocalFileMac> macDoc = do_QueryInterface(aDocToLoad);
2868 rv = macDoc->GetFSRef(&docFSRef);
2869 if (NS_FAILED(rv)) {
2870 return rv;
2874 LSLaunchFlags theLaunchFlags = kLSLaunchDefaults;
2875 LSLaunchFSRefSpec thelaunchSpec;
2877 if (aLaunchInBackground) {
2878 theLaunchFlags |= kLSLaunchDontSwitch;
2880 memset(&thelaunchSpec, 0, sizeof(LSLaunchFSRefSpec));
2882 thelaunchSpec.appRef = &appFSRef;
2883 if (aDocToLoad) {
2884 thelaunchSpec.numDocs = 1;
2885 thelaunchSpec.itemRefs = &docFSRef;
2887 thelaunchSpec.launchFlags = theLaunchFlags;
2889 OSErr err = ::LSOpenFromRefSpec(&thelaunchSpec, nullptr);
2890 if (err != noErr) {
2891 return MacErrorMapper(err);
2894 return NS_OK;
2897 NS_IMETHODIMP
2898 nsLocalFile::OpenDocWithApp(nsIFile* aAppToOpenWith, bool aLaunchInBackground) {
2899 FSRef docFSRef;
2900 nsresult rv = GetFSRef(&docFSRef);
2901 if (NS_FAILED(rv)) {
2902 return rv;
2905 if (!aAppToOpenWith) {
2906 OSErr err = ::LSOpenFSRef(&docFSRef, nullptr);
2907 return MacErrorMapper(err);
2910 nsCOMPtr<nsILocalFileMac> appFileMac = do_QueryInterface(aAppToOpenWith, &rv);
2911 if (!appFileMac) {
2912 return rv;
2915 bool isExecutable;
2916 rv = appFileMac->IsExecutable(&isExecutable);
2917 if (NS_FAILED(rv)) {
2918 return rv;
2920 if (!isExecutable) {
2921 return NS_ERROR_FILE_EXECUTION_FAILED;
2924 FSRef appFSRef;
2925 rv = appFileMac->GetFSRef(&appFSRef);
2926 if (NS_FAILED(rv)) {
2927 return 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);
2944 if (err != noErr) {
2945 return MacErrorMapper(err);
2948 return NS_OK;
2951 NS_IMETHODIMP
2952 nsLocalFile::IsPackage(bool* aResult) {
2953 if (NS_WARN_IF(!aResult)) {
2954 return NS_ERROR_INVALID_ARG;
2956 *aResult = false;
2958 CFURLRef url;
2959 nsresult rv = GetCFURL(&url);
2960 if (NS_FAILED(rv)) {
2961 return rv;
2964 LSItemInfoRecord info;
2965 OSStatus status =
2966 ::LSCopyItemInfoForURL(url, kLSRequestBasicFlagsOnly, &info);
2968 ::CFRelease(url);
2970 if (status != noErr) {
2971 return NS_ERROR_FAILURE;
2974 *aResult = !!(info.flags & kLSItemInfoIsPackage);
2976 return NS_OK;
2979 NS_IMETHODIMP
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;
2987 nsAutoString name;
2988 rv = GetLeafName(name);
2989 if (NS_FAILED(rv)) {
2990 return 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);
2997 } else {
2998 aOutBundleName = name;
3001 return NS_OK;
3004 NS_IMETHODIMP
3005 nsLocalFile::GetBundleIdentifier(nsACString& aOutBundleIdentifier) {
3006 nsresult rv = NS_ERROR_FAILURE;
3008 CFURLRef urlRef;
3009 if (NS_SUCCEEDED(GetCFURL(&urlRef))) {
3010 CFBundleRef bundle = ::CFBundleCreate(nullptr, urlRef);
3011 if (bundle) {
3012 CFStringRef bundleIdentifier = ::CFBundleGetIdentifier(bundle);
3013 if (bundleIdentifier) {
3014 rv = CFStringReftoUTF8(bundleIdentifier, aOutBundleIdentifier);
3016 ::CFRelease(bundle);
3018 ::CFRelease(urlRef);
3021 return rv;
3024 NS_IMETHODIMP
3025 nsLocalFile::GetBundleContentsLastModifiedTime(int64_t* aLastModTime) {
3026 CHECK_mPath();
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");
3039 PRFileInfo64 info;
3040 if (PR_GetFileInfo64(infoPlistPath.get(), &info) != PR_SUCCESS) {
3041 return GetLastModifiedTime(aLastModTime);
3043 int64_t modTime = int64_t(info.modifyTime);
3044 if (modTime == 0) {
3045 *aLastModTime = 0;
3046 } else {
3047 *aLastModTime = modTime / int64_t(PR_USEC_PER_MSEC);
3050 return NS_OK;
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)) {
3061 return 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)) {
3073 return rv;
3075 file.forget(aResult);
3076 return NS_OK;
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)) {
3085 return rv;
3087 file.forget(aResult);
3088 return NS_OK;
3091 #endif