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/. */
6
7 /**
8 * Implementation of nsIFile for "unixy" systems.
9 */
10
11 #include "mozilla/ArrayUtils.h"
12 #include "mozilla/Attributes.h"
13 #include "mozilla/DebugOnly.h"
14 #include "mozilla/Sprintf.h"
15 #include "mozilla/FilePreferences.h"
16 #include "prtime.h"
17
18 #include <sys/fcntl.h>
19 #include <sys/select.h>
20 #include <sys/stat.h>
21 #include <sys/time.h>
22 #include <sys/types.h>
23 #include <unistd.h>
24 #include <fcntl.h>
25 #include <errno.h>
26 #include <utime.h>
27 #include <dirent.h>
28 #include <ctype.h>
29 #include <locale.h>
30
31 #if defined(XP_MACOSX)
32 # include <sys/xattr.h>
33 #endif
34
35 #if defined(HAVE_SYS_QUOTA_H) && defined(HAVE_LINUX_QUOTA_H)
36 # define USE_LINUX_QUOTACTL
37 # include <sys/mount.h>
38 # include <sys/quota.h>
39 # include <sys/sysmacros.h>
40 # ifndef BLOCK_SIZE
41 # define BLOCK_SIZE 1024 /* kernel block size */
42 # endif
43 #endif
44
45 #include "nsDirectoryServiceDefs.h"
46 #include "nsCRT.h"
47 #include "nsCOMPtr.h"
48 #include "nsMemory.h"
49 #include "nsIFile.h"
50 #include "nsString.h"
51 #include "nsReadableUtils.h"
52 #include "nsLocalFile.h"
53 #include "prproces.h"
54 #include "nsIDirectoryEnumerator.h"
55 #include "nsSimpleEnumerator.h"
56 #include "private/pprio.h"
57 #include "prlink.h"
58
59 #ifdef MOZ_WIDGET_GTK
60 # include "nsIGIOService.h"
61 #endif
62
63 #ifdef MOZ_WIDGET_COCOA
64 # include <Carbon/Carbon.h>
65 # include "CocoaFileUtils.h"
66 # include "prmem.h"
67 # include "plbase64.h"
68
69 static nsresult MacErrorMapper(OSErr inErr);
70 #endif
71
72 #ifdef MOZ_WIDGET_ANDROID
73 # include "mozilla/java/GeckoAppShellWrappers.h"
74 # include "nsIMIMEService.h"
75 # include <linux/magic.h>
76 #endif
77
78 #include "nsNativeCharsetUtils.h"
79 #include "nsTraceRefcnt.h"
80 #include "nsHashKeys.h"
81
82 /**
83 * we need these for statfs()
84 */
85 #ifdef HAVE_SYS_STATVFS_H
86 # if defined(__osf__) && defined(__DECCXX)
87 extern "C" int statvfs(const char*, struct statvfs*);
88 # endif
89 # include <sys/statvfs.h>
90 #endif
91
92 #ifdef HAVE_SYS_STATFS_H
93 # include <sys/statfs.h>
94 #endif
95
96 #ifdef HAVE_SYS_VFS_H
97 # include <sys/vfs.h>
98 #endif
99
100 #ifdef HAVE_SYS_MOUNT_H
101 # include <sys/param.h>
102 # include <sys/mount.h>
103 #endif
104
105 #if defined(HAVE_STATVFS64) && (!defined(LINUX) && !defined(__osf__))
106 # define STATFS statvfs64
107 # define F_BSIZE f_frsize
108 #elif defined(HAVE_STATVFS) && (!defined(LINUX) && !defined(__osf__))
109 # define STATFS statvfs
110 # define F_BSIZE f_frsize
111 #elif defined(HAVE_STATFS64)
112 # define STATFS statfs64
113 # define F_BSIZE f_bsize
114 #elif defined(HAVE_STATFS)
115 # define STATFS statfs
116 # define F_BSIZE f_bsize
117 #endif
118
119 using namespace mozilla;
120
121 #define ENSURE_STAT_CACHE() \
122 do { \
123 if (!FillStatCache()) return NSRESULT_FOR_ERRNO(); \
124 } while (0)
125
126 #define CHECK_mPath() \
127 do { \
128 if (mPath.IsEmpty()) return NS_ERROR_NOT_INITIALIZED; \
129 if (!FilePreferences::IsAllowedPath(mPath)) \
130 return NS_ERROR_FILE_ACCESS_DENIED; \
131 } while (0)
132
TimespecToMillis(const struct timespec & aTimeSpec)133 static PRTime TimespecToMillis(const struct timespec& aTimeSpec) {
134 return PRTime(aTimeSpec.tv_sec) * PR_MSEC_PER_SEC +
135 PRTime(aTimeSpec.tv_nsec) / PR_NSEC_PER_MSEC;
136 }
137
138 /* directory enumerator */
139 class nsDirEnumeratorUnix final : public nsSimpleEnumerator,
140 public nsIDirectoryEnumerator {
141 public:
142 nsDirEnumeratorUnix();
143
144 // nsISupports interface
145 NS_DECL_ISUPPORTS_INHERITED
146
147 // nsISimpleEnumerator interface
148 NS_DECL_NSISIMPLEENUMERATOR
149
150 // nsIDirectoryEnumerator interface
151 NS_DECL_NSIDIRECTORYENUMERATOR
152
153 NS_IMETHOD Init(nsLocalFile* aParent, bool aIgnored);
154
NS_FORWARD_NSISIMPLEENUMERATORBASE(nsSimpleEnumerator::) const155 NS_FORWARD_NSISIMPLEENUMERATORBASE(nsSimpleEnumerator::)
156
157 const nsID& DefaultInterface() override { return NS_GET_IID(nsIFile); }
158
159 private:
160 ~nsDirEnumeratorUnix() override;
161
162 protected:
163 NS_IMETHOD GetNextEntry();
164
165 DIR* mDir;
166 struct dirent* mEntry;
167 nsCString mParentPath;
168 };
169
nsDirEnumeratorUnix()170 nsDirEnumeratorUnix::nsDirEnumeratorUnix() : mDir(nullptr), mEntry(nullptr) {}
171
~nsDirEnumeratorUnix()172 nsDirEnumeratorUnix::~nsDirEnumeratorUnix() { Close(); }
173
NS_IMPL_ISUPPORTS_INHERITED(nsDirEnumeratorUnix,nsSimpleEnumerator,nsIDirectoryEnumerator)174 NS_IMPL_ISUPPORTS_INHERITED(nsDirEnumeratorUnix, nsSimpleEnumerator,
175 nsIDirectoryEnumerator)
176
177 NS_IMETHODIMP
178 nsDirEnumeratorUnix::Init(nsLocalFile* aParent,
179 bool aResolveSymlinks /*ignored*/) {
180 nsAutoCString dirPath;
181 if (NS_FAILED(aParent->GetNativePath(dirPath)) || dirPath.IsEmpty()) {
182 return NS_ERROR_FILE_INVALID_PATH;
183 }
184
185 // When enumerating the directory, the paths must have a slash at the end.
186 nsAutoCString dirPathWithSlash(dirPath);
187 dirPathWithSlash.Append('/');
188 if (!FilePreferences::IsAllowedPath(dirPathWithSlash)) {
189 return NS_ERROR_FILE_ACCESS_DENIED;
190 }
191
192 if (NS_FAILED(aParent->GetNativePath(mParentPath))) {
193 return NS_ERROR_FAILURE;
194 }
195
196 mDir = opendir(dirPath.get());
197 if (!mDir) {
198 return NSRESULT_FOR_ERRNO();
199 }
200 return GetNextEntry();
201 }
202
203 NS_IMETHODIMP
HasMoreElements(bool * aResult)204 nsDirEnumeratorUnix::HasMoreElements(bool* aResult) {
205 *aResult = mDir && mEntry;
206 if (!*aResult) {
207 Close();
208 }
209 return NS_OK;
210 }
211
212 NS_IMETHODIMP
GetNext(nsISupports ** aResult)213 nsDirEnumeratorUnix::GetNext(nsISupports** aResult) {
214 nsCOMPtr<nsIFile> file;
215 nsresult rv = GetNextFile(getter_AddRefs(file));
216 if (NS_FAILED(rv)) {
217 return rv;
218 }
219 if (!file) {
220 return NS_ERROR_FAILURE;
221 }
222 file.forget(aResult);
223 return NS_OK;
224 }
225
226 NS_IMETHODIMP
GetNextEntry()227 nsDirEnumeratorUnix::GetNextEntry() {
228 do {
229 errno = 0;
230 mEntry = readdir(mDir);
231
232 // end of dir or error
233 if (!mEntry) {
234 return NSRESULT_FOR_ERRNO();
235 }
236
237 // keep going past "." and ".."
238 } while (mEntry->d_name[0] == '.' &&
239 (mEntry->d_name[1] == '\0' || // .\0
240 (mEntry->d_name[1] == '.' && mEntry->d_name[2] == '\0'))); // ..\0
241 return NS_OK;
242 }
243
244 NS_IMETHODIMP
GetNextFile(nsIFile ** aResult)245 nsDirEnumeratorUnix::GetNextFile(nsIFile** aResult) {
246 nsresult rv;
247 if (!mDir || !mEntry) {
248 *aResult = nullptr;
249 return NS_OK;
250 }
251
252 nsCOMPtr<nsIFile> file = new nsLocalFile();
253
254 if (NS_FAILED(rv = file->InitWithNativePath(mParentPath)) ||
255 NS_FAILED(rv = file->AppendNative(nsDependentCString(mEntry->d_name)))) {
256 return rv;
257 }
258
259 file.forget(aResult);
260 return GetNextEntry();
261 }
262
263 NS_IMETHODIMP
Close()264 nsDirEnumeratorUnix::Close() {
265 if (mDir) {
266 closedir(mDir);
267 mDir = nullptr;
268 }
269 return NS_OK;
270 }
271
nsLocalFile()272 nsLocalFile::nsLocalFile() : mCachedStat() {}
273
nsLocalFile(const nsACString & aFilePath)274 nsLocalFile::nsLocalFile(const nsACString& aFilePath) : mCachedStat() {
275 InitWithNativePath(aFilePath);
276 }
277
nsLocalFile(const nsLocalFile & aOther)278 nsLocalFile::nsLocalFile(const nsLocalFile& aOther) : mPath(aOther.mPath) {}
279
280 #ifdef MOZ_WIDGET_COCOA
NS_IMPL_ISUPPORTS(nsLocalFile,nsILocalFileMac,nsIFile)281 NS_IMPL_ISUPPORTS(nsLocalFile, nsILocalFileMac, nsIFile)
282 #else
283 NS_IMPL_ISUPPORTS(nsLocalFile, nsIFile)
284 #endif
285
286 nsresult nsLocalFile::nsLocalFileConstructor(nsISupports* aOuter,
287 const nsIID& aIID,
288 void** aInstancePtr) {
289 if (NS_WARN_IF(!aInstancePtr)) {
290 return NS_ERROR_INVALID_ARG;
291 }
292 if (NS_WARN_IF(aOuter)) {
293 return NS_ERROR_NO_AGGREGATION;
294 }
295
296 *aInstancePtr = nullptr;
297
298 nsCOMPtr<nsIFile> inst = new nsLocalFile();
299 return inst->QueryInterface(aIID, aInstancePtr);
300 }
301
FillStatCache()302 bool nsLocalFile::FillStatCache() {
303 if (!FilePreferences::IsAllowedPath(mPath)) {
304 errno = EACCES;
305 return false;
306 }
307
308 if (STAT(mPath.get(), &mCachedStat) == -1) {
309 // try lstat it may be a symlink
310 if (LSTAT(mPath.get(), &mCachedStat) == -1) {
311 return false;
312 }
313 }
314 return true;
315 }
316
317 NS_IMETHODIMP
Clone(nsIFile ** aFile)318 nsLocalFile::Clone(nsIFile** aFile) {
319 // Just copy-construct ourselves
320 RefPtr<nsLocalFile> copy = new nsLocalFile(*this);
321 copy.forget(aFile);
322 return NS_OK;
323 }
324
325 NS_IMETHODIMP
InitWithNativePath(const nsACString & aFilePath)326 nsLocalFile::InitWithNativePath(const nsACString& aFilePath) {
327 if (aFilePath.EqualsLiteral("~") ||
328 Substring(aFilePath, 0, 2).EqualsLiteral("~/")) {
329 nsCOMPtr<nsIFile> homeDir;
330 nsAutoCString homePath;
331 if (NS_FAILED(
332 NS_GetSpecialDirectory(NS_OS_HOME_DIR, getter_AddRefs(homeDir))) ||
333 NS_FAILED(homeDir->GetNativePath(homePath))) {
334 return NS_ERROR_FAILURE;
335 }
336
337 mPath = homePath;
338 if (aFilePath.Length() > 2) {
339 mPath.Append(Substring(aFilePath, 1, aFilePath.Length() - 1));
340 }
341 } else {
342 if (aFilePath.IsEmpty() || aFilePath.First() != '/') {
343 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
344 }
345 mPath = aFilePath;
346 }
347
348 if (!FilePreferences::IsAllowedPath(mPath)) {
349 mPath.Truncate();
350 return NS_ERROR_FILE_ACCESS_DENIED;
351 }
352
353 // trim off trailing slashes
354 ssize_t len = mPath.Length();
355 while ((len > 1) && (mPath[len - 1] == '/')) {
356 --len;
357 }
358 mPath.SetLength(len);
359
360 return NS_OK;
361 }
362
363 NS_IMETHODIMP
CreateAllAncestors(uint32_t aPermissions)364 nsLocalFile::CreateAllAncestors(uint32_t aPermissions) {
365 if (!FilePreferences::IsAllowedPath(mPath)) {
366 return NS_ERROR_FILE_ACCESS_DENIED;
367 }
368
369 // <jband> I promise to play nice
370 char* buffer = mPath.BeginWriting();
371 char* slashp = buffer;
372 int mkdir_result = 0;
373 int mkdir_errno;
374
375 #ifdef DEBUG_NSIFILE
376 fprintf(stderr, "nsIFile: before: %s\n", buffer);
377 #endif
378
379 while ((slashp = strchr(slashp + 1, '/'))) {
380 /*
381 * Sequences of '/' are equivalent to a single '/'.
382 */
383 if (slashp[1] == '/') {
384 continue;
385 }
386
387 /*
388 * If the path has a trailing slash, don't make the last component,
389 * because we'll get EEXIST in Create when we try to build the final
390 * component again, and it's easier to condition the logic here than
391 * there.
392 */
393 if (slashp[1] == '\0') {
394 break;
395 }
396
397 /* Temporarily NUL-terminate here */
398 *slashp = '\0';
399 #ifdef DEBUG_NSIFILE
400 fprintf(stderr, "nsIFile: mkdir(\"%s\")\n", buffer);
401 #endif
402 mkdir_result = mkdir(buffer, aPermissions);
403 if (mkdir_result == -1) {
404 mkdir_errno = errno;
405 /*
406 * Always set |errno| to EEXIST if the dir already exists
407 * (we have to do this here since the errno value is not consistent
408 * in all cases - various reasons like different platform,
409 * automounter-controlled dir, etc. can affect it (see bug 125489
410 * for details)).
411 */
412 if (mkdir_errno != EEXIST && access(buffer, F_OK) == 0) {
413 mkdir_errno = EEXIST;
414 }
415 #ifdef DEBUG_NSIFILE
416 fprintf(stderr, "nsIFile: errno: %d\n", mkdir_errno);
417 #endif
418 }
419
420 /* Put the / back */
421 *slashp = '/';
422 }
423
424 /*
425 * We could get EEXIST for an existing file -- not directory --
426 * but that's OK: we'll get ENOTDIR when we try to make the final
427 * component of the path back in Create and error out appropriately.
428 */
429 if (mkdir_result == -1 && mkdir_errno != EEXIST) {
430 return NS_ERROR_FAILURE;
431 }
432
433 return NS_OK;
434 }
435
436 NS_IMETHODIMP
OpenNSPRFileDesc(int32_t aFlags,int32_t aMode,PRFileDesc ** aResult)437 nsLocalFile::OpenNSPRFileDesc(int32_t aFlags, int32_t aMode,
438 PRFileDesc** aResult) {
439 if (!FilePreferences::IsAllowedPath(mPath)) {
440 return NS_ERROR_FILE_ACCESS_DENIED;
441 }
442 *aResult = PR_Open(mPath.get(), aFlags, aMode);
443 if (!*aResult) {
444 return NS_ErrorAccordingToNSPR();
445 }
446
447 if (aFlags & DELETE_ON_CLOSE) {
448 PR_Delete(mPath.get());
449 }
450
451 #if defined(HAVE_POSIX_FADVISE)
452 if (aFlags & OS_READAHEAD) {
453 posix_fadvise(PR_FileDesc2NativeHandle(*aResult), 0, 0,
454 POSIX_FADV_SEQUENTIAL);
455 }
456 #endif
457 return NS_OK;
458 }
459
460 NS_IMETHODIMP
OpenANSIFileDesc(const char * aMode,FILE ** aResult)461 nsLocalFile::OpenANSIFileDesc(const char* aMode, FILE** aResult) {
462 if (!FilePreferences::IsAllowedPath(mPath)) {
463 return NS_ERROR_FILE_ACCESS_DENIED;
464 }
465 *aResult = fopen(mPath.get(), aMode);
466 if (!*aResult) {
467 return NS_ERROR_FAILURE;
468 }
469
470 return NS_OK;
471 }
472
do_create(const char * aPath,int aFlags,mode_t aMode,PRFileDesc ** aResult)473 static int do_create(const char* aPath, int aFlags, mode_t aMode,
474 PRFileDesc** aResult) {
475 *aResult = PR_Open(aPath, aFlags, aMode);
476 return *aResult ? 0 : -1;
477 }
478
do_mkdir(const char * aPath,int aFlags,mode_t aMode,PRFileDesc ** aResult)479 static int do_mkdir(const char* aPath, int aFlags, mode_t aMode,
480 PRFileDesc** aResult) {
481 *aResult = nullptr;
482 return mkdir(aPath, aMode);
483 }
484
CreateAndKeepOpen(uint32_t aType,int aFlags,uint32_t aPermissions,bool aSkipAncestors,PRFileDesc ** aResult)485 nsresult nsLocalFile::CreateAndKeepOpen(uint32_t aType, int aFlags,
486 uint32_t aPermissions,
487 bool aSkipAncestors,
488 PRFileDesc** aResult) {
489 if (!FilePreferences::IsAllowedPath(mPath)) {
490 return NS_ERROR_FILE_ACCESS_DENIED;
491 }
492
493 if (aType != NORMAL_FILE_TYPE && aType != DIRECTORY_TYPE) {
494 return NS_ERROR_FILE_UNKNOWN_TYPE;
495 }
496
497 int (*createFunc)(const char*, int, mode_t, PRFileDesc**) =
498 (aType == NORMAL_FILE_TYPE) ? do_create : do_mkdir;
499
500 int result = createFunc(mPath.get(), aFlags, aPermissions, aResult);
501 if (result == -1 && errno == ENOENT && !aSkipAncestors) {
502 /*
503 * If we failed because of missing ancestor components, try to create
504 * them and then retry the original creation.
505 *
506 * Ancestor directories get the same permissions as the file we're
507 * creating, with the X bit set for each of (user,group,other) with
508 * an R bit in the original permissions. If you want to do anything
509 * fancy like setgid or sticky bits, do it by hand.
510 */
511 int dirperm = aPermissions;
512 if (aPermissions & S_IRUSR) {
513 dirperm |= S_IXUSR;
514 }
515 if (aPermissions & S_IRGRP) {
516 dirperm |= S_IXGRP;
517 }
518 if (aPermissions & S_IROTH) {
519 dirperm |= S_IXOTH;
520 }
521
522 #ifdef DEBUG_NSIFILE
523 fprintf(stderr, "nsIFile: perm = %o, dirperm = %o\n", aPermissions,
524 dirperm);
525 #endif
526
527 if (NS_FAILED(CreateAllAncestors(dirperm))) {
528 return NS_ERROR_FAILURE;
529 }
530
531 #ifdef DEBUG_NSIFILE
532 fprintf(stderr, "nsIFile: Create(\"%s\") again\n", mPath.get());
533 #endif
534 result = createFunc(mPath.get(), aFlags, aPermissions, aResult);
535 }
536 return NSRESULT_FOR_RETURN(result);
537 }
538
539 NS_IMETHODIMP
Create(uint32_t aType,uint32_t aPermissions,bool aSkipAncestors)540 nsLocalFile::Create(uint32_t aType, uint32_t aPermissions,
541 bool aSkipAncestors) {
542 if (!FilePreferences::IsAllowedPath(mPath)) {
543 return NS_ERROR_FILE_ACCESS_DENIED;
544 }
545
546 PRFileDesc* junk = nullptr;
547 nsresult rv = CreateAndKeepOpen(
548 aType, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE | PR_EXCL, aPermissions,
549 aSkipAncestors, &junk);
550 if (junk) {
551 PR_Close(junk);
552 }
553 return rv;
554 }
555
556 NS_IMETHODIMP
AppendNative(const nsACString & aFragment)557 nsLocalFile::AppendNative(const nsACString& aFragment) {
558 if (aFragment.IsEmpty()) {
559 return NS_OK;
560 }
561
562 // only one component of path can be appended
563 nsACString::const_iterator begin, end;
564 if (FindCharInReadable('/', aFragment.BeginReading(begin),
565 aFragment.EndReading(end))) {
566 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
567 }
568
569 return AppendRelativeNativePath(aFragment);
570 }
571
572 NS_IMETHODIMP
AppendRelativeNativePath(const nsACString & aFragment)573 nsLocalFile::AppendRelativeNativePath(const nsACString& aFragment) {
574 if (aFragment.IsEmpty()) {
575 return NS_OK;
576 }
577
578 // No leading '/'
579 if (aFragment.First() == '/') {
580 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
581 }
582
583 if (!mPath.EqualsLiteral("/")) {
584 mPath.Append('/');
585 }
586 mPath.Append(aFragment);
587
588 return NS_OK;
589 }
590
591 NS_IMETHODIMP
Normalize()592 nsLocalFile::Normalize() {
593 char resolved_path[PATH_MAX] = "";
594 char* resolved_path_ptr = nullptr;
595
596 if (!FilePreferences::IsAllowedPath(mPath)) {
597 return NS_ERROR_FILE_ACCESS_DENIED;
598 }
599
600 resolved_path_ptr = realpath(mPath.get(), resolved_path);
601
602 // if there is an error, the return is null.
603 if (!resolved_path_ptr) {
604 return NSRESULT_FOR_ERRNO();
605 }
606
607 mPath = resolved_path;
608 return NS_OK;
609 }
610
LocateNativeLeafName(nsACString::const_iterator & aBegin,nsACString::const_iterator & aEnd)611 void nsLocalFile::LocateNativeLeafName(nsACString::const_iterator& aBegin,
612 nsACString::const_iterator& aEnd) {
613 // XXX perhaps we should cache this??
614
615 mPath.BeginReading(aBegin);
616 mPath.EndReading(aEnd);
617
618 nsACString::const_iterator it = aEnd;
619 nsACString::const_iterator stop = aBegin;
620 --stop;
621 while (--it != stop) {
622 if (*it == '/') {
623 aBegin = ++it;
624 return;
625 }
626 }
627 // else, the entire path is the leaf name (which means this
628 // isn't an absolute path... unexpected??)
629 }
630
631 NS_IMETHODIMP
GetNativeLeafName(nsACString & aLeafName)632 nsLocalFile::GetNativeLeafName(nsACString& aLeafName) {
633 nsACString::const_iterator begin, end;
634 LocateNativeLeafName(begin, end);
635 aLeafName = Substring(begin, end);
636 return NS_OK;
637 }
638
639 NS_IMETHODIMP
SetNativeLeafName(const nsACString & aLeafName)640 nsLocalFile::SetNativeLeafName(const nsACString& aLeafName) {
641 nsACString::const_iterator begin, end;
642 LocateNativeLeafName(begin, end);
643 mPath.Replace(begin.get() - mPath.get(), Distance(begin, end), aLeafName);
644 return NS_OK;
645 }
646
NativePath()647 nsCString nsLocalFile::NativePath() { return mPath; }
648
GetNativePath(nsACString & aResult)649 nsresult nsIFile::GetNativePath(nsACString& aResult) {
650 aResult = NativePath();
651 return NS_OK;
652 }
653
HumanReadablePath()654 nsCString nsIFile::HumanReadablePath() {
655 nsCString path;
656 DebugOnly<nsresult> rv = GetNativePath(path);
657 MOZ_ASSERT(NS_SUCCEEDED(rv));
658 return path;
659 }
660
GetNativeTargetPathName(nsIFile * aNewParent,const nsACString & aNewName,nsACString & aResult)661 nsresult nsLocalFile::GetNativeTargetPathName(nsIFile* aNewParent,
662 const nsACString& aNewName,
663 nsACString& aResult) {
664 nsresult rv;
665 nsCOMPtr<nsIFile> oldParent;
666
667 if (!aNewParent) {
668 if (NS_FAILED(rv = GetParent(getter_AddRefs(oldParent)))) {
669 return rv;
670 }
671 aNewParent = oldParent.get();
672 } else {
673 // check to see if our target directory exists
674 bool targetExists;
675 if (NS_FAILED(rv = aNewParent->Exists(&targetExists))) {
676 return rv;
677 }
678
679 if (!targetExists) {
680 // XXX create the new directory with some permissions
681 rv = aNewParent->Create(DIRECTORY_TYPE, 0755);
682 if (NS_FAILED(rv)) {
683 return rv;
684 }
685 } else {
686 // make sure that the target is actually a directory
687 bool targetIsDirectory;
688 if (NS_FAILED(rv = aNewParent->IsDirectory(&targetIsDirectory))) {
689 return rv;
690 }
691 if (!targetIsDirectory) {
692 return NS_ERROR_FILE_DESTINATION_NOT_DIR;
693 }
694 }
695 }
696
697 nsACString::const_iterator nameBegin, nameEnd;
698 if (!aNewName.IsEmpty()) {
699 aNewName.BeginReading(nameBegin);
700 aNewName.EndReading(nameEnd);
701 } else {
702 LocateNativeLeafName(nameBegin, nameEnd);
703 }
704
705 nsAutoCString dirName;
706 if (NS_FAILED(rv = aNewParent->GetNativePath(dirName))) {
707 return rv;
708 }
709
710 aResult = dirName + "/"_ns + Substring(nameBegin, nameEnd);
711 return NS_OK;
712 }
713
CopyDirectoryTo(nsIFile * aNewParent)714 nsresult nsLocalFile::CopyDirectoryTo(nsIFile* aNewParent) {
715 nsresult rv;
716 /*
717 * dirCheck is used for various boolean test results such as from Equals,
718 * Exists, isDir, etc.
719 */
720 bool dirCheck, isSymlink;
721 uint32_t oldPerms;
722
723 if (NS_FAILED(rv = IsDirectory(&dirCheck))) {
724 return rv;
725 }
726 if (!dirCheck) {
727 return CopyToNative(aNewParent, ""_ns);
728 }
729
730 if (NS_FAILED(rv = Equals(aNewParent, &dirCheck))) {
731 return rv;
732 }
733 if (dirCheck) {
734 // can't copy dir to itself
735 return NS_ERROR_INVALID_ARG;
736 }
737
738 if (NS_FAILED(rv = aNewParent->Exists(&dirCheck))) {
739 return rv;
740 }
741 // get the dirs old permissions
742 if (NS_FAILED(rv = GetPermissions(&oldPerms))) {
743 return rv;
744 }
745 if (!dirCheck) {
746 if (NS_FAILED(rv = aNewParent->Create(DIRECTORY_TYPE, oldPerms))) {
747 return rv;
748 }
749 } else { // dir exists lets try to use leaf
750 nsAutoCString leafName;
751 if (NS_FAILED(rv = GetNativeLeafName(leafName))) {
752 return rv;
753 }
754 if (NS_FAILED(rv = aNewParent->AppendNative(leafName))) {
755 return rv;
756 }
757 if (NS_FAILED(rv = aNewParent->Exists(&dirCheck))) {
758 return rv;
759 }
760 if (dirCheck) {
761 return NS_ERROR_FILE_ALREADY_EXISTS; // dest exists
762 }
763 if (NS_FAILED(rv = aNewParent->Create(DIRECTORY_TYPE, oldPerms))) {
764 return rv;
765 }
766 }
767
768 nsCOMPtr<nsIDirectoryEnumerator> dirIterator;
769 if (NS_FAILED(rv = GetDirectoryEntries(getter_AddRefs(dirIterator)))) {
770 return rv;
771 }
772
773 nsCOMPtr<nsIFile> entry;
774 while (NS_SUCCEEDED(dirIterator->GetNextFile(getter_AddRefs(entry))) &&
775 entry) {
776 if (NS_FAILED(rv = entry->IsSymlink(&isSymlink))) {
777 return rv;
778 }
779 if (NS_FAILED(rv = entry->IsDirectory(&dirCheck))) {
780 return rv;
781 }
782 if (dirCheck && !isSymlink) {
783 nsCOMPtr<nsIFile> destClone;
784 rv = aNewParent->Clone(getter_AddRefs(destClone));
785 if (NS_SUCCEEDED(rv)) {
786 if (NS_FAILED(rv = entry->CopyToNative(destClone, ""_ns))) {
787 #ifdef DEBUG
788 nsresult rv2;
789 nsAutoCString pathName;
790 if (NS_FAILED(rv2 = entry->GetNativePath(pathName))) {
791 return rv2;
792 }
793 printf("Operation not supported: %s\n", pathName.get());
794 #endif
795 if (rv == NS_ERROR_OUT_OF_MEMORY) {
796 return rv;
797 }
798 continue;
799 }
800 }
801 } else {
802 if (NS_FAILED(rv = entry->CopyToNative(aNewParent, ""_ns))) {
803 #ifdef DEBUG
804 nsresult rv2;
805 nsAutoCString pathName;
806 if (NS_FAILED(rv2 = entry->GetNativePath(pathName))) {
807 return rv2;
808 }
809 printf("Operation not supported: %s\n", pathName.get());
810 #endif
811 if (rv == NS_ERROR_OUT_OF_MEMORY) {
812 return rv;
813 }
814 continue;
815 }
816 }
817 }
818 return NS_OK;
819 }
820
821 NS_IMETHODIMP
CopyToNative(nsIFile * aNewParent,const nsACString & aNewName)822 nsLocalFile::CopyToNative(nsIFile* aNewParent, const nsACString& aNewName) {
823 nsresult rv;
824 // check to make sure that this has been initialized properly
825 CHECK_mPath();
826
827 // we copy the parent here so 'aNewParent' remains immutable
828 nsCOMPtr<nsIFile> workParent;
829 if (aNewParent) {
830 if (NS_FAILED(rv = aNewParent->Clone(getter_AddRefs(workParent)))) {
831 return rv;
832 }
833 } else {
834 if (NS_FAILED(rv = GetParent(getter_AddRefs(workParent)))) {
835 return rv;
836 }
837 }
838
839 // check to see if we are a directory or if we are a file
840 bool isDirectory;
841 if (NS_FAILED(rv = IsDirectory(&isDirectory))) {
842 return rv;
843 }
844
845 nsAutoCString newPathName;
846 if (isDirectory) {
847 if (!aNewName.IsEmpty()) {
848 if (NS_FAILED(rv = workParent->AppendNative(aNewName))) {
849 return rv;
850 }
851 } else {
852 if (NS_FAILED(rv = GetNativeLeafName(newPathName))) {
853 return rv;
854 }
855 if (NS_FAILED(rv = workParent->AppendNative(newPathName))) {
856 return rv;
857 }
858 }
859 if (NS_FAILED(rv = CopyDirectoryTo(workParent))) {
860 return rv;
861 }
862 } else {
863 rv = GetNativeTargetPathName(workParent, aNewName, newPathName);
864 if (NS_FAILED(rv)) {
865 return rv;
866 }
867
868 #ifdef DEBUG_blizzard
869 printf("nsLocalFile::CopyTo() %s -> %s\n", mPath.get(), newPathName.get());
870 #endif
871
872 // actually create the file.
873 auto* newFile = new nsLocalFile();
874 nsCOMPtr<nsIFile> fileRef(newFile); // release on exit
875
876 rv = newFile->InitWithNativePath(newPathName);
877 if (NS_FAILED(rv)) {
878 return rv;
879 }
880
881 // get the old permissions
882 uint32_t myPerms = 0;
883 rv = GetPermissions(&myPerms);
884 if (NS_FAILED(rv)) {
885 return rv;
886 }
887
888 // Create the new file with the old file's permissions, even if write
889 // permission is missing. We can't create with write permission and
890 // then change back to myPerm on all filesystems (FAT on Linux, e.g.).
891 // But we can write to a read-only file on all Unix filesystems if we
892 // open it successfully for writing.
893
894 PRFileDesc* newFD;
895 rv = newFile->CreateAndKeepOpen(
896 NORMAL_FILE_TYPE, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, myPerms,
897 /* aSkipAncestors = */ false, &newFD);
898 if (NS_FAILED(rv)) {
899 return rv;
900 }
901
902 // open the old file, too
903 bool specialFile;
904 if (NS_FAILED(rv = IsSpecial(&specialFile))) {
905 PR_Close(newFD);
906 return rv;
907 }
908 if (specialFile) {
909 #ifdef DEBUG
910 printf("Operation not supported: %s\n", mPath.get());
911 #endif
912 // make sure to clean up properly
913 PR_Close(newFD);
914 return NS_OK;
915 }
916
917 #if defined(XP_MACOSX)
918 bool quarantined = true;
919 if (getxattr(mPath.get(), "com.apple.quarantine", nullptr, 0, 0, 0) == -1) {
920 if (errno == ENOATTR) {
921 quarantined = false;
922 }
923 }
924 #endif
925
926 PRFileDesc* oldFD;
927 rv = OpenNSPRFileDesc(PR_RDONLY, myPerms, &oldFD);
928 if (NS_FAILED(rv)) {
929 // make sure to clean up properly
930 PR_Close(newFD);
931 return rv;
932 }
933
934 #ifdef DEBUG_blizzard
935 int32_t totalRead = 0;
936 int32_t totalWritten = 0;
937 #endif
938 char buf[BUFSIZ];
939 int32_t bytesRead;
940
941 // record PR_Write() error for better error message later.
942 nsresult saved_write_error = NS_OK;
943 nsresult saved_read_error = NS_OK;
944 nsresult saved_read_close_error = NS_OK;
945 nsresult saved_write_close_error = NS_OK;
946
947 // DONE: Does PR_Read() return bytesRead < 0 for error?
948 // Yes., The errors from PR_Read are not so common and
949 // the value may not have correspondence in NS_ERROR_*, but
950 // we do catch it still, immediately after while() loop.
951 // We can differentiate errors pf PR_Read and PR_Write by
952 // looking at saved_write_error value. If PR_Write error occurs (and not
953 // PR_Read() error), save_write_error is not NS_OK.
954
955 while ((bytesRead = PR_Read(oldFD, buf, BUFSIZ)) > 0) {
956 #ifdef DEBUG_blizzard
957 totalRead += bytesRead;
958 #endif
959
960 // PR_Write promises never to do a short write
961 int32_t bytesWritten = PR_Write(newFD, buf, bytesRead);
962 if (bytesWritten < 0) {
963 saved_write_error = NSRESULT_FOR_ERRNO();
964 bytesRead = -1;
965 break;
966 }
967 NS_ASSERTION(bytesWritten == bytesRead, "short PR_Write?");
968
969 #ifdef DEBUG_blizzard
970 totalWritten += bytesWritten;
971 #endif
972 }
973
974 // TODO/FIXME: If CIFS (and NFS?) may force read/write to return EINTR,
975 // we are better off to prepare for retrying. But we need confirmation if
976 // EINTR is returned.
977
978 // Record error if PR_Read() failed.
979 // Must be done before any other I/O which may reset errno.
980 if (bytesRead < 0 && saved_write_error == NS_OK) {
981 saved_read_error = NSRESULT_FOR_ERRNO();
982 }
983
984 #ifdef DEBUG_blizzard
985 printf("read %d bytes, wrote %d bytes\n", totalRead, totalWritten);
986 #endif
987
988 // DONE: Errors of close can occur. Read man page of
989 // close(2);
990 // This is likely to happen if the file system is remote file
991 // system (NFS, CIFS, etc.) and network outage occurs.
992 // At least, we should tell the user that filesystem/disk is
993 // hosed (possibly due to network error, hard disk failure,
994 // etc.) so that users can take remedial action.
995
996 // close the files
997 if (PR_Close(newFD) < 0) {
998 saved_write_close_error = NSRESULT_FOR_ERRNO();
999 #if DEBUG
1000 // This error merits printing.
1001 fprintf(stderr, "ERROR: PR_Close(newFD) returned error. errno = %d\n",
1002 errno);
1003 #endif
1004 }
1005 #if defined(XP_MACOSX)
1006 else if (!quarantined) {
1007 // If the original file was not in quarantine, lift the quarantine that
1008 // file creation added because of LSFileQuarantineEnabled.
1009 removexattr(newPathName.get(), "com.apple.quarantine", 0);
1010 }
1011 #endif // defined(XP_MACOSX)
1012
1013 if (PR_Close(oldFD) < 0) {
1014 saved_read_close_error = NSRESULT_FOR_ERRNO();
1015 #if DEBUG
1016 fprintf(stderr, "ERROR: PR_Close(oldFD) returned error. errno = %d\n",
1017 errno);
1018 #endif
1019 }
1020
1021 // Let us report the failure to write and read.
1022 // check for write/read error after cleaning up
1023 if (bytesRead < 0) {
1024 if (saved_write_error != NS_OK) {
1025 return saved_write_error;
1026 }
1027 if (saved_read_error != NS_OK) {
1028 return saved_read_error;
1029 }
1030 #if DEBUG
1031 MOZ_ASSERT(0);
1032 #endif
1033 }
1034
1035 if (saved_write_close_error != NS_OK) {
1036 return saved_write_close_error;
1037 }
1038 if (saved_read_close_error != NS_OK) {
1039 return saved_read_close_error;
1040 }
1041 }
1042 return rv;
1043 }
1044
1045 NS_IMETHODIMP
CopyToFollowingLinksNative(nsIFile * aNewParent,const nsACString & aNewName)1046 nsLocalFile::CopyToFollowingLinksNative(nsIFile* aNewParent,
1047 const nsACString& aNewName) {
1048 return CopyToNative(aNewParent, aNewName);
1049 }
1050
1051 NS_IMETHODIMP
MoveToNative(nsIFile * aNewParent,const nsACString & aNewName)1052 nsLocalFile::MoveToNative(nsIFile* aNewParent, const nsACString& aNewName) {
1053 nsresult rv;
1054
1055 // check to make sure that this has been initialized properly
1056 CHECK_mPath();
1057
1058 // check to make sure that we have a new parent
1059 nsAutoCString newPathName;
1060 rv = GetNativeTargetPathName(aNewParent, aNewName, newPathName);
1061 if (NS_FAILED(rv)) {
1062 return rv;
1063 }
1064
1065 if (!FilePreferences::IsAllowedPath(newPathName)) {
1066 return NS_ERROR_FILE_ACCESS_DENIED;
1067 }
1068
1069 // try for atomic rename, falling back to copy/delete
1070 if (rename(mPath.get(), newPathName.get()) < 0) {
1071 if (errno == EXDEV) {
1072 rv = CopyToNative(aNewParent, aNewName);
1073 if (NS_SUCCEEDED(rv)) {
1074 rv = Remove(true);
1075 }
1076 } else {
1077 rv = NSRESULT_FOR_ERRNO();
1078 }
1079 }
1080
1081 if (NS_SUCCEEDED(rv)) {
1082 // Adjust this
1083 mPath = newPathName;
1084 }
1085 return rv;
1086 }
1087
1088 NS_IMETHODIMP
MoveToFollowingLinksNative(nsIFile * aNewParent,const nsACString & aNewName)1089 nsLocalFile::MoveToFollowingLinksNative(nsIFile* aNewParent,
1090 const nsACString& aNewName) {
1091 return MoveToNative(aNewParent, aNewName);
1092 }
1093
1094 NS_IMETHODIMP
Remove(bool aRecursive)1095 nsLocalFile::Remove(bool aRecursive) {
1096 CHECK_mPath();
1097 ENSURE_STAT_CACHE();
1098
1099 bool isSymLink;
1100
1101 nsresult rv = IsSymlink(&isSymLink);
1102 if (NS_FAILED(rv)) {
1103 return rv;
1104 }
1105
1106 if (isSymLink || !S_ISDIR(mCachedStat.st_mode)) {
1107 return NSRESULT_FOR_RETURN(unlink(mPath.get()));
1108 }
1109
1110 if (aRecursive) {
1111 auto* dir = new nsDirEnumeratorUnix();
1112
1113 RefPtr<nsSimpleEnumerator> dirRef(dir); // release on exit
1114
1115 rv = dir->Init(this, false);
1116 if (NS_FAILED(rv)) {
1117 return rv;
1118 }
1119
1120 bool more;
1121 while (NS_SUCCEEDED(dir->HasMoreElements(&more)) && more) {
1122 nsCOMPtr<nsISupports> item;
1123 rv = dir->GetNext(getter_AddRefs(item));
1124 if (NS_FAILED(rv)) {
1125 return NS_ERROR_FAILURE;
1126 }
1127
1128 nsCOMPtr<nsIFile> file = do_QueryInterface(item, &rv);
1129 if (NS_FAILED(rv)) {
1130 return NS_ERROR_FAILURE;
1131 }
1132 rv = file->Remove(aRecursive);
1133
1134 #ifdef ANDROID
1135 // See bug 580434 - Bionic gives us just deleted files
1136 if (rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
1137 continue;
1138 }
1139 #endif
1140 if (NS_FAILED(rv)) {
1141 return rv;
1142 }
1143 }
1144 }
1145
1146 return NSRESULT_FOR_RETURN(rmdir(mPath.get()));
1147 }
1148
GetLastModifiedTimeImpl(PRTime * aLastModTime,bool aFollowLinks)1149 nsresult nsLocalFile::GetLastModifiedTimeImpl(PRTime* aLastModTime,
1150 bool aFollowLinks) {
1151 CHECK_mPath();
1152 if (NS_WARN_IF(!aLastModTime)) {
1153 return NS_ERROR_INVALID_ARG;
1154 }
1155
1156 using StatFn = int (*)(const char*, struct STAT*);
1157 StatFn statFn = aFollowLinks ? &STAT : &LSTAT;
1158
1159 struct STAT fileStats {};
1160 if (statFn(mPath.get(), &fileStats) < 0) {
1161 return NSRESULT_FOR_ERRNO();
1162 }
1163
1164 #if (defined(__APPLE__) && defined(__MACH__))
1165 *aLastModTime = TimespecToMillis(fileStats.st_mtimespec);
1166 #else
1167 *aLastModTime = TimespecToMillis(fileStats.st_mtim);
1168 #endif
1169
1170 return NS_OK;
1171 }
1172
SetLastModifiedTimeImpl(PRTime aLastModTime,bool aFollowLinks)1173 nsresult nsLocalFile::SetLastModifiedTimeImpl(PRTime aLastModTime,
1174 bool aFollowLinks) {
1175 CHECK_mPath();
1176
1177 using UtimesFn = int (*)(const char*, const timeval*);
1178 UtimesFn utimesFn = &utimes;
1179
1180 #if HAVE_LUTIMES
1181 if (!aFollowLinks) {
1182 utimesFn = &lutimes;
1183 }
1184 #endif
1185
1186 int result;
1187 if (aLastModTime != 0) {
1188 ENSURE_STAT_CACHE();
1189 timeval access{};
1190 #if (defined(__APPLE__) && defined(__MACH__))
1191 access.tv_sec = mCachedStat.st_atimespec.tv_sec;
1192 access.tv_usec = mCachedStat.st_atimespec.tv_nsec / 1000;
1193 #else
1194 access.tv_sec = mCachedStat.st_atim.tv_sec;
1195 access.tv_usec = mCachedStat.st_atim.tv_nsec / 1000;
1196 #endif
1197 timeval modification{};
1198 modification.tv_sec = aLastModTime / PR_MSEC_PER_SEC;
1199 modification.tv_usec = (aLastModTime % PR_MSEC_PER_SEC) * PR_USEC_PER_MSEC;
1200
1201 timeval times[2];
1202 times[0] = access;
1203 times[1] = modification;
1204 result = utimesFn(mPath.get(), times);
1205 } else {
1206 result = utimesFn(mPath.get(), nullptr);
1207 }
1208 return NSRESULT_FOR_RETURN(result);
1209 }
1210
1211 NS_IMETHODIMP
GetLastModifiedTime(PRTime * aLastModTime)1212 nsLocalFile::GetLastModifiedTime(PRTime* aLastModTime) {
1213 return GetLastModifiedTimeImpl(aLastModTime, /* follow links? */ true);
1214 }
1215
1216 NS_IMETHODIMP
SetLastModifiedTime(PRTime aLastModTime)1217 nsLocalFile::SetLastModifiedTime(PRTime aLastModTime) {
1218 return SetLastModifiedTimeImpl(aLastModTime, /* follow links ? */ true);
1219 }
1220
1221 NS_IMETHODIMP
GetLastModifiedTimeOfLink(PRTime * aLastModTimeOfLink)1222 nsLocalFile::GetLastModifiedTimeOfLink(PRTime* aLastModTimeOfLink) {
1223 return GetLastModifiedTimeImpl(aLastModTimeOfLink, /* follow link? */ false);
1224 }
1225
1226 NS_IMETHODIMP
SetLastModifiedTimeOfLink(PRTime aLastModTimeOfLink)1227 nsLocalFile::SetLastModifiedTimeOfLink(PRTime aLastModTimeOfLink) {
1228 return SetLastModifiedTimeImpl(aLastModTimeOfLink, /* follow links? */ false);
1229 }
1230
1231 NS_IMETHODIMP
GetCreationTime(PRTime * aCreationTime)1232 nsLocalFile::GetCreationTime(PRTime* aCreationTime) {
1233 return GetCreationTimeImpl(aCreationTime, false);
1234 }
1235
1236 NS_IMETHODIMP
GetCreationTimeOfLink(PRTime * aCreationTimeOfLink)1237 nsLocalFile::GetCreationTimeOfLink(PRTime* aCreationTimeOfLink) {
1238 return GetCreationTimeImpl(aCreationTimeOfLink, /* aFollowLinks = */ true);
1239 }
1240
GetCreationTimeImpl(PRTime * aCreationTime,bool aFollowLinks)1241 nsresult nsLocalFile::GetCreationTimeImpl(PRTime* aCreationTime,
1242 bool aFollowLinks) {
1243 CHECK_mPath();
1244 if (NS_WARN_IF(!aCreationTime)) {
1245 return NS_ERROR_INVALID_ARG;
1246 }
1247
1248 #if defined(_DARWIN_FEATURE_64_BIT_INODE)
1249 using StatFn = int (*)(const char*, struct STAT*);
1250 StatFn statFn = aFollowLinks ? &STAT : &LSTAT;
1251
1252 struct STAT fileStats {};
1253 if (statFn(mPath.get(), &fileStats) < 0) {
1254 return NSRESULT_FOR_ERRNO();
1255 }
1256
1257 *aCreationTime = TimespecToMillis(fileStats.st_birthtimespec);
1258 return NS_OK;
1259 #else
1260 return NS_ERROR_NOT_IMPLEMENTED;
1261 #endif
1262 }
1263
1264 /*
1265 * Only send back permissions bits: maybe we want to send back the whole
1266 * mode_t to permit checks against other file types?
1267 */
1268
1269 #define NORMALIZE_PERMS(mode) ((mode) & (S_IRWXU | S_IRWXG | S_IRWXO))
1270
1271 NS_IMETHODIMP
GetPermissions(uint32_t * aPermissions)1272 nsLocalFile::GetPermissions(uint32_t* aPermissions) {
1273 if (NS_WARN_IF(!aPermissions)) {
1274 return NS_ERROR_INVALID_ARG;
1275 }
1276 ENSURE_STAT_CACHE();
1277 *aPermissions = NORMALIZE_PERMS(mCachedStat.st_mode);
1278 return NS_OK;
1279 }
1280
1281 NS_IMETHODIMP
GetPermissionsOfLink(uint32_t * aPermissionsOfLink)1282 nsLocalFile::GetPermissionsOfLink(uint32_t* aPermissionsOfLink) {
1283 CHECK_mPath();
1284 if (NS_WARN_IF(!aPermissionsOfLink)) {
1285 return NS_ERROR_INVALID_ARG;
1286 }
1287
1288 struct STAT sbuf;
1289 if (LSTAT(mPath.get(), &sbuf) == -1) {
1290 return NSRESULT_FOR_ERRNO();
1291 }
1292 *aPermissionsOfLink = NORMALIZE_PERMS(sbuf.st_mode);
1293 return NS_OK;
1294 }
1295
1296 NS_IMETHODIMP
SetPermissions(uint32_t aPermissions)1297 nsLocalFile::SetPermissions(uint32_t aPermissions) {
1298 CHECK_mPath();
1299
1300 /*
1301 * Race condition here: we should use fchmod instead, there's no way to
1302 * guarantee the name still refers to the same file.
1303 */
1304 if (chmod(mPath.get(), aPermissions) >= 0) {
1305 return NS_OK;
1306 }
1307 #if defined(ANDROID) && defined(STATFS)
1308 // For the time being, this is restricted for use by Android, but we
1309 // will figure out what to do for all platforms in bug 638503
1310 struct STATFS sfs;
1311 if (STATFS(mPath.get(), &sfs) < 0) {
1312 return NSRESULT_FOR_ERRNO();
1313 }
1314
1315 // if this is a FAT file system we can't set file permissions
1316 if (sfs.f_type == MSDOS_SUPER_MAGIC) {
1317 return NS_OK;
1318 }
1319 #endif
1320 return NSRESULT_FOR_ERRNO();
1321 }
1322
1323 NS_IMETHODIMP
SetPermissionsOfLink(uint32_t aPermissions)1324 nsLocalFile::SetPermissionsOfLink(uint32_t aPermissions) {
1325 // There isn't a consistent mechanism for doing this on UNIX platforms. We
1326 // might want to carefully implement this in the future though.
1327 return NS_ERROR_NOT_IMPLEMENTED;
1328 }
1329
1330 NS_IMETHODIMP
GetFileSize(int64_t * aFileSize)1331 nsLocalFile::GetFileSize(int64_t* aFileSize) {
1332 if (NS_WARN_IF(!aFileSize)) {
1333 return NS_ERROR_INVALID_ARG;
1334 }
1335 *aFileSize = 0;
1336 ENSURE_STAT_CACHE();
1337
1338 if (!S_ISDIR(mCachedStat.st_mode)) {
1339 *aFileSize = (int64_t)mCachedStat.st_size;
1340 }
1341 return NS_OK;
1342 }
1343
1344 NS_IMETHODIMP
SetFileSize(int64_t aFileSize)1345 nsLocalFile::SetFileSize(int64_t aFileSize) {
1346 CHECK_mPath();
1347
1348 #if defined(ANDROID)
1349 /* no truncate on bionic */
1350 int fd = open(mPath.get(), O_WRONLY);
1351 if (fd == -1) {
1352 return NSRESULT_FOR_ERRNO();
1353 }
1354
1355 int ret = ftruncate(fd, (off_t)aFileSize);
1356 close(fd);
1357
1358 if (ret == -1) {
1359 return NSRESULT_FOR_ERRNO();
1360 }
1361 #elif defined(HAVE_TRUNCATE64)
1362 if (truncate64(mPath.get(), (off64_t)aFileSize) == -1) {
1363 return NSRESULT_FOR_ERRNO();
1364 }
1365 #else
1366 off_t size = (off_t)aFileSize;
1367 if (truncate(mPath.get(), size) == -1) {
1368 return NSRESULT_FOR_ERRNO();
1369 }
1370 #endif
1371 return NS_OK;
1372 }
1373
1374 NS_IMETHODIMP
GetFileSizeOfLink(int64_t * aFileSize)1375 nsLocalFile::GetFileSizeOfLink(int64_t* aFileSize) {
1376 CHECK_mPath();
1377 if (NS_WARN_IF(!aFileSize)) {
1378 return NS_ERROR_INVALID_ARG;
1379 }
1380
1381 struct STAT sbuf;
1382 if (LSTAT(mPath.get(), &sbuf) == -1) {
1383 return NSRESULT_FOR_ERRNO();
1384 }
1385
1386 *aFileSize = (int64_t)sbuf.st_size;
1387 return NS_OK;
1388 }
1389
1390 #if defined(USE_LINUX_QUOTACTL)
1391 /*
1392 * Searches /proc/self/mountinfo for given device (Major:Minor),
1393 * returns exported name from /dev
1394 *
1395 * Fails when /proc/self/mountinfo or diven device don't exist.
1396 */
GetDeviceName(unsigned int aDeviceMajor,unsigned int aDeviceMinor,nsACString & aDeviceName)1397 static bool GetDeviceName(unsigned int aDeviceMajor, unsigned int aDeviceMinor,
1398 nsACString& aDeviceName) {
1399 bool ret = false;
1400
1401 const int kMountInfoLineLength = 200;
1402 const int kMountInfoDevPosition = 6;
1403
1404 char mountinfoLine[kMountInfoLineLength];
1405 char deviceNum[kMountInfoLineLength];
1406
1407 SprintfLiteral(deviceNum, "%u:%u", aDeviceMajor, aDeviceMinor);
1408
1409 FILE* f = fopen("/proc/self/mountinfo", "rt");
1410 if (!f) {
1411 return ret;
1412 }
1413
1414 // Expects /proc/self/mountinfo in format:
1415 // 'ID ID major:minor root mountpoint flags - type devicename flags'
1416 while (fgets(mountinfoLine, kMountInfoLineLength, f)) {
1417 char* p_dev = strstr(mountinfoLine, deviceNum);
1418
1419 for (int i = 0; i < kMountInfoDevPosition && p_dev; ++i) {
1420 p_dev = strchr(p_dev, ' ');
1421 if (p_dev) {
1422 p_dev++;
1423 }
1424 }
1425
1426 if (p_dev) {
1427 char* p_dev_end = strchr(p_dev, ' ');
1428 if (p_dev_end) {
1429 *p_dev_end = '\0';
1430 aDeviceName.Assign(p_dev);
1431 ret = true;
1432 break;
1433 }
1434 }
1435 }
1436
1437 fclose(f);
1438 return ret;
1439 }
1440 #endif
1441
1442 NS_IMETHODIMP
GetDiskSpaceAvailable(int64_t * aDiskSpaceAvailable)1443 nsLocalFile::GetDiskSpaceAvailable(int64_t* aDiskSpaceAvailable) {
1444 if (NS_WARN_IF(!aDiskSpaceAvailable)) {
1445 return NS_ERROR_INVALID_ARG;
1446 }
1447
1448 // These systems have the operations necessary to check disk space.
1449
1450 #ifdef STATFS
1451
1452 // check to make sure that mPath is properly initialized
1453 CHECK_mPath();
1454
1455 struct STATFS fs_buf;
1456
1457 /*
1458 * Members of the STATFS struct that you should know about:
1459 * F_BSIZE = block size on disk.
1460 * f_bavail = number of free blocks available to a non-superuser.
1461 * f_bfree = number of total free blocks in file system.
1462 */
1463
1464 if (STATFS(mPath.get(), &fs_buf) < 0) {
1465 // The call to STATFS failed.
1466 # ifdef DEBUG
1467 printf("ERROR: GetDiskSpaceAvailable: STATFS call FAILED. \n");
1468 # endif
1469 return NS_ERROR_FAILURE;
1470 }
1471
1472 *aDiskSpaceAvailable = (int64_t)fs_buf.F_BSIZE * fs_buf.f_bavail;
1473
1474 # ifdef DEBUG_DISK_SPACE
1475 printf("DiskSpaceAvailable: %lu bytes\n", *aDiskSpaceAvailable);
1476 # endif
1477
1478 # if defined(USE_LINUX_QUOTACTL)
1479
1480 if (!FillStatCache()) {
1481 // Return available size from statfs
1482 return NS_OK;
1483 }
1484
1485 nsCString deviceName;
1486 if (!GetDeviceName(major(mCachedStat.st_dev), minor(mCachedStat.st_dev),
1487 deviceName)) {
1488 return NS_OK;
1489 }
1490
1491 struct dqblk dq;
1492 if (!quotactl(QCMD(Q_GETQUOTA, USRQUOTA), deviceName.get(), getuid(),
1493 (caddr_t)&dq)
1494 # ifdef QIF_BLIMITS
1495 && dq.dqb_valid & QIF_BLIMITS
1496 # endif
1497 && dq.dqb_bhardlimit) {
1498 int64_t QuotaSpaceAvailable = 0;
1499 // dqb_bhardlimit is count of BLOCK_SIZE blocks, dqb_curspace is bytes
1500 if ((BLOCK_SIZE * dq.dqb_bhardlimit) > dq.dqb_curspace)
1501 QuotaSpaceAvailable =
1502 int64_t(BLOCK_SIZE * dq.dqb_bhardlimit - dq.dqb_curspace);
1503 if (QuotaSpaceAvailable < *aDiskSpaceAvailable) {
1504 *aDiskSpaceAvailable = QuotaSpaceAvailable;
1505 }
1506 }
1507 # endif
1508
1509 return NS_OK;
1510
1511 #else
1512 /*
1513 * This platform doesn't have statfs or statvfs. I'm sure that there's
1514 * a way to check for free disk space on platforms that don't have statfs
1515 * (I'm SURE they have df, for example).
1516 *
1517 * Until we figure out how to do that, lets be honest and say that this
1518 * command isn't implemented properly for these platforms yet.
1519 */
1520 # ifdef DEBUG
1521 printf(
1522 "ERROR: GetDiskSpaceAvailable: Not implemented for plaforms without "
1523 "statfs.\n");
1524 # endif
1525 return NS_ERROR_NOT_IMPLEMENTED;
1526
1527 #endif /* STATFS */
1528 }
1529
1530 NS_IMETHODIMP
GetParent(nsIFile ** aParent)1531 nsLocalFile::GetParent(nsIFile** aParent) {
1532 CHECK_mPath();
1533 if (NS_WARN_IF(!aParent)) {
1534 return NS_ERROR_INVALID_ARG;
1535 }
1536 *aParent = nullptr;
1537
1538 // if '/' we are at the top of the volume, return null
1539 if (mPath.EqualsLiteral("/")) {
1540 return NS_OK;
1541 }
1542
1543 // <brendan, after jband> I promise to play nice
1544 char* buffer = mPath.BeginWriting();
1545 // find the last significant slash in buffer
1546 char* slashp = strrchr(buffer, '/');
1547 NS_ASSERTION(slashp, "non-canonical path?");
1548 if (!slashp) {
1549 return NS_ERROR_FILE_INVALID_PATH;
1550 }
1551
1552 // for the case where we are at '/'
1553 if (slashp == buffer) {
1554 slashp++;
1555 }
1556
1557 // temporarily terminate buffer at the last significant slash
1558 char c = *slashp;
1559 *slashp = '\0';
1560
1561 nsCOMPtr<nsIFile> localFile;
1562 nsresult rv = NS_NewNativeLocalFile(nsDependentCString(buffer), true,
1563 getter_AddRefs(localFile));
1564
1565 // make buffer whole again
1566 *slashp = c;
1567
1568 if (NS_FAILED(rv)) {
1569 return rv;
1570 }
1571
1572 localFile.forget(aParent);
1573 return NS_OK;
1574 }
1575
1576 /*
1577 * The results of Exists, isWritable and isReadable are not cached.
1578 */
1579
1580 NS_IMETHODIMP
Exists(bool * aResult)1581 nsLocalFile::Exists(bool* aResult) {
1582 CHECK_mPath();
1583 if (NS_WARN_IF(!aResult)) {
1584 return NS_ERROR_INVALID_ARG;
1585 }
1586
1587 *aResult = (access(mPath.get(), F_OK) == 0);
1588 return NS_OK;
1589 }
1590
1591 NS_IMETHODIMP
IsWritable(bool * aResult)1592 nsLocalFile::IsWritable(bool* aResult) {
1593 CHECK_mPath();
1594 if (NS_WARN_IF(!aResult)) {
1595 return NS_ERROR_INVALID_ARG;
1596 }
1597
1598 *aResult = (access(mPath.get(), W_OK) == 0);
1599 if (*aResult || errno == EACCES) {
1600 return NS_OK;
1601 }
1602 return NSRESULT_FOR_ERRNO();
1603 }
1604
1605 NS_IMETHODIMP
IsReadable(bool * aResult)1606 nsLocalFile::IsReadable(bool* aResult) {
1607 CHECK_mPath();
1608 if (NS_WARN_IF(!aResult)) {
1609 return NS_ERROR_INVALID_ARG;
1610 }
1611
1612 *aResult = (access(mPath.get(), R_OK) == 0);
1613 if (*aResult || errno == EACCES) {
1614 return NS_OK;
1615 }
1616 return NSRESULT_FOR_ERRNO();
1617 }
1618
1619 NS_IMETHODIMP
IsExecutable(bool * aResult)1620 nsLocalFile::IsExecutable(bool* aResult) {
1621 CHECK_mPath();
1622 if (NS_WARN_IF(!aResult)) {
1623 return NS_ERROR_INVALID_ARG;
1624 }
1625
1626 // Check extension (bug 663899). On certain platforms, the file
1627 // extension may cause the OS to treat it as executable regardless of
1628 // the execute bit, such as .jar on Mac OS X. We borrow the code from
1629 // nsLocalFileWin, slightly modified.
1630
1631 // Don't be fooled by symlinks.
1632 bool symLink;
1633 nsresult rv = IsSymlink(&symLink);
1634 if (NS_FAILED(rv)) {
1635 return rv;
1636 }
1637
1638 nsAutoString path;
1639 if (symLink) {
1640 GetTarget(path);
1641 } else {
1642 GetPath(path);
1643 }
1644
1645 int32_t dotIdx = path.RFindChar(char16_t('.'));
1646 if (dotIdx != kNotFound) {
1647 // Convert extension to lower case.
1648 char16_t* p = path.BeginWriting();
1649 for (p += dotIdx + 1; *p; ++p) {
1650 *p += (*p >= L'A' && *p <= L'Z') ? 'a' - 'A' : 0;
1651 }
1652
1653 // Search for any of the set of executable extensions.
1654 static const char* const executableExts[] = {
1655 "air", // Adobe AIR installer
1656 #ifdef MOZ_WIDGET_COCOA
1657 "fileloc", // File location files can be used to point to other
1658 // files.
1659 "inetloc", // Shouldn't be able to do the same, but can, due to
1660 // macOS vulnerabilities.
1661 #endif
1662 "jar" // java application bundle
1663 };
1664 nsDependentSubstring ext = Substring(path, dotIdx + 1);
1665 for (auto executableExt : executableExts) {
1666 if (ext.EqualsASCII(executableExt)) {
1667 // Found a match. Set result and quit.
1668 *aResult = true;
1669 return NS_OK;
1670 }
1671 }
1672 }
1673
1674 // On OS X, then query Launch Services.
1675 #ifdef MOZ_WIDGET_COCOA
1676 // Certain Mac applications, such as Classic applications, which
1677 // run under Rosetta, might not have the +x mode bit but are still
1678 // considered to be executable by Launch Services (bug 646748).
1679 CFURLRef url;
1680 if (NS_FAILED(GetCFURL(&url))) {
1681 return NS_ERROR_FAILURE;
1682 }
1683
1684 LSRequestedInfo theInfoRequest = kLSRequestAllInfo;
1685 LSItemInfoRecord theInfo;
1686 OSStatus result = ::LSCopyItemInfoForURL(url, theInfoRequest, &theInfo);
1687 ::CFRelease(url);
1688 if (result == noErr) {
1689 if ((theInfo.flags & kLSItemInfoIsApplication) != 0) {
1690 *aResult = true;
1691 return NS_OK;
1692 }
1693 }
1694 #endif
1695
1696 // Then check the execute bit.
1697 *aResult = (access(mPath.get(), X_OK) == 0);
1698 #ifdef SOLARIS
1699 // On Solaris, access will always return 0 for root user, however
1700 // the file is only executable if S_IXUSR | S_IXGRP | S_IXOTH is set.
1701 // See bug 351950, https://bugzilla.mozilla.org/show_bug.cgi?id=351950
1702 if (*aResult) {
1703 struct STAT buf;
1704
1705 *aResult = (STAT(mPath.get(), &buf) == 0);
1706 if (*aResult || errno == EACCES) {
1707 *aResult = *aResult && (buf.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH));
1708 return NS_OK;
1709 }
1710
1711 return NSRESULT_FOR_ERRNO();
1712 }
1713 #endif
1714 if (*aResult || errno == EACCES) {
1715 return NS_OK;
1716 }
1717 return NSRESULT_FOR_ERRNO();
1718 }
1719
1720 NS_IMETHODIMP
IsDirectory(bool * aResult)1721 nsLocalFile::IsDirectory(bool* aResult) {
1722 if (NS_WARN_IF(!aResult)) {
1723 return NS_ERROR_INVALID_ARG;
1724 }
1725 *aResult = false;
1726 ENSURE_STAT_CACHE();
1727 *aResult = S_ISDIR(mCachedStat.st_mode);
1728 return NS_OK;
1729 }
1730
1731 NS_IMETHODIMP
IsFile(bool * aResult)1732 nsLocalFile::IsFile(bool* aResult) {
1733 if (NS_WARN_IF(!aResult)) {
1734 return NS_ERROR_INVALID_ARG;
1735 }
1736 *aResult = false;
1737 ENSURE_STAT_CACHE();
1738 *aResult = S_ISREG(mCachedStat.st_mode);
1739 return NS_OK;
1740 }
1741
1742 NS_IMETHODIMP
IsHidden(bool * aResult)1743 nsLocalFile::IsHidden(bool* aResult) {
1744 if (NS_WARN_IF(!aResult)) {
1745 return NS_ERROR_INVALID_ARG;
1746 }
1747 nsACString::const_iterator begin, end;
1748 LocateNativeLeafName(begin, end);
1749 *aResult = (*begin == '.');
1750 return NS_OK;
1751 }
1752
1753 NS_IMETHODIMP
IsSymlink(bool * aResult)1754 nsLocalFile::IsSymlink(bool* aResult) {
1755 if (NS_WARN_IF(!aResult)) {
1756 return NS_ERROR_INVALID_ARG;
1757 }
1758 CHECK_mPath();
1759
1760 struct STAT symStat;
1761 if (LSTAT(mPath.get(), &symStat) == -1) {
1762 return NSRESULT_FOR_ERRNO();
1763 }
1764 *aResult = S_ISLNK(symStat.st_mode);
1765 return NS_OK;
1766 }
1767
1768 NS_IMETHODIMP
IsSpecial(bool * aResult)1769 nsLocalFile::IsSpecial(bool* aResult) {
1770 if (NS_WARN_IF(!aResult)) {
1771 return NS_ERROR_INVALID_ARG;
1772 }
1773 ENSURE_STAT_CACHE();
1774 *aResult = S_ISCHR(mCachedStat.st_mode) || S_ISBLK(mCachedStat.st_mode) ||
1775 #ifdef S_ISSOCK
1776 S_ISSOCK(mCachedStat.st_mode) ||
1777 #endif
1778 S_ISFIFO(mCachedStat.st_mode);
1779
1780 return NS_OK;
1781 }
1782
1783 NS_IMETHODIMP
Equals(nsIFile * aInFile,bool * aResult)1784 nsLocalFile::Equals(nsIFile* aInFile, bool* aResult) {
1785 if (NS_WARN_IF(!aInFile)) {
1786 return NS_ERROR_INVALID_ARG;
1787 }
1788 if (NS_WARN_IF(!aResult)) {
1789 return NS_ERROR_INVALID_ARG;
1790 }
1791 *aResult = false;
1792
1793 nsAutoCString inPath;
1794 nsresult rv = aInFile->GetNativePath(inPath);
1795 if (NS_FAILED(rv)) {
1796 return rv;
1797 }
1798
1799 // We don't need to worry about "/foo/" vs. "/foo" here
1800 // because trailing slashes are stripped on init.
1801 *aResult = !strcmp(inPath.get(), mPath.get());
1802 return NS_OK;
1803 }
1804
1805 NS_IMETHODIMP
Contains(nsIFile * aInFile,bool * aResult)1806 nsLocalFile::Contains(nsIFile* aInFile, bool* aResult) {
1807 CHECK_mPath();
1808 if (NS_WARN_IF(!aInFile)) {
1809 return NS_ERROR_INVALID_ARG;
1810 }
1811 if (NS_WARN_IF(!aResult)) {
1812 return NS_ERROR_INVALID_ARG;
1813 }
1814
1815 nsAutoCString inPath;
1816 nsresult rv;
1817
1818 if (NS_FAILED(rv = aInFile->GetNativePath(inPath))) {
1819 return rv;
1820 }
1821
1822 *aResult = false;
1823
1824 ssize_t len = mPath.Length();
1825 if (strncmp(mPath.get(), inPath.get(), len) == 0) {
1826 // Now make sure that the |aInFile|'s path has a separator at len,
1827 // which implies that it has more components after len.
1828 if (inPath[len] == '/') {
1829 *aResult = true;
1830 }
1831 }
1832
1833 return NS_OK;
1834 }
1835
1836 NS_IMETHODIMP
GetNativeTarget(nsACString & aResult)1837 nsLocalFile::GetNativeTarget(nsACString& aResult) {
1838 CHECK_mPath();
1839 aResult.Truncate();
1840
1841 struct STAT symStat;
1842 if (LSTAT(mPath.get(), &symStat) == -1) {
1843 return NSRESULT_FOR_ERRNO();
1844 }
1845
1846 if (!S_ISLNK(symStat.st_mode)) {
1847 return NS_ERROR_FILE_INVALID_PATH;
1848 }
1849
1850 int32_t size = (int32_t)symStat.st_size;
1851 nsAutoCString target;
1852 if (!target.SetLength(size, mozilla::fallible)) {
1853 return NS_ERROR_OUT_OF_MEMORY;
1854 }
1855
1856 if (readlink(mPath.get(), target.BeginWriting(), (size_t)size) < 0) {
1857 return NSRESULT_FOR_ERRNO();
1858 }
1859
1860 nsresult rv = NS_OK;
1861 nsCOMPtr<nsIFile> self(this);
1862 int32_t maxLinks = 40;
1863 while (true) {
1864 if (maxLinks-- == 0) {
1865 rv = NS_ERROR_FILE_UNRESOLVABLE_SYMLINK;
1866 break;
1867 }
1868
1869 if (target[0] != '/') {
1870 nsCOMPtr<nsIFile> parent;
1871 if (NS_FAILED(rv = self->GetParent(getter_AddRefs(parent)))) {
1872 break;
1873 }
1874 if (NS_FAILED(rv = parent->AppendRelativeNativePath(target))) {
1875 break;
1876 }
1877 if (NS_FAILED(rv = parent->GetNativePath(aResult))) {
1878 break;
1879 }
1880 self = parent;
1881 } else {
1882 aResult = target;
1883 }
1884
1885 const nsPromiseFlatCString& flatRetval = PromiseFlatCString(aResult);
1886
1887 // Any failure in testing the current target we'll just interpret
1888 // as having reached our destiny.
1889 if (LSTAT(flatRetval.get(), &symStat) == -1) {
1890 break;
1891 }
1892
1893 // And of course we're done if it isn't a symlink.
1894 if (!S_ISLNK(symStat.st_mode)) {
1895 break;
1896 }
1897
1898 int32_t newSize = (int32_t)symStat.st_size;
1899 size = newSize;
1900 nsAutoCString newTarget;
1901 if (!newTarget.SetLength(size, mozilla::fallible)) {
1902 rv = NS_ERROR_OUT_OF_MEMORY;
1903 break;
1904 }
1905
1906 int32_t linkLen =
1907 readlink(flatRetval.get(), newTarget.BeginWriting(), size);
1908 if (linkLen == -1) {
1909 rv = NSRESULT_FOR_ERRNO();
1910 break;
1911 }
1912 target = newTarget;
1913 }
1914
1915 if (NS_FAILED(rv)) {
1916 aResult.Truncate();
1917 }
1918 return rv;
1919 }
1920
1921 NS_IMETHODIMP
GetDirectoryEntriesImpl(nsIDirectoryEnumerator ** aEntries)1922 nsLocalFile::GetDirectoryEntriesImpl(nsIDirectoryEnumerator** aEntries) {
1923 RefPtr<nsDirEnumeratorUnix> dir = new nsDirEnumeratorUnix();
1924
1925 nsresult rv = dir->Init(this, false);
1926 if (NS_FAILED(rv)) {
1927 *aEntries = nullptr;
1928 } else {
1929 dir.forget(aEntries);
1930 }
1931
1932 return rv;
1933 }
1934
1935 NS_IMETHODIMP
Load(PRLibrary ** aResult)1936 nsLocalFile::Load(PRLibrary** aResult) {
1937 CHECK_mPath();
1938 if (NS_WARN_IF(!aResult)) {
1939 return NS_ERROR_INVALID_ARG;
1940 }
1941
1942 #ifdef NS_BUILD_REFCNT_LOGGING
1943 nsTraceRefcnt::SetActivityIsLegal(false);
1944 #endif
1945
1946 *aResult = PR_LoadLibrary(mPath.get());
1947
1948 #ifdef NS_BUILD_REFCNT_LOGGING
1949 nsTraceRefcnt::SetActivityIsLegal(true);
1950 #endif
1951
1952 if (!*aResult) {
1953 return NS_ERROR_FAILURE;
1954 }
1955 return NS_OK;
1956 }
1957
1958 NS_IMETHODIMP
GetPersistentDescriptor(nsACString & aPersistentDescriptor)1959 nsLocalFile::GetPersistentDescriptor(nsACString& aPersistentDescriptor) {
1960 return GetNativePath(aPersistentDescriptor);
1961 }
1962
1963 NS_IMETHODIMP
SetPersistentDescriptor(const nsACString & aPersistentDescriptor)1964 nsLocalFile::SetPersistentDescriptor(const nsACString& aPersistentDescriptor) {
1965 #ifdef MOZ_WIDGET_COCOA
1966 if (aPersistentDescriptor.IsEmpty()) {
1967 return NS_ERROR_INVALID_ARG;
1968 }
1969
1970 // Support pathnames as user-supplied descriptors if they begin with '/'
1971 // or '~'. These characters do not collide with the base64 set used for
1972 // encoding alias records.
1973 char first = aPersistentDescriptor.First();
1974 if (first == '/' || first == '~') {
1975 return InitWithNativePath(aPersistentDescriptor);
1976 }
1977
1978 uint32_t dataSize = aPersistentDescriptor.Length();
1979 char* decodedData = PL_Base64Decode(
1980 PromiseFlatCString(aPersistentDescriptor).get(), dataSize, nullptr);
1981 if (!decodedData) {
1982 NS_ERROR("SetPersistentDescriptor was given bad data");
1983 return NS_ERROR_FAILURE;
1984 }
1985
1986 // Cast to an alias record and resolve.
1987 AliasRecord aliasHeader = *(AliasPtr)decodedData;
1988 int32_t aliasSize = ::GetAliasSizeFromPtr(&aliasHeader);
1989 if (aliasSize >
1990 ((int32_t)dataSize * 3) / 4) { // be paranoid about having too few data
1991 PR_Free(decodedData); // PL_Base64Decode() uses PR_Malloc().
1992 return NS_ERROR_FAILURE;
1993 }
1994
1995 nsresult rv = NS_OK;
1996
1997 // Move the now-decoded data into the Handle.
1998 // The size of the decoded data is 3/4 the size of the encoded data. See
1999 // plbase64.h
2000 Handle newHandle = nullptr;
2001 if (::PtrToHand(decodedData, &newHandle, aliasSize) != noErr) {
2002 rv = NS_ERROR_OUT_OF_MEMORY;
2003 }
2004 PR_Free(decodedData); // PL_Base64Decode() uses PR_Malloc().
2005 if (NS_FAILED(rv)) {
2006 return rv;
2007 }
2008
2009 Boolean changed;
2010 FSRef resolvedFSRef;
2011 OSErr err = ::FSResolveAlias(nullptr, (AliasHandle)newHandle, &resolvedFSRef,
2012 &changed);
2013
2014 rv = MacErrorMapper(err);
2015 DisposeHandle(newHandle);
2016 if (NS_FAILED(rv)) {
2017 return rv;
2018 }
2019
2020 return InitWithFSRef(&resolvedFSRef);
2021 #else
2022 return InitWithNativePath(aPersistentDescriptor);
2023 #endif
2024 }
2025
2026 NS_IMETHODIMP
Reveal()2027 nsLocalFile::Reveal() {
2028 if (!FilePreferences::IsAllowedPath(mPath)) {
2029 return NS_ERROR_FILE_ACCESS_DENIED;
2030 }
2031
2032 #ifdef MOZ_WIDGET_GTK
2033 nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
2034 if (!giovfs) {
2035 return NS_ERROR_FAILURE;
2036 }
2037
2038 bool isDirectory;
2039 if (NS_FAILED(IsDirectory(&isDirectory))) {
2040 return NS_ERROR_FAILURE;
2041 }
2042
2043 if (isDirectory) {
2044 return giovfs->ShowURIForInput(mPath);
2045 }
2046 if (NS_SUCCEEDED(giovfs->OrgFreedesktopFileManager1ShowItems(mPath))) {
2047 return NS_OK;
2048 }
2049 nsCOMPtr<nsIFile> parentDir;
2050 nsAutoCString dirPath;
2051 if (NS_FAILED(GetParent(getter_AddRefs(parentDir)))) {
2052 return NS_ERROR_FAILURE;
2053 }
2054 if (NS_FAILED(parentDir->GetNativePath(dirPath))) {
2055 return NS_ERROR_FAILURE;
2056 }
2057
2058 return giovfs->ShowURIForInput(dirPath);
2059 #elif defined(MOZ_WIDGET_COCOA)
2060 CFURLRef url;
2061 if (NS_SUCCEEDED(GetCFURL(&url))) {
2062 nsresult rv = CocoaFileUtils::RevealFileInFinder(url);
2063 ::CFRelease(url);
2064 return rv;
2065 }
2066 return NS_ERROR_FAILURE;
2067 #else
2068 return NS_ERROR_FAILURE;
2069 #endif
2070 }
2071
2072 NS_IMETHODIMP
Launch()2073 nsLocalFile::Launch() {
2074 if (!FilePreferences::IsAllowedPath(mPath)) {
2075 return NS_ERROR_FILE_ACCESS_DENIED;
2076 }
2077
2078 #ifdef MOZ_WIDGET_GTK
2079 nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
2080 if (!giovfs) {
2081 return NS_ERROR_FAILURE;
2082 }
2083
2084 return giovfs->ShowURIForInput(mPath);
2085 #elif defined(MOZ_WIDGET_ANDROID)
2086 // Not supported on GeckoView
2087 return NS_ERROR_NOT_IMPLEMENTED;
2088 #elif defined(MOZ_WIDGET_COCOA)
2089 CFURLRef url;
2090 if (NS_SUCCEEDED(GetCFURL(&url))) {
2091 nsresult rv = CocoaFileUtils::OpenURL(url);
2092 ::CFRelease(url);
2093 return rv;
2094 }
2095 return NS_ERROR_FAILURE;
2096 #else
2097 return NS_ERROR_FAILURE;
2098 #endif
2099 }
2100
NS_NewNativeLocalFile(const nsACString & aPath,bool aFollowSymlinks,nsIFile ** aResult)2101 nsresult NS_NewNativeLocalFile(const nsACString& aPath, bool aFollowSymlinks,
2102 nsIFile** aResult) {
2103 RefPtr<nsLocalFile> file = new nsLocalFile();
2104
2105 if (!aPath.IsEmpty()) {
2106 nsresult rv = file->InitWithNativePath(aPath);
2107 if (NS_FAILED(rv)) {
2108 return rv;
2109 }
2110 }
2111 file.forget(aResult);
2112 return NS_OK;
2113 }
2114
2115 //-----------------------------------------------------------------------------
2116 // unicode support
2117 //-----------------------------------------------------------------------------
2118
2119 #define SET_UCS(func, ucsArg) \
2120 { \
2121 nsAutoCString buf; \
2122 nsresult rv = NS_CopyUnicodeToNative(ucsArg, buf); \
2123 if (NS_FAILED(rv)) return rv; \
2124 return (func)(buf); \
2125 }
2126
2127 #define GET_UCS(func, ucsArg) \
2128 { \
2129 nsAutoCString buf; \
2130 nsresult rv = (func)(buf); \
2131 if (NS_FAILED(rv)) return rv; \
2132 return NS_CopyNativeToUnicode(buf, ucsArg); \
2133 }
2134
2135 #define SET_UCS_2ARGS_2(func, opaqueArg, ucsArg) \
2136 { \
2137 nsAutoCString buf; \
2138 nsresult rv = NS_CopyUnicodeToNative(ucsArg, buf); \
2139 if (NS_FAILED(rv)) return rv; \
2140 return (func)(opaqueArg, buf); \
2141 }
2142
2143 // Unicode interface Wrapper
InitWithPath(const nsAString & aFilePath)2144 nsresult nsLocalFile::InitWithPath(const nsAString& aFilePath) {
2145 SET_UCS(InitWithNativePath, aFilePath);
2146 }
Append(const nsAString & aNode)2147 nsresult nsLocalFile::Append(const nsAString& aNode) {
2148 SET_UCS(AppendNative, aNode);
2149 }
AppendRelativePath(const nsAString & aNode)2150 nsresult nsLocalFile::AppendRelativePath(const nsAString& aNode) {
2151 SET_UCS(AppendRelativeNativePath, aNode);
2152 }
GetLeafName(nsAString & aLeafName)2153 nsresult nsLocalFile::GetLeafName(nsAString& aLeafName) {
2154 GET_UCS(GetNativeLeafName, aLeafName);
2155 }
SetLeafName(const nsAString & aLeafName)2156 nsresult nsLocalFile::SetLeafName(const nsAString& aLeafName) {
2157 SET_UCS(SetNativeLeafName, aLeafName);
2158 }
GetPath(nsAString & aResult)2159 nsresult nsLocalFile::GetPath(nsAString& aResult) {
2160 return NS_CopyNativeToUnicode(mPath, aResult);
2161 }
CopyTo(nsIFile * aNewParentDir,const nsAString & aNewName)2162 nsresult nsLocalFile::CopyTo(nsIFile* aNewParentDir,
2163 const nsAString& aNewName) {
2164 SET_UCS_2ARGS_2(CopyToNative, aNewParentDir, aNewName);
2165 }
CopyToFollowingLinks(nsIFile * aNewParentDir,const nsAString & aNewName)2166 nsresult nsLocalFile::CopyToFollowingLinks(nsIFile* aNewParentDir,
2167 const nsAString& aNewName) {
2168 SET_UCS_2ARGS_2(CopyToFollowingLinksNative, aNewParentDir, aNewName);
2169 }
MoveTo(nsIFile * aNewParentDir,const nsAString & aNewName)2170 nsresult nsLocalFile::MoveTo(nsIFile* aNewParentDir,
2171 const nsAString& aNewName) {
2172 SET_UCS_2ARGS_2(MoveToNative, aNewParentDir, aNewName);
2173 }
2174 NS_IMETHODIMP
MoveToFollowingLinks(nsIFile * aNewParentDir,const nsAString & aNewName)2175 nsLocalFile::MoveToFollowingLinks(nsIFile* aNewParentDir,
2176 const nsAString& aNewName) {
2177 SET_UCS_2ARGS_2(MoveToFollowingLinksNative, aNewParentDir, aNewName);
2178 }
2179
2180 NS_IMETHODIMP
RenameTo(nsIFile * aNewParentDir,const nsAString & aNewName)2181 nsLocalFile::RenameTo(nsIFile* aNewParentDir, const nsAString& aNewName) {
2182 SET_UCS_2ARGS_2(RenameToNative, aNewParentDir, aNewName);
2183 }
2184
2185 NS_IMETHODIMP
RenameToNative(nsIFile * aNewParentDir,const nsACString & aNewName)2186 nsLocalFile::RenameToNative(nsIFile* aNewParentDir,
2187 const nsACString& aNewName) {
2188 nsresult rv;
2189
2190 // check to make sure that this has been initialized properly
2191 CHECK_mPath();
2192
2193 // check to make sure that we have a new parent
2194 nsAutoCString newPathName;
2195 rv = GetNativeTargetPathName(aNewParentDir, aNewName, newPathName);
2196 if (NS_FAILED(rv)) {
2197 return rv;
2198 }
2199
2200 if (!FilePreferences::IsAllowedPath(newPathName)) {
2201 return NS_ERROR_FILE_ACCESS_DENIED;
2202 }
2203
2204 // try for atomic rename
2205 if (rename(mPath.get(), newPathName.get()) < 0) {
2206 if (errno == EXDEV) {
2207 rv = NS_ERROR_FILE_ACCESS_DENIED;
2208 } else {
2209 rv = NSRESULT_FOR_ERRNO();
2210 }
2211 }
2212
2213 return rv;
2214 }
2215
GetTarget(nsAString & aResult)2216 nsresult nsLocalFile::GetTarget(nsAString& aResult) {
2217 GET_UCS(GetNativeTarget, aResult);
2218 }
2219
NS_NewLocalFile(const nsAString & aPath,bool aFollowLinks,nsIFile ** aResult)2220 nsresult NS_NewLocalFile(const nsAString& aPath, bool aFollowLinks,
2221 nsIFile** aResult) {
2222 nsAutoCString buf;
2223 nsresult rv = NS_CopyUnicodeToNative(aPath, buf);
2224 if (NS_FAILED(rv)) {
2225 return rv;
2226 }
2227 return NS_NewNativeLocalFile(buf, aFollowLinks, aResult);
2228 }
2229
2230 // nsILocalFileMac
2231
2232 #ifdef MOZ_WIDGET_COCOA
2233
MacErrorMapper(OSErr inErr)2234 static nsresult MacErrorMapper(OSErr inErr) {
2235 nsresult outErr;
2236
2237 switch (inErr) {
2238 case noErr:
2239 outErr = NS_OK;
2240 break;
2241
2242 case fnfErr:
2243 case afpObjectNotFound:
2244 case afpDirNotFound:
2245 outErr = NS_ERROR_FILE_NOT_FOUND;
2246 break;
2247
2248 case dupFNErr:
2249 case afpObjectExists:
2250 outErr = NS_ERROR_FILE_ALREADY_EXISTS;
2251 break;
2252
2253 case dskFulErr:
2254 case afpDiskFull:
2255 outErr = NS_ERROR_FILE_NO_DEVICE_SPACE;
2256 break;
2257
2258 case fLckdErr:
2259 case afpVolLocked:
2260 outErr = NS_ERROR_FILE_IS_LOCKED;
2261 break;
2262
2263 case afpAccessDenied:
2264 outErr = NS_ERROR_FILE_ACCESS_DENIED;
2265 break;
2266
2267 case afpDirNotEmpty:
2268 outErr = NS_ERROR_FILE_DIR_NOT_EMPTY;
2269 break;
2270
2271 // Can't find good map for some
2272 case bdNamErr:
2273 outErr = NS_ERROR_FAILURE;
2274 break;
2275
2276 default:
2277 outErr = NS_ERROR_FAILURE;
2278 break;
2279 }
2280
2281 return outErr;
2282 }
2283
CFStringReftoUTF8(CFStringRef aInStrRef,nsACString & aOutStr)2284 static nsresult CFStringReftoUTF8(CFStringRef aInStrRef, nsACString& aOutStr) {
2285 // first see if the conversion would succeed and find the length of the
2286 // result
2287 CFIndex usedBufLen, inStrLen = ::CFStringGetLength(aInStrRef);
2288 CFIndex charsConverted = ::CFStringGetBytes(
2289 aInStrRef, CFRangeMake(0, inStrLen), kCFStringEncodingUTF8, 0, false,
2290 nullptr, 0, &usedBufLen);
2291 if (charsConverted == inStrLen) {
2292 // all characters converted, do the actual conversion
2293 aOutStr.SetLength(usedBufLen);
2294 if (aOutStr.Length() != (unsigned int)usedBufLen) {
2295 return NS_ERROR_OUT_OF_MEMORY;
2296 }
2297 UInt8* buffer = (UInt8*)aOutStr.BeginWriting();
2298 ::CFStringGetBytes(aInStrRef, CFRangeMake(0, inStrLen),
2299 kCFStringEncodingUTF8, 0, false, buffer, usedBufLen,
2300 &usedBufLen);
2301 return NS_OK;
2302 }
2303
2304 return NS_ERROR_FAILURE;
2305 }
2306
2307 NS_IMETHODIMP
InitWithCFURL(CFURLRef aCFURL)2308 nsLocalFile::InitWithCFURL(CFURLRef aCFURL) {
2309 UInt8 path[PATH_MAX];
2310 if (::CFURLGetFileSystemRepresentation(aCFURL, true, path, PATH_MAX)) {
2311 nsDependentCString nativePath((char*)path);
2312 return InitWithNativePath(nativePath);
2313 }
2314
2315 return NS_ERROR_FAILURE;
2316 }
2317
2318 NS_IMETHODIMP
InitWithFSRef(const FSRef * aFSRef)2319 nsLocalFile::InitWithFSRef(const FSRef* aFSRef) {
2320 if (NS_WARN_IF(!aFSRef)) {
2321 return NS_ERROR_INVALID_ARG;
2322 }
2323
2324 CFURLRef newURLRef = ::CFURLCreateFromFSRef(kCFAllocatorDefault, aFSRef);
2325 if (newURLRef) {
2326 nsresult rv = InitWithCFURL(newURLRef);
2327 ::CFRelease(newURLRef);
2328 return rv;
2329 }
2330
2331 return NS_ERROR_FAILURE;
2332 }
2333
2334 NS_IMETHODIMP
GetCFURL(CFURLRef * aResult)2335 nsLocalFile::GetCFURL(CFURLRef* aResult) {
2336 CHECK_mPath();
2337
2338 bool isDir;
2339 IsDirectory(&isDir);
2340 *aResult = ::CFURLCreateFromFileSystemRepresentation(
2341 kCFAllocatorDefault, (UInt8*)mPath.get(), mPath.Length(), isDir);
2342
2343 return (*aResult ? NS_OK : NS_ERROR_FAILURE);
2344 }
2345
2346 NS_IMETHODIMP
GetFSRef(FSRef * aResult)2347 nsLocalFile::GetFSRef(FSRef* aResult) {
2348 if (NS_WARN_IF(!aResult)) {
2349 return NS_ERROR_INVALID_ARG;
2350 }
2351
2352 nsresult rv = NS_ERROR_FAILURE;
2353
2354 CFURLRef url = nullptr;
2355 if (NS_SUCCEEDED(GetCFURL(&url))) {
2356 if (::CFURLGetFSRef(url, aResult)) {
2357 rv = NS_OK;
2358 }
2359 ::CFRelease(url);
2360 }
2361
2362 return rv;
2363 }
2364
2365 NS_IMETHODIMP
GetFSSpec(FSSpec * aResult)2366 nsLocalFile::GetFSSpec(FSSpec* aResult) {
2367 if (NS_WARN_IF(!aResult)) {
2368 return NS_ERROR_INVALID_ARG;
2369 }
2370
2371 FSRef fsRef;
2372 nsresult rv = GetFSRef(&fsRef);
2373 if (NS_SUCCEEDED(rv)) {
2374 OSErr err = ::FSGetCatalogInfo(&fsRef, kFSCatInfoNone, nullptr, nullptr,
2375 aResult, nullptr);
2376 return MacErrorMapper(err);
2377 }
2378
2379 return rv;
2380 }
2381
2382 NS_IMETHODIMP
GetFileSizeWithResFork(int64_t * aFileSizeWithResFork)2383 nsLocalFile::GetFileSizeWithResFork(int64_t* aFileSizeWithResFork) {
2384 if (NS_WARN_IF(!aFileSizeWithResFork)) {
2385 return NS_ERROR_INVALID_ARG;
2386 }
2387
2388 FSRef fsRef;
2389 nsresult rv = GetFSRef(&fsRef);
2390 if (NS_FAILED(rv)) {
2391 return rv;
2392 }
2393
2394 FSCatalogInfo catalogInfo;
2395 OSErr err =
2396 ::FSGetCatalogInfo(&fsRef, kFSCatInfoDataSizes + kFSCatInfoRsrcSizes,
2397 &catalogInfo, nullptr, nullptr, nullptr);
2398 if (err != noErr) {
2399 return MacErrorMapper(err);
2400 }
2401
2402 *aFileSizeWithResFork =
2403 catalogInfo.dataLogicalSize + catalogInfo.rsrcLogicalSize;
2404 return NS_OK;
2405 }
2406
2407 NS_IMETHODIMP
GetFileType(OSType * aFileType)2408 nsLocalFile::GetFileType(OSType* aFileType) {
2409 CFURLRef url;
2410 if (NS_SUCCEEDED(GetCFURL(&url))) {
2411 nsresult rv = CocoaFileUtils::GetFileTypeCode(url, aFileType);
2412 ::CFRelease(url);
2413 return rv;
2414 }
2415 return NS_ERROR_FAILURE;
2416 }
2417
2418 NS_IMETHODIMP
SetFileType(OSType aFileType)2419 nsLocalFile::SetFileType(OSType aFileType) {
2420 CFURLRef url;
2421 if (NS_SUCCEEDED(GetCFURL(&url))) {
2422 nsresult rv = CocoaFileUtils::SetFileTypeCode(url, aFileType);
2423 ::CFRelease(url);
2424 return rv;
2425 }
2426 return NS_ERROR_FAILURE;
2427 }
2428
2429 NS_IMETHODIMP
GetFileCreator(OSType * aFileCreator)2430 nsLocalFile::GetFileCreator(OSType* aFileCreator) {
2431 CFURLRef url;
2432 if (NS_SUCCEEDED(GetCFURL(&url))) {
2433 nsresult rv = CocoaFileUtils::GetFileCreatorCode(url, aFileCreator);
2434 ::CFRelease(url);
2435 return rv;
2436 }
2437 return NS_ERROR_FAILURE;
2438 }
2439
2440 NS_IMETHODIMP
SetFileCreator(OSType aFileCreator)2441 nsLocalFile::SetFileCreator(OSType aFileCreator) {
2442 CFURLRef url;
2443 if (NS_SUCCEEDED(GetCFURL(&url))) {
2444 nsresult rv = CocoaFileUtils::SetFileCreatorCode(url, aFileCreator);
2445 ::CFRelease(url);
2446 return rv;
2447 }
2448 return NS_ERROR_FAILURE;
2449 }
2450
2451 NS_IMETHODIMP
LaunchWithDoc(nsIFile * aDocToLoad,bool aLaunchInBackground)2452 nsLocalFile::LaunchWithDoc(nsIFile* aDocToLoad, bool aLaunchInBackground) {
2453 bool isExecutable;
2454 nsresult rv = IsExecutable(&isExecutable);
2455 if (NS_FAILED(rv)) {
2456 return rv;
2457 }
2458 if (!isExecutable) {
2459 return NS_ERROR_FILE_EXECUTION_FAILED;
2460 }
2461
2462 FSRef appFSRef, docFSRef;
2463 rv = GetFSRef(&appFSRef);
2464 if (NS_FAILED(rv)) {
2465 return rv;
2466 }
2467
2468 if (aDocToLoad) {
2469 nsCOMPtr<nsILocalFileMac> macDoc = do_QueryInterface(aDocToLoad);
2470 rv = macDoc->GetFSRef(&docFSRef);
2471 if (NS_FAILED(rv)) {
2472 return rv;
2473 }
2474 }
2475
2476 LSLaunchFlags theLaunchFlags = kLSLaunchDefaults;
2477 LSLaunchFSRefSpec thelaunchSpec;
2478
2479 if (aLaunchInBackground) {
2480 theLaunchFlags |= kLSLaunchDontSwitch;
2481 }
2482 memset(&thelaunchSpec, 0, sizeof(LSLaunchFSRefSpec));
2483
2484 thelaunchSpec.appRef = &appFSRef;
2485 if (aDocToLoad) {
2486 thelaunchSpec.numDocs = 1;
2487 thelaunchSpec.itemRefs = &docFSRef;
2488 }
2489 thelaunchSpec.launchFlags = theLaunchFlags;
2490
2491 OSErr err = ::LSOpenFromRefSpec(&thelaunchSpec, nullptr);
2492 if (err != noErr) {
2493 return MacErrorMapper(err);
2494 }
2495
2496 return NS_OK;
2497 }
2498
2499 NS_IMETHODIMP
OpenDocWithApp(nsIFile * aAppToOpenWith,bool aLaunchInBackground)2500 nsLocalFile::OpenDocWithApp(nsIFile* aAppToOpenWith, bool aLaunchInBackground) {
2501 FSRef docFSRef;
2502 nsresult rv = GetFSRef(&docFSRef);
2503 if (NS_FAILED(rv)) {
2504 return rv;
2505 }
2506
2507 if (!aAppToOpenWith) {
2508 OSErr err = ::LSOpenFSRef(&docFSRef, nullptr);
2509 return MacErrorMapper(err);
2510 }
2511
2512 nsCOMPtr<nsILocalFileMac> appFileMac = do_QueryInterface(aAppToOpenWith, &rv);
2513 if (!appFileMac) {
2514 return rv;
2515 }
2516
2517 bool isExecutable;
2518 rv = appFileMac->IsExecutable(&isExecutable);
2519 if (NS_FAILED(rv)) {
2520 return rv;
2521 }
2522 if (!isExecutable) {
2523 return NS_ERROR_FILE_EXECUTION_FAILED;
2524 }
2525
2526 FSRef appFSRef;
2527 rv = appFileMac->GetFSRef(&appFSRef);
2528 if (NS_FAILED(rv)) {
2529 return rv;
2530 }
2531
2532 LSLaunchFlags theLaunchFlags = kLSLaunchDefaults;
2533 LSLaunchFSRefSpec thelaunchSpec;
2534
2535 if (aLaunchInBackground) {
2536 theLaunchFlags |= kLSLaunchDontSwitch;
2537 }
2538 memset(&thelaunchSpec, 0, sizeof(LSLaunchFSRefSpec));
2539
2540 thelaunchSpec.appRef = &appFSRef;
2541 thelaunchSpec.numDocs = 1;
2542 thelaunchSpec.itemRefs = &docFSRef;
2543 thelaunchSpec.launchFlags = theLaunchFlags;
2544
2545 OSErr err = ::LSOpenFromRefSpec(&thelaunchSpec, nullptr);
2546 if (err != noErr) {
2547 return MacErrorMapper(err);
2548 }
2549
2550 return NS_OK;
2551 }
2552
2553 NS_IMETHODIMP
IsPackage(bool * aResult)2554 nsLocalFile::IsPackage(bool* aResult) {
2555 if (NS_WARN_IF(!aResult)) {
2556 return NS_ERROR_INVALID_ARG;
2557 }
2558 *aResult = false;
2559
2560 CFURLRef url;
2561 nsresult rv = GetCFURL(&url);
2562 if (NS_FAILED(rv)) {
2563 return rv;
2564 }
2565
2566 LSItemInfoRecord info;
2567 OSStatus status =
2568 ::LSCopyItemInfoForURL(url, kLSRequestBasicFlagsOnly, &info);
2569
2570 ::CFRelease(url);
2571
2572 if (status != noErr) {
2573 return NS_ERROR_FAILURE;
2574 }
2575
2576 *aResult = !!(info.flags & kLSItemInfoIsPackage);
2577
2578 return NS_OK;
2579 }
2580
2581 NS_IMETHODIMP
GetBundleDisplayName(nsAString & aOutBundleName)2582 nsLocalFile::GetBundleDisplayName(nsAString& aOutBundleName) {
2583 bool isPackage = false;
2584 nsresult rv = IsPackage(&isPackage);
2585 if (NS_FAILED(rv) || !isPackage) {
2586 return NS_ERROR_FAILURE;
2587 }
2588
2589 nsAutoString name;
2590 rv = GetLeafName(name);
2591 if (NS_FAILED(rv)) {
2592 return rv;
2593 }
2594
2595 int32_t length = name.Length();
2596 if (Substring(name, length - 4, length).EqualsLiteral(".app")) {
2597 // 4 characters in ".app"
2598 aOutBundleName = Substring(name, 0, length - 4);
2599 } else {
2600 aOutBundleName = name;
2601 }
2602
2603 return NS_OK;
2604 }
2605
2606 NS_IMETHODIMP
GetBundleIdentifier(nsACString & aOutBundleIdentifier)2607 nsLocalFile::GetBundleIdentifier(nsACString& aOutBundleIdentifier) {
2608 nsresult rv = NS_ERROR_FAILURE;
2609
2610 CFURLRef urlRef;
2611 if (NS_SUCCEEDED(GetCFURL(&urlRef))) {
2612 CFBundleRef bundle = ::CFBundleCreate(nullptr, urlRef);
2613 if (bundle) {
2614 CFStringRef bundleIdentifier = ::CFBundleGetIdentifier(bundle);
2615 if (bundleIdentifier) {
2616 rv = CFStringReftoUTF8(bundleIdentifier, aOutBundleIdentifier);
2617 }
2618 ::CFRelease(bundle);
2619 }
2620 ::CFRelease(urlRef);
2621 }
2622
2623 return rv;
2624 }
2625
2626 NS_IMETHODIMP
GetBundleContentsLastModifiedTime(int64_t * aLastModTime)2627 nsLocalFile::GetBundleContentsLastModifiedTime(int64_t* aLastModTime) {
2628 CHECK_mPath();
2629 if (NS_WARN_IF(!aLastModTime)) {
2630 return NS_ERROR_INVALID_ARG;
2631 }
2632
2633 bool isPackage = false;
2634 nsresult rv = IsPackage(&isPackage);
2635 if (NS_FAILED(rv) || !isPackage) {
2636 return GetLastModifiedTime(aLastModTime);
2637 }
2638
2639 nsAutoCString infoPlistPath(mPath);
2640 infoPlistPath.AppendLiteral("/Contents/Info.plist");
2641 PRFileInfo64 info;
2642 if (PR_GetFileInfo64(infoPlistPath.get(), &info) != PR_SUCCESS) {
2643 return GetLastModifiedTime(aLastModTime);
2644 }
2645 int64_t modTime = int64_t(info.modifyTime);
2646 if (modTime == 0) {
2647 *aLastModTime = 0;
2648 } else {
2649 *aLastModTime = modTime / int64_t(PR_USEC_PER_MSEC);
2650 }
2651
2652 return NS_OK;
2653 }
2654
InitWithFile(nsIFile * aFile)2655 NS_IMETHODIMP nsLocalFile::InitWithFile(nsIFile* aFile) {
2656 if (NS_WARN_IF(!aFile)) {
2657 return NS_ERROR_INVALID_ARG;
2658 }
2659
2660 nsAutoCString nativePath;
2661 nsresult rv = aFile->GetNativePath(nativePath);
2662 if (NS_FAILED(rv)) {
2663 return rv;
2664 }
2665
2666 return InitWithNativePath(nativePath);
2667 }
2668
NS_NewLocalFileWithFSRef(const FSRef * aFSRef,bool aFollowLinks,nsILocalFileMac ** aResult)2669 nsresult NS_NewLocalFileWithFSRef(const FSRef* aFSRef, bool aFollowLinks,
2670 nsILocalFileMac** aResult) {
2671 RefPtr<nsLocalFile> file = new nsLocalFile();
2672
2673 nsresult rv = file->InitWithFSRef(aFSRef);
2674 if (NS_FAILED(rv)) {
2675 return rv;
2676 }
2677 file.forget(aResult);
2678 return NS_OK;
2679 }
2680
NS_NewLocalFileWithCFURL(const CFURLRef aURL,bool aFollowLinks,nsILocalFileMac ** aResult)2681 nsresult NS_NewLocalFileWithCFURL(const CFURLRef aURL, bool aFollowLinks,
2682 nsILocalFileMac** aResult) {
2683 RefPtr<nsLocalFile> file = new nsLocalFile();
2684
2685 nsresult rv = file->InitWithCFURL(aURL);
2686 if (NS_FAILED(rv)) {
2687 return rv;
2688 }
2689 file.forget(aResult);
2690 return NS_OK;
2691 }
2692
2693 #endif
2694