diff --git a/xpcom/io/nsLocalFile.h b/xpcom/io/nsLocalFile.h new file mode 100644 index 000000000000..b01d1e677287 --- /dev/null +++ b/xpcom/io/nsLocalFile.h @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* + * 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): + * Doug Turner + */ + +#ifndef _NS_LOCAL_FILE_H_ +#define _NS_LOCAL_FILE_H_ + +#define NS_LOCAL_FILE_CID {0x2e23e220, 0x60be, 0x11d3, {0x8c, 0x4a, 0x00, 0x00, 0x64, 0x65, 0x73, 0x74}} + +// nsXPComInit needs to know about how we are implmented, +// so here we will export it. Other users should not depend +// on this. + +#ifdef XP_PC +#include "nsLocalFileWin.h" +#else +#ifdef XP_UNIX +#include "nsLocalFileUnix.h" +#else +#error NOT_IMPLEMENTED +#endif /* XP_UNIX */ +#endif /* XP_PC */ +#endif diff --git a/xpcom/io/nsLocalFileUnix.cpp b/xpcom/io/nsLocalFileUnix.cpp new file mode 100644 index 000000000000..c27e9f3bcbc6 --- /dev/null +++ b/xpcom/io/nsLocalFileUnix.cpp @@ -0,0 +1,575 @@ +/* -*- 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 + */ + +/* + * Implementation of nsIFile for ``Unixy'' systems. + */ + +/* we're going to need some autoconf loving, I can just tell */ +#include +/* XXXautoconf for glibc */ +#define __USE_BSD +#include +#include +#include +#include +#include + +#include "nsCRT.h" +#include "nsCOMPtr.h" +#include "nsFileUtils.h" +#include "nsIAllocator.h" +#include "nsIDirectoryEnumerator.h" +#include "nsIFile.h" +#include "nsLocalFileUnix.h" + +#define FILL_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 + +nsLocalFile::nsLocalFile() : + mHaveCachedStat(PR_FALSE) +{ + mPath = ""; + NS_INIT_REFCNT(); +} + +nsLocalFile::~nsLocalFile() +{ +} + +NS_IMPL_ISUPPORTS1(nsLocalFile, nsIFile); + +nsresult +nsLocalFile::Create(nsISupports *outer, const nsIID &aIID, void **aInstancePtr) +{ + NS_ENSURE_ARG_POINTER(aInstancePtr); + NS_ENSURE_PROPER_AGGREGATION(outer, aIID); + + *aInstancePtr = 0; + + nsCOMPtr inst = new nsLocalFile(); + if (!inst) + return NS_ERROR_OUT_OF_MEMORY; + return inst->QueryInterface(aIID, aInstancePtr); +} + +NS_IMETHODIMP +nsLocalFile::InitWithKey(const char *fileKey) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsLocalFile::InitWithFile(nsIFile *file) +{ + NS_ENSURE_ARG(file); + + invalidateCache(); + if (NS_FAILED(file->GetPath(nsIFile::NATIVE_PATH, getter_Copies(mPath)))) + return NS_ERROR_FAILURE; + + if ((const char *)mPath == 0) + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::InitWithPath(PRUint32 pathType, const char *filePath) +{ + NS_ENSURE_ARG(filePath); + NS_ASSERTION(pathType == NATIVE_PATH || + pathType == UNIX_PATH || + pathType == NSPR_PATH, "unrecognized path type"); + + /* NATIVE_PATH == UNIX_PATH == NSPR_PATH for us */ + mPath = filePath; + invalidateCache(); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::createAllParentDirectories(PRUint32 permissions) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +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 */ + int (*creationFunc)(const char *, mode_t) = + type == NORMAL_FILE_TYPE ? creat : mkdir; + + result = creationFunc((const char *)mPath, permissions); + + if (result == -1 && errno == ENOENT) { + /* + * if we failed because of missing parent components, try to create them + * and then retry the original creation. + */ + if (NS_FAILED(createAllParentDirectories(permissions))) + return NS_ERROR_FAILURE; + 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::AppendPath(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() +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult +nsLocalFile::getLeafNameRaw(const char **_retval) +{ + CHECK_mPath(); + char *leafName = strrchr((const char *)mPath, '/'); + if (!leafName) + return NS_ERROR_FILE_INVALID_PATH; + *_retval = ++leafName; + 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::GetPath(PRUint32 pathType, char **_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + + if (!(const char *)mPath) { + *_retval = nsnull; + return NS_OK; + } + + NS_ASSERTION(pathType == NATIVE_PATH || + pathType == UNIX_PATH || + pathType == NSPR_PATH, "unrecognized path type"); + *_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) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +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::MoveToFollowingLinks(nsIFile *newParent, const char *newName) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsLocalFile::Execute(const char *args) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsLocalFile::Delete(PRBool recursive) +{ + FILL_STAT_CACHE(); + PRBool isDir = S_ISDIR(mCachedStat.st_mode); + + /* XXX ? + * if (!isDir && recursive) + * return NS_ERROR_INVALID_ARG; + */ + invalidateCache(); + + if (isDir) { + if (recursive) { + nsCOMPtr iterator; + nsresult rv = NS_NewDirectoryEnumerator(this, PR_FALSE, + getter_AddRefs(iterator)); + if (NS_FAILED(rv)) + return rv; + PRBool more; + rv = iterator->HasMoreElements(&more); + while (NS_SUCCEEDED(rv) && more) { + nsCOMPtr item; + nsCOMPtr 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(PRUint32 *aLastModificationDate) +{ + NS_ENSURE_ARG(aLastModificationDate); + FILL_STAT_CACHE(); + *aLastModificationDate = (PRUint32)mCachedStat.st_mtime; + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::SetLastModificationDate(PRUint32 aLastModificationDate) +{ + int result; + if (aLastModificationDate) { + FILL_STAT_CACHE(); + struct utimbuf ut; + ut.actime = mCachedStat.st_atime; + ut.modtime = (time_t)aLastModificationDate; + result = utime(mPath, &ut); + } else { + result = utime(mPath, NULL); + } + invalidateCache(); + return NSRESULT_FOR_RETURN(result); +} + +NS_IMETHODIMP +nsLocalFile::GetLastModificationDateOfLink(PRUint32 *aLastModificationDateOfLink) +{ + NS_ENSURE_ARG(aLastModificationDateOfLink); + struct stat sbuf; + if (lstat(mPath, &sbuf) == -1) + return NSRESULT_FOR_ERRNO(); + *aLastModificationDateOfLink = (PRUint32)sbuf.st_mtime; + return NS_OK; +} + +/* + * utime(2) may or may not dereference symlinks, joy. + */ +NS_IMETHODIMP +nsLocalFile::SetLastModificationDateOfLink(PRUint32 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); + FILL_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(PRUint32 *aFileSize) +{ + NS_ENSURE_ARG_POINTER(aFileSize); + FILL_STAT_CACHE(); + if (sizeof(off_t) > 4 && mCachedStat.st_size > (off_t)0xffffffff) + *aFileSize = 0xffffffff; // return error code? + else + *aFileSize = (PRUint32)mCachedStat.st_size; + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetFileSizeOfLink(PRUint32 *aFileSize) +{ + NS_ENSURE_ARG(aFileSize); + struct stat sbuf; + if (lstat(mPath, &sbuf) == -1) + return NSRESULT_FOR_ERRNO(); + if (sizeof(off_t) > 4 && mCachedStat.st_size > (off_t)0xffffffff) + *aFileSize = 0xffffffff; // return error code? + else + *aFileSize = (PRUint32)sbuf.st_size; + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetDiskSpaceAvailable(PRInt64 *aDiskSpaceAvailable) +{ + NS_ENSURE_ARG_POINTER(aDiskSpaceAvailable); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsLocalFile::GetParent(nsIFile **aParent) +{ + NS_ENSURE_ARG_POINTER(aParent); + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* + * 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); + if (accessOK || errno == EACCES) + return NS_OK; + return NSRESULT_FOR_ERRNO(); +} + +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); + FILL_STAT_CACHE(); + *_retval = S_ISDIR(mCachedStat.st_mode); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::IsFile(PRBool *_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + FILL_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); + FILL_STAT_CACHE(); + *_retval = S_ISLNK(mCachedStat.st_mode); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::IsSpecial(PRBool *_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + FILL_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); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsLocalFile::IsContainedIn(nsIFile *inFile, PRBool recur, PRBool *_retval) +{ + NS_ENSURE_ARG(inFile); + NS_ENSURE_ARG_POINTER(_retval); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsLocalFile::Truncate(PRUint32 aLength) +{ + return NSRESULT_FOR_RETURN(truncate(mPath, (off_t)aLength)); +} + +NS_IMETHODIMP +nsLocalFile::GetTarget(char **_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + FILL_STAT_CACHE(); + if (!S_ISLNK(mCachedStat.st_mode)) + return NS_ERROR_FILE_INVALID_PATH; + + PRUint32 targetSize; + if (NS_FAILED(GetFileSizeOfLink(&targetSize))) + return NS_ERROR_FAILURE; + + char *target = (char *)nsAllocator::Alloc(targetSize); + if (!target) + return NS_ERROR_OUT_OF_MEMORY; + + int result = readlink(mPath, target, (size_t)targetSize); + if (!result) { + *_retval = target; + return NS_OK; + } + nsAllocator::Free(target); + return NSRESULT_FOR_ERRNO(); +} diff --git a/xpcom/io/nsLocalFileUnix.h b/xpcom/io/nsLocalFileUnix.h new file mode 100644 index 000000000000..9c5b307b86fb --- /dev/null +++ b/xpcom/io/nsLocalFileUnix.h @@ -0,0 +1,106 @@ +/* -*- 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 + */ + +/* + * Implementation of nsIFile for ``Unixy'' systems. + */ + +#ifndef _nsLocalFileUNIX_H_ +#define _nsLocalFileUNIX_H_ + +#include +#include + +#include "nscore.h" +#include "nsIFile.h" +#include "nsLocalFile.h" +#include "nsXPIDLString.h" + +#define NSRESULT_FOR_RETURN(ret) (!(ret) ? NS_OK : NSRESULT_FOR_ERRNO()) + +inline nsresult +nsresultForErrno(int err) +{ +#ifdef DEBUG_shaver + if (err) + fprintf(stderr, "errno %d\n", err); +#endif + switch(err) { + case 0: + return NS_OK; + case ENOENT: + return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST; + case ENOTDIR: + return NS_ERROR_FILE_DESTINATION_NOT_DIR; + case ENOLINK: + return NS_ERROR_FILE_UNRESOLVABLE_SYMLINK; + case EEXIST: + return NS_ERROR_FILE_ALREADY_EXISTS; + case EACCES: + default: + return NS_ERROR_FAILURE; + } +} + +#define NSRESULT_FOR_ERRNO() nsresultForErrno(errno) + +class NS_COM nsLocalFile : public nsIFile +{ +public: + NS_DEFINE_STATIC_CID_ACCESSOR(NS_LOCAL_FILE_CID) + + nsLocalFile(); + virtual ~nsLocalFile(); + + static NS_METHOD Create(nsISupports* outer, const nsIID& aIID, void* *aInstancePtr); + + // nsISupports + NS_DECL_ISUPPORTS + + // nsIFile + NS_DECL_NSIFILE + +protected: + PRBool mHaveCachedStat; + struct stat mCachedStat; + nsXPIDLCString mPath; + + nsresult createAllParentDirectories(PRUint32 permissions); + nsresult getLeafNameRaw(const char **_retval); + + void invalidateCache() { mHaveCachedStat = PR_FALSE; } + + nsresult fillStatCache() { + if (stat(mPath, &mCachedStat) == -1) { +#ifdef DEBUG_shaver + fprintf(stderr, "stat(%s) -> %d\n", (const char *)mPath, errno); +#endif + return NS_ERROR_FAILURE; + } + mHaveCachedStat = PR_TRUE; + return NS_OK; + } + +}; +#endif /* _nsLocalFileUNIX_H_ */ diff --git a/xpcom/io/nsLocalFileWin.cpp b/xpcom/io/nsLocalFileWin.cpp new file mode 100644 index 000000000000..dc1188fc064f --- /dev/null +++ b/xpcom/io/nsLocalFileWin.cpp @@ -0,0 +1,1596 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* + * 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): + * Doug Turner + */ + + +#include "nsCOMPtr.h" +#include "nsIAllocator.h" + +#include "nsLocalFileWin.h" + +#include "nsISimpleEnumerator.h" +#include "nsIComponentManager.h" +#include "prtypes.h" +#include "prio.h" + +#include +#include + +#include "shellapi.h" +#include "shlguid.h" + + +// certainly not all the error that can be +// encountered, but many of them common ones +static nsresult ConvertWinError(DWORD winErr) +{ + nsresult rv; + + switch (winErr) + { + case ERROR_FILE_NOT_FOUND: + case ERROR_PATH_NOT_FOUND: + case ERROR_INVALID_DRIVE: + rv = NS_ERROR_FILE_NOT_FOUND; + break; + case ERROR_ACCESS_DENIED: + case ERROR_NOT_SAME_DEVICE: + rv = NS_ERROR_FILE_ACCESS_DENIED; + break; + case ERROR_NOT_ENOUGH_MEMORY: + case ERROR_INVALID_BLOCK: + case ERROR_INVALID_HANDLE: + case ERROR_ARENA_TRASHED: + rv = NS_ERROR_OUT_OF_MEMORY; + break; + case ERROR_CURRENT_DIRECTORY: + rv = NS_ERROR_FILE_DIR_NOT_EMPTY; + break; + case ERROR_WRITE_PROTECT: + rv = NS_ERROR_FILE_READ_ONLY; + break; + case ERROR_HANDLE_DISK_FULL: + rv = NS_ERROR_FILE_TOO_BIG; + break; + case 0: + rv = NS_OK; + default: + rv = NS_ERROR_FAILURE; + } + return rv; +} + + +static void +myLL_II2L(PRInt32 hi, PRInt32 lo, PRInt64 *result) +{ + PRInt64 a64, b64; // probably could have been done with + // only one PRInt64, but these are macros, + // and I am a wimp. + + // put hi in the low bits of a64. + LL_I2L(a64, hi); + // now shift it to the upperbit and place it the result in result + LL_SHL(b64, a64, 32); + // now put the low bits on by adding them to the result. + LL_ADD(*result, b64, lo); +} + + +class nsDirEnumerator : public nsISimpleEnumerator +{ + public: + + NS_DECL_ISUPPORTS + + nsDirEnumerator() : mDir(nsnull) + { + NS_INIT_REFCNT(); + } + + nsresult Init(nsILocalFile* parent) + { + char* filepath; + parent->GetTarget(&filepath); + + if (filepath == nsnull) + { + parent->GetPath(&filepath); + } + + if (filepath == nsnull) + { + return NS_ERROR_OUT_OF_MEMORY; + } + + mDir = PR_OpenDir(filepath); + if (mDir == nsnull) // not a directory? + return NS_ERROR_FAILURE; + + mParent = parent; + return NS_OK; + } + + NS_IMETHOD HasMoreElements(PRBool *result) + { + nsresult rv; + if (mNext == nsnull && mDir) + { + PRDirEntry* entry = PR_ReadDir(mDir, PR_SKIP_BOTH); + if (entry == nsnull) + { + // end of dir entries + + PRStatus status = PR_CloseDir(mDir); + if (status != PR_SUCCESS) + return NS_ERROR_FAILURE; + mDir = nsnull; + + *result = PR_FALSE; + return NS_OK; + } + + nsCOMPtr file; + + rv = nsComponentManager::CreateInstance(NS_LOCAL_FILE_PROGID, + nsnull, + nsCOMTypeInfo::GetIID(), + getter_AddRefs(file)); + if (NS_FAILED(rv)) + return rv; + + rv = file->InitWithFile(mParent); + if (NS_FAILED(rv)) + return rv; + + rv = file->AppendPath(entry->name); + if (NS_FAILED(rv)) + return rv; + + mNext = file; + } + *result = mNext != nsnull; + return NS_OK; + } + + NS_IMETHOD GetNext(nsISupports **result) + { + nsresult rv; + PRBool hasMore; + rv = HasMoreElements(&hasMore); + if (NS_FAILED(rv)) return rv; + + *result = mNext; // might return nsnull + NS_IF_ADDREF(*result); + + mNext = null_nsCOMPtr(); + return NS_OK; + } + + virtual ~nsDirEnumerator() + { + if (mDir) + { + PRStatus status = PR_CloseDir(mDir); + NS_ASSERTION(status == PR_SUCCESS, "close failed"); + } + } + + protected: + PRDir* mDir; + nsCOMPtr mParent; + nsCOMPtr mNext; +}; + +NS_IMPL_ISUPPORTS(nsDirEnumerator, NS_GET_IID(nsISimpleEnumerator)); + + + + + + + + +nsLocalFile::nsLocalFile() +{ + NS_INIT_REFCNT(); + CoInitialize(NULL); // FIX: we should probably move somewhere higher up during startup + + HRESULT hres; + + + // Get a pointer to the IShellLink interface. + hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (void**)&mShellLink); + if (SUCCEEDED(hres)) + { + // Get a pointer to the IPersistFile interface. + hres = mShellLink->QueryInterface(IID_IPersistFile, (void**)&mPersistFile); + + + } + + MakeDirty(); +} + +nsLocalFile::~nsLocalFile() +{ + // Release the pointer to the IPersistFile interface. + if (mPersistFile) + mPersistFile->Release(); + + // Release the pointer to the IShellLink interface. + if(mShellLink) + mShellLink->Release(); + + CoUninitialize(); +} + +/* nsISupports interface implementation. */ +NS_IMPL_ISUPPORTS2(nsLocalFile, nsILocalFile, nsIFile) +NS_METHOD +nsLocalFile::Create(nsISupports* outer, const nsIID& aIID, void* *aInstancePtr) +{ + NS_ENSURE_ARG_POINTER(aInstancePtr); + NS_ENSURE_PROPER_AGGREGATION(outer, aIID); + + nsLocalFile* inst = new nsLocalFile(); + if (inst == NULL) + return NS_ERROR_OUT_OF_MEMORY; + + nsresult rv = inst->QueryInterface(aIID, aInstancePtr); + if (NS_FAILED(rv)) + { + delete inst; + return rv; + } + return NS_OK; +} + +// This function resets any cached information about the file. +void +nsLocalFile::MakeDirty() +{ + mStatDirty = PR_TRUE; +} + +//---------------------------------------------------------------------------------------- +// +// ResolvePath +// this function will walk the native path of |this| resolving any symbolic +// links found. The new resulting path will be placed into mResolvedPath. +//---------------------------------------------------------------------------------------- + +nsresult +nsLocalFile::ResolvePath(const char* workingPath, PRBool resolveTerminal, char** resolvedPath) +{ + nsresult rv = NS_OK; + + + // Make sure that we were able to get the PersistFile interface during the + // construction of this object. + if (mPersistFile == nsnull || mShellLink == nsnull) + return NS_ERROR_FILE_INVALID_PATH; + + // Get the native path for |this| + char* filePath = (char*) nsAllocator::Clone( workingPath, strlen(workingPath)+1 ); + + if (filePath == nsnull) + return NS_ERROR_NULL_POINTER; + + // We are going to walk the native file path + // and stop at each slash. For each partial + // path (the string to the left of the slash) + // we will check to see if it is a shortcut. + // if it is, we will resolve it and continue + // with that resolved path. + + // Get the first slash. + char* slash = strchr(filePath, '\\'); + + if (slash == nsnull) + { + if (filePath[1] == ':' && filePath[2] == '\0') + { + // we have a drive letter and a colon (eg 'c:' + // this is resolve already + + *resolvedPath = (char*) nsAllocator::Clone( filePath, strlen(filePath)+2 ); + strcat(*resolvedPath, "\\"); + + nsAllocator::Free(filePath); + return NS_OK; + } + else + { + nsAllocator::Free(filePath); + return NS_ERROR_FILE_INVALID_PATH; + } + } + + + // We really cant have just a drive letter as + // a shortcut, so we will skip the first '\\' + slash = strchr(++slash, '\\'); + + while (slash || resolveTerminal) + { + // Change the slash into a null so that + // we can use the partial path. It is is + // null, we know it is the terminal node. + + if (slash) + { + *slash = '\0'; + } + else + { + if (resolveTerminal) + { + // this is our last time in this loop. + // set loop condition to false + + resolveTerminal = PR_FALSE; + } + else + { + // something is wrong. we should not have + // both slash being null and resolveTerminal + // not set! + nsAllocator::Free(filePath); + return NS_ERROR_NULL_POINTER; + } + } + + WORD wsz[MAX_PATH]; // TODO, Make this dynamically allocated. + + // check to see the file is a shortcut by the magic .lnk extension. + size_t offset = strlen(filePath) - 4; + if ((offset > 0) && (strncmp( (filePath + offset), ".lnk", 4) == 0)) + { + // Ensure that the string is Unicode. + MultiByteToWideChar(CP_ACP, 0, filePath, -1, wsz, MAX_PATH); + + HRESULT hres; + + // see if we can Load the path. + hres = mPersistFile->Load(wsz, STGM_READ); + + if (SUCCEEDED(hres)) + { + // Resolve the link. + hres = mShellLink->Resolve(nsnull, SLR_NO_UI ); + if (SUCCEEDED(hres)) + { + WIN32_FIND_DATA wfd; + + char *temp = (char*) nsAllocator::Alloc( MAX_PATH ); + if (temp == nsnull) + return NS_ERROR_NULL_POINTER; + + // Get the path to the link target. + hres = mShellLink->GetPath( temp, MAX_PATH, &wfd, SLGP_UNCPRIORITY ); + + if (SUCCEEDED(hres)) + { + // found a new path. + + // addend a slash on it since it does not come out of GetPath() + // with one only if it is a directory. If it is not a directory + // and there is more to append, than we have a problem. + + struct stat st; + int statrv = stat(temp, &st); + + if (0 == statrv && (_S_IFDIR & st.st_mode)) + { + strcat(temp, "\\"); + } + else + { + // check to see the file is a shortcut by the magic .lnk extension. + size_t offset = strlen(temp) - 4; + if ((offset > 0) && (strncmp( (temp + offset), ".lnk", 4) == 0)) + { + strcat(temp, "\\"); + } + else + { + // it is not an shortcut, we have an error! + nsAllocator::Free(temp); + nsAllocator::Free(filePath); + return NS_ERROR_FILE_INVALID_PATH; + } + } + + if (slash) + { + // save where we left off. + char *carot= (temp + strlen(temp) -1 ); + + // append all the stuff that we have not done. + strcat(temp, ++slash); + + slash = carot; + } + + nsAllocator::Free(filePath); + filePath = temp; + + } + else + { + nsAllocator::Free(temp); + } + } + else + { + // could not resolve shortcut. Return error; + nsAllocator::Free(filePath); + return NS_ERROR_FILE_INVALID_PATH; + } + } + else + { + // could not load shortcut. Return error; + nsAllocator::Free(filePath); + return NS_ERROR_FILE_INVALID_PATH; + } + } + + if (slash) + { + *slash = '\\'; + ++slash; + slash = strchr(slash, '\\'); + } + } + *resolvedPath = filePath; + return rv; +} + +nsresult +nsLocalFile::ResolveAndStat(PRBool resolveTerminal) +{ +// if (!mStatDirty) +// { +// return NS_OK; todo need to cache information about both the path and the target both +// follow this code path. +// } + + char *resolvePath; + nsresult rv = ResolvePath(mWorkingPath.GetBuffer(), resolveTerminal, &resolvePath); + if (NS_FAILED(rv)) + return rv; + mResolvedPath.SetString(resolvePath); + nsAllocator::Free(resolvePath); + + + const char *filePath = mResolvedPath.GetBuffer(); + + if ( 0 == GetFileAttributesEx( filePath, + GetFileExInfoStandard, + &mFileAttrData) ) + { + return ConvertWinError(GetLastError()); + } + + mStatDirty = PR_FALSE; + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::InitWithKey(const char *fileKey) +{ + MakeDirty(); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsLocalFile::InitWithFile(nsIFile *file) +{ + MakeDirty(); + NS_ENSURE_ARG(file); + + // TODO: how do we get to the |file|'s working XXXXXX + // directory so that we do not expose + // symlinked directories. + + char* aFilePath; + file->GetPath(&aFilePath); + + if (aFilePath == nsnull) + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + + mWorkingPath.SetString(aFilePath); + + nsAllocator::Free(aFilePath); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::InitWithPath(const char *filePath) +{ + MakeDirty(); + NS_ENSURE_ARG(filePath); + + char* nativeFilePath = nsnull; + + // just do a sanity check. if it has any forward slashes, it is not a Native path + // on windows. Also, it must have a colon at after the first char. + + if ( ( (filePath[1] == ':') && ( strchr(filePath, '/') == 0) ) || // normal windows path + ( (filePath[0] == '\\') && (filePath[1] == '\\') ) ) // netwerk path + { + // This is a native path + nativeFilePath = (char*) nsAllocator::Clone( filePath, strlen(filePath)+1 ); + } + + if (nativeFilePath == nsnull) + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + + // kill any trailing seperator + char* temp = nativeFilePath; + int len = strlen(temp) - 1; + if(temp[len] == '\\') + temp[len] = '\0'; + + mWorkingPath.SetString(nativeFilePath); + nsAllocator::Free( nativeFilePath ); + return NS_OK; +} + + +NS_IMETHODIMP +nsLocalFile::Create(PRUint32 type, PRUint32 attributes) +{ + if (type != NORMAL_FILE_TYPE && type != DIRECTORY_TYPE) + return NS_ERROR_FILE_UNKNOWN_TYPE; + + nsresult rv = ResolveAndStat(PR_FALSE); + if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) + return rv; + + // create nested directories to target + char* slash = strchr(mResolvedPath, '\\'); + // skip the first '\\' + ++slash; + slash = strchr(slash, '\\'); + + while (slash) + { + *slash = '\0'; + + CreateDirectoryA(mResolvedPath, NULL);// todo: pass back the result + + *slash = '\\'; + ++slash; + slash = strchr(slash, '\\'); + } + + + if (type == NORMAL_FILE_TYPE) + { + PRFileDesc* file = PR_Open(mResolvedPath, PR_RDONLY | PR_CREATE_FILE | PR_APPEND, attributes); + if (file) PR_Close(file); + return NS_OK; + } + + if (type == DIRECTORY_TYPE) + { + CreateDirectoryA(mResolvedPath, NULL); // todo: pass back the result + return NS_OK; + } + + return NS_ERROR_FILE_UNKNOWN_TYPE; +} + +NS_IMETHODIMP +nsLocalFile::AppendPath(const char *node) +{ + if ( (node == nsnull) || (*node == '/') || strchr(node, '\\') ) + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + + MakeDirty(); + + // We only can append relative unix styles strings. + + // Convert '\' to '/' + nsString path(node); + + char* nodeCString = path.ToNewCString(); + char* temp = nodeCString; + for (; *temp; temp++) + { + if (*temp == '/') + *temp = '\\'; + } + + // kill any trailing seperator + if(nodeCString) + { + temp = nodeCString; + int len = strlen(temp) - 1; + if(temp[len] == '\\') + temp[len] = '\0'; + } + + mWorkingPath.Append("\\"); + mWorkingPath.Append(nodeCString); + + Recycle(nodeCString); + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::Normalize() +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsLocalFile::GetLeafName(char * *aLeafName) +{ + NS_ENSURE_ARG_POINTER(aLeafName); + + const char* temp = mWorkingPath.GetBuffer(); + if(temp == nsnull) + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + + const char* leaf = strrchr(temp, '\\'); + + // if the working path is just a node without any lashes. + if (leaf == nsnull) + leaf = temp; + else + leaf++; + + *aLeafName = (char*) nsAllocator::Clone(leaf, strlen(leaf)+1); + return NS_OK; +} + + +NS_IMETHODIMP +nsLocalFile::GetPath(char **_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + *_retval = (char*) nsAllocator::Clone(mWorkingPath, strlen(mWorkingPath)+1); + return NS_OK; +} + + + +nsresult +nsLocalFile::CopySingleFile(nsIFile *sourceFile, nsIFile *destParent, const char * newName, PRBool followSymlinks, PRBool move) +{ + nsresult rv; + char* filePath; + + // get the path that we are going to copy to. + // Since windows does not know how to auto + // resolve shortcust, we must work with the + // target. + char* inFilePath; + destParent->GetTarget(&inFilePath); + nsCString destPath = inFilePath; + nsAllocator::Free(inFilePath); + + destPath.Append("\\"); + + if (newName == nsnull) + { + char *aFileName; + sourceFile->GetLeafName(&aFileName); + destPath.Append(aFileName); + nsAllocator::Free(aFileName); + } + else + { + destPath.Append(newName); + } + + + if (followSymlinks) + { + rv = sourceFile->GetTarget(&filePath); + } + else + { + rv = sourceFile->GetPath(&filePath); + } + + if (NS_FAILED(rv)) + return rv; + + int copyOK; + + if (!move) + copyOK = CopyFile(filePath, destPath, PR_TRUE); + else + copyOK = MoveFile(filePath, destPath); + + if (!copyOK) // CopyFile and MoveFile returns non-zero if succeeds (backward if you ask me). + rv = ConvertWinError(GetLastError()); + + nsAllocator::Free(filePath); + + return rv; +} + + +nsresult +nsLocalFile::CopyMove(nsIFile *newParentDir, const char *newName, PRBool followSymlinks, PRBool move) +{ + NS_ENSURE_ARG(newParentDir); + + // check to see if this exists, otherwise return an error. + // we will check this by resolving. If the user wants us + // to follow links, then we are talking about the target, + // hence we can use the |followSymlinks| parameter. + nsresult rv = ResolveAndStat(followSymlinks); + if (NS_FAILED(rv)) + return rv; + + // make sure it exists and is a directory. Create it if not there. + PRBool exists; + newParentDir->Exists(&exists); + if (exists == PR_FALSE) + { + rv = newParentDir->Create(DIRECTORY_TYPE, 0644); // TODO, what permissions should we use + if (NS_FAILED(rv)) + return rv; + } + else + { + PRBool isDir; + newParentDir->IsDirectory(&isDir); + if (isDir == PR_FALSE) + { + if (followSymlinks) + { + PRBool isLink; + newParentDir->IsSymlink(&isLink); + if (isLink) + { + char* target; + newParentDir->GetTarget(&target); + + nsCOMPtr realDest; + + rv = nsComponentManager::CreateInstance(NS_LOCAL_FILE_PROGID, + nsnull, + nsCOMTypeInfo::GetIID(), + getter_AddRefs(realDest)); + if (NS_FAILED(rv)) + return rv; + + rv = realDest->InitWithPath(target); + + nsAllocator::Free(target); + + if (NS_FAILED(rv)) + return rv; + + return CopyMove(realDest, newName, followSymlinks, move); + } + } + else + { + return NS_ERROR_FILE_DESTINATION_NOT_DIR; + } + } + } + + // check to see if we are a directory, if so enumerate it. + + PRBool isDir; + IsDirectory(&isDir); + if (!isDir) + { + rv = CopySingleFile(this, newParentDir, newName, followSymlinks, move); + if (NS_FAILED(rv)) + return rv; + } + else + { + // create a new target destination in the new parentDir; + nsCOMPtr target; + + rv = nsComponentManager::CreateInstance(NS_LOCAL_FILE_PROGID, + nsnull, + nsCOMTypeInfo::GetIID(), + getter_AddRefs(target)); + if (NS_FAILED(rv)) + return rv; + + rv = target->InitWithFile(newParentDir); + if (NS_FAILED(rv)) + return rv; + + char *allocatedNewName; + if (!newName) + { + GetLeafName(&allocatedNewName);// this should be the leaf name of the + } + else + { + allocatedNewName = (char*) nsAllocator::Clone( newName, strlen(newName)+1 ); + } + + rv = target->AppendPath(allocatedNewName); + if (NS_FAILED(rv)) + return rv; + + nsAllocator::Free(allocatedNewName); + + target->Create(DIRECTORY_TYPE, 0644); // TODO, what permissions should we use + if (NS_FAILED(rv)) + return rv; + + nsDirEnumerator* dirEnum = new nsDirEnumerator(); + if (!dirEnum) + return NS_ERROR_OUT_OF_MEMORY; + + rv = dirEnum->Init(this); + + nsCOMPtr iterator = do_QueryInterface(dirEnum); + + PRBool more; + iterator->HasMoreElements(&more); + while (more) + { + nsCOMPtr item; + nsCOMPtr file; + iterator->GetNext(getter_AddRefs(item)); + file = do_QueryInterface(item); + PRBool isDir, isLink; + + file->IsDirectory(&isDir); + file->IsSymlink(&isLink); + + if (move) + { + if (followSymlinks) + rv = file->MoveToFollowingLinks(target, nsnull); + else + rv = file->MoveTo(target, nsnull); + } + else + { + if (followSymlinks) + rv = file->CopyToFollowingLinks(target, nsnull); + else + rv = file->CopyTo(target, nsnull); + } + + iterator->HasMoreElements(&more); + } + } + + + // If we moved, we want to adjust this. + if (move) + { + if (newName == nsnull) + { + char *aFileName; + GetLeafName(&aFileName); + InitWithFile(newParentDir); + AppendPath(aFileName); + nsAllocator::Free(aFileName); + } + else + { + InitWithFile(newParentDir); + AppendPath(newName); + } + MakeDirty(); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::CopyTo(nsIFile *newParentDir, const char *newName) +{ + return CopyMove(newParentDir, newName, PR_FALSE, PR_FALSE); +} + +NS_IMETHODIMP +nsLocalFile::CopyToFollowingLinks(nsIFile *newParentDir, const char *newName) +{ + return CopyMove(newParentDir, newName, PR_TRUE, PR_FALSE); +} + +NS_IMETHODIMP +nsLocalFile::MoveTo(nsIFile *newParentDir, const char *newName) +{ + return CopyMove(newParentDir, newName, PR_FALSE, PR_TRUE); +} + +NS_IMETHODIMP +nsLocalFile::MoveToFollowingLinks(nsIFile *newParentDir, const char *newName) +{ + return CopyMove(newParentDir, newName, PR_TRUE, PR_TRUE); +} + +NS_IMETHODIMP +nsLocalFile::Spawn(const char *args) +{ + PRBool isFile; + nsresult rv = IsFile(&isFile); + + if (NS_FAILED(rv)) + return rv; + + nsCString fileNameWithArgs(mResolvedPath); + + if(args) + { + fileNameWithArgs.Append(" "); + fileNameWithArgs.Append(args); + } + + int execResult = WinExec( fileNameWithArgs, SW_NORMAL ); + if (execResult > 31) + return NS_OK; + + return NS_ERROR_FILE_EXECUTION_FAILED; +} + +NS_IMETHODIMP +nsLocalFile::Delete(PRBool recursive) +{ + MakeDirty(); + + PRBool isDir; + + nsresult rv = IsDirectory(&isDir); + if (NS_FAILED(rv)) + return rv; + + const char *filePath = mResolvedPath.GetBuffer(); + + if (isDir) + { + if (recursive) + { + nsDirEnumerator* dirEnum = new nsDirEnumerator(); + if (dirEnum) + return NS_ERROR_OUT_OF_MEMORY; + + rv = dirEnum->Init(this); + + nsCOMPtr iterator = do_QueryInterface(dirEnum); + + PRBool more; + iterator->HasMoreElements(&more); + while (more) + { + nsCOMPtr item; + nsCOMPtr file; + iterator->GetNext(getter_AddRefs(item)); + file = do_QueryInterface(item); + + file->Delete(recursive); + + iterator->HasMoreElements(&more); + } + } + rmdir(filePath); // todo: save return value? + } + else + { + remove(filePath); // todo: save return value? + } + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetLastModificationDate(PRInt64 *aLastModificationDate) +{ + NS_ENSURE_ARG(aLastModificationDate); + + *aLastModificationDate = 0; + + nsresult rv = ResolveAndStat(PR_TRUE); + + if (NS_FAILED(rv)) + return rv; + + PRUint32 high = mFileAttrData.ftLastAccessTime.dwHighDateTime; + PRUint32 low = mFileAttrData.ftLastAccessTime.dwLowDateTime; + + myLL_II2L(high, low, aLastModificationDate); + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::SetLastModificationDate(PRInt64 aLastModificationDate) +{ + MakeDirty(); + + nsresult rv = ResolveAndStat(PR_TRUE); + + if (NS_FAILED(rv)) + return rv; + + FILETIME time = mFileAttrData.ftLastAccessTime; + + time.dwLowDateTime = aLastModificationDate; + + const char *filePath = mResolvedPath.GetBuffer(); + + HANDLE file = CreateFile( filePath, // pointer to name of the file + GENERIC_WRITE, // access (write) mode + 0, // share mode + NULL, // pointer to security attributes + OPEN_EXISTING, // how to create + 0, // file attributes (??xxx) + NULL); + + if (!file) + { + // could not open file for writing. + return NS_ERROR_FAILURE; //TODO better error code + } + + if ( 0 == SetFileTime(file, NULL, &time, &time) ) + { + // could not set time + return NS_ERROR_FAILURE; + } + + + MakeDirty(); + + CloseHandle( file ); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetLastModificationDateOfLink(PRInt64 *aLastModificationDate) +{ + NS_ENSURE_ARG(aLastModificationDate); + + *aLastModificationDate = 0; + + nsresult rv = ResolveAndStat(PR_FALSE); + + if (NS_FAILED(rv)) + return rv; + + FILETIME time = mFileAttrData.ftLastAccessTime; + + myLL_II2L(time.dwHighDateTime, time.dwLowDateTime, aLastModificationDate); + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::SetLastModificationDateOfLink(PRInt64 aLastModificationDate) +{ + MakeDirty(); + + nsresult rv = ResolveAndStat(PR_FALSE); + + if (NS_FAILED(rv)) + return rv; + + FILETIME time = mFileAttrData.ftLastAccessTime; + + time.dwLowDateTime = aLastModificationDate; + + const char *filePath = mResolvedPath.GetBuffer(); + + HANDLE file = CreateFile( filePath, // pointer to name of the file + GENERIC_WRITE, // access (write) mode + 0, // share mode + NULL, // pointer to security attributes + OPEN_EXISTING, // how to create + 0, // file attributes (??xxx) + NULL); + + if (!file) + { + // could not open file for writing. + return NS_ERROR_FAILURE; //TODO better error code + } + + if ( 0 == SetFileTime(file, NULL, &time, &time) ) + { + // could not set time + return NS_ERROR_FAILURE; + } + + + MakeDirty(); + + CloseHandle( file ); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetPermissions(PRUint32 *aPermissions) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsLocalFile::GetPermissionsOfLink(PRUint32 *aPermissionsOfLink) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + + +NS_IMETHODIMP +nsLocalFile::SetPermissions(PRUint32 aPermissions) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsLocalFile::SetPermissionsOfLink(PRUint32 aPermissions) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + + +NS_IMETHODIMP +nsLocalFile::GetFileSize(PRInt64 *aFileSize) +{ + NS_ENSURE_ARG(aFileSize); + + *aFileSize = 0; + + nsresult rv = ResolveAndStat(PR_TRUE); + + if (NS_FAILED(rv)) + return rv; + + myLL_II2L(mFileAttrData.nFileSizeHigh, mFileAttrData.nFileSizeLow, aFileSize); + + return NS_OK; +} + + +NS_IMETHODIMP +nsLocalFile::SetFileSize(PRInt64 aFileSize) +{ + + DWORD status; + HANDLE hFile; + + nsresult rv = ResolveAndStat(PR_TRUE); + + if (NS_FAILED(rv)) + return rv; + + const char *filePath = mResolvedPath.GetBuffer(); + + + // Leave it to Microsoft to open an existing file with a function + // named "CreateFile". + hFile = CreateFile(filePath, + GENERIC_WRITE, + FILE_SHARE_READ, + NULL, + OPEN_EXISTING, + FILE_FLAG_NO_BUFFERING | FILE_FLAG_OVERLAPPED, + NULL); + + if (hFile == INVALID_HANDLE_VALUE) + return NS_ERROR_FAILURE; + + // Seek to new, desired end of file + status = SetFilePointer(hFile, aFileSize, NULL, FILE_BEGIN); + if (status == 0xffffffff) + goto error; + + // Truncate file at current cursor position + if (!SetEndOfFile(hFile)) + goto error; + + if (!CloseHandle(hFile)) + return NS_ERROR_FAILURE; + + return NS_OK; + + error: + CloseHandle(hFile); + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsLocalFile::GetFileSizeOfLink(PRInt64 *aFileSize) +{ + NS_ENSURE_ARG(aFileSize); + + *aFileSize = 0; + + nsresult rv = ResolveAndStat(PR_FALSE); + + if (NS_FAILED(rv)) + return rv; + + myLL_II2L(mFileAttrData.nFileSizeHigh, mFileAttrData.nFileSizeLow, aFileSize); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetDiskSpaceAvailable(PRInt64 *aDiskSpaceAvailable) +{ + NS_ENSURE_ARG(aDiskSpaceAvailable); + + ResolveAndStat(PR_FALSE); + + const char *filePath = mResolvedPath.GetBuffer(); + + PRInt64 int64; + + LL_I2L(int64 , LONG_MAX); + + char aDrive[_MAX_DRIVE + 2]; + _splitpath( (const char*)filePath, aDrive, NULL, NULL, NULL); + strcat(aDrive, "\\"); + + // Check disk space + DWORD dwSecPerClus, dwBytesPerSec, dwFreeClus, dwTotalClus; + ULARGE_INTEGER liFreeBytesAvailableToCaller, liTotalNumberOfBytes, liTotalNumberOfFreeBytes; + double nBytes = 0; + + BOOL (WINAPI* getDiskFreeSpaceExA)(LPCTSTR lpDirectoryName, + PULARGE_INTEGER lpFreeBytesAvailableToCaller, + PULARGE_INTEGER lpTotalNumberOfBytes, + PULARGE_INTEGER lpTotalNumberOfFreeBytes) = NULL; + + HINSTANCE hInst = LoadLibrary("KERNEL32.DLL"); + NS_ASSERTION(hInst != NULL, "COULD NOT LOAD KERNEL32.DLL"); + if (hInst != NULL) + { + getDiskFreeSpaceExA = (BOOL (WINAPI*)(LPCTSTR lpDirectoryName, + PULARGE_INTEGER lpFreeBytesAvailableToCaller, + PULARGE_INTEGER lpTotalNumberOfBytes, + PULARGE_INTEGER lpTotalNumberOfFreeBytes)) + GetProcAddress(hInst, "GetDiskFreeSpaceExA"); + FreeLibrary(hInst); + } + + if (getDiskFreeSpaceExA && (*getDiskFreeSpaceExA)(aDrive, + &liFreeBytesAvailableToCaller, + &liTotalNumberOfBytes, + &liTotalNumberOfFreeBytes)) + { + nBytes = (double)(signed __int64)liFreeBytesAvailableToCaller.QuadPart; + } + else if ( GetDiskFreeSpace(aDrive, &dwSecPerClus, &dwBytesPerSec, &dwFreeClus, &dwTotalClus)) + { + nBytes = (double)dwFreeClus*(double)dwSecPerClus*(double) dwBytesPerSec; + } + + LL_D2L(*aDiskSpaceAvailable, nBytes); + + return NS_OK; + +} + +NS_IMETHODIMP +nsLocalFile::GetParent(nsIFile * *aParent) +{ + NS_ENSURE_ARG_POINTER(aParent); + + nsCString parentPath = mWorkingPath; + + PRInt32 offset = parentPath.RFindChar('\\'); + if (offset == -1) + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + + parentPath.Truncate(offset); + + const char* filePath = parentPath.GetBuffer(); + + nsLocalFile* file = new nsLocalFile(); + if (file == nsnull) + return NS_ERROR_OUT_OF_MEMORY; + + file->AddRef(); + file->InitWithPath(filePath); + *aParent = file; + + return NS_OK; +} +NS_IMETHODIMP +nsLocalFile::Exists(PRBool *_retval) +{ + NS_ENSURE_ARG(_retval); + + nsresult rv = ResolveAndStat( PR_TRUE ); + + if (NS_SUCCEEDED(rv)) + { + *_retval = PR_TRUE; + } + else + { + *_retval = PR_FALSE; + if (GetLastError() != ERROR_FILE_NOT_FOUND) + return NS_ERROR_FAILURE; + } + + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::IsWritable(PRBool *_retval) +{ + NS_ENSURE_ARG(_retval); + *_retval = PR_FALSE; + + nsresult rv = ResolveAndStat(PR_TRUE); + + if (NS_FAILED(rv)) + return rv; + + *_retval = (PRBool) !( mFileAttrData.dwFileAttributes & FILE_ATTRIBUTE_READONLY); + + return NS_OK; + + +} + +NS_IMETHODIMP +nsLocalFile::IsReadable(PRBool *_retval) +{ + NS_ENSURE_ARG(_retval); + *_retval = PR_FALSE; + + nsresult rv = ResolveAndStat( PR_TRUE ); + if (NS_FAILED(rv)) + return rv; + + *_retval = PR_TRUE; + return NS_OK; +} + + +NS_IMETHODIMP +nsLocalFile::IsExecutable(PRBool *_retval) +{ + NS_ENSURE_ARG(_retval); + *_retval = PR_FALSE; + + + nsresult rv = ResolveAndStat( PR_TRUE ); + if (NS_FAILED(rv)) + return rv; + + char* path; + PRBool symLink; + + rv = IsSymlink(&symLink); + if (NS_FAILED(rv)) + return rv; + + if (symLink) + GetTarget(&path); + else + GetPath(&path); + + const char* leaf = strrchr(path, '\\'); + + if ( (strstr(leaf, ".bat") != nsnull) || + (strstr(leaf, ".exe") != nsnull) ) // are there more that we care about?? + { + *_retval = PR_TRUE; + } + else + { + *_retval = PR_FALSE; + } + + nsAllocator::Free(path); + + return NS_OK; +} + + +NS_IMETHODIMP +nsLocalFile::IsDirectory(PRBool *_retval) +{ + NS_ENSURE_ARG(_retval); + *_retval = PR_FALSE; + + nsresult rv = ResolveAndStat(PR_TRUE); + + if (NS_FAILED(rv)) + return rv; + + *_retval = (PRBool)( mFileAttrData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::IsFile(PRBool *_retval) +{ + NS_ENSURE_ARG(_retval); + *_retval = PR_FALSE; + + nsresult rv = ResolveAndStat(PR_TRUE); + + if (NS_FAILED(rv)) + return rv; + + *_retval = (PRBool) !( mFileAttrData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::IsHidden(PRBool *_retval) +{ + NS_ENSURE_ARG(_retval); + *_retval = PR_FALSE; + + nsresult rv = ResolveAndStat(PR_TRUE); + + if (NS_FAILED(rv)) + return rv; + + *_retval = (PRBool) ( mFileAttrData.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN); + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::IsSymlink(PRBool *_retval) +{ + NS_ENSURE_ARG(_retval); + *_retval = PR_FALSE; + + char* path; + int pathLen; + PRBool symLink; + + GetPath(&path); + pathLen = strlen(path); + + const char* leaf = path + pathLen - 4; + + if ( (strcmp(leaf, ".lnk") == 0)) + { + *_retval = PR_TRUE; + } + + nsAllocator::Free(path); + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::IsSpecial(PRBool *_retval) +{ + NS_ENSURE_ARG(_retval); + *_retval = PR_FALSE; + + nsresult rv = ResolveAndStat(PR_TRUE); + + if (NS_FAILED(rv)) + return rv; + + *_retval = (PRBool) ( mFileAttrData.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM); + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::Equals(nsIFile *inFile, PRBool *_retval) +{ + NS_ENSURE_ARG(inFile); + NS_ENSURE_ARG(_retval); + *_retval = PR_FALSE; + + char* inFilePath; + inFile->GetPath(&inFilePath); + + char* filePath; + GetPath(&filePath); + + if (strcmp(inFilePath, filePath) == 0) + *_retval = PR_TRUE; + + nsAllocator::Free(inFilePath); + nsAllocator::Free(filePath); + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::IsContainedIn(nsIFile *inFile, PRBool recur, PRBool *_retval) +{ + NS_ENSURE_ARG(_retval); + *_retval = PR_FALSE; + return NS_ERROR_NOT_IMPLEMENTED; +} + + + +NS_IMETHODIMP +nsLocalFile::GetTarget(char **_retval) +{ + NS_ENSURE_ARG(_retval); + *_retval = nsnull; + + PRBool symLink; + + nsresult rv = IsSymlink(&symLink); + if (NS_FAILED(rv)) + return rv; + + if (!symLink) + { + return NS_ERROR_FILE_INVALID_PATH; + } + + ResolveAndStat(PR_TRUE); + + *_retval = (char*) nsAllocator::Clone( mResolvedPath, strlen(mResolvedPath)+1 ); + return NS_OK; +} + + +NS_IMETHODIMP +nsLocalFile::GetDirectoryEntries(nsISimpleEnumerator * *entries) +{ + nsresult rv; + + *entries = nsnull; + + PRBool isDir; + rv = IsDirectory(&isDir); + if (NS_FAILED(rv)) + return rv; + if (!isDir) + return NS_ERROR_FILE_NOT_DIRECTORY; + + nsDirEnumerator* dirEnum = new nsDirEnumerator(); + if (dirEnum == nsnull) + return NS_ERROR_OUT_OF_MEMORY; + NS_ADDREF(dirEnum); + rv = dirEnum->Init(this); + if (NS_FAILED(rv)) + { + NS_RELEASE(dirEnum); + return rv; + } + + *entries = dirEnum; + return NS_OK; +} + + + + diff --git a/xpcom/io/nsLocalFileWin.h b/xpcom/io/nsLocalFileWin.h new file mode 100644 index 000000000000..c0b8c7d56bc2 --- /dev/null +++ b/xpcom/io/nsLocalFileWin.h @@ -0,0 +1,97 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* + * 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): + * Doug Turner + */ + +#ifndef _nsLocalFileWIN_H_ +#define _nsLocalFileWIN_H_ + +#include "nscore.h" +#include "nsError.h" +#include "nsString.h" +#include "nsCRT.h" +#include "nsIFile.h" +#include "nsILocalFile.h" +#include "nsIFactory.h" +#include "nsLocalFile.h" + +#include "windows.h" + +// For older version (<6.0) of the VC Compiler +#if (_MSC_VER == 1100) +#define INITGUID +#include "objbase.h" +DEFINE_OLEGUID(IID_IPersistFile, 0x0000010BL, 0, 0); +#endif + +#include "shlobj.h" + +#include + +class NS_COM nsLocalFile : public nsILocalFile +{ +public: + NS_DEFINE_STATIC_CID_ACCESSOR(NS_LOCAL_FILE_CID) + + nsLocalFile(); + virtual ~nsLocalFile(); + + static NS_METHOD Create(nsISupports* outer, const nsIID& aIID, void* *aInstancePtr); + + // nsISupports interface + NS_DECL_ISUPPORTS + + // nsIFile interface + NS_DECL_NSIFILE + + // nsILocalFile interface + NS_DECL_NSILOCALFILE + +private: + + // this is the flag which indicates if I can used cached information about the file + PRBool mStatDirty; + + // this string will alway be in native format! + nsCString mWorkingPath; + + // this will be the resolve path which will *NEVER* be return to the user + nsCString mResolvedPath; + + IPersistFile* mPersistFile; + IShellLink* mShellLink; + + + WIN32_FILE_ATTRIBUTE_DATA mFileAttrData; + + + + void MakeDirty(); + nsresult ResolveAndStat(PRBool resolveTerminal); + nsresult ResolvePath(const char* workingPath, PRBool resolveTerminal, char** resolvedPath); + + nsresult CopyMove(nsIFile *newParentDir, const char *newName, PRBool followSymlinks, PRBool move); + nsresult CopySingleFile(nsIFile *source, nsIFile* dest, const char * newName, PRBool followSymlinks, PRBool move); + +}; + +#endif