/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Implementation of nsIFile for "unixy" systems.
 */

#include "mozilla/ArrayUtils.h"
#include "mozilla/Attributes.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <utime.h>
#include <dirent.h>
#include <ctype.h>
#include <locale.h>
#if defined(VMS)
    #include <fabdef.h>
#endif

#if defined(HAVE_SYS_QUOTA_H) && defined(HAVE_LINUX_QUOTA_H)
#define USE_LINUX_QUOTACTL
#include <sys/quota.h>
#endif

#include "xpcom-private.h"
#include "nsDirectoryServiceDefs.h"
#include "nsCRT.h"
#include "nsCOMPtr.h"
#include "nsMemory.h"
#include "nsIFile.h"
#include "nsString.h"
#include "nsReadableUtils.h"
#include "nsLocalFile.h"
#include "nsIComponentManager.h"
#include "nsXPIDLString.h"
#include "prproces.h"
#include "nsIDirectoryEnumerator.h"
#include "nsISimpleEnumerator.h"
#include "private/pprio.h"
#include "prlink.h"

#ifdef MOZ_WIDGET_GTK
#include "nsIGIOService.h"
#include "nsIGnomeVFSService.h"
#endif

#ifdef MOZ_WIDGET_COCOA
#include <Carbon/Carbon.h>
#include "CocoaFileUtils.h"
#include "prmem.h"
#include "plbase64.h"

static nsresult MacErrorMapper(OSErr inErr);
#endif

#ifdef MOZ_WIDGET_ANDROID
#include "AndroidBridge.h"
#include "nsIMIMEService.h"
#include <linux/magic.h>
#endif

#ifdef MOZ_ENABLE_CONTENTACTION
#include <contentaction/contentaction.h>
#endif

#include "nsNativeCharsetUtils.h"
#include "nsTraceRefcnt.h"
#include "nsHashKeys.h"

using namespace mozilla;

#define ENSURE_STAT_CACHE()                     \
    PR_BEGIN_MACRO                              \
        if (!FillStatCache())                   \
             return NSRESULT_FOR_ERRNO();       \
    PR_END_MACRO

#define CHECK_mPath()                           \
    PR_BEGIN_MACRO                              \
        if (mPath.IsEmpty())                    \
            return NS_ERROR_NOT_INITIALIZED;    \
    PR_END_MACRO

/* directory enumerator */
class
nsDirEnumeratorUnix MOZ_FINAL : public nsISimpleEnumerator,
                                public nsIDirectoryEnumerator
{
    public:
    nsDirEnumeratorUnix();

    // nsISupports interface
    NS_DECL_ISUPPORTS

    // nsISimpleEnumerator interface
    NS_DECL_NSISIMPLEENUMERATOR

    // nsIDirectoryEnumerator interface
    NS_DECL_NSIDIRECTORYENUMERATOR

    NS_IMETHOD Init(nsLocalFile *parent, bool ignored);

    private:
    ~nsDirEnumeratorUnix();

    protected:
    NS_IMETHOD GetNextEntry();

    DIR           *mDir;
    struct dirent *mEntry;
    nsCString      mParentPath;
};

nsDirEnumeratorUnix::nsDirEnumeratorUnix() :
                         mDir(nullptr), 
                         mEntry(nullptr)
{
}

nsDirEnumeratorUnix::~nsDirEnumeratorUnix()
{
    Close();
}

NS_IMPL_ISUPPORTS2(nsDirEnumeratorUnix, nsISimpleEnumerator, nsIDirectoryEnumerator)

NS_IMETHODIMP
nsDirEnumeratorUnix::Init(nsLocalFile *parent, bool resolveSymlinks /*ignored*/)
{
    nsAutoCString dirPath;
    if (NS_FAILED(parent->GetNativePath(dirPath)) ||
        dirPath.IsEmpty()) {
        return NS_ERROR_FILE_INVALID_PATH;
    }

    if (NS_FAILED(parent->GetNativePath(mParentPath)))
        return NS_ERROR_FAILURE;

    mDir = opendir(dirPath.get());
    if (!mDir)
        return NSRESULT_FOR_ERRNO();
    return GetNextEntry();
}

NS_IMETHODIMP
nsDirEnumeratorUnix::HasMoreElements(bool *result)
{
    *result = mDir && mEntry;
    if (!*result)
        Close();
    return NS_OK;
}

NS_IMETHODIMP
nsDirEnumeratorUnix::GetNext(nsISupports **_retval)
{
    nsCOMPtr<nsIFile> file;
    nsresult rv = GetNextFile(getter_AddRefs(file));
    if (NS_FAILED(rv))
        return rv;
    NS_IF_ADDREF(*_retval = file);
    return NS_OK;
}

NS_IMETHODIMP
nsDirEnumeratorUnix::GetNextEntry()
{
    do {
        errno = 0;
        mEntry = readdir(mDir);

        // end of dir or error
        if (!mEntry)
            return NSRESULT_FOR_ERRNO();

        // keep going past "." and ".."
    } while (mEntry->d_name[0] == '.'     &&
            (mEntry->d_name[1] == '\0'    ||   // .\0
            (mEntry->d_name[1] == '.'     &&
            mEntry->d_name[2] == '\0')));      // ..\0
    return NS_OK;
}

NS_IMETHODIMP
nsDirEnumeratorUnix::GetNextFile(nsIFile **_retval)
{
    nsresult rv;
    if (!mDir || !mEntry) {
        *_retval = nullptr;
        return NS_OK;
    }

    nsCOMPtr<nsIFile> file = new nsLocalFile();

    if (NS_FAILED(rv = file->InitWithNativePath(mParentPath)) ||
        NS_FAILED(rv = file->AppendNative(nsDependentCString(mEntry->d_name))))
        return rv;

    file.forget(_retval);
    return GetNextEntry();
}

NS_IMETHODIMP 
nsDirEnumeratorUnix::Close()
{
    if (mDir) {
        closedir(mDir);
        mDir = nullptr;
    }
    return NS_OK;
}

nsLocalFile::nsLocalFile()
{
}

nsLocalFile::nsLocalFile(const nsLocalFile& other)
  : mPath(other.mPath)
{
}

#ifdef MOZ_WIDGET_COCOA
NS_IMPL_ISUPPORTS4(nsLocalFile,
                   nsILocalFileMac,
                   nsILocalFile,
                   nsIFile,
                   nsIHashable)
#else
NS_IMPL_ISUPPORTS3(nsLocalFile,
                   nsILocalFile,
                   nsIFile,
                   nsIHashable)
#endif

nsresult
nsLocalFile::nsLocalFileConstructor(nsISupports *outer, 
                                    const nsIID &aIID,
                                    void **aInstancePtr)
{
    if (NS_WARN_IF(!aInstancePtr))
        return NS_ERROR_INVALID_ARG;
    if (NS_WARN_IF(outer))
        return NS_ERROR_NO_AGGREGATION;

    *aInstancePtr = nullptr;

    nsCOMPtr<nsIFile> inst = new nsLocalFile();
    return inst->QueryInterface(aIID, aInstancePtr);
}

bool 
nsLocalFile::FillStatCache() {
    if (STAT(mPath.get(), &mCachedStat) == -1) {
        // try lstat it may be a symlink
        if (LSTAT(mPath.get(), &mCachedStat) == -1) {
            return false;
        }
    }
    return true;
}

NS_IMETHODIMP
nsLocalFile::Clone(nsIFile **file)
{
    // Just copy-construct ourselves
    nsRefPtr<nsLocalFile> copy = new nsLocalFile(*this);
    copy.forget(file);
    return NS_OK;
}

NS_IMETHODIMP
nsLocalFile::InitWithNativePath(const nsACString &filePath)
{
    if (filePath.Equals("~") || Substring(filePath, 0, 2).EqualsLiteral("~/")) {
        nsCOMPtr<nsIFile> homeDir;
        nsAutoCString homePath;
        if (NS_FAILED(NS_GetSpecialDirectory(NS_OS_HOME_DIR,
                                             getter_AddRefs(homeDir))) ||
            NS_FAILED(homeDir->GetNativePath(homePath))) {
            return NS_ERROR_FAILURE;
        }
 
        mPath = homePath;
        if (filePath.Length() > 2)
          mPath.Append(Substring(filePath, 1, filePath.Length() - 1));
    } else {
        if (filePath.IsEmpty() || filePath.First() != '/')
            return NS_ERROR_FILE_UNRECOGNIZED_PATH;
        mPath = filePath;
    }

    // trim off trailing slashes
    ssize_t len = mPath.Length();
    while ((len > 1) && (mPath[len - 1] == '/'))
        --len;
    mPath.SetLength(len);

    return NS_OK;
}

NS_IMETHODIMP
nsLocalFile::CreateAllAncestors(uint32_t permissions)
{
    // <jband> I promise to play nice
    char *buffer = mPath.BeginWriting(),
         *slashp = buffer;

#ifdef DEBUG_NSIFILE
    fprintf(stderr, "nsIFile: before: %s\n", buffer);
#endif

    while ((slashp = strchr(slashp + 1, '/'))) {
        /*
         * Sequences of '/' are equivalent to a single '/'.
         */
        if (slashp[1] == '/')
            continue;

        /*
         * If the path has a trailing slash, don't make the last component,
         * because we'll get EEXIST in Create when we try to build the final
         * component again, and it's easier to condition the logic here than
         * there.
         */
        if (slashp[1] == '\0')
            break;

        /* Temporarily NUL-terminate here */
        *slashp = '\0';
#ifdef DEBUG_NSIFILE
        fprintf(stderr, "nsIFile: mkdir(\"%s\")\n", buffer);
#endif
        int mkdir_result = mkdir(buffer, permissions);
        int mkdir_errno  = errno;
        if (mkdir_result == -1) {
            /*
             * Always set |errno| to EEXIST if the dir already exists
             * (we have to do this here since the errno value is not consistent
             * in all cases - various reasons like different platform,
             * automounter-controlled dir, etc. can affect it (see bug 125489
             * for details)).
             */
            if (access(buffer, F_OK) == 0) {
                mkdir_errno = EEXIST;
            }
        }

        /* Put the / back before we (maybe) return */
        *slashp = '/';

        /*
         * We could get EEXIST for an existing file -- not directory --
         * with the name of one of our ancestors, but that's OK: we'll get
         * ENOTDIR when we try to make the next component in the path,
         * either here on back in Create, and error out appropriately.
         */
        if (mkdir_result == -1 && mkdir_errno != EEXIST)
            return nsresultForErrno(mkdir_errno);
    }

#ifdef DEBUG_NSIFILE
    fprintf(stderr, "nsIFile: after: %s\n", buffer);
#endif

    return NS_OK;
}

