diff --git a/README.md b/README.md index 3e8736c..ced46f0 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ fs-ext ====== -Extras not included in Node's fs module. +Extras not included in Node's fs module +and cross-platform file ownership handling. Installation ------------ @@ -71,5 +72,68 @@ Just like for utime(2), the absence of the `atime` and `mtime` means 'now'. Synchronous version of utime(). Throws an exception on error. +### fs.stat(path, [callback]) +Replaces the `fs.stat` returning the string representation of +[SIDs](http://msdn.microsoft.com/en-us/library/windows/desktop/aa379594.aspx) +of the owning user and group in the `uid` and `gid` attributes of the output +`stats` object. +### fs.statSync(path) + +Replaces the `fs.statSync` returning the string representation of +[SIDs](http://msdn.microsoft.com/en-us/library/windows/desktop/aa379594.aspx) +of the owning user and group in the `uid` and `gid` attributes of the output +`stats` object. + + +### fs.lstat(path, [callback]) + +Replaces the `fs.lstat` returning the string representation of +[SIDs](http://msdn.microsoft.com/en-us/library/windows/desktop/aa379594.aspx) +of the owning user and group in the `uid` and `gid` attributes of the output +`stats` object. + +### fs.lstatSync(path) + +Replaces the `fs.lstatSync` returning the string representation of +[SIDs](http://msdn.microsoft.com/en-us/library/windows/desktop/aa379594.aspx) +of the owning user and group in the `uid` and `gid` attributes of the output +`stats` object. + + +### fs.fstat(fd, [callback]) + +Replaces the `fs.fstat` returning the string representation of +[SIDs](http://msdn.microsoft.com/en-us/library/windows/desktop/aa379594.aspx) +of the owning user and group in the `uid` and `gid` attributes of the output +`stats` object. + +### fs.fstatSync(fd) + +Replaces the `fs.fstatSync` returning the string representation of +[SIDs](http://msdn.microsoft.com/en-us/library/windows/desktop/aa379594.aspx) +of the owning user and group in the `uid` and `gid` attributes of the output +`stats` object. + + +### fs.chown(path, [callback]) + +Replaces the `fs.chown` expecting `uid` and `gid` as the string representation +of [SIDs](http://msdn.microsoft.com/en-us/library/windows/desktop/aa379594.aspx). + +### fs.chownSync(path) + +Replaces the `fs.chownSync` expecting `uid` and `gid` as the string representation +of [SIDs](http://msdn.microsoft.com/en-us/library/windows/desktop/aa379594.aspx). + + +### fs.fchown(fd, [callback]) + +Replaces the `fs.fchown` expecting `uid` and `gid` as the string representation +of [SIDs](http://msdn.microsoft.com/en-us/library/windows/desktop/aa379594.aspx). + +### fs.fchownSync(fd) + +Replaces the `fs.fchownSync` expecting `uid` and `gid` as the string representation +of [SIDs](http://msdn.microsoft.com/en-us/library/windows/desktop/aa379594.aspx). diff --git a/autores.cc b/autores.cc new file mode 100644 index 0000000..e05ebb6 --- /dev/null +++ b/autores.cc @@ -0,0 +1,12 @@ +#ifdef _WIN32 + +#include "autores.h" + +namespace autores { + +// the process heap will be initialized on the first usage +HANDLE HeapBase::processHeap = NULL; + +} // namespace autores + +#endif // _WIN32 diff --git a/autores.h b/autores.h new file mode 100644 index 0000000..a2c9db6 --- /dev/null +++ b/autores.h @@ -0,0 +1,459 @@ +#ifndef AUTORES_H +#define AUTORES_H + +#ifdef _WIN32 +#include +#endif + +#include +#include +#include + +namespace autores { + +// abstract class for wrappers of resources which need to be freed; +// the destructor disposes of the wrapped resource autmatically +// +// descendant class template: +// template class ManagedResource : +// public ManagedResource, T> { ... }; +// variable declaration: +// ManagedResource resource = OpenResource(...); +template < + typename Derived, + typename T + > +class AutoRes { + protected: + T handle; + + public: + // the default constructor; creates an empty wrapper + AutoRes() : handle(Derived::InitialValue()) {} + + // initializing constructor; this object will own the handle + AutoRes(T handle) : handle(handle) {} + + // ownership moving constructor; the source object will become empty + // and this object will own its handle + AutoRes(AutoRes & source) : handle(source.Detach()) {} + + // the destructor disposes of the wrapped handle, if it is valid + ~AutoRes() { + static_cast(this)->Dispose(); + } + + // ownership moving assignment operator + Derived & operator =(Derived & source) { + // dispose of the owned handle before hosting the new one + static_cast(this)->Dispose(); + // read the handle from the source object and leave it empty + // so that its destructor doesn't dispose of it when owned here + handle = source.Detach(); + return static_cast(*this); + } + + Derived & operator =(T source) { + // dispose of the owned handle before hosting the new one + static_cast(this)->Dispose(); + handle = source; + return static_cast(*this); + } + + // getting a pointer to the wrapper returns a pointer to the + // the wrapped value to be able to ue it as output parameter + // as well as the wrapped value alone would be + T * operator &() { + return &handle; + } + + // the wrapper instance can be used as input parameter as well + // as the wrapped value alone would be, thanks to this cast + operator T() const { + return handle; + } + + // gets the wrapped handle in statements where the casting + // operator is not practicable + T Get() const { + return handle; + } + + // returns the wrapped handle and removes it from the wrapper; so that + // when the wrapper is disposed of, the handle will stay intact + T Detach() { + T result = handle; + // the wrapper is left with an invalid value + handle = Derived::InitialValue(); + return result; + } + + // disposes of the wrapped handle, if the handle is valid + bool Dispose() { + // proceed only if the wrapped handle is valid + if (static_cast(this)->IsValid()) { + // delegate the disposal to the descended class; the DisposeInternal + // should be protected and AutoRes should be friend to the descendant + static_cast(this)->DisposeInternal(); + // leave the wrapper with an invalid value to allow multiple calls + // to this metod without failures + handle = Derived::InitialValue(); + } + return true; + } + + // checks whether a valid handle is stored in the wrapper; more values + // can be invalid, but at least one must be the InitialValue + bool IsValid() const { + return Derived::IsValidValue(handle); + } + + // checks whether the specified handle is valid + static bool IsValidValue(T handle) { + return handle != Derived::InitialValue(); + } + + // returns an invalid handle value stored in an empty wrapper + static T InitialValue() { + return NULL; + } +}; + +// abstract class for wrappers of allocated memory blocks +// +// descendant class template: +// template class ManagedMemory : +// public AutoMem, T> { ... }; +// variable declaration: +// ManagedMemory item = AllocateAndInitializeItem(...); +template < + typename Derived, + typename T + > +class AutoMem : public AutoRes< + Derived, T + > { + private: + typedef AutoRes< + Derived, T + > Base; + + protected: + typedef Base Res; + + bool DisposeInternal() { + return Derived::Unallocate(Base::handle); + } + + T & Dereference() { + // the value in the dereferenced memory is being accessed; + // it must not be posible if the wrapper is empty + assert(static_cast(this)->IsValid()); + return Base::handle; + } + + T const & Dereference() const { + // the value in the dereferenced memory is being accessed; + // it must not be posible if the wrapper is empty + assert(static_cast(this)->IsValid()); + return Base::handle; + } + + public: + AutoMem() {} + + AutoMem(T handle) : Base(handle) {} + + // ownership moving constructor + AutoMem(AutoMem & source) : Base(source) {} + + Derived & operator =(Derived & source) { + return Base::operator =(source); + } + + Derived & operator =(T source) { + return Base::operator =(source); + } + + // chain the dereferencing operator to make the members of the + // wrapped value accessible via the wrapper instance the same way + T operator ->() { + return Dereference(); + } +}; + +// wraps a pointer to an object allocated by new and disposed by delete +// +// variable declaration: +// CppObj item = new Item(...); +template < + typename T + > +class CppObj : public AutoMem< + CppObj, T + > { + private: + typedef AutoMem< + CppObj, T + > Base; + + friend class Base::Res; + + public: + CppObj() {} + + CppObj(T handle) : Base(handle) {} + + CppObj(CppObj & source) : Base(source) {} + + // ownership moving assignment operator + CppObj & operator =(CppObj & source) { + Base::operator =(source); + return *this; + } + + CppObj & operator =(T source) { + Base::operator =(source); + return *this; + } + + static bool Unallocate(T handle) { + delete handle; + return true; + } +}; + +#ifdef _WIN32 +// wraps a handle to a kernel object which is disposed by CloseHandle +// +// variable declaration: +// WinHandle file = CreateFile(...); +template < + typename T + > +class WinHandle : public AutoRes< + WinHandle, T + > { + private: + typedef AutoRes< + WinHandle, T + > Base; + + friend class Base; + + protected: + bool DisposeInternal() { + return CloseHandle(Base::handle) != FALSE; + } + + public: + WinHandle() {} + + WinHandle(T handle) : Base(handle) {} + + WinHandle(WinHandle & source) : Base(source) {} + + // ownership moving assignment operator + WinHandle & operator =(WinHandle & source) { + Base::operator =(source); + return *this; + } + + WinHandle & operator =(T source) { + Base::operator =(source); + return *this; + } + + static bool IsValidValue(T handle) { + return Base::IsValidValue(handle) && handle != INVALID_HANDLE_VALUE; + } +}; + +// wraps a pointer to memory allocated by LocalAlloc and disposed by LocalFree +// +// variable declaration: +// LocalMem memory = LocalAlloc(LMEM_FIXED, size); +template < + typename T + > +class LocalMem : public AutoMem< + LocalMem, T + > { + private: + typedef AutoMem< + LocalMem, T + > Base; + + friend class Base::Res; + + public: + LocalMem() {} + + LocalMem(T handle) : Base(handle) {} + + LocalMem(LocalMem & source) : Base(source) {} + + // ownership moving assignment operator + LocalMem & operator =(LocalMem & source) { + Base::operator =(source); + return *this; + } + + LocalMem & operator =(T source) { + Base::operator =(source); + return *this; + } + + static T Allocate(size_t size) { + return (T) LocalAlloc(LMEM_FIXED, size); + } + + static bool Unallocate(T handle) { + return LocalFree(handle) == NULL; + } +}; + +// wraps a pointer to memory allocated by GlobalAlloc and disposed by GlobalFree +// +// variable declaration: +// GlobalMem memory = GlobAlloc(GMEM_FIXED, size); +template < + typename T + > +class GlobalMem : public AutoMem< + GlobalMem, T + > { + private: + typedef AutoMem< + GlobalMem, T + > Base; + + friend class Base::Res; + + public: + GlobalMem() {} + + GlobalMem(T handle) : Base(handle) {} + + GlobalMem(GlobalMem & source) : Base(source) {} + + // ownership moving assignment operator + GlobalMem & operator =(GlobalMem & source) { + Base::operator =(source); + return *this; + } + + GlobalMem & operator =(T source) { + Base::operator =(source); + return *this; + } + + static T Allocate(size_t size) { + return (T) GlobalAlloc(GMEM_FIXED, size); + } + + static bool Unallocate(T handle) { + return GlobalFree(handle) == NULL; + } +}; + +// base class storing the heap which the memory block was allocated from; +// the descended class can set it or rely on the process heap by default +class HeapBase { + private: + static HANDLE processHeap; + + protected: + mutable HANDLE heap; + + public: + HeapBase() : heap(NULL) {} + + HeapBase(HANDLE heap) : heap(heap) {} + + HeapBase(HeapBase const & source) : heap(source.heap) {} + + HANDLE Heap() const { + if (heap == NULL) { + heap = ProcessHeap(); + } + return heap; + } + + static HANDLE ProcessHeap() { + if (processHeap == NULL) { + processHeap = GetProcessHeap(); + } + return processHeap; + } +}; + +// wraps a pointer to memory allocated by HeapAlloc and disposed by HeapFree +// +// variable declaration: +// HeapMem memory = HeapAlloc(GetProcessHeap(), 0, size); +template < + typename T + > +class HeapMem : public AutoMem< + HeapMem, T + >, + public HeapBase { + private: + typedef AutoMem< + HeapMem, T + > Base; + + friend class Base::Res; + + protected: + bool DisposeInternal() { + return Unallocate(Base::handle, HeapBase::Heap()); + } + + public: + HeapMem() {} + + HeapMem(T handle) : Base(handle) {} + + HeapMem(T handle, HANDLE heap) : Base(handle), HeapBase(heap) {} + + HeapMem(HeapMem & source) : Base(source), HeapBase(source.heap) {} + + // ownership moving assignment operator + HeapMem & operator =(HeapMem & source) { + Base::operator =(source); + heap = source.heap; + return *this; + } + + HeapMem & operator =(T source) { + Base::operator =(source); + return *this; + } + + HeapMem & Assign(T source, HANDLE heap = NULL) { + Base::operator =(source); + heap = heap; + return *this; + } + + static T Allocate(size_t size, HANDLE heap = NULL) { + if (heap == NULL) { + heap = HeapBase::ProcessHeap(); + } + return (T) HeapAlloc(heap, 0, size); + } + + static bool Unallocate(T handle, HANDLE heap = NULL) { + if (heap == NULL) { + heap = HeapBase::ProcessHeap(); + } + return HeapFree(heap, 0, handle) != FALSE; + } +}; +#endif // _WIN32 + +} // namespace autores + +#endif // AUTORES_H diff --git a/binding.gyp b/binding.gyp index be1f9c4..a7f3050 100644 --- a/binding.gyp +++ b/binding.gyp @@ -3,7 +3,10 @@ { "target_name": "fs-ext", "sources": [ - "fs-ext.cc" + "fs-ext.cc", + "fs-win.cc", + "autores.cc", + "winwrap.cc" ] } ] diff --git a/fs-ext.cc b/fs-ext.cc index d9e730a..f61e2cd 100644 --- a/fs-ext.cc +++ b/fs-ext.cc @@ -38,6 +38,8 @@ #include #endif +#include "fs-win.h" + using namespace v8; using namespace node; @@ -497,6 +499,10 @@ init (Handle target) f_favail_symbol = NODE_PSYMBOL("f_favail"); f_ffree_symbol = NODE_PSYMBOL("f_ffree"); #endif + +#ifdef _WIN32 + fs_win::init(target); +#endif } #if NODE_MODULE_VERSION > 1 diff --git a/fs-ext.js b/fs-ext.js index ae800a8..0af50ee 100644 --- a/fs-ext.js +++ b/fs-ext.js @@ -112,11 +112,243 @@ exports.statVFS = function(path, callback) { return binding.statVFS(path, callback); }; -// populate with fs functions from there -for (var key in fs) { - exports[key] = fs[key]; +// merges all members from the source object to the target object; +// it's like the underscore.extend +function merge(target, source) { + var key; + for (key in source) { + if (source.hasOwnProperty(key)) { + target[key] = source[key]; + } + } } +// stat and chown function group is reimplemented to work +// with SIDs for Windows only +var fsExt = process.platform.match(/^win/i) ? + (function () { + + // the result of fs.readlink is the exact string used when the link + // was created by `ln -s`, which can be a relative path; however, + // the path was relative to the current directory when the command + // was executed; not to the path of the link; if it wasn't so, this + // method will resolve to an invalid path and that's wahy you should + // always create links using the absolute target path + function resolveLink(fpath, lpath) { + // check if the path is absolute on both Windows and POSIX platforms + if (/^([a-z]:)?[\/\\]/i.test(lpath)) { + return lpath; + } + return path.join(path.dirname(fpath), lpath); + } + + var path = require("path"), + + // declare the extra methods for the built-in fs module + // which provide the POSIX functionality on Windows + fsExt = (function () { + + // merges the ownership to the stats + function completeStats(stats, fd, callback) { + // allow calling with both fd and path + (typeof fd === "string" ? binding.getown : + binding.fgetown)(fd, function(error, ownership) { + if (error) { + callback(error); + } else { + // replace the uid and gid members in the original stats + // with the values containing SIDs + merge(stats, ownership); + callback(undefined, stats); + } + }); + } + + // merges the ownership to the stats + function completeStatsSync(stats, fd) { + // allow calling with both fd and path + var ownership = (typeof fd === "string" ? + binding.getown : binding.fgetown)(fd); + // replace the uid and gid members in the original stats + // with the values containing SIDs + merge(stats, ownership); + return stats; + } + + return { + + // fs.fstat returning uid and gid as SIDs + fstat: function(fd, callback) { + // get the built-in stats which work on Windows too + fs.fstat(fd, function(error, stats) { + if (error) { + callback(error); + } else { + // replace the ownership information (uid and gid) + // with the data useful on Windows - principal SIDs + completeStats(stats, fd, callback); + } + }); + }, + + // fs.fstatSync returning uid and gid as SIDs + fstatSync: function(fd) { + // get the built-in stats which work on Windows too + var stats = fs.fstatSync(fd); + // replace the ownership information (uid and gid) + // with the data useful on Windows - principal SIDs + return completeStatsSync(stats, fd); + }, + + // fs.stat returning uid and gid as SIDs + stat: function(fpath, callback) { + // get the built-in stats which work on Windows too + fs.lstat(fpath, function(error, stats) { + if (error) { + callback(error); + } else { + // GetNamedSecurityInfo, which is used by binding.getown, + // doesn't resolve sybolic links automatically; do the + // resolution here and call the lstat implementation + if (stats.isSymbolicLink()) { + fs.readlink(fpath, function(error, lpath) { + if (error) { + callback(error); + } else { + fpath = resolveLink(fpath, lpath); + fsExt.lstat(fpath, callback); + } + }); + } else { + // replace the ownership information (uid and gid) + // with the data useful on Windows - principal SIDs + completeStats(stats, fpath, callback); + } + } + }); + }, + + // fs.statSync returning uid and gid as SIDs + statSync: function(fpath) { + // get the built-in stats which work on Windows too + // GetNamedSecurityInfo, which is used by binding.getown, + // doesn't resolve sybolic links automatically; do the + // resolution here and call the lstat implementation + var stats = fs.lstatSync(fpath); + if (stats.isSymbolicLink()) { + var lpath = fs.readlinkSync(fpath); + fpath = resolveLink(fpath, lpath); + return fsExt.lstatSync(fpath); + } + // replace the ownership information (uid and gid) + // with the data useful on Windows - principal SIDs + return completeStatsSync(stats, fpath); + }, + + // fs.lstat returning uid and gid as SIDs + lstat: function(fpath, callback) { + // get the built-in stats which work on Windows too + fs.lstat(fpath, function(error, stats) { + if (error) { + callback(error); + } else { + // replace the ownership information (uid and gid) + // with the data useful on Windows - principal SIDs + completeStats(stats, fpath, callback); + } + }); + }, + + // fs.lstatSync returning uid and gid as SIDs + lstatSync: function(fpath) { + // get the built-in stats which work on Windows too + // GetNamedSecurityInfo, which is used by binding.getown, + // doesn't resolve sybolic links automatically; it's + // suitable for the lstat implementation as-is + var stats = fs.lstatSync(fpath); + // replace the ownership information (uid and gid) + // with the data useful on Windows - principal SIDs + return completeStatsSync(stats, fpath); + }, + + // fs.fchown accepting uid and gid as SIDs + fchown: function(fd, uid, gid, callback) { + binding.fchown(fd, uid, gid, function(error) { + callback(error); + }); + }, + + // fs.fchownSync accepting uid and gid as SIDs + fchownSync: function(fd, uid, gid) { + binding.fchown(fd, uid, gid); + }, + + // fs.chown accepting uid and gid as SIDs + chown: function(fpath, uid, gid, callback) { + fs.lstat(fpath, function(error, stats) { + if (error) { + callback(error); + } else { + if (stats.isSymbolicLink()) { + fs.readlink(fpath, function(error, lpath) { + if (error) { + callback(error); + } else { + fpath = resolveLink(fpath, lpath); + fsExt.lchown(fpath, uid, gid, callback); + } + }); + } else { + fsExt.lchown(fpath, uid, gid, callback); + } + } + }); + }, + + // fs.chownSync accepting uid and gid as SIDs + chownSync: function(fpath, uid, gid) { + // SetNamedSecurityInfo, which is used by binding.chown, + // doesn't resolve sybolic links automatically; do the + // resolution here and call the lchown implementation + var stats = fs.lstatSync(fpath); + if (stats.isSymbolicLink()) { + var lpath = fs.readlinkSync(fpath); + fpath = resolveLink(fpath, lpath); + } + fsExt.lchownSync(fpath, uid, gid); + }, + + // fs.lchown accepting uid and gid as SIDs + lchown: function(fpath, uid, gid, callback) { + binding.chown(fpath, uid, gid, function(error) { + callback(error); + }); + }, + + // fs.lchownSync accepting uid and gid as SIDs + lchownSync: function(fpath, uid, gid) { + // SetNamedSecurityInfo, which is used by binding.chown, + // doesn't resolve sybolic links automatically; it's + // suitable for the lchown implementation as-is + binding.chown(fpath, uid, gid); + } + + }; + + }()); + + return fsExt; + }()) + : + // the implementation doesn't need the native add-on on POSIX + {}; + +// populate with fs functions from there +merge(exports, fs); + +// replace the functions enhanced on Windows +merge(exports, fsExt); + // put constants into constants module (don't like doing this but...) for (var key in binding) { if (/^[A-Z_]+$/.test(key) && !constants.hasOwnProperty(key)) { diff --git a/fs-win.cc b/fs-win.cc new file mode 100644 index 0000000..0a5f94a --- /dev/null +++ b/fs-win.cc @@ -0,0 +1,688 @@ +#ifdef _WIN32 + +#include "fs-win.h" +#include "autores.h" +#include "winwrap.h" + +#include +#include +#include +#include + +// methods: +// fgetown, getown, +// fchown, chown +// +// method implementation pattern: +// +// register method_impl as exports.method +// method_impl { +// if sync: call method_sync, return convert_result +// if async: queue method_async with after_async +// } +// method_async { +// call method_sync +// } +// after_async { +// for OPERATION_METHOD: return convert_result to callback +// } + +namespace fs_win { + +using namespace node; +using namespace v8; +using namespace autores; + +// helpers for returning errors from native methods +#define THROW_TYPE_ERROR(msg) \ + ThrowException(Exception::TypeError(String::New(msg))) +#define LAST_WINAPI_ERROR \ + ((int) GetLastError()) +#define THROW_WINAPI_ERROR(err) \ + ThrowException(WinapiErrnoException(err)) +#define THROW_LAST_WINAPI_ERROR \ + THROW_WINAPI_ERROR(LAST_WINAPI_ERROR) + +// members names of result object literals +static Persistent uid_symbol; +static Persistent gid_symbol; + +// ------------------------------------------------ +// internal functions to support the native exports + +// class helper to enable and disable taking object ownership in this +// process; it's used explicitly by calling Enable and Disable to be +// able to check for errors, but it supports RAII too for error cases +class TakingOwhership { + private: + WinHandle process; + bool enabled; + + // changes the privileges necessary for taking ownership + // in the current process - either enabling or disabling it + BOOL SetPrivileges(BOOL enable) { + LPCTSTR const names[] = { + SE_TAKE_OWNERSHIP_NAME, SE_SECURITY_NAME, + SE_BACKUP_NAME, SE_RESTORE_NAME + }; + + HeapMem privileges = + HeapMem::Allocate(FIELD_OFFSET( + TOKEN_PRIVILEGES, Privileges[sizeof(names) / sizeof(names[0])])); + if (privileges == NULL) { + return FALSE; + } + privileges->PrivilegeCount = sizeof(names) / sizeof(names[0]); + for (size_t i = 0; i < privileges->PrivilegeCount; ++i) { + if (LookupPrivilegeValue(NULL, names[i], + &privileges->Privileges[i].Luid) == FALSE) { + return FALSE; + } + privileges->Privileges[i].Attributes = + enable != FALSE ? SE_PRIVILEGE_ENABLED : 0; + } + + if (AdjustTokenPrivileges(process, FALSE, privileges, + sizeof(privileges), NULL, NULL) == FALSE) { + return FALSE; + } + if (GetLastError() == ERROR_NOT_ALL_ASSIGNED) { + SetLastError(ERROR_NOT_ALL_ASSIGNED); + return FALSE; + } + + return TRUE; + } + + public: + TakingOwhership() : enabled(false) {} + + ~TakingOwhership() { + Disable(); + } + + DWORD Enable() { + if (OpenProcessToken(GetCurrentProcess(), + TOKEN_ADJUST_PRIVILEGES, &process) == FALSE) { + return LAST_WINAPI_ERROR; + } + if (SetPrivileges(TRUE) == FALSE) { + return LAST_WINAPI_ERROR; + } + enabled = true; + return ERROR_SUCCESS; + } + + DWORD Disable() { + if (enabled) { + if (SetPrivileges(FALSE) == FALSE) { + return LAST_WINAPI_ERROR; + } + if (!process.Dispose()) { + return LAST_WINAPI_ERROR; + } + enabled = false; + } + return ERROR_SUCCESS; + } +}; + +// ----------------------------------------- +// support for asynchronous method execution + +// codes of exposed native methods +typedef enum { + OPERATION_FGETOWN, + OPERATION_GETOWN, + OPERATION_FCHOWN, + OPERATION_CHOWN +} operation_t; + +// passes input/output parameters between the native method entry point +// and the worker method doing the work, which is called asynchronously +struct async_data_t { + uv_work_t request; + Persistent callback; + DWORD error; + + operation_t operation; + int fd; + HeapMem path; + LocalMem susid, sgsid; + + async_data_t(Local lcallback) { + if (!lcallback.IsEmpty()) { + callback = Persistent::New(lcallback); + } + request.data = this; + } + + ~async_data_t() { + if (!callback.IsEmpty()) { + callback.Dispose(); + } + } +}; + +// makes a JavaScript result object literal of user and group SIDs +static Local convert_ownership(LPSTR uid, LPSTR gid) { + Local result = Object::New(); + if (!result.IsEmpty()) { + result->Set(uid_symbol, String::New(uid)); + result->Set(gid_symbol, String::New(gid)); + } + return result; +} + +// called after an asynchronously called method (method_sync) has +// finished to convert the results to JavaScript objects and pass +// them to JavaScript callback +static void after_async(uv_work_t * req) { + assert(req != NULL); + HandleScope scope; + + Local argv[2]; + int argc = 1; + + async_data_t * async_data = static_cast(req->data); + if (async_data->error != ERROR_SUCCESS) { + argv[0] = WinapiErrnoException(async_data->error); + } else { + // in case of success, make the first argument (error) null + argv[0] = Local::New(Null()); + // in case of success, populate the second and other arguments + switch (async_data->operation) { + case OPERATION_FGETOWN: + case OPERATION_GETOWN: { + argv[1] = convert_ownership( + async_data->susid, async_data->sgsid); + argc = 2; + break; + } + case OPERATION_FCHOWN: + case OPERATION_CHOWN: + break; + default: + assert(FALSE && "Unknown operation"); + } + } + + // pass the results to the external callback + TryCatch tryCatch; + async_data->callback->Call(Context::GetCurrent()->Global(), argc, argv); + if (tryCatch.HasCaught()) { + FatalException(tryCatch); + } + + async_data->callback.Dispose(); + delete async_data; +} + +// ------------------------------------------------------- +// fgetown - gets the file or directory ownership as SIDs: +// { uid, gid } fgetown( fd, [callback] ) + +static int fgetown_sync(int fd, LPSTR *uid, LPSTR *gid) { + assert(uid != NULL); + assert(gid != NULL); + + HANDLE fh = (HANDLE) _get_osfhandle(fd); + + PSID usid = NULL, gsid = NULL; + LocalMem sd; + DWORD error = GetSecurityInfo(fh, SE_FILE_OBJECT, + OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION, + &usid, &gsid, NULL, NULL, &sd); + if (error != ERROR_SUCCESS) { + return error; + } + + LocalMem susid; + if (ConvertSidToStringSid(usid, &susid) == FALSE) { + return LAST_WINAPI_ERROR; + } + + LocalMem sgsid; + if (ConvertSidToStringSid(gsid, &sgsid) == FALSE) { + return LAST_WINAPI_ERROR; + } + + *uid = susid.Detach(); + *gid = sgsid.Detach(); + + return ERROR_SUCCESS; +} + +// passes the execution to fgetown_sync; the results will be processed +// by after_async +static void fgetown_async(uv_work_t * req) { + assert(req != NULL); + async_data_t * async_data = static_cast(req->data); + async_data->error = fgetown_sync(async_data->fd, + &async_data->susid, &async_data->sgsid); +} + +// the native entry point for the exposed fgetown function +static Handle fgetown_impl(Arguments const & args) { + HandleScope scope; + + int argc = args.Length(); + if (argc < 1) + return THROW_TYPE_ERROR("fd required"); + if (argc > 2) + return THROW_TYPE_ERROR("too many arguments"); + if (!args[0]->IsInt32()) + return THROW_TYPE_ERROR("fd must be an int"); + if (argc > 1 && !args[1]->IsFunction()) + return THROW_TYPE_ERROR("callback must be a function"); + + int fd = args[0]->Int32Value(); + + // if no callback was provided, assume the synchronous scenario, + // call the method_sync immediately and return its results + if (!args[1]->IsFunction()) { + LocalMem susid, sgsid; + DWORD error = fgetown_sync(fd, &susid, &sgsid); + if (error != ERROR_SUCCESS) { + return THROW_WINAPI_ERROR(error); + } + Local result = convert_ownership(susid, sgsid); + return scope.Close(result); + } + + // prepare parameters for the method_sync to be called later + // from the method_async called from the worker thread + CppObj async_data = new async_data_t( + Local::Cast(args[1])); + async_data->operation = OPERATION_FGETOWN; + async_data->fd = fd; + + // queue the method_async to be called when posibble and + // after_async to send its result to the external callback + uv_queue_work(uv_default_loop(), &async_data->request, + fgetown_async, (uv_after_work_cb) after_async); + + async_data.Detach(); + return Undefined(); +} + +// ------------------------------------------------------ +// getown - gets the file or directory ownership as SIDs: +// { uid, gid } getown( path, [callback] ) + +// gets the file ownership (uid and gid) for the file path +static int getown_sync(LPCSTR path, LPSTR *uid, LPSTR *gid) { + assert(path != NULL); + assert(uid != NULL); + assert(gid != NULL); + + PSID usid = NULL, gsid = NULL; + LocalMem sd; + DWORD error = GetNamedSecurityInfo(path, SE_FILE_OBJECT, + OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION, + &usid, &gsid, NULL, NULL, &sd); + if (error != ERROR_SUCCESS) { + return error; + } + + LocalMem susid; + if (ConvertSidToStringSid(usid, &susid) == FALSE) { + return LAST_WINAPI_ERROR; + } + + LocalMem sgsid; + if (ConvertSidToStringSid(gsid, &sgsid) == FALSE) { + return LAST_WINAPI_ERROR; + } + + *uid = susid.Detach(); + *gid = sgsid.Detach(); + + return ERROR_SUCCESS; +} + +// passes the execution to getown_sync; the results will be processed +// by after_async +static void getown_async(uv_work_t * req) { + assert(req != NULL); + async_data_t * async_data = static_cast(req->data); + async_data->error = getown_sync(async_data->path, + &async_data->susid, &async_data->sgsid); +} + +// the native entry point for the exposed getown function +static Handle getown_impl(Arguments const & args) { + HandleScope scope; + + int argc = args.Length(); + if (argc < 1) + return THROW_TYPE_ERROR("path required"); + if (argc > 2) + return THROW_TYPE_ERROR("too many arguments"); + if (!args[0]->IsString()) + return THROW_TYPE_ERROR("path must be a string"); + if (argc > 1 && !args[1]->IsFunction()) + return THROW_TYPE_ERROR("callback must be a function"); + + String::Utf8Value path(args[0]->ToString()); + + // if no callback was provided, assume the synchronous scenario, + // call the method_sync immediately and return its results + if (!args[1]->IsFunction()) { + LocalMem susid, sgsid; + DWORD error = getown_sync(*path, &susid, &sgsid); + if (error != ERROR_SUCCESS) { + return THROW_WINAPI_ERROR(error); + } + Local result = convert_ownership(susid, sgsid); + return scope.Close(result); + } + + // prepare parameters for the method_sync to be called later + // from the method_async called from the worker thread + CppObj async_data = new async_data_t( + Local::Cast(args[1])); + async_data->operation = OPERATION_GETOWN; + async_data->path = HeapStrDup(HeapBase::ProcessHeap(), *path); + if (!async_data->path.IsValid()) { + return THROW_LAST_WINAPI_ERROR; + } + + // queue the method_async to be called when posibble and + // after_async to send its result to the external callback + uv_queue_work(uv_default_loop(), &async_data->request, + getown_async, (uv_after_work_cb) after_async); + + async_data.Detach(); + return Undefined(); +} + +// -------------------------------------------------------- +// fchown - sets the file or directory ownership with SIDs: +// fchown( fd, uid, gid, [callback] ) + +// change the ownership (uid and gid) of the file specified by the +// file descriptor; either uid or gid can be empty ("") to change +// just one of them +static int fchown_sync(int fd, LPCSTR uid, LPCSTR gid) { + assert(uid != NULL); + assert(gid != NULL); + + // get the OS file handle for the specified file descriptor + HANDLE fh = (HANDLE) _get_osfhandle(fd); + + // convert the input SIDs from strings to SID structures + LocalMem usid; + if (*uid && ConvertStringSidToSid(uid, &usid) == FALSE) { + return LAST_WINAPI_ERROR; + } + LocalMem gsid; + if (*gid && ConvertStringSidToSid(gid, &gsid) == FALSE) { + return LAST_WINAPI_ERROR; + } + + // enable taking object ownership in the current process + // if the effective user has enough permissions + TakingOwhership takingOwhership; + DWORD error = takingOwhership.Enable(); + if (error != ERROR_SUCCESS) { + return error; + } + + // take ownership of the object specified by the file handle + if (*uid && *gid) { + if (SetSecurityInfo(fh, SE_FILE_OBJECT, + OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION, + usid, gsid, NULL, NULL) != ERROR_SUCCESS) { + return LAST_WINAPI_ERROR; + } + } else if (*uid) { + if (SetSecurityInfo(fh, SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION, + usid, NULL, NULL, NULL) != ERROR_SUCCESS) { + return LAST_WINAPI_ERROR; + } + } else if (*gid) { + if (SetSecurityInfo(fh, SE_FILE_OBJECT, GROUP_SECURITY_INFORMATION, + NULL, gsid, NULL, NULL) != ERROR_SUCCESS) { + return LAST_WINAPI_ERROR; + } + } + + // disnable taking object ownership in the current process + // not to leak the availability of this privileged operation + error = takingOwhership.Disable(); + if (error != ERROR_SUCCESS) { + return error; + } + + return ERROR_SUCCESS; +} + +// passes the execution to fchown_sync; the results will be processed +// by after_async +static void fchown_async(uv_work_t * req) { + assert(req != NULL); + async_data_t * async_data = static_cast(req->data); + async_data->error = fchown_sync(async_data->fd, + async_data->susid, async_data->sgsid); +} + +// the native entry point for the exposed fchown function +static Handle fchown_impl(Arguments const & args) { + HandleScope scope; + + int argc = args.Length(); + if (argc < 1) + return THROW_TYPE_ERROR("fd required"); + if (argc > 4) + return THROW_TYPE_ERROR("too many arguments"); + if (!args[0]->IsInt32()) + return THROW_TYPE_ERROR("fd must be an int"); + if (argc < 2) + return THROW_TYPE_ERROR("uid required"); + if (!args[1]->IsString() && !args[1]->IsUndefined()) + return THROW_TYPE_ERROR("uid must be a string or undefined"); + if (argc < 3) + return THROW_TYPE_ERROR("gid required"); + if (!args[2]->IsString() && !args[2]->IsUndefined()) + return THROW_TYPE_ERROR("gid must be a string or undefined"); + if (argc > 3 && !args[3]->IsFunction()) + return THROW_TYPE_ERROR("callback must be a function"); + if (args[1]->IsUndefined() && args[2]->IsUndefined()) + return THROW_TYPE_ERROR("either uid or gid must be defined"); + + int fd = args[0]->Int32Value(); + String::AsciiValue susid(args[1]->IsString() ? + args[1]->ToString() : String::New("")); + String::AsciiValue sgsid(args[2]->IsString() ? + args[2]->ToString() : String::New("")); + + // if no callback was provided, assume the synchronous scenario, + // call the method_sync immediately and return its results + if (!args[3]->IsFunction()) { + DWORD error = fchown_sync(fd, *susid, *sgsid); + if (error != ERROR_SUCCESS) { + return THROW_WINAPI_ERROR(error); + } + return Undefined(); + } + + // prepare parameters for the method_sync to be called later + // from the method_async called from the worker thread + CppObj async_data = new async_data_t( + Local::Cast(args[3])); + async_data->operation = OPERATION_FCHOWN; + async_data->fd = fd; + async_data->susid = LocalStrDup(*sgsid); + if (!async_data->susid.IsValid()) { + return THROW_LAST_WINAPI_ERROR; + } + async_data->sgsid = LocalStrDup(*susid); + if (!async_data->sgsid.IsValid()) { + return THROW_LAST_WINAPI_ERROR; + } + + // queue the method_async to be called when posibble and + // after_async to send its result to the external callback + uv_queue_work(uv_default_loop(), &async_data->request, + fchown_async, (uv_after_work_cb) after_async); + + async_data.Detach(); + return Undefined(); +} + +// ------------------------------------------------------- +// chown - sets the file or directory ownership with SIDs: +// chown( name, uid, gid, [callback] ) + +// change the ownership (uid and gid) of the file specified by the +// file path; either uid or gid can be empty ("") to change +// just one of them +static int chown_sync(LPCSTR path, LPCSTR uid, LPCSTR gid) { + assert(path != NULL); + assert(uid != NULL); + assert(gid != NULL); + + // convert the input SIDs from strings to SID structures + LocalMem usid; + if (*uid && ConvertStringSidToSid(uid, &usid) == FALSE) { + return LAST_WINAPI_ERROR; + } + LocalMem gsid; + if (*gid && ConvertStringSidToSid(gid, &gsid) == FALSE) { + return LAST_WINAPI_ERROR; + } + + // enable taking object ownership in the current process + // if the effective user has enough permissions + TakingOwhership takingOwhership; + DWORD error = takingOwhership.Enable(); + if (error != ERROR_SUCCESS) { + return error; + } + + // take ownership of the object specified by its path + if (*uid && *gid) { + if (SetNamedSecurityInfo(const_cast(path), SE_FILE_OBJECT, + OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION, + usid, gsid, NULL, NULL) != ERROR_SUCCESS) { + return LAST_WINAPI_ERROR; + } + } else if (*uid) { + if (SetNamedSecurityInfo(const_cast(path), + SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION, + usid, gsid, NULL, NULL) != ERROR_SUCCESS) { + return LAST_WINAPI_ERROR; + } + } else if (*gid) { + if (SetNamedSecurityInfo(const_cast(path), + SE_FILE_OBJECT, GROUP_SECURITY_INFORMATION, + NULL, gsid, NULL, NULL) != ERROR_SUCCESS) { + return LAST_WINAPI_ERROR; + } + } + + // disnable taking object ownership in the current process + // not to leak the availability of this privileged operation + error = takingOwhership.Disable(); + if (error != ERROR_SUCCESS) { + return error; + } + + return ERROR_SUCCESS; +} + +// passes the execution to chown_sync; the results will be processed +// by after_async +static void chown_async(uv_work_t * req) { + assert(req != NULL); + async_data_t * async_data = static_cast(req->data); + async_data->error = chown_sync(async_data->path, + async_data->susid, async_data->sgsid); +} + +// the native entry point for the exposed chown function +static Handle chown_impl(Arguments const & args) { + HandleScope scope; + + int argc = args.Length(); + if (argc < 1) + return THROW_TYPE_ERROR("path required"); + if (argc > 4) + return THROW_TYPE_ERROR("too many arguments"); + if (!args[0]->IsString()) + return THROW_TYPE_ERROR("path must be a string"); + if (argc < 2) + return THROW_TYPE_ERROR("uid required"); + if (!args[1]->IsString() && !args[1]->IsUndefined()) + return THROW_TYPE_ERROR("uid must be a string or undefined"); + if (argc < 3) + return THROW_TYPE_ERROR("gid required"); + if (!args[2]->IsString() && !args[2]->IsUndefined()) + return THROW_TYPE_ERROR("gid must be a string or undefined"); + if (argc > 3 && !args[3]->IsFunction()) + return THROW_TYPE_ERROR("callback must be a function"); + if (args[1]->IsUndefined() && args[2]->IsUndefined()) + return THROW_TYPE_ERROR("either uid or gid must be defined"); + + String::Utf8Value path(args[0]->ToString()); + String::AsciiValue susid(args[1]->IsString() ? + args[1]->ToString() : String::New("")); + String::AsciiValue sgsid(args[2]->IsString() ? + args[2]->ToString() : String::New("")); + + // if no callback was provided, assume the synchronous scenario, + // call the method_sync immediately and return its results + if (!args[3]->IsFunction()) { + DWORD error = chown_sync(*path, *susid, *sgsid); + if (error != ERROR_SUCCESS) { + return THROW_WINAPI_ERROR(error); + } + return Undefined(); + } + + // prepare parameters for the method_sync to be called later + // from the method_async called from the worker thread + CppObj async_data = new async_data_t( + Local::Cast(args[3])); + async_data->operation = OPERATION_CHOWN; + async_data->path = HeapStrDup(HeapBase::ProcessHeap(), *path); + if (!async_data->path.IsValid()) { + return THROW_LAST_WINAPI_ERROR; + } + async_data->susid = LocalStrDup(*sgsid); + if (!async_data->susid.IsValid()) { + return THROW_LAST_WINAPI_ERROR; + } + async_data->sgsid = LocalStrDup(*susid); + if (!async_data->sgsid.IsValid()) { + return THROW_LAST_WINAPI_ERROR; + } + + // queue the method_async to be called when posibble and + // after_async to send its result to the external callback + uv_queue_work(uv_default_loop(), &async_data->request, + chown_async, (uv_after_work_cb) after_async); + + async_data.Detach(); + return Undefined(); +} + +// exposes methods implemented by this sub-package and initializes the +// string symbols for the converted resulting object literals; to be +// called from the add-on module-initializing function +void init(Handle target) { + HandleScope scope; + + NODE_SET_METHOD(target, "fgetown", fgetown_impl); + NODE_SET_METHOD(target, "getown", getown_impl); + NODE_SET_METHOD(target, "fchown", fchown_impl); + NODE_SET_METHOD(target, "chown", chown_impl); + + uid_symbol = NODE_PSYMBOL("uid"); + gid_symbol = NODE_PSYMBOL("gid"); +} + +} // namespace fs_win + +#endif // _WIN32 diff --git a/fs-win.h b/fs-win.h new file mode 100644 index 0000000..f40a4c0 --- /dev/null +++ b/fs-win.h @@ -0,0 +1,17 @@ +#ifndef FS_WIN_H +#define FS_WIN_H + +#include +#include + +namespace fs_win { + +using namespace node; +using namespace v8; + +// to be called during the node add-on initialization +void init(Handle target); + +} // namespace fs_win + +#endif // FS_WIN_H diff --git a/run_tests b/run_tests index 72b1c8e..3f6a1dc 100755 --- a/run_tests +++ b/run_tests @@ -6,6 +6,11 @@ node tests/test-fs-flock.js node tests/test-fs-utime.js +node tests/test-fs-stat.js + +# this test must be run as root +#sudo node tests/test-fs-chown.js + # for stress testing only # node tests/test-fs-seek_stress.js # node tests/test-fs-flock_stress.js diff --git a/tests/test-fs-chown.js b/tests/test-fs-chown.js new file mode 100644 index 0000000..6b9bcf2 --- /dev/null +++ b/tests/test-fs-chown.js @@ -0,0 +1,53 @@ +// tests the fs methods chown and fchown +"use strict"; +var assert = require("assert"), + path = require("path"), + fs = require("../fs-ext"), + tmp_dir = process.env.TMP || process.env.TEMP || "/tmp", + file_path = path.join(tmp_dir, "fs-ext_chown.test"), + // use the current process user or Administrator on Windows + uid = process.getuid ? process.getuid() : "S-1-5-32-500", + // use the "nobody" user or the Users group on Windows + other_uid = process.platform.match(/^win/i) ? "S-1-5-32-513" : 65534, + fd; + +function check_stats(uid) { + var stats = fs.statSync(file_path); + assert.equal(stats.uid, uid); + assert.equal(stats.gid, uid); +} + +fd = fs.openSync(file_path, "w"); +fs.closeSync(fd); +fs.chmodSync(file_path, "0666"); + +fs.chownSync(file_path, other_uid, other_uid); +check_stats(other_uid); + +fd = fs.openSync(file_path, "w+"); +fs.fchownSync(fd, uid, uid); +fs.closeSync(fd); +check_stats(uid); + +fs.chown(file_path, other_uid, other_uid, function (error) { + assert.ok(!error); + check_stats(other_uid); + + fd = fs.openSync(file_path, "w+"); + fs.fchown(fd, uid, uid, function (error) { + fs.closeSync(fd); + assert.ok(!error); + check_stats(uid); + }); +}); + +process.addListener("exit", function() { + try { + fs.closeSync(fd); + } catch (error) {} + try { + fs.unlinkSync(file_path); + } catch (error) { + console.warn(" deleting", file_path, "failed"); + } +}); diff --git a/tests/test-fs-stat.js b/tests/test-fs-stat.js new file mode 100644 index 0000000..47d5ca6 --- /dev/null +++ b/tests/test-fs-stat.js @@ -0,0 +1,57 @@ +// tests the fs methods stat, lstat and fstat +"use strict"; +var assert = require("assert"), + path = require("path"), + fs = require("../fs-ext"), + tmp_dir = process.env.TMP || process.env.TEMP || "/tmp", + file_path = path.join(tmp_dir, "fs-ext_stat.test"), + fd, stats; + +function check_stats(stats) { + if (process.platform.match(/^win/i)) { + assert.equal(typeof stats.uid, "string"); + assert.equal(stats.uid.indexOf("S-"), 0); + assert.equal(typeof stats.gid, "string"); + assert.equal(stats.gid.indexOf("S-"), 0); + } else { + assert.equal(typeof stats.uid, "number"); + assert.equal(typeof stats.gid, "number"); + } +} + +fd = fs.openSync(file_path, "w"); +fs.closeSync(fd); +fs.chmodSync(file_path, "0666"); + +stats = fs.statSync(file_path); +check_stats(stats); +fs.stat(file_path, function (error, stats) { + assert.ok(!error && stats); + check_stats(stats); +}); + +stats = fs.lstatSync(file_path); +check_stats(stats); +fs.lstat(file_path, function (error, stats) { + assert.ok(!error && stats); + check_stats(stats); +}); + +fd = fs.openSync(file_path, "r"); +stats = fs.fstatSync(fd); +check_stats(stats); +fs.fstat(fd, function (error, stats) { + assert.ok(!error && stats); + check_stats(stats); +}); + +process.addListener("exit", function() { + try { + fs.closeSync(fd); + } catch (error) {} + try { + fs.unlinkSync(file_path); + } catch (error) { + console.warn(" deleting", file_path, "failed"); + } +}); diff --git a/winwrap.cc b/winwrap.cc new file mode 100644 index 0000000..2952563 --- /dev/null +++ b/winwrap.cc @@ -0,0 +1,48 @@ +#ifdef _WIN32 + +#include "winwrap.h" +#include "autores.h" + +#include + +using namespace autores; + +// duplicates a string using the memory allocating method of a memory +// managing template descended from AutoMem +template LPTSTR StrDup(LPCTSTR source) { + assert(source != NULL); + // the allocating method accepts byte size; not item count + size_t size = (_tcslen(source) + 1) * sizeof(TCHAR); + LPTSTR target = A::Allocate(size); + if (target != NULL) { + // copy including the terminating zero character + CopyMemory(target, source, size); + } + return target; +} + +// duplicates a string using the LocalAlloc to allocate memory +LPTSTR LocalStrDup(LPCTSTR source) { + return StrDup >(source); +} + +// duplicates a string using the GlobalAlloc to allocate memory +LPTSTR GlobalStrDup(LPCTSTR source) { + return StrDup >(source); +} + +// duplicates a string using the HeapAlloc to allocate memory +LPTSTR HeapStrDup(HANDLE heap, LPCTSTR source) { + assert(heap != NULL); + assert(source != NULL); + // the allocating method accepts byte size; not item count + size_t size = (_tcslen(source) + 1) * sizeof(TCHAR); + LPTSTR target = HeapMem::Allocate(size, heap); + if (target != NULL) { + // copy including the terminating zero character + CopyMemory(target, source, size); + } + return target; +} + +#endif // _WIN32 diff --git a/winwrap.h b/winwrap.h new file mode 100644 index 0000000..08f9c1b --- /dev/null +++ b/winwrap.h @@ -0,0 +1,14 @@ +#ifndef WINWRAP_H +#define WINWRAP_H + +#include +#include + +// duplicates a string using the LocalAlloc to allocate memory +LPTSTR LocalStrDup(LPCTSTR source); +// duplicates a string using the GlobalAlloc to allocate memory +LPTSTR GlobalStrDup(LPCTSTR source); +// duplicates a string using the HeapAlloc to allocate memory +LPTSTR HeapStrDup(HANDLE heap, LPCTSTR source); + +#endif // WINWRAP_H