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