NS_IMETHODIMP
nsLocalFile::OpenNSPRFileDesc(int32_t flags, int32_t mode, PRFileDesc **_retval)
{
    *_retval = PR_Open(mPath.get(), flags, mode);
    if (! *_retval)
        return NS_ErrorAccordingToNSPR();

    if (flags & DELETE_ON_CLOSE) {
        PR_Delete(mPath.get());
    }

#if defined(HAVE_POSIX_FADVISE)
    if (flags & OS_READAHEAD) {
        posix_fadvise(PR_FileDesc2NativeHandle(*_retval), 0, 0,
                      POSIX_FADV_SEQUENTIAL);
    }
#endif
    return NS_OK;
}

NS_IMETHODIMP
nsLocalFile::OpenANSIFileDesc(const char *mode, FILE **_retval)
{
    *_retval = fopen(mPath.get(), mode);
    if (! *_retval)
        return NS_ERROR_FAILURE;

    return NS_OK;
}

static int
do_create(const char *path, int flags, mode_t mode, PRFileDesc **_retval)
{
    *_retval = PR_Open(path, flags, mode);
    return *_retval ? 0 : -1;
}

static int
do_mkdir(const char *path, int flags, mode_t mode, PRFileDesc **_retval)
{
    *_retval = nullptr;
    return mkdir(path, mode);
}

nsresult
nsLocalFile::CreateAndKeepOpen(uint32_t type, int flags,
                               uint32_t permissions, PRFileDesc **_retval)
{
    if (type != NORMAL_FILE_TYPE && type != DIRECTORY_TYPE)
        return NS_ERROR_FILE_UNKNOWN_TYPE;

    int result;
    int (*createFunc)(const char *, int, mode_t, PRFileDesc **) =
        (type == NORMAL_FILE_TYPE) ? do_create : do_mkdir;

    result = createFunc(mPath.get(), flags, permissions, _retval);
    if (result == -1 && errno == ENOENT) {
        /*
         * If we failed because of missing ancestor components, try to create
         * them and then retry the original creation.
         *
         * Ancestor directories get the same permissions as the file we're
         * creating, with the X bit set for each of (user,group,other) with
         * an R bit in the original permissions.    If you want to do anything
         * fancy like setgid or sticky bits, do it by hand.
         */
        int dirperm = permissions;
        if (permissions & S_IRUSR)
            dirperm |= S_IXUSR;
        if (permissions & S_IRGRP)
            dirperm |= S_IXGRP;
        if (permissions & S_IROTH)
            dirperm |= S_IXOTH;

#ifdef DEBUG_NSIFILE
        fprintf(stderr, "nsIFile: perm = %o, dirperm = %o\n", permissions,
                dirperm);
#endif

        if (NS_FAILED(CreateAllAncestors(dirperm)))
            return NS_ERROR_FAILURE;

#ifdef DEBUG_NSIFILE
        fprintf(stderr, "nsIFile: Create(\"%s\") again\n", mPath.get());
#endif
        result = createFunc(mPath.get(), flags, permissions, _retval);
    }
    return NSRESULT_FOR_RETURN(result);
}

NS_IMETHODIMP
nsLocalFile::Create(uint32_t type, uint32_t permissions)
{
    PRFileDesc *junk = nullptr;
    nsresult rv = CreateAndKeepOpen(type,
                                    PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE |
                                    PR_EXCL,
                                    permissions,
                                    &junk);
    if (junk)
        PR_Close(junk);
    return rv;
}

NS_IMETHODIMP
nsLocalFile::AppendNative(const nsACString &fragment)
{
    if (fragment.IsEmpty())
        return NS_OK;

    // only one component of path can be appended
    nsACString::const_iterator begin, end;
    if (FindCharInReadable('/', fragment.BeginReading(begin),
                                fragment.EndReading(end)))
        return NS_ERROR_FILE_UNRECOGNIZED_PATH;

    return AppendRelativeNativePath(fragment);
}

NS_IMETHODIMP
nsLocalFile::AppendRelativeNativePath(const nsACString &fragment)
{
    if (fragment.IsEmpty())
        return NS_OK;

    // No leading '/' 
    if (fragment.First() == '/')
        return NS_ERROR_FILE_UNRECOGNIZED_PATH;

    if (mPath.EqualsLiteral("/"))
        mPath.Append(fragment);
    else
        mPath.Append(NS_LITERAL_CSTRING("/") + fragment);

    return NS_OK;
}

NS_IMETHODIMP
nsLocalFile::Normalize()
{
    char    resolved_path[PATH_MAX] = "";
    char *resolved_path_ptr = nullptr;

    resolved_path_ptr = realpath(mPath.get(), resolved_path);

    // if there is an error, the return is null.
    if (!resolved_path_ptr)
        return NSRESULT_FOR_ERRNO();

    mPath = resolved_path;
    return NS_OK;
}

void
nsLocalFile::LocateNativeLeafName(nsACString::const_iterator &begin, 
                                  nsACString::const_iterator &end)
{
    // XXX perhaps we should cache this??

    mPath.BeginReading(begin);
    mPath.EndReading(end);
    
    nsACString::const_iterator it = end;
    nsACString::const_iterator stop = begin;
    --stop;
    while (--it != stop) {
        if (*it == '/') {
            begin = ++it;
            return;
        }
    }
    // else, the entire path is the leaf name (which means this
    // isn't an absolute path... unexpected??)
}

NS_IMETHODIMP
nsLocalFile::GetNativeLeafName(nsACString &aLeafName)
{
    nsACString::const_iterator begin, end;
    LocateNativeLeafName(begin, end);
    aLeafName = Substring(begin, end);
    return NS_OK;
}

NS_IMETHODIMP
nsLocalFile::SetNativeLeafName(const nsACString &aLeafName)
{
    nsACString::const_iterator begin, end;
    LocateNativeLeafName(begin, end);
    mPath.Replace(begin.get() - mPath.get(), Distance(begin, end), aLeafName);
    return NS_OK;
}

NS_IMETHODIMP
nsLocalFile::GetNativePath(nsACString &_retval)
{
    _retval = mPath;
    return NS_OK;
}

nsresult
nsLocalFile::GetNativeTargetPathName(nsIFile *newParent, 
                                     const nsACString &newName,
                                     nsACString &_retval)
{
    nsresult rv;
    nsCOMPtr<nsIFile> oldParent;

    if (!newParent) {
        if (NS_FAILED(rv = GetParent(getter_AddRefs(oldParent))))
            return rv;
        newParent = oldParent.get();
    } else {
        // check to see if our target directory exists
        bool targetExists;
        if (NS_FAILED(rv = newParent->Exists(&targetExists)))
            return rv;

        if (!targetExists) {
            // XXX create the new directory with some permissions
            rv = newParent->Create(DIRECTORY_TYPE, 0755);
            if (NS_FAILED(rv))
                return rv;
        } else {
            // make sure that the target is actually a directory
            bool targetIsDirectory;
            if (NS_FAILED(rv = newParent->IsDirectory(&targetIsDirectory)))
                return rv;
            if (!targetIsDirectory)
                return NS_ERROR_FILE_DESTINATION_NOT_DIR;
        }
    }

    nsACString::const_iterator nameBegin, nameEnd;
    if (!newName.IsEmpty()) {
        newName.BeginReading(nameBegin);
        newName.EndReading(nameEnd);
    }
    else
        LocateNativeLeafName(nameBegin, nameEnd);

    nsAutoCString dirName;
    if (NS_FAILED(rv = newParent->GetNativePath(dirName)))
        return rv;

    _retval = dirName
            + NS_LITERAL_CSTRING("/")
            + Substring(nameBegin, nameEnd);
    return NS_OK;
}

nsresult
nsLocalFile::CopyDirectoryTo(nsIFile *newParent)
{
    nsresult rv;
    /*
     * dirCheck is used for various boolean test results such as from Equals,
     * Exists, isDir, etc.
     */
    bool dirCheck, isSymlink;
    uint32_t oldPerms;

    if (NS_FAILED(rv = IsDirectory(&dirCheck)))
        return rv;
    if (!dirCheck)
        return CopyToNative(newParent, EmptyCString());
    
    if (NS_FAILED(rv = Equals(newParent, &dirCheck)))
        return rv;
    if (dirCheck) { 
        // can't copy dir to itself
        return NS_ERROR_INVALID_ARG;
    }
    
    if (NS_FAILED(rv = newParent->Exists(&dirCheck))) 
        return rv;
    // get the dirs old permissions
    if (NS_FAILED(rv = GetPermissions(&oldPerms)))
        return rv;
    if (!dirCheck) {
        if (NS_FAILED(rv = newParent->Create(DIRECTORY_TYPE, oldPerms)))
            return rv;
    } else {    // dir exists lets try to use leaf
        nsAutoCString leafName;
        if (NS_FAILED(rv = GetNativeLeafName(leafName)))
            return rv;
        if (NS_FAILED(rv = newParent->AppendNative(leafName)))
            return rv;
        if (NS_FAILED(rv = newParent->Exists(&dirCheck)))
            return rv;
        if (dirCheck) 
            return NS_ERROR_FILE_ALREADY_EXISTS; // dest exists
        if (NS_FAILED(rv = newParent->Create(DIRECTORY_TYPE, oldPerms)))
            return rv;
    }

    nsCOMPtr<nsISimpleEnumerator> dirIterator;
    if (NS_FAILED(rv = GetDirectoryEntries(getter_AddRefs(dirIterator))))
        return rv;

    bool hasMore = false;
    while (dirIterator->HasMoreElements(&hasMore), hasMore) {
        nsCOMPtr<nsISupports> supports;
        nsCOMPtr<nsIFile> entry;
        rv = dirIterator->GetNext(getter_AddRefs(supports));
        entry = do_QueryInterface(supports);
        if (NS_FAILED(rv) || !entry)
            continue;
        if (NS_FAILED(rv = entry->IsSymlink(&isSymlink)))
            return rv;
        if (NS_FAILED(rv = entry->IsDirectory(&dirCheck)))
            return rv;
        if (dirCheck && !isSymlink) {
            nsCOMPtr<nsIFile> destClone;
            rv = newParent->Clone(getter_AddRefs(destClone));
            if (NS_SUCCEEDED(rv)) {
                if (NS_FAILED(rv = entry->CopyToNative(destClone, EmptyCString()))) {
#ifdef DEBUG
                    nsresult rv2;
                    nsAutoCString pathName;
                    if (NS_FAILED(rv2 = entry->GetNativePath(pathName)))
                        return rv2;
                    printf("Operation not supported: %s\n", pathName.get());
#endif
                    if (rv == NS_ERROR_OUT_OF_MEMORY) 
                        return rv;
                    continue;
                }
            }
        } else {
            if (NS_FAILED(rv = entry->CopyToNative(newParent, EmptyCString()))) {
#ifdef DEBUG
                nsresult rv2;
                nsAutoCString pathName;
                if (NS_FAILED(rv2 = entry->GetNativePath(pathName)))
                    return rv2;
                printf("Operation not supported: %s\n", pathName.get());
#endif
                if (rv == NS_ERROR_OUT_OF_MEMORY) 
                    return rv;
                continue;
            }
        }
    }
    return NS_OK;
}

