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