mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-01 14:45:29 +00:00
5c44f0195a
a=leaf. This is #ifdefed for Tru64 Unix, but if it causes bustage and I am not on IRC, you can get me at 919.859.3885
1293 lines
32 KiB
C++
1293 lines
32 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
/*
|
|
* The contents of this file are subject to the Netscape Public
|
|
* License Version 1.1 (the "License"); you may not use this file
|
|
* except in compliance with the License. You may obtain a copy of
|
|
* the License at http://www.mozilla.org/NPL/
|
|
*
|
|
* Software distributed under the License is distributed on an "AS
|
|
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
|
* implied. See the License for the specific language governing
|
|
* rights and limitations under the License.
|
|
*
|
|
* The Original Code is Mozilla Communicator client code,
|
|
* released March 31, 1998.
|
|
*
|
|
* The Initial Developer of the Original Code is Netscape Communications
|
|
* Corporation. Portions created by Netscape are
|
|
* Copyright (C) 1998-1999 Netscape Communications Corporation. All
|
|
* Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
* Mike Shaver <shaver@mozilla.org>
|
|
* Christopher Blizzard <blizzard@mozilla.org>
|
|
* Jason Eager <jce2@po.cwru.edu>
|
|
* Stuart Parmenter <pavlov@netscape.com>
|
|
*/
|
|
|
|
/*
|
|
* Implementation of nsIFile for ``Unixy'' systems.
|
|
*/
|
|
|
|
/* we're going to need some autoconf loving, I can just tell */
|
|
#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>
|
|
#ifdef XP_BEOS
|
|
#include <Path.h>
|
|
#include <Entry.h>
|
|
#endif
|
|
|
|
#include "nsCRT.h"
|
|
#include "nsCOMPtr.h"
|
|
#include "nsIAllocator.h"
|
|
#include "nsIFile.h"
|
|
#include "nsILocalFile.h"
|
|
#include "nsLocalFileUnix.h"
|
|
#include "nsIComponentManager.h"
|
|
#include "nsString.h"
|
|
#include "prproces.h"
|
|
|
|
// we need these for statfs()
|
|
|
|
#ifdef HAVE_SYS_STATVFS_H
|
|
#if defined(__osf__) && defined(__DECCXX)
|
|
extern "C" int statvfs(const char *, struct statvfs *);
|
|
#endif
|
|
#include <sys/statvfs.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_SYS_STATFS_H
|
|
#include <sys/statfs.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_STATVFS
|
|
#define STATFS statvfs
|
|
#else
|
|
#define STATFS statfs
|
|
#endif
|
|
|
|
// On some platforms file/directory name comparisons need to
|
|
// be case-blind.
|
|
#if defined(VMS)
|
|
#define FILE_STRCMP strcasecmp
|
|
#define FILE_STRNCMP strncasecmp
|
|
#else
|
|
#define FILE_STRCMP strcmp
|
|
#define FILE_STRNCMP strncmp
|
|
#endif
|
|
|
|
#define VALIDATE_STAT_CACHE() \
|
|
PR_BEGIN_MACRO \
|
|
if (!mHaveCachedStat) { \
|
|
FillStatCache(); \
|
|
if (!mHaveCachedStat) \
|
|
return NSRESULT_FOR_ERRNO(); \
|
|
} \
|
|
PR_END_MACRO
|
|
|
|
#define CHECK_mPath() \
|
|
PR_BEGIN_MACRO \
|
|
if (!(const char *)mPath) \
|
|
return NS_ERROR_NOT_INITIALIZED; \
|
|
PR_END_MACRO
|
|
|
|
#define mLL_II2L(hi, lo, res) \
|
|
LL_I2L(res, hi); \
|
|
LL_SHL(res, res, 32); \
|
|
LL_ADD(res, res, lo);
|
|
|
|
#define mLL_L2II(a64, hi, lo) \
|
|
PR_BEGIN_MACRO \
|
|
PRInt64 tmp; \
|
|
LL_SHR(tmp, a64, 32); \
|
|
LL_L2I(hi, tmp); \
|
|
LL_SHL(tmp, a64, 32); \
|
|
LL_SHR(tmp, tmp, 32); \
|
|
LL_L2I(lo, tmp); \
|
|
PR_END_MACRO
|
|
|
|
/* directory enumerator */
|
|
class NS_COM
|
|
nsDirEnumeratorUnix : public nsISimpleEnumerator
|
|
{
|
|
public:
|
|
nsDirEnumeratorUnix();
|
|
virtual ~nsDirEnumeratorUnix();
|
|
|
|
// nsISupports interface
|
|
NS_DECL_ISUPPORTS
|
|
|
|
// nsISimpleEnumerator interface
|
|
NS_DECL_NSISIMPLEENUMERATOR
|
|
|
|
NS_IMETHOD Init(nsIFile *parent, PRBool ignored);
|
|
protected:
|
|
NS_IMETHOD GetNextEntry();
|
|
|
|
DIR *mDir;
|
|
struct dirent *mEntry;
|
|
nsXPIDLCString mParentPath;
|
|
};
|
|
|
|
nsDirEnumeratorUnix::nsDirEnumeratorUnix() :
|
|
mDir(nsnull), mEntry(nsnull)
|
|
{
|
|
NS_INIT_REFCNT();
|
|
}
|
|
|
|
nsDirEnumeratorUnix::~nsDirEnumeratorUnix()
|
|
{
|
|
if (mDir)
|
|
closedir(mDir);
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS1(nsDirEnumeratorUnix, nsISimpleEnumerator)
|
|
|
|
NS_IMETHODIMP
|
|
nsDirEnumeratorUnix::Init(nsIFile *parent, PRBool resolveSymlinks /*ignored*/)
|
|
{
|
|
nsXPIDLCString dirPath;
|
|
if (NS_FAILED(parent->GetPath(getter_Copies(dirPath))) ||
|
|
(const char *)dirPath == 0)
|
|
return NS_ERROR_FILE_INVALID_PATH;
|
|
|
|
if (NS_FAILED(parent->GetPath(getter_Copies(mParentPath))))
|
|
return NS_ERROR_FAILURE;
|
|
|
|
mDir = opendir(dirPath);
|
|
if (!mDir)
|
|
return NSRESULT_FOR_ERRNO();
|
|
return GetNextEntry();
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsDirEnumeratorUnix::HasMoreElements(PRBool *result)
|
|
{
|
|
*result = mDir && mEntry;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsDirEnumeratorUnix::GetNext(nsISupports **_retval)
|
|
{
|
|
nsresult rv;
|
|
if (!mDir || !mEntry) {
|
|
*_retval = nsnull;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsCOMPtr<nsILocalFile> file = new nsLocalFile();
|
|
if (!file)
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
|
|
if (NS_FAILED(rv = file->InitWithPath(mParentPath)) ||
|
|
NS_FAILED(rv = file->Append(mEntry->d_name))) {
|
|
return rv;
|
|
}
|
|
*_retval = file;
|
|
NS_ADDREF(*_retval);
|
|
return GetNextEntry();
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
nsLocalFile::nsLocalFile() :
|
|
mHaveCachedStat(PR_FALSE)
|
|
{
|
|
mPath = "";
|
|
NS_INIT_REFCNT();
|
|
}
|
|
|
|
nsLocalFile::~nsLocalFile()
|
|
{
|
|
}
|
|
|
|
NS_IMPL_THREADSAFE_ISUPPORTS2(nsLocalFile, nsILocalFile, nsIFile)
|
|
|
|
nsresult
|
|
nsLocalFile::nsLocalFileConstructor(nsISupports *outer, const nsIID &aIID, void **aInstancePtr)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(aInstancePtr);
|
|
NS_ENSURE_NO_AGGREGATION(outer);
|
|
|
|
*aInstancePtr = 0;
|
|
|
|
nsCOMPtr<nsIFile> inst = new nsLocalFile();
|
|
if (!inst)
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
return inst->QueryInterface(aIID, aInstancePtr);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsLocalFile::Clone(nsIFile **file)
|
|
{
|
|
nsresult rv;
|
|
NS_ENSURE_ARG(file);
|
|
|
|
*file = nsnull;
|
|
|
|
nsCOMPtr<nsILocalFile> localFile = new nsLocalFile();
|
|
if (localFile == nsnull)
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
|
|
rv = localFile->InitWithPath(mPath);
|
|
|
|
if (NS_FAILED(rv))
|
|
return rv;
|
|
|
|
*file = localFile;
|
|
NS_ADDREF(*file);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsLocalFile::InitWithPath(const char *filePath)
|
|
{
|
|
NS_ENSURE_ARG(filePath);
|
|
mPath = filePath;
|
|
InvalidateCache();
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsLocalFile::CreateAllAncestors(PRUint32 permissions)
|
|
{
|
|
/* <jband> I promise to play nice */
|
|
char *buffer = NS_CONST_CAST(char *, (const char *)mPath),
|
|
*ptr = buffer;
|
|
|
|
#ifdef DEBUG_NSIFILE
|
|
fprintf(stderr, "nsIFile: before: %s\n", buffer);
|
|
#endif
|
|
|
|
while ((ptr = strchr(ptr + 1, '/'))) {
|
|
/*
|
|
* Sequences of '/' are equivalent to a single '/'.
|
|
*/
|
|
if (ptr[1] == '/')
|
|
continue;
|
|
|
|
/*
|
|
* If the path has a trailing slash, don't make the last component here,
|
|
* because we'll get EEXISTS in Create when we try to build the final
|
|
* component again, and it's easier to condition the logic here than
|
|
* there.
|
|
*/
|
|
if (!ptr[1])
|
|
break;
|
|
/* Temporarily NUL-terminate here */
|
|
*ptr = '\0';
|
|
#ifdef DEBUG_NSIFILE
|
|
fprintf(stderr, "nsIFile: mkdir(\"%s\")\n", buffer);
|
|
#endif
|
|
int result = mkdir(buffer, permissions);
|
|
/* Put the / back before we (maybe) return */
|
|
*ptr = '/';
|
|
|
|
/*
|
|
* We could get EEXISTS 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 (result == -1 && errno != EEXIST)
|
|
return NSRESULT_FOR_ERRNO();
|
|
}
|
|
|
|
#ifdef DEBUG_NSIFILE
|
|
fprintf(stderr, "nsIFile: after: %s\n", buffer);
|
|
#endif
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsLocalFile::OpenNSPRFileDesc(PRInt32 flags, PRInt32 mode, PRFileDesc **_retval)
|
|
{
|
|
CHECK_mPath();
|
|
|
|
*_retval = PR_Open(mPath, flags, mode);
|
|
|
|
if (*_retval)
|
|
return NS_OK;
|
|
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsLocalFile::OpenANSIFileDesc(const char *mode, FILE * *_retval)
|
|
{
|
|
CHECK_mPath();
|
|
|
|
*_retval = fopen(mPath, mode);
|
|
|
|
if (*_retval)
|
|
return NS_OK;
|
|
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsLocalFile::Create(PRUint32 type, PRUint32 permissions)
|
|
{
|
|
CHECK_mPath();
|
|
if (type != NORMAL_FILE_TYPE && type != DIRECTORY_TYPE)
|
|
return NS_ERROR_FILE_UNKNOWN_TYPE;
|
|
|
|
int result;
|
|
/* use creat(2) for NORMAL_FILE, mkdir(2) for DIRECTORY */
|
|
#if defined(VMS)
|
|
int (*creationFunc)(const char *, mode_t, ...) =
|
|
#else
|
|
int (*creationFunc)(const char *, mode_t) =
|
|
#endif
|
|
type == NORMAL_FILE_TYPE ? creat : mkdir;
|
|
|
|
result = creationFunc((const char *)mPath, permissions);
|
|
|
|
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", (const char *)mPath);
|
|
#endif
|
|
result = creationFunc((const char *)mPath, permissions);
|
|
}
|
|
|
|
/* creat(2) leaves the file open */
|
|
if (result >= 0 && type == NORMAL_FILE_TYPE) {
|
|
close(result);
|
|
return NS_OK;
|
|
}
|
|
|
|
return NSRESULT_FOR_RETURN(result);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsLocalFile::Append(const char *fragment)
|
|
{
|
|
NS_ENSURE_ARG(fragment);
|
|
CHECK_mPath();
|
|
char * newPath = (char *)nsAllocator::Alloc(strlen(mPath) +
|
|
strlen(fragment) + 2);
|
|
if (!newPath)
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
strcpy(newPath, mPath);
|
|
strcat(newPath, "/");
|
|
strcat(newPath, fragment);
|
|
mPath = newPath;
|
|
InvalidateCache();
|
|
nsAllocator::Free(newPath);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsLocalFile::Normalize()
|
|
{
|
|
char resolved_path[PATH_MAX];
|
|
char *resolved_path_ptr = NULL;
|
|
CHECK_mPath();
|
|
#ifdef XP_BEOS
|
|
BEntry be_e((const char*)mPath, true);
|
|
BPath be_p;
|
|
status_t err;
|
|
if ((err = be_e.GetPath(&be_p)) == B_OK) {
|
|
resolved_path_ptr = be_p.Path();
|
|
};
|
|
#else
|
|
resolved_path_ptr = realpath((const char *)mPath, resolved_path);
|
|
#endif
|
|
// if there is an error, the return is null.
|
|
if (resolved_path_ptr == NULL) {
|
|
return NSRESULT_FOR_ERRNO();
|
|
}
|
|
// nsXPIDLCString will copy.
|
|
mPath = resolved_path;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsLocalFile::GetLeafNameRaw(const char **_retval)
|
|
{
|
|
CHECK_mPath();
|
|
const char *leafName = strrchr((const char *)mPath, '/');
|
|
if (!leafName)
|
|
return NS_ERROR_FILE_INVALID_PATH;
|
|
*_retval = leafName+1;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsLocalFile::GetLeafName(char **aLeafName)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(aLeafName);
|
|
nsresult rv;
|
|
const char *leafName;
|
|
if (NS_FAILED(rv = GetLeafNameRaw(&leafName)))
|
|
return rv;
|
|
|
|
*aLeafName = nsCRT::strdup(leafName);
|
|
if (!*aLeafName)
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsLocalFile::SetLeafName(const char *aLeafName)
|
|
{
|
|
NS_ENSURE_ARG(aLeafName);
|
|
CHECK_mPath();
|
|
|
|
nsresult rv;
|
|
char *leafName;
|
|
if (NS_FAILED(rv = GetLeafNameRaw((const char**)&leafName)))
|
|
return rv;
|
|
char* newPath = (char *)nsAllocator::Alloc(strlen(mPath) +
|
|
strlen(aLeafName) + 2);
|
|
*leafName = 0;
|
|
|
|
strcpy(newPath, mPath);
|
|
strcat(newPath, "/");
|
|
strcat(newPath, aLeafName);
|
|
mPath = newPath;
|
|
InvalidateCache();
|
|
nsAllocator::Free(newPath);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsLocalFile::GetPath(char **_retval)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(_retval);
|
|
|
|
if (!(const char *)mPath) {
|
|
*_retval = nsnull;
|
|
return NS_OK;
|
|
}
|
|
|
|
*_retval = nsCRT::strdup((const char *)mPath);
|
|
if (!*_retval)
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsLocalFile::CopyTo(nsIFile *newParent, const char *newName)
|
|
{
|
|
nsresult rv;
|
|
|
|
// check to make sure that we have a new parent
|
|
// or have a new name
|
|
if (newParent == nsnull && newName == nsnull)
|
|
return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST;
|
|
|
|
// check to make sure that this has been initialized properly
|
|
CHECK_mPath();
|
|
|
|
// check to see if we are a directory or if we are a file
|
|
PRBool isDirectory;
|
|
IsDirectory(&isDirectory);
|
|
|
|
if (isDirectory)
|
|
{
|
|
// XXX
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
else
|
|
{
|
|
// ok, we're a file so this should be easy as pie.
|
|
// check to see if our target exists
|
|
PRBool targetExists;
|
|
newParent->Exists(&targetExists);
|
|
// check to see if we need to create it
|
|
if (targetExists == PR_FALSE)
|
|
{
|
|
// XXX create the new directory with some permissions
|
|
rv = newParent->Create(DIRECTORY_TYPE, 0755);
|
|
if (NS_FAILED(rv))
|
|
return rv;
|
|
}
|
|
// make sure that the target is actually a directory
|
|
PRBool targetIsDirectory;
|
|
newParent->IsDirectory(&targetIsDirectory);
|
|
if (targetIsDirectory == PR_FALSE)
|
|
return NS_ERROR_FILE_DESTINATION_NOT_DIR;
|
|
// ok, we got this far. create the name of the new file.
|
|
nsXPIDLCString leafName;
|
|
nsXPIDLCString newPath;
|
|
const char *tmpConstChar;
|
|
char *tmpChar;
|
|
// get the leaf name of the new file if a name isn't supplied.
|
|
if (newName == nsnull)
|
|
{
|
|
if (NS_FAILED(rv = GetLeafNameRaw(&tmpConstChar)))
|
|
return rv;
|
|
leafName = tmpConstChar;
|
|
}
|
|
else
|
|
{
|
|
leafName = newName;
|
|
}
|
|
// get the path name
|
|
if (NS_FAILED(rv = newParent->GetPath(&tmpChar)))
|
|
return rv;
|
|
|
|
// this is the full path to the dir of the new file
|
|
newPath = tmpChar;
|
|
nsAllocator::Free(tmpChar);
|
|
|
|
// create the final name
|
|
char *newPathName;
|
|
newPathName = (char *)nsAllocator::Alloc(strlen(newPath) + strlen(leafName) + 2);
|
|
if (!newPathName)
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
|
|
strcpy(newPathName, newPath);
|
|
strcat(newPathName, "/");
|
|
strcat(newPathName, leafName);
|
|
|
|
#ifdef DEBUG_blizzard
|
|
printf("nsLocalFile::CopyTo() %s -> %s\n", (const char *)mPath, newPathName);
|
|
#endif
|
|
|
|
nsXPIDLCString newPathNameAuto;
|
|
newPathNameAuto = newPathName;
|
|
nsAllocator::Free(newPathName);
|
|
|
|
// actually create the file.
|
|
|
|
nsLocalFile *newFile = new nsLocalFile();
|
|
newFile->AddRef(); // we own this.
|
|
if (newFile == nsnull)
|
|
{
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
rv = newFile->InitWithPath(newPathNameAuto);
|
|
|
|
if (NS_FAILED(rv))
|
|
{
|
|
NS_RELEASE(newFile);
|
|
return rv;
|
|
}
|
|
|
|
// get the old permissions
|
|
PRUint32 myPerms;
|
|
GetPermissions(&myPerms);
|
|
// create the new file with the same permissions
|
|
rv = newFile->Create(NORMAL_FILE_TYPE, myPerms);
|
|
if (NS_FAILED(rv))
|
|
{
|
|
NS_RELEASE(newFile);
|
|
return rv;
|
|
}
|
|
|
|
// open the new file.
|
|
|
|
PRInt32 openFlags = PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE;
|
|
PRInt32 modeFlags = myPerms;
|
|
PRFileDesc *newFD = nsnull;
|
|
|
|
rv = newFile->OpenNSPRFileDesc(openFlags, modeFlags, &newFD);
|
|
if (NS_FAILED(rv))
|
|
{
|
|
NS_RELEASE(newFile);
|
|
return rv;
|
|
}
|
|
if (newFD == nsnull)
|
|
{
|
|
NS_RELEASE(newFile);
|
|
NSRESULT_FOR_ERRNO();
|
|
}
|
|
|
|
// open the old file, too
|
|
|
|
openFlags = PR_RDONLY;
|
|
PRFileDesc *oldFD = nsnull;
|
|
|
|
rv = OpenNSPRFileDesc(openFlags, modeFlags, &oldFD);
|
|
// make sure to clean up properly
|
|
if (NS_FAILED(rv))
|
|
{
|
|
NS_RELEASE(newFile);
|
|
PR_Close(newFD);
|
|
return rv;
|
|
}
|
|
if (newFD == nsnull)
|
|
{
|
|
NS_RELEASE(newFile);
|
|
PR_Close(newFD);
|
|
NSRESULT_FOR_ERRNO();
|
|
}
|
|
|
|
// get the length of the file
|
|
|
|
PRStatus status;
|
|
PRFileInfo fileInfo;
|
|
status = PR_GetFileInfo(mPath, &fileInfo);
|
|
if (status != PR_SUCCESS)
|
|
{
|
|
NS_RELEASE(newFile);
|
|
PR_Close(newFD);
|
|
NSRESULT_FOR_ERRNO();
|
|
}
|
|
|
|
// get the size of the file.
|
|
|
|
PROffset32 fileSize;
|
|
fileSize = fileInfo.size;
|
|
PRInt32 bytesRead = 0;
|
|
PRInt32 bytesWritten = 0;
|
|
PRInt32 totalRead = 0;
|
|
PRInt32 totalWritten = 0;
|
|
|
|
char buf[BUFSIZ];
|
|
|
|
while(1)
|
|
{
|
|
bytesRead = PR_Read(oldFD, &buf, BUFSIZ);
|
|
if (bytesRead == 0)
|
|
break;
|
|
if (bytesRead == -1)
|
|
return NS_ERROR_FAILURE;
|
|
totalRead += bytesRead;
|
|
bytesWritten = PR_Write(newFD, &buf, bytesRead);
|
|
if (bytesWritten == -1)
|
|
return NS_ERROR_FAILURE;
|
|
totalWritten += bytesWritten;
|
|
}
|
|
|
|
#ifdef DEBUG_blizzard
|
|
printf("read %d bytes, wrote %d bytes\n",
|
|
totalRead, totalWritten);
|
|
#endif
|
|
|
|
// close the files
|
|
PR_Close(newFD);
|
|
PR_Close(oldFD);
|
|
|
|
// free our resources
|
|
NS_RELEASE(newFile);
|
|
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsLocalFile::CopyToFollowingLinks(nsIFile *newParent, const char *newName)
|
|
{
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsLocalFile::MoveTo(nsIFile *newParent, const char *newName)
|
|
{
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsLocalFile::Delete(PRBool recursive)
|
|
{
|
|
VALIDATE_STAT_CACHE();
|
|
PRBool isDir = S_ISDIR(mCachedStat.st_mode);
|
|
|
|
/* XXX ?
|
|
* if (!isDir && recursive)
|
|
* return NS_ERROR_INVALID_ARG;
|
|
*/
|
|
InvalidateCache();
|
|
|
|
if (isDir) {
|
|
if (recursive) {
|
|
nsDirEnumeratorUnix *dir = new nsDirEnumeratorUnix();
|
|
if (!dir)
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
|
|
nsresult rv = dir->Init(this, PR_FALSE);
|
|
if (NS_FAILED(rv)) {
|
|
delete dir;
|
|
return rv;
|
|
}
|
|
|
|
nsCOMPtr<nsISimpleEnumerator> iterator;
|
|
iterator = do_QueryInterface(dir, &rv);
|
|
if (NS_FAILED(rv)) {
|
|
delete dir;
|
|
return rv;
|
|
}
|
|
|
|
PRBool more;
|
|
rv = iterator->HasMoreElements(&more);
|
|
while (NS_SUCCEEDED(rv) && more) {
|
|
nsCOMPtr<nsISupports> item;
|
|
nsCOMPtr<nsIFile> file;
|
|
rv = iterator->GetNext(getter_AddRefs(item));
|
|
if (NS_FAILED(rv))
|
|
return NS_ERROR_FAILURE;
|
|
file = do_QueryInterface(item, &rv);
|
|
if (NS_FAILED(rv))
|
|
return NS_ERROR_FAILURE;
|
|
if (NS_FAILED(rv = file->Delete(recursive)))
|
|
return rv;
|
|
rv = iterator->HasMoreElements(&more);
|
|
}
|
|
}
|
|
|
|
if (rmdir(mPath) == -1)
|
|
return NSRESULT_FOR_ERRNO();
|
|
} else {
|
|
if (unlink(mPath) == -1)
|
|
return NSRESULT_FOR_ERRNO();
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsLocalFile::GetLastModificationDate(PRInt64 *aLastModificationDate)
|
|
{
|
|
NS_ENSURE_ARG(aLastModificationDate);
|
|
CHECK_mPath();
|
|
PRFileInfo64 info;
|
|
if (PR_GetFileInfo64(mPath, &info) != PR_SUCCESS) {
|
|
return NSRESULT_FOR_ERRNO();
|
|
}
|
|
// PRTime is a 64 bit value
|
|
*aLastModificationDate = info.modifyTime;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsLocalFile::SetLastModificationDate(PRInt64 aLastModificationDate)
|
|
{
|
|
int result;
|
|
if (aLastModificationDate) {
|
|
VALIDATE_STAT_CACHE();
|
|
struct utimbuf ut;
|
|
ut.actime = mCachedStat.st_atime;
|
|
|
|
// convert PRTime microsecs to unix seconds since the epoch
|
|
double dTime;
|
|
LL_L2D(dTime, aLastModificationDate);
|
|
ut.modtime = (time_t)( (PRUint32)(dTime * 1e-6 + 0.5) );
|
|
result = utime(mPath, &ut);
|
|
} else {
|
|
result = utime(mPath, NULL);
|
|
}
|
|
InvalidateCache();
|
|
return NSRESULT_FOR_RETURN(result);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsLocalFile::GetLastModificationDateOfLink(PRInt64 *aLastModificationDateOfLink)
|
|
{
|
|
NS_ENSURE_ARG(aLastModificationDateOfLink);
|
|
struct stat sbuf;
|
|
if (lstat(mPath, &sbuf) == -1)
|
|
return NSRESULT_FOR_ERRNO();
|
|
mLL_II2L(0, (PRUint32)sbuf.st_mtime, *aLastModificationDateOfLink);
|
|
return NS_OK;
|
|
}
|
|
|
|
/*
|
|
* utime(2) may or may not dereference symlinks, joy.
|
|
*/
|
|
NS_IMETHODIMP
|
|
nsLocalFile::SetLastModificationDateOfLink(PRInt64 aLastModificationDateOfLink)
|
|
{
|
|
return SetLastModificationDate(aLastModificationDateOfLink);
|
|
}
|
|
|
|
/*
|
|
* 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(PRUint32 *aPermissions)
|
|
{
|
|
NS_ENSURE_ARG(aPermissions);
|
|
VALIDATE_STAT_CACHE();
|
|
*aPermissions = NORMALIZE_PERMS(mCachedStat.st_mode);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsLocalFile::GetPermissionsOfLink(PRUint32 *aPermissionsOfLink)
|
|
{
|
|
NS_ENSURE_ARG(aPermissionsOfLink);
|
|
struct stat sbuf;
|
|
if (lstat(mPath, &sbuf) == -1)
|
|
return NSRESULT_FOR_ERRNO();
|
|
*aPermissionsOfLink = NORMALIZE_PERMS(sbuf.st_mode);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsLocalFile::SetPermissions(PRUint32 aPermissions)
|
|
{
|
|
InvalidateCache();
|
|
return NSRESULT_FOR_RETURN(chmod(mPath, aPermissions));
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsLocalFile::SetPermissionsOfLink(PRUint32 aPermissions)
|
|
{
|
|
return SetPermissions(aPermissions);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsLocalFile::GetFileSize(PRInt64 *aFileSize)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(aFileSize);
|
|
VALIDATE_STAT_CACHE();
|
|
|
|
/* XXX autoconf for and use stat64 if available */
|
|
if( S_ISDIR(mCachedStat.st_mode) )
|
|
{
|
|
mLL_II2L(0, (PRUint32)0, *aFileSize);
|
|
}
|
|
else
|
|
{
|
|
mLL_II2L(0, (PRUint32)mCachedStat.st_size, *aFileSize);
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsLocalFile::SetFileSize(PRInt64 aFileSize)
|
|
{
|
|
PRInt32 hi, lo;
|
|
mLL_L2II(aFileSize, hi, lo);
|
|
/* XXX truncate64? */
|
|
InvalidateCache();
|
|
if (truncate((const char *)mPath, (off_t)lo) == -1)
|
|
return NSRESULT_FOR_ERRNO();
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsLocalFile::GetFileSizeOfLink(PRInt64 *aFileSize)
|
|
{
|
|
NS_ENSURE_ARG(aFileSize);
|
|
struct stat sbuf;
|
|
if (lstat(mPath, &sbuf) == -1)
|
|
return NSRESULT_FOR_ERRNO();
|
|
/* XXX autoconf for and use lstat64 if available */
|
|
mLL_II2L(0, (PRInt32)sbuf.st_size, *aFileSize);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsLocalFile::GetDiskSpaceAvailable(PRInt64 *aDiskSpaceAvailable)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(aDiskSpaceAvailable);
|
|
|
|
PRInt64 bytesFree = 0;
|
|
|
|
//These systems have the operations necessary to check disk space.
|
|
|
|
#if defined(HAVE_SYS_STATFS_H) || defined(HAVE_SYS_STATVFS_H)
|
|
|
|
// 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, &fs_buf) < 0)
|
|
// The call to STATFS failed.
|
|
{
|
|
#ifdef DEBUG
|
|
printf("ERROR: GetDiskSpaceAvailable: STATFS call FAILED. \n");
|
|
#endif
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
#ifdef DEBUG_DISK_SPACE
|
|
printf("DiskSpaceAvailable: %d bytes\n",
|
|
fs_buf.f_bsize * (fs_buf.f_bavail - 1));
|
|
#endif
|
|
|
|
// The number of Bytes free = The number of free blocks available to
|
|
// a non-superuser, minus one as a fudge factor, multiplied by the size
|
|
// of the beforementioned blocks.
|
|
|
|
LL_I2L( bytesFree, (fs_buf.f_bsize * (fs_buf.f_bavail - 1) ) );
|
|
*aDiskSpaceAvailable = bytesFree;
|
|
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 /* HAVE_SYS_STATFS_H or HAVE_SYS_STATVFS_H */
|
|
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsLocalFile::GetParent(nsIFile **aParent)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(aParent);
|
|
*aParent = nsnull;
|
|
|
|
CHECK_mPath();
|
|
|
|
nsCString parentPath = NS_STATIC_CAST(const char*, mPath);
|
|
|
|
// check to see whether or not we need to cut off any trailing
|
|
// slashes
|
|
|
|
PRInt32 offset = parentPath.RFindChar('/');
|
|
PRInt32 length = parentPath.Length();
|
|
|
|
// eat of trailing slash marks
|
|
while ((length > 1) && offset && (length == (offset + 1))) {
|
|
parentPath.Truncate(offset);
|
|
offset = parentPath.RFindChar('/');
|
|
length = parentPath.Length();
|
|
}
|
|
|
|
if (offset == -1)
|
|
return NS_ERROR_FILE_UNRECOGNIZED_PATH;
|
|
|
|
// for the case where we are at '/'
|
|
if (offset == 0)
|
|
offset++;
|
|
parentPath.Truncate(offset);
|
|
|
|
nsCOMPtr<nsILocalFile> localFile;
|
|
nsresult rv = NS_NewLocalFile(parentPath.GetBuffer(), getter_AddRefs(localFile));
|
|
|
|
if(NS_SUCCEEDED(rv) && localFile)
|
|
{
|
|
return localFile->QueryInterface(NS_GET_IID(nsIFile), (void**)aParent);
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
/*
|
|
* The results of Exists, isWritable and isReadable are not cached.
|
|
*/
|
|
|
|
NS_IMETHODIMP
|
|
nsLocalFile::Exists(PRBool *_retval)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(_retval);
|
|
PRBool accessOK;
|
|
*_retval = accessOK = (access(mPath, F_OK) == 0);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsLocalFile::IsWritable(PRBool *_retval)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(_retval);
|
|
PRBool accessOK;
|
|
*_retval = accessOK = (access(mPath, W_OK) == 0);
|
|
if (accessOK || errno == EACCES)
|
|
return NS_OK;
|
|
return NSRESULT_FOR_ERRNO();
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsLocalFile::IsReadable(PRBool *_retval)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(_retval);
|
|
PRBool accessOK;
|
|
*_retval = accessOK = (access(mPath, R_OK) == 0);
|
|
if (accessOK || errno == EACCES)
|
|
return NS_OK;
|
|
return NSRESULT_FOR_ERRNO();
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsLocalFile::IsExecutable(PRBool *_retval)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(_retval);
|
|
PRBool accessOK;
|
|
*_retval = accessOK = (access(mPath, X_OK) == 0);
|
|
if (accessOK || errno == EACCES)
|
|
return NS_OK;
|
|
return NSRESULT_FOR_ERRNO();
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsLocalFile::IsDirectory(PRBool *_retval)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(_retval);
|
|
VALIDATE_STAT_CACHE();
|
|
*_retval = S_ISDIR(mCachedStat.st_mode);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsLocalFile::IsFile(PRBool *_retval)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(_retval);
|
|
VALIDATE_STAT_CACHE();
|
|
*_retval = S_ISREG(mCachedStat.st_mode);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsLocalFile::IsHidden(PRBool *_retval)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(_retval);
|
|
nsresult rv;
|
|
const char *leafName;
|
|
if (NS_FAILED(rv = GetLeafNameRaw(&leafName)))
|
|
return rv;
|
|
*_retval = (leafName[0] == '.');
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsLocalFile::IsSymlink(PRBool *_retval)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(_retval);
|
|
VALIDATE_STAT_CACHE();
|
|
*_retval = S_ISLNK(mCachedStat.st_mode);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsLocalFile::IsSpecial(PRBool *_retval)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(_retval);
|
|
VALIDATE_STAT_CACHE();
|
|
*_retval = !(S_ISLNK(mCachedStat.st_mode) || S_ISREG(mCachedStat.st_mode) ||
|
|
S_ISDIR(mCachedStat.st_mode));
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsLocalFile::Equals(nsIFile *inFile, PRBool *_retval)
|
|
{
|
|
NS_ENSURE_ARG(inFile);
|
|
NS_ENSURE_ARG_POINTER(_retval);
|
|
*_retval = PR_FALSE;
|
|
|
|
nsresult rv;
|
|
nsXPIDLCString myPath, inPath;
|
|
|
|
if (NS_FAILED(rv = GetPath(getter_Copies(myPath))))
|
|
return rv;
|
|
if (NS_FAILED(rv = inFile->GetPath(getter_Copies(inPath))))
|
|
return rv;
|
|
*_retval = !FILE_STRCMP(inPath, myPath);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsLocalFile::Contains(nsIFile *inFile, PRBool recur, PRBool *_retval)
|
|
{
|
|
NS_ENSURE_ARG(inFile);
|
|
NS_ENSURE_ARG_POINTER(_retval);
|
|
|
|
nsXPIDLCString inPath;
|
|
nsresult rv;
|
|
|
|
*_retval = PR_FALSE;
|
|
|
|
if (NS_FAILED(rv = inFile->GetPath(getter_Copies(inPath))))
|
|
return rv;
|
|
|
|
ssize_t len = strlen(mPath);
|
|
|
|
if ( FILE_STRNCMP( mPath, inPath, len) == 0)
|
|
{
|
|
// now make sure that the |inFile|'s path has a trailing
|
|
// separator.
|
|
|
|
if (inPath[len] == '/')
|
|
{
|
|
*_retval = PR_TRUE;
|
|
}
|
|
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsLocalFile::GetTarget(char **_retval)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(_retval);
|
|
VALIDATE_STAT_CACHE();
|
|
if (!S_ISLNK(mCachedStat.st_mode))
|
|
return NS_ERROR_FILE_INVALID_PATH;
|
|
|
|
PRInt64 targetSize64;
|
|
if (NS_FAILED(GetFileSizeOfLink(&targetSize64)))
|
|
return NS_ERROR_FAILURE;
|
|
|
|
PRInt32 hi, lo;
|
|
mLL_L2II(targetSize64, hi, lo);
|
|
char *target = (char *)nsAllocator::Alloc(lo);
|
|
if (!target)
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
|
|
int result = readlink(mPath, target, (size_t)lo);
|
|
if (!result) {
|
|
*_retval = target;
|
|
return NS_OK;
|
|
}
|
|
nsAllocator::Free(target);
|
|
return NSRESULT_FOR_ERRNO();
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsLocalFile::Spawn(const char **args, PRUint32 count)
|
|
{
|
|
CHECK_mPath();
|
|
|
|
// status from our create process
|
|
PRStatus rv = PR_FAILURE;
|
|
|
|
// this is the argv that will hold the args that
|
|
char ** my_argv = NULL;
|
|
|
|
// make sure that when we allocate we have 1 greater than the
|
|
// count since we need to null terminate the list for the argv to
|
|
// pass into PR_CreateProcessDetached
|
|
my_argv = (char **)malloc(sizeof(char *) * (count + 2) );
|
|
if (!my_argv) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
// copy the args
|
|
PRUint32 i;
|
|
for (i=0; i < count; i++) {
|
|
my_argv[i+1] = (char *)args[i];
|
|
}
|
|
// we need to set argv[0] to the program name.
|
|
my_argv[0] = (char *)(const char *)mPath;
|
|
|
|
// null terminate the array
|
|
my_argv[count+1] = NULL;
|
|
|
|
rv = PR_CreateProcessDetached(mPath, my_argv, NULL, NULL);
|
|
|
|
// free up our argv
|
|
free(my_argv);
|
|
|
|
if (PR_SUCCESS == rv)
|
|
return NS_OK;
|
|
else
|
|
return NS_ERROR_FILE_EXECUTION_FAILED;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsLocalFile::GetDirectoryEntries(nsISimpleEnumerator **entries)
|
|
{
|
|
nsDirEnumeratorUnix *dir = new nsDirEnumeratorUnix();
|
|
if (!dir)
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
|
|
nsresult rv = dir->Init(this, PR_FALSE);
|
|
if (NS_FAILED(rv)) {
|
|
delete dir;
|
|
return rv;
|
|
}
|
|
|
|
/* QI needed? */
|
|
return dir->QueryInterface(NS_GET_IID(nsISimpleEnumerator),
|
|
(void **)entries);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsLocalFile::Load(PRLibrary **_retval)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(_retval);
|
|
*_retval = PR_LoadLibrary(mPath);
|
|
if (!*_retval)
|
|
return NS_ERROR_FAILURE;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
NS_NewLocalFile(const char* path, nsILocalFile* *result)
|
|
{
|
|
nsLocalFile* file = new nsLocalFile();
|
|
if (file == nsnull)
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
NS_ADDREF(file);
|
|
|
|
nsresult rv = file->InitWithPath(path);
|
|
if (NS_FAILED(rv)) {
|
|
NS_RELEASE(file);
|
|
return rv;
|
|
}
|
|
*result = file;
|
|
return NS_OK;
|
|
}
|