NS_IMETHODIMP
nsLocalFile::CopyToNative(nsIFile *newParent, const nsACString &newName)
{
    nsresult rv;
    // check to make sure that this has been initialized properly
    CHECK_mPath();

    // we copy the parent here so 'newParent' remains immutable
    nsCOMPtr <nsIFile> workParent;
    if (newParent) {
        if (NS_FAILED(rv = newParent->Clone(getter_AddRefs(workParent))))
            return rv;
    } else {
        if (NS_FAILED(rv = GetParent(getter_AddRefs(workParent))))
            return rv;
    }
    
    // check to see if we are a directory or if we are a file
    bool isDirectory;
    if (NS_FAILED(rv = IsDirectory(&isDirectory)))
        return rv;

    nsAutoCString newPathName;
    if (isDirectory) {
        if (!newName.IsEmpty()) {
            if (NS_FAILED(rv = workParent->AppendNative(newName)))
                return rv;
        } else {
            if (NS_FAILED(rv = GetNativeLeafName(newPathName)))
                return rv;
            if (NS_FAILED(rv = workParent->AppendNative(newPathName)))
                return rv;
        }
        if (NS_FAILED(rv = CopyDirectoryTo(workParent)))
            return rv;
    } else {
        rv = GetNativeTargetPathName(workParent, newName, newPathName);
        if (NS_FAILED(rv)) 
            return rv;

#ifdef DEBUG_blizzard
        printf("nsLocalFile::CopyTo() %s -> %s\n", mPath.get(), newPathName.get());
#endif

        // actually create the file.
        nsLocalFile *newFile = new nsLocalFile();
        if (!newFile)
            return NS_ERROR_OUT_OF_MEMORY;

        nsCOMPtr<nsIFile> fileRef(newFile); // release on exit

        rv = newFile->InitWithNativePath(newPathName);
        if (NS_FAILED(rv))
            return rv;

        // get the old permissions
        uint32_t myPerms;
        GetPermissions(&myPerms);

        // Create the new file with the old file's permissions, even if write
        // permission is missing.  We can't create with write permission and
        // then change back to myPerm on all filesystems (FAT on Linux, e.g.).
        // But we can write to a read-only file on all Unix filesystems if we
        // open it successfully for writing.

        PRFileDesc *newFD;
        rv = newFile->CreateAndKeepOpen(NORMAL_FILE_TYPE,
                                        PR_WRONLY|PR_CREATE_FILE|PR_TRUNCATE,
                                        myPerms,
                                        &newFD);
        if (NS_FAILED(rv))
            return rv;

        // open the old file, too
        bool specialFile;
        if (NS_FAILED(rv = IsSpecial(&specialFile))) {
            PR_Close(newFD);
            return rv;
        }
        if (specialFile) {
#ifdef DEBUG
            printf("Operation not supported: %s\n", mPath.get());
#endif
            // make sure to clean up properly
            PR_Close(newFD);
            return NS_OK;
        }
               
        PRFileDesc *oldFD;
        rv = OpenNSPRFileDesc(PR_RDONLY, myPerms, &oldFD);
        if (NS_FAILED(rv)) {
            // make sure to clean up properly
            PR_Close(newFD);
            return rv;
        }

#ifdef DEBUG_blizzard
        int32_t totalRead = 0;
        int32_t totalWritten = 0;
#endif
        char buf[BUFSIZ];
        int32_t bytesRead;

        // record PR_Write() error for better error message later.
        nsresult saved_write_error = NS_OK;
        nsresult saved_read_error = NS_OK;
        nsresult saved_read_close_error = NS_OK;
        nsresult saved_write_close_error = NS_OK;

        // DONE: Does PR_Read() return bytesRead < 0 for error?
        // Yes., The errors from PR_Read are not so common and
        // the value may not have correspondence in NS_ERROR_*, but
        // we do catch it still, immediately after while() loop.
        // We can differentiate errors pf PR_Read and PR_Write by
        // looking at saved_write_error value. If PR_Write error occurs (and not
        // PR_Read() error), save_write_error is not NS_OK.

        while ((bytesRead = PR_Read(oldFD, buf, BUFSIZ)) > 0) {
#ifdef DEBUG_blizzard
            totalRead += bytesRead;
#endif

            // PR_Write promises never to do a short write
            int32_t bytesWritten = PR_Write(newFD, buf, bytesRead);
            if (bytesWritten < 0) {
                saved_write_error = NSRESULT_FOR_ERRNO();
                bytesRead = -1;
                break;
            }
            NS_ASSERTION(bytesWritten == bytesRead, "short PR_Write?");

#ifdef DEBUG_blizzard
            totalWritten += bytesWritten;
#endif
        }

        // TODO/FIXME: If CIFS (and NFS?) may force read/write to return EINTR,
        // we are better off to prepare for retrying. But we need confirmation if
        // EINTR is returned.

        // Record error if PR_Read() failed.
        // Must be done before any other I/O which may reset errno.
        if ( (bytesRead < 0) && (saved_write_error == NS_OK)) {
            saved_read_error = NSRESULT_FOR_ERRNO();
        }

#ifdef DEBUG_blizzard
        printf("read %d bytes, wrote %d bytes\n",
                 totalRead, totalWritten);
#endif

        // DONE: Errors of close can occur.  Read man page of
        // close(2);
        // This is likely to happen if the file system is remote file
        // system (NFS, CIFS, etc.) and network outage occurs.
        // At least, we should tell the user that filesystem/disk is
        // hosed (possibly due to network error, hard disk failure,
        // etc.) so that users can take remedial action.

        // close the files
        if (PR_Close(newFD) < 0) {
            saved_write_close_error = NSRESULT_FOR_ERRNO();
#if DEBUG
            // This error merits printing.
            fprintf(stderr, "ERROR: PR_Close(newFD) returned error. errno = %d\n", errno);
#endif
        }

        if (PR_Close(oldFD) < 0) {
            saved_read_close_error = NSRESULT_FOR_ERRNO();
#if DEBUG
            fprintf(stderr, "ERROR: PR_Close(oldFD) returned error. errno = %d\n", errno);
#endif
        }

        // Let us report the failure to write and read.
        // check for write/read error after cleaning up
        if (bytesRead < 0) {
            if (saved_write_error != NS_OK)
                return saved_write_error;
            else if (saved_read_error != NS_OK)
                return saved_read_error;
#if DEBUG
            else                // sanity check. Die and debug.
                MOZ_ASSERT(0);
#endif
        }

        if (saved_write_close_error != NS_OK)
            return saved_write_close_error;
        if (saved_read_close_error != NS_OK)
            return saved_read_close_error;
    }
    return rv;
}

NS_IMETHODIMP
nsLocalFile::CopyToFollowingLinksNative(nsIFile *newParent, const nsACString &newName)
{
    return CopyToNative(newParent, newName);
}

NS_IMETHODIMP
nsLocalFile::MoveToNative(nsIFile *newParent, const nsACString &newName)
{
    nsresult rv;

    // check to make sure that this has been initialized properly
    CHECK_mPath();

    // check to make sure that we have a new parent
    nsAutoCString newPathName;
    rv = GetNativeTargetPathName(newParent, newName, newPathName);
    if (NS_FAILED(rv))
        return rv;

    // try for atomic rename, falling back to copy/delete
    if (rename(mPath.get(), newPathName.get()) < 0) {
#ifdef VMS
        if (errno == EXDEV || errno == ENXIO) {
#else
        if (errno == EXDEV) {
#endif
            rv = CopyToNative(newParent, newName);
            if (NS_SUCCEEDED(rv))
                rv = Remove(true);
        } else {
            rv = NSRESULT_FOR_ERRNO();
        }
    }

    if (NS_SUCCEEDED(rv)) {
        // Adjust this
        mPath = newPathName;
    }
    return rv;
}

NS_IMETHODIMP
nsLocalFile::Remove(bool recursive)
{
    CHECK_mPath();
    ENSURE_STAT_CACHE();

    bool isSymLink;

    nsresult rv = IsSymlink(&isSymLink);
    if (NS_FAILED(rv))
        return rv;

    if (isSymLink || !S_ISDIR(mCachedStat.st_mode))
        return NSRESULT_FOR_RETURN(unlink(mPath.get()));

    if (recursive) {
        nsDirEnumeratorUnix *dir = new nsDirEnumeratorUnix();

        nsCOMPtr<nsISimpleEnumerator> dirRef(dir); // release on exit

        rv = dir->Init(this, false);
        if (NS_FAILED(rv))
            return rv;

        bool more;
        while (dir->HasMoreElements(&more), more) {
            nsCOMPtr<nsISupports> item;
            rv = dir->GetNext(getter_AddRefs(item));
            if (NS_FAILED(rv))
                return NS_ERROR_FAILURE;

            nsCOMPtr<nsIFile> file = do_QueryInterface(item, &rv);
            if (NS_FAILED(rv))
                return NS_ERROR_FAILURE;
            rv = file->Remove(recursive);

#ifdef ANDROID
            // See bug 580434 - Bionic gives us just deleted files
            if (rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST)
                continue;
#endif
            if (NS_FAILED(rv))
                return rv;
        }
    }

    return NSRESULT_FOR_RETURN(rmdir(mPath.get()));
}

NS_IMETHODIMP
nsLocalFile::GetLastModifiedTime(PRTime *aLastModTime)
{
    CHECK_mPath();
    if (NS_WARN_IF(!aLastModTime))
        return NS_ERROR_INVALID_ARG;

    PRFileInfo64 info;
    if (PR_GetFileInfo64(mPath.get(), &info) != PR_SUCCESS)
        return NSRESULT_FOR_ERRNO();
    PRTime modTime = info.modifyTime;
    if (modTime == 0)
        *aLastModTime = 0;
    else
        *aLastModTime = modTime / PR_USEC_PER_MSEC;

    return NS_OK;
}

NS_IMETHODIMP
nsLocalFile::SetLastModifiedTime(PRTime aLastModTime)
{
    CHECK_mPath();

    int result;
    if (aLastModTime != 0) {
        ENSURE_STAT_CACHE();
        struct utimbuf ut;
        ut.actime = mCachedStat.st_atime;

        // convert milliseconds to seconds since the unix epoch
        ut.modtime = (time_t)(aLastModTime / PR_MSEC_PER_SEC);
        result = utime(mPath.get(), &ut);
    } else {
        result = utime(mPath.get(), nullptr);
    }
    return NSRESULT_FOR_RETURN(result);
}

NS_IMETHODIMP
nsLocalFile::GetLastModifiedTimeOfLink(PRTime *aLastModTimeOfLink)
{
    CHECK_mPath();
    if (NS_WARN_IF(!aLastModTimeOfLink))
        return NS_ERROR_INVALID_ARG;

    struct STAT sbuf;
    if (LSTAT(mPath.get(), &sbuf) == -1)
        return NSRESULT_FOR_ERRNO();
    *aLastModTimeOfLink = PRTime(sbuf.st_mtime) * PR_MSEC_PER_SEC;

    return NS_OK;
}

/*
 * utime(2) may or may not dereference symlinks, joy.
 */
NS_IMETHODIMP
nsLocalFile::SetLastModifiedTimeOfLink(PRTime aLastModTimeOfLink)
{
    return SetLastModifiedTime(aLastModTimeOfLink);
}

/*
 * Only send back permissions bits: maybe we want to send back the whole
 * mode_t to permit checks against other file types?
 */

#define NORMALIZE_PERMS(mode)    ((mode)& (S_IRWXU | S_IRWXG | S_IRWXO))

NS_IMETHODIMP
nsLocalFile::GetPermissions(uint32_t *aPermissions)
{
    if (NS_WARN_IF(!aPermissions))
        return NS_ERROR_INVALID_ARG;
    ENSURE_STAT_CACHE();
    *aPermissions = NORMALIZE_PERMS(mCachedStat.st_mode);
    return NS_OK;
}

NS_IMETHODIMP
nsLocalFile::GetPermissionsOfLink(uint32_t *aPermissionsOfLink)
{
    CHECK_mPath();
    if (NS_WARN_IF(!aPermissionsOfLink))
        return NS_ERROR_INVALID_ARG;

    struct STAT sbuf;
    if (LSTAT(mPath.get(), &sbuf) == -1)
        return NSRESULT_FOR_ERRNO();
    *aPermissionsOfLink = NORMALIZE_PERMS(sbuf.st_mode);
    return NS_OK;
}

NS_IMETHODIMP
nsLocalFile::SetPermissions(uint32_t aPermissions)
{
    CHECK_mPath();

    /*
     * Race condition here: we should use fchmod instead, there's no way to 
     * guarantee the name still refers to the same file.
     */
    if (chmod(mPath.get(), aPermissions) >= 0)
        return NS_OK;
#if defined(ANDROID) && defined(STATFS)
    // For the time being, this is restricted for use by Android, but we 
    // will figure out what to do for all platforms in bug 638503
    struct STATFS sfs;
    if (STATFS(mPath.get(), &sfs) < 0)
         return NSRESULT_FOR_ERRNO();

    // if this is a FAT file system we can't set file permissions
    if (sfs.f_type == MSDOS_SUPER_MAGIC )
        return NS_OK;
#endif
    return NSRESULT_FOR_ERRNO();
}

NS_IMETHODIMP
nsLocalFile::SetPermissionsOfLink(uint32_t aPermissions)
{
    // There isn't a consistent mechanism for doing this on UNIX platforms. We
    // might want to carefully implement this in the future though.
    return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP
nsLocalFile::GetFileSize(int64_t *aFileSize)
{
    if (NS_WARN_IF(!aFileSize))
        return NS_ERROR_INVALID_ARG;
    *aFileSize = 0;
    ENSURE_STAT_CACHE();

#if defined(VMS)
    /* Only two record formats can report correct file content size */
    if ((mCachedStat.st_fab_rfm != FAB$C_STMLF) &&
        (mCachedStat.st_fab_rfm != FAB$C_STMCR)) {
        return NS_ERROR_FAILURE;
    }
#endif

    if (!S_ISDIR(mCachedStat.st_mode)) {
        *aFileSize = (int64_t)mCachedStat.st_size;
    }
    return NS_OK;
}

NS_IMETHODIMP
nsLocalFile::SetFileSize(int64_t aFileSize)
{
    CHECK_mPath();

#if defined(ANDROID)
    /* no truncate on bionic */
    int fd = open(mPath.get(), O_WRONLY);
    if (fd == -1)
        return NSRESULT_FOR_ERRNO();

    int ret = ftruncate(fd, (off_t)aFileSize);
    close(fd);

    if (ret == -1)
        return NSRESULT_FOR_ERRNO();
#elif defined(HAVE_TRUNCATE64)
    if (truncate64(mPath.get(), (off64_t)aFileSize) == -1)
        return NSRESULT_FOR_ERRNO();
#else
    off_t size = (off_t)aFileSize;
    if (truncate(mPath.get(), size) == -1)
        return NSRESULT_FOR_ERRNO();
#endif
    return NS_OK;
}

NS_IMETHODIMP
nsLocalFile::GetFileSizeOfLink(int64_t *aFileSize)
{
    CHECK_mPath();
    if (NS_WARN_IF(!aFileSize))
        return NS_ERROR_INVALID_ARG;

    struct STAT sbuf;
    if (LSTAT(mPath.get(), &sbuf) == -1)
        return NSRESULT_FOR_ERRNO();

    *aFileSize = (int64_t)sbuf.st_size;
    return NS_OK;
}

#if defined(USE_LINUX_QUOTACTL)
/*
 * Searches /proc/self/mountinfo for given device (Major:Minor), 
 * returns exported name from /dev
 *
 * Fails when /proc/self/mountinfo or diven device don't exist.
 */
static bool
GetDeviceName(int deviceMajor, int deviceMinor, nsACString &deviceName)
{
    bool ret = false;
    
    const int kMountInfoLineLength = 200;
    const int kMountInfoDevPosition = 6;

    char mountinfo_line[kMountInfoLineLength];
    char device_num[kMountInfoLineLength];
    
    snprintf(device_num,kMountInfoLineLength,"%d:%d", deviceMajor, deviceMinor);
    
    FILE *f = fopen("/proc/self/mountinfo","rt");
    if(!f)
        return ret;

    // Expects /proc/self/mountinfo in format:
    // 'ID ID major:minor root mountpoint flags - type devicename flags'
    while(fgets(mountinfo_line,kMountInfoLineLength,f)) {
        char *p_dev = strstr(mountinfo_line,device_num);
    
        int i;
        for(i = 0; i < kMountInfoDevPosition && p_dev != nullptr; i++) {
            p_dev = strchr(p_dev,' ');
            if(p_dev)
              p_dev++;
        }
    
        if(p_dev) {
            char *p_dev_end = strchr(p_dev,' ');
            if(p_dev_end) {
                *p_dev_end = '\0';
                deviceName.Assign(p_dev);
                ret = true;
                break;
            }
        }
    }
    
    fclose(f);
    return ret; 
}
#endif

NS_IMETHODIMP
nsLocalFile::GetDiskSpaceAvailable(int64_t *aDiskSpaceAvailable)
{
    if (NS_WARN_IF(!aDiskSpaceAvailable))
        return NS_ERROR_INVALID_ARG;

    // These systems have the operations necessary to check disk space.

#ifdef STATFS

    // check to make sure that mPath is properly initialized
    CHECK_mPath();

    struct STATFS fs_buf;

    /* 
     * Members of the STATFS struct that you should know about:
     * F_BSIZE = block size on disk.
     * f_bavail = number of free blocks available to a non-superuser.
     * f_bfree = number of total free blocks in file system.
     */

    if (STATFS(mPath.get(), &fs_buf) < 0) {
        // The call to STATFS failed.
#ifdef DEBUG
        printf("ERROR: GetDiskSpaceAvailable: STATFS call FAILED. \n");
#endif
        return NS_ERROR_FAILURE;
    }

    *aDiskSpaceAvailable = (int64_t) fs_buf.F_BSIZE * fs_buf.f_bavail;

#ifdef DEBUG_DISK_SPACE
    printf("DiskSpaceAvailable: %lu bytes\n",
         *aDiskSpaceAvailable);
#endif

#if defined(USE_LINUX_QUOTACTL)

    if(!FillStatCache()) {
        // Return available size from statfs
        return NS_OK;
    }

    nsCString deviceName;
    if(!GetDeviceName(major(mCachedStat.st_dev), minor(mCachedStat.st_dev), deviceName)) {
        return NS_OK;
    }

    struct dqblk dq;
    if(!quotactl(QCMD(Q_GETQUOTA, USRQUOTA), deviceName.get(), getuid(), (caddr_t)&dq)
#ifdef QIF_BLIMITS
       && dq.dqb_valid & QIF_BLIMITS
#endif
       && dq.dqb_bhardlimit)
    {
        int64_t QuotaSpaceAvailable = 0;
        if (dq.dqb_bhardlimit > dq.dqb_curspace)
            QuotaSpaceAvailable = int64_t(fs_buf.F_BSIZE * (dq.dqb_bhardlimit - dq.dqb_curspace));
        if(QuotaSpaceAvailable < *aDiskSpaceAvailable) {
            *aDiskSpaceAvailable = QuotaSpaceAvailable;
        }
    }
#endif

    return NS_OK;

#else
    /*
     * This platform doesn't have statfs or statvfs.  I'm sure that there's
     * a way to check for free disk space on platforms that don't have statfs
     * (I'm SURE they have df, for example).
     *
     * Until we figure out how to do that, lets be honest and say that this
     * command isn't implemented properly for these platforms yet.
     */
#ifdef DEBUG
    printf("ERROR: GetDiskSpaceAvailable: Not implemented for plaforms without statfs.\n");
#endif
    return NS_ERROR_NOT_IMPLEMENTED;

#endif /* STATFS */

}

NS_IMETHODIMP
nsLocalFile::GetParent(nsIFile **aParent)
{
    CHECK_mPath();
    if (NS_WARN_IF(!aParent))
        return NS_ERROR_INVALID_ARG;
    *aParent = nullptr;

    // if '/' we are at the top of the volume, return null
    if (mPath.Equals("/"))
        return  NS_OK;
 
    // <brendan, after jband> I promise to play nice
    char *buffer   = mPath.BeginWriting(),
         *slashp   = buffer;

    // find the last significant slash in buffer
    slashp = strrchr(buffer, '/');
    NS_ASSERTION(slashp, "non-canonical path?");
    if (!slashp)
        return NS_ERROR_FILE_INVALID_PATH;

    // for the case where we are at '/'
    if (slashp == buffer)
        slashp++;

    // temporarily terminate buffer at the last significant slash
    char c = *slashp;
    *slashp = '\0';

    nsCOMPtr<nsIFile> localFile;
    nsresult rv = NS_NewNativeLocalFile(nsDependentCString(buffer), true,
                                        getter_AddRefs(localFile));

    // make buffer whole again
    *slashp = c;

    if (NS_FAILED(rv)) {
        return rv;
    }

    localFile.forget(aParent);
    return NS_OK;
}

/*
 * The results of Exists, isWritable and isReadable are not cached.
 */


NS_IMETHODIMP
nsLocalFile::Exists(bool *_retval)
{
    CHECK_mPath();
    if (NS_WARN_IF(!_retval))
        return NS_ERROR_INVALID_ARG;

    *_retval = (access(mPath.get(), F_OK) == 0);
    return NS_OK;
}


NS_IMETHODIMP
nsLocalFile::IsWritable(bool *_retval)
{
    CHECK_mPath();
    if (NS_WARN_IF(!_retval))
        return NS_ERROR_INVALID_ARG;

    *_retval = (access(mPath.get(), W_OK) == 0);
    if (*_retval || errno == EACCES)
        return NS_OK;
    return NSRESULT_FOR_ERRNO();
}

NS_IMETHODIMP
nsLocalFile::IsReadable(bool *_retval)
{
    CHECK_mPath();
    if (NS_WARN_IF(!_retval))
        return NS_ERROR_INVALID_ARG;

    *_retval = (access(mPath.get(), R_OK) == 0);
    if (*_retval || errno == EACCES)
        return NS_OK;
    return NSRESULT_FOR_ERRNO();
}

NS_IMETHODIMP
nsLocalFile::IsExecutable(bool *_retval)
{
    CHECK_mPath();
    if (NS_WARN_IF(!_retval))
        return NS_ERROR_INVALID_ARG;

    // Check extension (bug 663899). On certain platforms, the file
    // extension may cause the OS to treat it as executable regardless of
    // the execute bit, such as .jar on Mac OS X. We borrow the code from
    // nsLocalFileWin, slightly modified.

    // Don't be fooled by symlinks.
    bool symLink;
    nsresult rv = IsSymlink(&symLink);
    if (NS_FAILED(rv))
        return rv;

    nsAutoString path;
    if (symLink)
        GetTarget(path);
    else
        GetPath(path);

    int32_t dotIdx = path.RFindChar(char16_t('.'));
    if (dotIdx != kNotFound) {
        // Convert extension to lower case.
        char16_t *p = path.BeginWriting();
        for(p += dotIdx + 1; *p; p++)
            *p +=  (*p >= L'A' && *p <= L'Z') ? 'a' - 'A' : 0; 
        
        // Search for any of the set of executable extensions.
        static const char * const executableExts[] = {
            "air",         // Adobe AIR installer
            "jar"};        // java application bundle
        nsDependentSubstring ext = Substring(path, dotIdx + 1);
        for (size_t i = 0; i < ArrayLength(executableExts); i++) {
            if (ext.EqualsASCII(executableExts[i])) {
                // Found a match.  Set result and quit.
                *_retval = true;
                return NS_OK;
            }
        }
    }

    // On OS X, then query Launch Services.
#ifdef MOZ_WIDGET_COCOA
    // Certain Mac applications, such as Classic applications, which
    // run under Rosetta, might not have the +x mode bit but are still
    // considered to be executable by Launch Services (bug 646748).
    CFURLRef url;
    if (NS_FAILED(GetCFURL(&url))) {
        return NS_ERROR_FAILURE;
    }

    LSRequestedInfo theInfoRequest = kLSRequestAllInfo;
    LSItemInfoRecord theInfo;
    OSStatus result = ::LSCopyItemInfoForURL(url, theInfoRequest, &theInfo);
    ::CFRelease(url);
    if (result == noErr) {
        if ((theInfo.flags & kLSItemInfoIsApplication) != 0) {
            *_retval = true;
            return NS_OK;
        }
    }
#endif

    // Then check the execute bit.
    *_retval = (access(mPath.get(), X_OK) == 0);
#ifdef SOLARIS
    // On Solaris, access will always return 0 for root user, however
    // the file is only executable if S_IXUSR | S_IXGRP | S_IXOTH is set.
    // See bug 351950, https://bugzilla.mozilla.org/show_bug.cgi?id=351950
    if (*_retval) {
        struct STAT buf;

        *_retval = (STAT(mPath.get(), &buf) == 0);
        if (*_retval || errno == EACCES) {
            *_retval = *_retval &&
                       (buf.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH ));
            return NS_OK;
        }

        return NSRESULT_FOR_ERRNO();
    }
#endif
    if (*_retval || errno == EACCES)
        return NS_OK;
    return NSRESULT_FOR_ERRNO();
}

NS_IMETHODIMP
nsLocalFile::IsDirectory(bool *_retval)
{
    if (NS_WARN_IF(!_retval))
        return NS_ERROR_INVALID_ARG;
    *_retval = false;
    ENSURE_STAT_CACHE();
    *_retval = S_ISDIR(mCachedStat.st_mode);
    return NS_OK;
}

NS_IMETHODIMP
nsLocalFile::IsFile(bool *_retval)
{
    if (NS_WARN_IF(!_retval))
        return NS_ERROR_INVALID_ARG;
    *_retval = false;
    ENSURE_STAT_CACHE();
    *_retval = S_ISREG(mCachedStat.st_mode);
    return NS_OK;
}

NS_IMETHODIMP
nsLocalFile::IsHidden(bool *_retval)
{
    if (NS_WARN_IF(!_retval))
        return NS_ERROR_INVALID_ARG;
    nsACString::const_iterator begin, end;
    LocateNativeLeafName(begin, end);
    *_retval = (*begin == '.');
    return NS_OK;
}

NS_IMETHODIMP
nsLocalFile::IsSymlink(bool *_retval)
{
    if (NS_WARN_IF(!_retval))
        return NS_ERROR_INVALID_ARG;
    CHECK_mPath();

    struct STAT symStat;
    if (LSTAT(mPath.get(), &symStat) == -1)
        return NSRESULT_FOR_ERRNO();
    *_retval=S_ISLNK(symStat.st_mode);
    return NS_OK;
}

NS_IMETHODIMP
nsLocalFile::IsSpecial(bool *_retval)
{
    if (NS_WARN_IF(!_retval))
        return NS_ERROR_INVALID_ARG;
    ENSURE_STAT_CACHE();
    *_retval = S_ISCHR(mCachedStat.st_mode)      ||
                 S_ISBLK(mCachedStat.st_mode)    ||
#ifdef S_ISSOCK
                 S_ISSOCK(mCachedStat.st_mode)   ||
#endif
                 S_ISFIFO(mCachedStat.st_mode);

    return NS_OK;
}

NS_IMETHODIMP
nsLocalFile::Equals(nsIFile *inFile, bool *_retval)
{
    if (NS_WARN_IF(!inFile))
        return NS_ERROR_INVALID_ARG;
    if (NS_WARN_IF(!_retval))
        return NS_ERROR_INVALID_ARG;
    *_retval = false;

    nsAutoCString inPath;
    nsresult rv = inFile->GetNativePath(inPath);
    if (NS_FAILED(rv))
        return rv;

    // We don't need to worry about "/foo/" vs. "/foo" here
    // because trailing slashes are stripped on init.
    *_retval = !strcmp(inPath.get(), mPath.get());
    return NS_OK;
}

NS_IMETHODIMP
nsLocalFile::Contains(nsIFile *inFile, bool recur, bool *_retval)
{
    CHECK_mPath();
    if (NS_WARN_IF(!inFile))
        return NS_ERROR_INVALID_ARG;
    if (NS_WARN_IF(!_retval))
        return NS_ERROR_INVALID_ARG;

    nsAutoCString inPath;
    nsresult rv;

    if (NS_FAILED(rv = inFile->GetNativePath(inPath)))
        return rv;

    *_retval = false;

    ssize_t len = mPath.Length();
    if (strncmp(mPath.get(), inPath.get(), len) == 0) {
        // Now make sure that the |inFile|'s path has a separator at len,
        // which implies that it has more components after len.
        if (inPath[len] == '/')
            *_retval = true;
    }

    return NS_OK;
}

NS_IMETHODIMP
nsLocalFile::GetNativeTarget(nsACString &_retval)
{
    CHECK_mPath();
    _retval.Truncate();

    struct STAT symStat;
    if (LSTAT(mPath.get(), &symStat) == -1)
        return NSRESULT_FOR_ERRNO();

    if (!S_ISLNK(symStat.st_mode))
        return NS_ERROR_FILE_INVALID_PATH;

    int32_t size = (int32_t)symStat.st_size;
    char *target = (char *)nsMemory::Alloc(size + 1);
    if (!target)
        return NS_ERROR_OUT_OF_MEMORY;

    if (readlink(mPath.get(), target, (size_t)size) < 0) {
        nsMemory::Free(target);
        return NSRESULT_FOR_ERRNO();
    }
    target[size] = '\0';

    nsresult rv = NS_OK;
    nsCOMPtr<nsIFile> self(this);
    int32_t maxLinks = 40;
    while (true) {
        if (maxLinks-- == 0) {
            rv = NS_ERROR_FILE_UNRESOLVABLE_SYMLINK;
            break;
        }

        if (target[0] != '/') {
            nsCOMPtr<nsIFile> parent;
            if (NS_FAILED(rv = self->GetParent(getter_AddRefs(parent))))
                break;
            if (NS_FAILED(rv = parent->AppendRelativeNativePath(nsDependentCString(target))))
                break;
            if (NS_FAILED(rv = parent->GetNativePath(_retval)))
                break;
            self = parent;
        } else {
            _retval = target;
        }

        const nsPromiseFlatCString &flatRetval = PromiseFlatCString(_retval);

        // Any failure in testing the current target we'll just interpret
        // as having reached our destiny.
        if (LSTAT(flatRetval.get(), &symStat) == -1)
            break;

        // And of course we're done if it isn't a symlink.
        if (!S_ISLNK(symStat.st_mode))
            break;

        int32_t newSize = (int32_t)symStat.st_size;
        if (newSize > size) {
            char *newTarget = (char *)nsMemory::Realloc(target, newSize + 1);
            if (!newTarget) {
                rv = NS_ERROR_OUT_OF_MEMORY;
                break;
            }
            target = newTarget;
            size = newSize;
        }

        int32_t linkLen = readlink(flatRetval.get(), target, size);
        if (linkLen == -1) {
            rv = NSRESULT_FOR_ERRNO();
            break;
        }
        target[linkLen] = '\0';
    }

    nsMemory::Free(target);

    if (NS_FAILED(rv))
        _retval.Truncate();
    return rv;
}

NS_IMETHODIMP
nsLocalFile::GetFollowLinks(bool *aFollowLinks)
{
    *aFollowLinks = true;
    return NS_OK;
}

NS_IMETHODIMP
nsLocalFile::SetFollowLinks(bool aFollowLinks)
{
    return NS_OK;
}

NS_IMETHODIMP
nsLocalFile::GetDirectoryEntries(nsISimpleEnumerator **entries)
{
    nsRefPtr<nsDirEnumeratorUnix> dir = new nsDirEnumeratorUnix();

    nsresult rv = dir->Init(this, false);
    if (NS_FAILED(rv)) {
        *entries = nullptr;
    } else {
        dir.forget(entries);
    }

    return rv;
}

NS_IMETHODIMP
nsLocalFile::Load(PRLibrary **_retval)
{
    CHECK_mPath();
    if (NS_WARN_IF(!_retval))
        return NS_ERROR_INVALID_ARG;

#ifdef NS_BUILD_REFCNT_LOGGING
    nsTraceRefcnt::SetActivityIsLegal(false);
#endif

    *_retval = PR_LoadLibrary(mPath.get());

#ifdef NS_BUILD_REFCNT_LOGGING
    nsTraceRefcnt::SetActivityIsLegal(true);
#endif

    if (!*_retval)
        return NS_ERROR_FAILURE;
    return NS_OK;
}

NS_IMETHODIMP
nsLocalFile::GetPersistentDescriptor(nsACString &aPersistentDescriptor)
{
    return GetNativePath(aPersistentDescriptor);
}

NS_IMETHODIMP
nsLocalFile::SetPersistentDescriptor(const nsACString &aPersistentDescriptor)
{
#ifdef MOZ_WIDGET_COCOA
    if (aPersistentDescriptor.IsEmpty())
        return NS_ERROR_INVALID_ARG;

    // Support pathnames as user-supplied descriptors if they begin with '/'
    // or '~'.  These characters do not collide with the base64 set used for
    // encoding alias records.
    char first = aPersistentDescriptor.First();
    if (first == '/' || first == '~')
        return InitWithNativePath(aPersistentDescriptor);

    uint32_t dataSize = aPersistentDescriptor.Length();    
    char* decodedData = PL_Base64Decode(PromiseFlatCString(aPersistentDescriptor).get(), dataSize, nullptr);
    if (!decodedData) {
        NS_ERROR("SetPersistentDescriptor was given bad data");
        return NS_ERROR_FAILURE;
    }

    // Cast to an alias record and resolve.
    AliasRecord aliasHeader = *(AliasPtr)decodedData;
    int32_t aliasSize = ::GetAliasSizeFromPtr(&aliasHeader);
    if (aliasSize > ((int32_t)dataSize * 3) / 4) { // be paranoid about having too few data
        PR_Free(decodedData);
        return NS_ERROR_FAILURE;
    }

    nsresult rv = NS_OK;

    // Move the now-decoded data into the Handle.
    // The size of the decoded data is 3/4 the size of the encoded data. See plbase64.h
    Handle  newHandle = nullptr;
    if (::PtrToHand(decodedData, &newHandle, aliasSize) != noErr)
        rv = NS_ERROR_OUT_OF_MEMORY;
    PR_Free(decodedData);
    if (NS_FAILED(rv))
        return rv;

    Boolean changed;
    FSRef resolvedFSRef;
    OSErr err = ::FSResolveAlias(nullptr, (AliasHandle)newHandle, &resolvedFSRef, &changed);

    rv = MacErrorMapper(err);
    DisposeHandle(newHandle);
    if (NS_FAILED(rv))
        return rv;

    return InitWithFSRef(&resolvedFSRef);  
#else
    return InitWithNativePath(aPersistentDescriptor);
#endif
}

NS_IMETHODIMP
nsLocalFile::Reveal()
{
#ifdef MOZ_WIDGET_GTK
    nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
    nsCOMPtr<nsIGnomeVFSService> gnomevfs = do_GetService(NS_GNOMEVFSSERVICE_CONTRACTID);
    if (!giovfs && !gnomevfs)
        return NS_ERROR_FAILURE;

    bool isDirectory;
    if (NS_FAILED(IsDirectory(&isDirectory)))
        return NS_ERROR_FAILURE;

    if (isDirectory) {
        if (giovfs)
            return giovfs->ShowURIForInput(mPath);
        else 
            /* Fallback to GnomeVFS */
            return gnomevfs->ShowURIForInput(mPath);
    } else if (giovfs && NS_SUCCEEDED(giovfs->OrgFreedesktopFileManager1ShowItems(mPath))) {
        return NS_OK;
    } else {
        nsCOMPtr<nsIFile> parentDir;
        nsAutoCString dirPath;
        if (NS_FAILED(GetParent(getter_AddRefs(parentDir))))
            return NS_ERROR_FAILURE;
        if (NS_FAILED(parentDir->GetNativePath(dirPath)))
            return NS_ERROR_FAILURE;

        if (giovfs)
            return giovfs->ShowURIForInput(dirPath);
        else 
            return gnomevfs->ShowURIForInput(dirPath);        
    }
#elif defined(MOZ_WIDGET_COCOA)
    CFURLRef url;
    if (NS_SUCCEEDED(GetCFURL(&url))) {
      nsresult rv = CocoaFileUtils::RevealFileInFinder(url);
      ::CFRelease(url);
      return rv;
    }
    return NS_ERROR_FAILURE;
#else
    return NS_ERROR_FAILURE;
#endif
}

NS_IMETHODIMP
nsLocalFile::Launch()
{
#ifdef MOZ_WIDGET_GTK
    nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
    nsCOMPtr<nsIGnomeVFSService> gnomevfs = do_GetService(NS_GNOMEVFSSERVICE_CONTRACTID);
    if (giovfs) {
      return giovfs->ShowURIForInput(mPath);
    } else if (gnomevfs) {
      /* GnomeVFS fallback */
      return gnomevfs->ShowURIForInput(mPath);
    }
    
    return NS_ERROR_FAILURE;
#elif defined(MOZ_ENABLE_CONTENTACTION)
    QUrl uri = QUrl::fromLocalFile(QString::fromUtf8(mPath.get()));
    ContentAction::Action action =
        ContentAction::Action::defaultActionForFile(uri);

    if (action.isValid()) {
      action.trigger();
      return NS_OK;
    }

    return NS_ERROR_FAILURE;
#elif defined(MOZ_WIDGET_ANDROID)
    // Try to get a mimetype, if this fails just use the file uri alone
    nsresult rv;
    nsAutoCString type;
    nsCOMPtr<nsIMIMEService> mimeService(do_GetService("@mozilla.org/mime;1", &rv));
    if (NS_SUCCEEDED(rv))
        rv = mimeService->GetTypeFromFile(this, type);

    nsAutoCString fileUri = NS_LITERAL_CSTRING("file://") + mPath;
    return GeckoAppShell::OpenUriExternal(NS_ConvertUTF8toUTF16(fileUri), NS_ConvertUTF8toUTF16(type)) ? NS_OK : NS_ERROR_FAILURE;
#elif defined(MOZ_WIDGET_COCOA)
    CFURLRef url;
    if (NS_SUCCEEDED(GetCFURL(&url))) {
        nsresult rv = CocoaFileUtils::OpenURL(url);
        ::CFRelease(url);
        return rv;
    }
    return NS_ERROR_FAILURE;
#else
    return NS_ERROR_FAILURE;
#endif
}

nsresult
NS_NewNativeLocalFile(const nsACString &path, bool followSymlinks, nsIFile **result)
{
    nsRefPtr<nsLocalFile> file = new nsLocalFile();

    file->SetFollowLinks(followSymlinks);

    if (!path.IsEmpty()) {
        nsresult rv = file->InitWithNativePath(path);
        if (NS_FAILED(rv)) {
            return rv;
        }
    }
    file.forget(result);
    return NS_OK;
}

//-----------------------------------------------------------------------------
// unicode support
//-----------------------------------------------------------------------------

#define SET_UCS(func, ucsArg) \
    { \
        nsAutoCString buf; \
        nsresult rv = NS_CopyUnicodeToNative(ucsArg, buf); \
        if (NS_FAILED(rv)) \
            return rv; \
        return (func)(buf); \
    }

#define GET_UCS(func, ucsArg) \
    { \
        nsAutoCString buf; \
        nsresult rv = (func)(buf); \
        if (NS_FAILED(rv)) return rv; \
        return NS_CopyNativeToUnicode(buf, ucsArg); \
    }

#define SET_UCS_2ARGS_2(func, opaqueArg, ucsArg) \
    { \
        nsAutoCString buf; \
        nsresult rv = NS_CopyUnicodeToNative(ucsArg, buf); \
        if (NS_FAILED(rv)) \
            return rv; \
        return (func)(opaqueArg, buf); \
    }

// Unicode interface Wrapper
nsresult  
nsLocalFile::InitWithPath(const nsAString &filePath)
{
    SET_UCS(InitWithNativePath, filePath);
}
nsresult  
nsLocalFile::Append(const nsAString &node)
{
    SET_UCS(AppendNative, node);
}
nsresult  
nsLocalFile::AppendRelativePath(const nsAString &node)
{
    SET_UCS(AppendRelativeNativePath, node);
}
nsresult  
nsLocalFile::GetLeafName(nsAString &aLeafName)
{
    GET_UCS(GetNativeLeafName, aLeafName);
}
nsresult  
nsLocalFile::SetLeafName(const nsAString &aLeafName)
{
    SET_UCS(SetNativeLeafName, aLeafName);
}
nsresult  
nsLocalFile::GetPath(nsAString &_retval)
{
    return NS_CopyNativeToUnicode(mPath, _retval);
}
nsresult  
nsLocalFile::CopyTo(nsIFile *newParentDir, const nsAString &newName)
{
    SET_UCS_2ARGS_2(CopyToNative , newParentDir, newName);
}
nsresult  
nsLocalFile::CopyToFollowingLinks(nsIFile *newParentDir, const nsAString &newName)
{
    SET_UCS_2ARGS_2(CopyToFollowingLinksNative , newParentDir, newName);
}
nsresult  
nsLocalFile::MoveTo(nsIFile *newParentDir, const nsAString &newName)
{
    SET_UCS_2ARGS_2(MoveToNative, newParentDir, newName);
}

NS_IMETHODIMP
nsLocalFile::RenameTo(nsIFile *newParentDir, const nsAString &newName)
{
  nsresult rv;

  // check to make sure that this has been initialized properly
  CHECK_mPath();

  // check to make sure that we have a new parent
  nsAutoCString newPathName;
  nsAutoCString newNativeName;
  rv = NS_CopyUnicodeToNative(newName, newNativeName);
  if (NS_FAILED(rv)) {
    return rv;
  }
  rv = GetNativeTargetPathName(newParentDir, newNativeName, newPathName);
  if (NS_FAILED(rv)) {
    return rv;
  }

  // try for atomic rename
  if (rename(mPath.get(), newPathName.get()) < 0) {
#ifdef VMS
    if (errno == EXDEV || errno == ENXIO) {
#else
    if (errno == EXDEV) {
#endif
      rv = NS_ERROR_FILE_ACCESS_DENIED;
    } else {
      rv = NSRESULT_FOR_ERRNO();
    }
  }

  return rv;
}

nsresult
nsLocalFile::GetTarget(nsAString &_retval)
{   
    GET_UCS(GetNativeTarget, _retval);
}

// nsIHashable

NS_IMETHODIMP
nsLocalFile::Equals(nsIHashable* aOther, bool *aResult)
{
    nsCOMPtr<nsIFile> otherFile(do_QueryInterface(aOther));
    if (!otherFile) {
        *aResult = false;
        return NS_OK;
    }

    return Equals(otherFile, aResult);
}

NS_IMETHODIMP
nsLocalFile::GetHashCode(uint32_t *aResult)
{
    *aResult = HashString(mPath);
    return NS_OK;
}

nsresult 
NS_NewLocalFile(const nsAString &path, bool followLinks, nsIFile* *result)
{
    nsAutoCString buf;
    nsresult rv = NS_CopyUnicodeToNative(path, buf);
    if (NS_FAILED(rv))
        return rv;
    return NS_NewNativeLocalFile(buf, followLinks, result);
}

//-----------------------------------------------------------------------------
// global init/shutdown
//-----------------------------------------------------------------------------

void
nsLocalFile::GlobalInit()
{
}

void
nsLocalFile::GlobalShutdown()
{
}

// nsILocalFileMac

#ifdef MOZ_WIDGET_COCOA

static nsresult MacErrorMapper(OSErr inErr)
{
  nsresult outErr;

  switch (inErr)
  {
    case noErr:
      outErr = NS_OK;
      break;

    case fnfErr:
    case afpObjectNotFound:
    case afpDirNotFound:
      outErr = NS_ERROR_FILE_NOT_FOUND;
      break;

    case dupFNErr:
    case afpObjectExists:
      outErr = NS_ERROR_FILE_ALREADY_EXISTS;
      break;

    case dskFulErr:
    case afpDiskFull:
      outErr = NS_ERROR_FILE_DISK_FULL;
      break;

    case fLckdErr:
    case afpVolLocked:
      outErr = NS_ERROR_FILE_IS_LOCKED;
      break;

    case afpAccessDenied:
      outErr = NS_ERROR_FILE_ACCESS_DENIED;
      break;

    case afpDirNotEmpty:
      outErr = NS_ERROR_FILE_DIR_NOT_EMPTY;
      break;

    // Can't find good map for some
    case bdNamErr:
      outErr = NS_ERROR_FAILURE;
      break;

    default:
      outErr = NS_ERROR_FAILURE;
      break;
  }

  return outErr;
}

static nsresult CFStringReftoUTF8(CFStringRef aInStrRef, nsACString& aOutStr)
{
  // first see if the conversion would succeed and find the length of the result
  CFIndex usedBufLen, inStrLen = ::CFStringGetLength(aInStrRef);
  CFIndex charsConverted = ::CFStringGetBytes(aInStrRef, CFRangeMake(0, inStrLen),
                                              kCFStringEncodingUTF8, 0, false,
                                              nullptr, 0, &usedBufLen);
  if (charsConverted == inStrLen) {
    // all characters converted, do the actual conversion
    aOutStr.SetLength(usedBufLen);
    if (aOutStr.Length() != (unsigned int)usedBufLen)
      return NS_ERROR_OUT_OF_MEMORY;
    UInt8 *buffer = (UInt8*)aOutStr.BeginWriting();
    ::CFStringGetBytes(aInStrRef, CFRangeMake(0, inStrLen), kCFStringEncodingUTF8,
                       0, false, buffer, usedBufLen, &usedBufLen);
    return NS_OK;
  }

  return NS_ERROR_FAILURE;
}

NS_IMETHODIMP
nsLocalFile::InitWithCFURL(CFURLRef aCFURL)
{
  UInt8 path[PATH_MAX];
  if (::CFURLGetFileSystemRepresentation(aCFURL, false, path, PATH_MAX)) {
    nsDependentCString nativePath((char*)path);
    return InitWithNativePath(nativePath);
  }

  return NS_ERROR_FAILURE;
}

NS_IMETHODIMP
nsLocalFile::InitWithFSRef(const FSRef *aFSRef)
{
  if (NS_WARN_IF(!aFSRef))
      return NS_ERROR_INVALID_ARG;

  CFURLRef newURLRef = ::CFURLCreateFromFSRef(kCFAllocatorDefault, aFSRef);
  if (newURLRef) {
    nsresult rv = InitWithCFURL(newURLRef);
    ::CFRelease(newURLRef);
    return rv;
  }

  return NS_ERROR_FAILURE;
}

NS_IMETHODIMP
nsLocalFile::GetCFURL(CFURLRef *_retval)
{
  CHECK_mPath();

  bool isDir;
  IsDirectory(&isDir);
  *_retval = ::CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault,
                                                       (UInt8*)mPath.get(),
                                                       mPath.Length(),
                                                       isDir);

  return (*_retval ? NS_OK : NS_ERROR_FAILURE);
}

NS_IMETHODIMP
nsLocalFile::GetFSRef(FSRef *_retval)
{
  if (NS_WARN_IF(!_retval))
      return NS_ERROR_INVALID_ARG;

  nsresult rv = NS_ERROR_FAILURE;

  CFURLRef url = nullptr;
  if (NS_SUCCEEDED(GetCFURL(&url))) {
    if (::CFURLGetFSRef(url, _retval)) {
      rv = NS_OK;
    }
    ::CFRelease(url);
  }

  return rv;
}

NS_IMETHODIMP
nsLocalFile::GetFSSpec(FSSpec *_retval)
{
  if (NS_WARN_IF(!_retval))
      return NS_ERROR_INVALID_ARG;

  FSRef fsRef;
  nsresult rv = GetFSRef(&fsRef);
  if (NS_SUCCEEDED(rv)) {
    OSErr err = ::FSGetCatalogInfo(&fsRef, kFSCatInfoNone, nullptr, nullptr, _retval, nullptr);
    return MacErrorMapper(err);
  }

  return rv;
}

NS_IMETHODIMP
nsLocalFile::GetFileSizeWithResFork(int64_t *aFileSizeWithResFork)
{
  if (NS_WARN_IF(!aFileSizeWithResFork))
      return NS_ERROR_INVALID_ARG;

  FSRef fsRef;
  nsresult rv = GetFSRef(&fsRef);
  if (NS_FAILED(rv))
    return rv;

  FSCatalogInfo catalogInfo;
  OSErr err = ::FSGetCatalogInfo(&fsRef, kFSCatInfoDataSizes + kFSCatInfoRsrcSizes,
                                 &catalogInfo, nullptr, nullptr, nullptr);
  if (err != noErr)
    return MacErrorMapper(err);

  *aFileSizeWithResFork = catalogInfo.dataLogicalSize + catalogInfo.rsrcLogicalSize;
  return NS_OK;
}

NS_IMETHODIMP
nsLocalFile::GetFileType(OSType *aFileType)
{
  CFURLRef url;
  if (NS_SUCCEEDED(GetCFURL(&url))) {
    nsresult rv = CocoaFileUtils::GetFileTypeCode(url, aFileType);
    ::CFRelease(url);
    return rv;
  }
  return NS_ERROR_FAILURE;
}
  
NS_IMETHODIMP
nsLocalFile::SetFileType(OSType aFileType)
{
  CFURLRef url;
  if (NS_SUCCEEDED(GetCFURL(&url))) {
    nsresult rv = CocoaFileUtils::SetFileTypeCode(url, aFileType);
    ::CFRelease(url);
    return rv;
  }
  return NS_ERROR_FAILURE;
}
  
NS_IMETHODIMP
nsLocalFile::GetFileCreator(OSType *aFileCreator)
{
  CFURLRef url;
  if (NS_SUCCEEDED(GetCFURL(&url))) {
    nsresult rv = CocoaFileUtils::GetFileCreatorCode(url, aFileCreator);
    ::CFRelease(url);
    return rv;
  }
  return NS_ERROR_FAILURE;
}

NS_IMETHODIMP
nsLocalFile::SetFileCreator(OSType aFileCreator)
{
  CFURLRef url;
  if (NS_SUCCEEDED(GetCFURL(&url))) {
    nsresult rv = CocoaFileUtils::SetFileCreatorCode(url, aFileCreator);
    ::CFRelease(url);
    return rv;
  }
  return NS_ERROR_FAILURE;
}
  
NS_IMETHODIMP
nsLocalFile::LaunchWithDoc(nsIFile *aDocToLoad, bool aLaunchInBackground)
{    
  bool isExecutable;
  nsresult rv = IsExecutable(&isExecutable);
  if (NS_FAILED(rv))
    return rv;
  if (!isExecutable)
    return NS_ERROR_FILE_EXECUTION_FAILED;

  FSRef appFSRef, docFSRef;
  rv = GetFSRef(&appFSRef);
  if (NS_FAILED(rv))
    return rv;

  if (aDocToLoad) {
    nsCOMPtr<nsILocalFileMac> macDoc = do_QueryInterface(aDocToLoad);
    rv = macDoc->GetFSRef(&docFSRef);
    if (NS_FAILED(rv))
      return rv;
  }

  LSLaunchFlags theLaunchFlags = kLSLaunchDefaults;
  LSLaunchFSRefSpec thelaunchSpec;

  if (aLaunchInBackground)
    theLaunchFlags |= kLSLaunchDontSwitch;
  memset(&thelaunchSpec, 0, sizeof(LSLaunchFSRefSpec));

  thelaunchSpec.appRef = &appFSRef;
  if (aDocToLoad) {
    thelaunchSpec.numDocs = 1;
    thelaunchSpec.itemRefs = &docFSRef;
  }
  thelaunchSpec.launchFlags = theLaunchFlags;

  OSErr err = ::LSOpenFromRefSpec(&thelaunchSpec, nullptr);
  if (err != noErr)
    return MacErrorMapper(err);

  return NS_OK;
}

NS_IMETHODIMP
nsLocalFile::OpenDocWithApp(nsIFile *aAppToOpenWith, bool aLaunchInBackground)
{
  FSRef docFSRef;
  nsresult rv = GetFSRef(&docFSRef);
  if (NS_FAILED(rv))
    return rv;

  if (!aAppToOpenWith) {
    OSErr err = ::LSOpenFSRef(&docFSRef, nullptr);
    return MacErrorMapper(err);
  }

  nsCOMPtr<nsILocalFileMac> appFileMac = do_QueryInterface(aAppToOpenWith, &rv);
  if (!appFileMac)
    return rv;

  bool isExecutable;
  rv = appFileMac->IsExecutable(&isExecutable);
  if (NS_FAILED(rv))
    return rv;
  if (!isExecutable)
    return NS_ERROR_FILE_EXECUTION_FAILED;

  FSRef appFSRef;
  rv = appFileMac->GetFSRef(&appFSRef);
  if (NS_FAILED(rv))
    return rv;

  LSLaunchFlags theLaunchFlags = kLSLaunchDefaults;
  LSLaunchFSRefSpec thelaunchSpec;

  if (aLaunchInBackground)
    theLaunchFlags |= kLSLaunchDontSwitch;
  memset(&thelaunchSpec, 0, sizeof(LSLaunchFSRefSpec));

  thelaunchSpec.appRef = &appFSRef;
  thelaunchSpec.numDocs = 1;
  thelaunchSpec.itemRefs = &docFSRef;
  thelaunchSpec.launchFlags = theLaunchFlags;

  OSErr err = ::LSOpenFromRefSpec(&thelaunchSpec, nullptr);
  if (err != noErr)
    return MacErrorMapper(err);

  return NS_OK;
}

NS_IMETHODIMP
nsLocalFile::IsPackage(bool *_retval)
{
  if (NS_WARN_IF(!_retval))
      return NS_ERROR_INVALID_ARG;
  *_retval = false;

  CFURLRef url;
  nsresult rv = GetCFURL(&url);
  if (NS_FAILED(rv))
    return rv;

  LSItemInfoRecord info;
  OSStatus status = ::LSCopyItemInfoForURL(url, kLSRequestBasicFlagsOnly, &info);

  ::CFRelease(url);

  if (status != noErr) {
    return NS_ERROR_FAILURE;
  }

  *_retval = !!(info.flags & kLSItemInfoIsPackage);

  return NS_OK;
}

NS_IMETHODIMP
nsLocalFile::GetBundleDisplayName(nsAString& outBundleName)
{
  bool isPackage = false;
  nsresult rv = IsPackage(&isPackage);
  if (NS_FAILED(rv) || !isPackage)
    return NS_ERROR_FAILURE;

  nsAutoString name;
  rv = GetLeafName(name);
  if (NS_FAILED(rv))
    return rv;

  int32_t length = name.Length();
  if (Substring(name, length - 4, length).EqualsLiteral(".app")) {
    // 4 characters in ".app"
    outBundleName = Substring(name, 0, length - 4);
  }
  else {
    outBundleName = name;
  }

  return NS_OK;
}

NS_IMETHODIMP
nsLocalFile::GetBundleIdentifier(nsACString& outBundleIdentifier)
{
  nsresult rv = NS_ERROR_FAILURE;

  CFURLRef urlRef;
  if (NS_SUCCEEDED(GetCFURL(&urlRef))) {
    CFBundleRef bundle = ::CFBundleCreate(nullptr, urlRef);
    if (bundle) {
      CFStringRef bundleIdentifier = ::CFBundleGetIdentifier(bundle);
      if (bundleIdentifier)
        rv = CFStringReftoUTF8(bundleIdentifier, outBundleIdentifier);
      ::CFRelease(bundle);
    }
    ::CFRelease(urlRef);
  }

  return rv;
}

NS_IMETHODIMP
nsLocalFile::GetBundleContentsLastModifiedTime(int64_t *aLastModTime)
{
  CHECK_mPath();
  if (NS_WARN_IF(!aLastModTime))
      return NS_ERROR_INVALID_ARG;

  bool isPackage = false;
  nsresult rv = IsPackage(&isPackage);
  if (NS_FAILED(rv) || !isPackage) {
    return GetLastModifiedTime(aLastModTime);
  }

  nsAutoCString infoPlistPath(mPath);
  infoPlistPath.AppendLiteral("/Contents/Info.plist");
  PRFileInfo64 info;
  if (PR_GetFileInfo64(infoPlistPath.get(), &info) != PR_SUCCESS) {
    return GetLastModifiedTime(aLastModTime);
  }
  int64_t modTime = int64_t(info.modifyTime);
  if (modTime == 0) {
    *aLastModTime = 0;
  } else {
    *aLastModTime = modTime / int64_t(PR_USEC_PER_MSEC);
  }

  return NS_OK;
}

NS_IMETHODIMP nsLocalFile::InitWithFile(nsIFile *aFile)
{
  if (NS_WARN_IF(!aFile))
      return NS_ERROR_INVALID_ARG;

  nsAutoCString nativePath;
  nsresult rv = aFile->GetNativePath(nativePath);
  if (NS_FAILED(rv))
    return rv;

  return InitWithNativePath(nativePath);
}

nsresult
NS_NewLocalFileWithFSRef(const FSRef* aFSRef, bool aFollowLinks, nsILocalFileMac** result)
{
  nsRefPtr<nsLocalFile> file = new nsLocalFile();

  file->SetFollowLinks(aFollowLinks);

  nsresult rv = file->InitWithFSRef(aFSRef);
  if (NS_FAILED(rv)) {
    return rv;
  }
  file.forget(result);
  return NS_OK;
}

nsresult
NS_NewLocalFileWithCFURL(const CFURLRef aURL, bool aFollowLinks, nsILocalFileMac** result)
{
  nsRefPtr<nsLocalFile> file = new nsLocalFile();

  file->SetFollowLinks(aFollowLinks);

  nsresult rv = file->InitWithCFURL(aURL);
  if (NS_FAILED(rv)) {
    return rv;
  }
  file.forget(result);
  return NS_OK;
}

#endif
