dlopen: switch to new pathfinder class
Instead of find_exec, without changing behaviour use new pathfinder class with new allocator_interface around tmp_pathbuf and new vstrlist class. * pathfinder.h (pathfinder): New file. * vstrlist.h (allocator_interface, allocated_type, vstrlist): New file. * dlfcn.cc (dlopen): Avoid redundant GetModuleHandleExW with RTLD_NOLOAD and RTLD_NODELETE. Switch to new pathfinder class, using (tmp_pathbuf_allocator): New class. (get_full_path_of_dll): Drop.
This commit is contained in:
parent
091a0ac120
commit
97974e3076
|
@ -20,6 +20,74 @@ details. */
|
|||
#include "cygtls.h"
|
||||
#include "tls_pbuf.h"
|
||||
#include "ntdll.h"
|
||||
#include "pathfinder.h"
|
||||
|
||||
/* Dumb allocator using memory from tmp_pathbuf.w_get ().
|
||||
|
||||
Does not reuse free'd memory areas. Instead, memory
|
||||
is released when the tmp_pathbuf goes out of scope.
|
||||
|
||||
ATTENTION: Requesting memory from an instance of tmp_pathbuf breaks
|
||||
when another instance on a newer stack frame has provided memory. */
|
||||
class tmp_pathbuf_allocator
|
||||
: public allocator_interface
|
||||
{
|
||||
tmp_pathbuf & tp_;
|
||||
union
|
||||
{
|
||||
PWCHAR wideptr;
|
||||
void * voidptr;
|
||||
char * byteptr;
|
||||
} freemem_;
|
||||
size_t freesize_;
|
||||
|
||||
/* allocator_interface */
|
||||
virtual void * alloc (size_t need)
|
||||
{
|
||||
if (need == 0)
|
||||
need = 1; /* GNU-ish */
|
||||
size_t chunksize = NT_MAX_PATH * sizeof (WCHAR);
|
||||
if (need > chunksize)
|
||||
api_fatal ("temporary buffer too small for %d bytes", need);
|
||||
if (need > freesize_)
|
||||
{
|
||||
/* skip remaining, use next chunk */
|
||||
freemem_.wideptr = tp_.w_get ();
|
||||
freesize_ = chunksize;
|
||||
}
|
||||
|
||||
void * ret = freemem_.voidptr;
|
||||
|
||||
/* adjust remaining, aligned at 8 byte boundary */
|
||||
need = need + 7 - (need - 1) % 8;
|
||||
freemem_.byteptr += need;
|
||||
if (need > freesize_)
|
||||
freesize_ = 0;
|
||||
else
|
||||
freesize_ -= need;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* allocator_interface */
|
||||
virtual void free (void *)
|
||||
{
|
||||
/* no-op: released by tmp_pathbuf at end of scope */
|
||||
}
|
||||
|
||||
tmp_pathbuf_allocator ();
|
||||
tmp_pathbuf_allocator (tmp_pathbuf_allocator const &);
|
||||
tmp_pathbuf_allocator & operator = (tmp_pathbuf_allocator const &);
|
||||
|
||||
public:
|
||||
/* use tmp_pathbuf of current stack frame */
|
||||
tmp_pathbuf_allocator (tmp_pathbuf & tp)
|
||||
: allocator_interface ()
|
||||
, tp_ (tp)
|
||||
, freemem_ ()
|
||||
, freesize_ (0)
|
||||
{}
|
||||
};
|
||||
|
||||
static void
|
||||
set_dl_error (const char *str)
|
||||
|
@ -28,84 +96,61 @@ set_dl_error (const char *str)
|
|||
_my_tls.locals.dl_error = 1;
|
||||
}
|
||||
|
||||
/* Look for an executable file given the name and the environment
|
||||
variable to use for searching (eg., PATH); returns the full
|
||||
pathname (static buffer) if found or NULL if not. */
|
||||
inline const char *
|
||||
check_path_access (const char *mywinenv, const char *name, path_conv& buf)
|
||||
{
|
||||
return find_exec (name, buf, mywinenv, FE_NNF | FE_DLL);
|
||||
}
|
||||
|
||||
/* Search LD_LIBRARY_PATH for dll, if it exists. Search /usr/bin and /usr/lib
|
||||
by default. Return valid full path in path_conv real_filename. */
|
||||
static inline bool
|
||||
gfpod_helper (const char *name, path_conv &real_filename)
|
||||
{
|
||||
if (strchr (name, '/'))
|
||||
real_filename.check (name, PC_SYM_FOLLOW | PC_NULLEMPTY);
|
||||
else if (!check_path_access ("LD_LIBRARY_PATH", name, real_filename))
|
||||
check_path_access ("/usr/bin:/usr/lib", name, real_filename);
|
||||
if (!real_filename.exists ())
|
||||
real_filename.error = ENOENT;
|
||||
return !real_filename.error;
|
||||
}
|
||||
|
||||
/* Identify basename and baselen within name,
|
||||
return true if there is a dir in name. */
|
||||
static bool
|
||||
get_full_path_of_dll (const char* str, path_conv &real_filename)
|
||||
spot_basename (const char * &basename, int &baselen, const char * name)
|
||||
{
|
||||
int len = strlen (str);
|
||||
|
||||
/* empty string? */
|
||||
if (len == 0)
|
||||
{
|
||||
set_errno (EINVAL);
|
||||
return false; /* Yes. Let caller deal with it. */
|
||||
}
|
||||
|
||||
tmp_pathbuf tp;
|
||||
char *name = tp.c_get ();
|
||||
|
||||
strcpy (name, str); /* Put it somewhere where we can manipulate it. */
|
||||
|
||||
char *basename = strrchr (name, '/');
|
||||
basename = strrchr (name, '/');
|
||||
basename = basename ? basename + 1 : name;
|
||||
char *suffix = strrchr (name, '.');
|
||||
if (suffix && suffix < basename)
|
||||
suffix = NULL;
|
||||
baselen = name + strlen (name) - basename;
|
||||
return basename > name;
|
||||
}
|
||||
|
||||
/* Is suffix ".so"? */
|
||||
if (suffix && !strcmp (suffix, ".so"))
|
||||
/* Setup basenames using basename and baselen,
|
||||
return true if basenames do have some suffix. */
|
||||
static void
|
||||
collect_basenames (pathfinder::basenamelist & basenames,
|
||||
const char * basename, int baselen)
|
||||
{
|
||||
/* like strrchr (basename, '.'), but limited to baselen */
|
||||
const char *suffix = basename + baselen;
|
||||
while (--suffix >= basename)
|
||||
if (*suffix == '.')
|
||||
break;
|
||||
|
||||
int suffixlen;
|
||||
if (suffix >= basename)
|
||||
suffixlen = basename + baselen - suffix;
|
||||
else
|
||||
{
|
||||
/* Does the file exist? */
|
||||
if (gfpod_helper (name, real_filename))
|
||||
return true;
|
||||
/* No, replace ".so" with ".dll". */
|
||||
strcpy (suffix, ".dll");
|
||||
suffixlen = 0;
|
||||
suffix = NULL;
|
||||
}
|
||||
/* Does the filename start with "lib"? */
|
||||
|
||||
char const * ext = "";
|
||||
/* Without some suffix, reserve space for a trailing dot to override
|
||||
GetModuleHandleExA's automatic adding of the ".dll" suffix. */
|
||||
int extlen = suffix ? 0 : 1;
|
||||
|
||||
/* If we have the ".so" suffix, ... */
|
||||
if (suffixlen == 3 && !strncmp (suffix, ".so", 3))
|
||||
{
|
||||
/* ... keep the basename with original suffix, before ... */
|
||||
basenames.appendv (basename, baselen, NULL);
|
||||
/* ... replacing the ".so" with the ".dll" suffix. */
|
||||
baselen -= 3;
|
||||
ext = ".dll";
|
||||
extlen = 4;
|
||||
}
|
||||
/* If the basename starts with "lib", ... */
|
||||
if (!strncmp (basename, "lib", 3))
|
||||
{
|
||||
/* Yes, replace "lib" with "cyg". */
|
||||
strncpy (basename, "cyg", 3);
|
||||
/* Does the file exist? */
|
||||
if (gfpod_helper (name, real_filename))
|
||||
return true;
|
||||
/* No, revert back to "lib". */
|
||||
strncpy (basename, "lib", 3);
|
||||
/* ... replace "lib" with "cyg", before ... */
|
||||
basenames.appendv ("cyg", 3, basename+3, baselen-3, ext, extlen, NULL);
|
||||
}
|
||||
if (gfpod_helper (name, real_filename))
|
||||
return true;
|
||||
|
||||
/* If nothing worked, create a relative path from the original incoming
|
||||
filename and let LoadLibrary search for it using the system default
|
||||
DLL search path. */
|
||||
real_filename.check (str, PC_SYM_FOLLOW | PC_NOFULL | PC_NULLEMPTY);
|
||||
if (!real_filename.error)
|
||||
return true;
|
||||
|
||||
set_errno (real_filename.error);
|
||||
return false;
|
||||
/* ... using original basename with new suffix. */
|
||||
basenames.appendv (basename, baselen, ext, extlen, NULL);
|
||||
}
|
||||
|
||||
extern "C" void *
|
||||
|
@ -113,64 +158,111 @@ dlopen (const char *name, int flags)
|
|||
{
|
||||
void *ret = NULL;
|
||||
|
||||
if (name == NULL)
|
||||
do
|
||||
{
|
||||
ret = (void *) GetModuleHandle (NULL); /* handle for the current module */
|
||||
if (name == NULL || *name == '\0')
|
||||
{
|
||||
ret = (void *) GetModuleHandle (NULL); /* handle for the current module */
|
||||
if (!ret)
|
||||
__seterrno ();
|
||||
break;
|
||||
}
|
||||
|
||||
DWORD gmheflags = (flags & RTLD_NODELETE)
|
||||
? GET_MODULE_HANDLE_EX_FLAG_PIN
|
||||
: 0;
|
||||
|
||||
tmp_pathbuf tp; /* single one per stack frame */
|
||||
tmp_pathbuf_allocator allocator (tp);
|
||||
pathfinder::basenamelist basenames (allocator);
|
||||
|
||||
const char *basename;
|
||||
int baselen;
|
||||
bool have_dir = spot_basename (basename, baselen, name);
|
||||
collect_basenames (basenames, basename, baselen);
|
||||
|
||||
/* handle for the named library */
|
||||
path_conv real_filename;
|
||||
wchar_t *wpath = tp.w_get ();
|
||||
|
||||
pathfinder finder (allocator, basenames); /* eats basenames */
|
||||
|
||||
if (have_dir)
|
||||
{
|
||||
/* search the specified dir */
|
||||
finder.add_searchdir (name, basename - 1 - name);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* NOTE: The Windows loader (for linked dlls) does
|
||||
not use the LD_LIBRARY_PATH environment variable. */
|
||||
finder.add_envsearchpath ("LD_LIBRARY_PATH");
|
||||
|
||||
/* Finally we better have some fallback. */
|
||||
finder.add_searchdir ("/usr/bin", 8);
|
||||
finder.add_searchdir ("/usr/lib", 8);
|
||||
}
|
||||
|
||||
/* now search the file system */
|
||||
if (!finder.find (pathfinder::
|
||||
exists_and_not_dir (real_filename,
|
||||
PC_SYM_FOLLOW | PC_POSIX)))
|
||||
{
|
||||
/* If nothing worked, create a relative path from the original
|
||||
incoming filename and let LoadLibrary search for it using the
|
||||
system default DLL search path. */
|
||||
real_filename.check (name, PC_SYM_FOLLOW | PC_NOFULL | PC_NULLEMPTY);
|
||||
if (real_filename.error)
|
||||
break;
|
||||
}
|
||||
|
||||
real_filename.get_wide_win32_path (wpath);
|
||||
/* Check if the last path component contains a dot. If so,
|
||||
leave the filename alone. Otherwise add a trailing dot
|
||||
to override LoadLibrary's automatic adding of a ".dll" suffix. */
|
||||
wchar_t *last_bs = wcsrchr (wpath, L'\\') ?: wpath;
|
||||
if (last_bs && !wcschr (last_bs, L'.'))
|
||||
wcscat (last_bs, L".");
|
||||
|
||||
if (flags & RTLD_NOLOAD)
|
||||
{
|
||||
GetModuleHandleExW (gmheflags, wpath, (HMODULE *) &ret);
|
||||
if (ret)
|
||||
break;
|
||||
}
|
||||
|
||||
#ifndef __x86_64__
|
||||
/* Workaround for broken DLLs built against Cygwin versions 1.7.0-49
|
||||
up to 1.7.0-57. They override the cxx_malloc pointer in their
|
||||
DLL initialization code even if loaded dynamically. This is a
|
||||
no-no since a later dlclose lets cxx_malloc point into nirvana.
|
||||
The below kludge "fixes" that by reverting the original cxx_malloc
|
||||
pointer after LoadLibrary. This implies that their overrides
|
||||
won't be applied; that's OK. All overrides should be present at
|
||||
final link time, as Windows doesn't allow undefined references;
|
||||
it would actually be wrong for a dlopen'd DLL to opportunistically
|
||||
override functions in a way that wasn't known then. We're not
|
||||
going to try and reproduce the full ELF dynamic loader here! */
|
||||
|
||||
/* Store original cxx_malloc pointer. */
|
||||
struct per_process_cxx_malloc *tmp_malloc;
|
||||
tmp_malloc = __cygwin_user_data.cxx_malloc;
|
||||
#endif
|
||||
|
||||
ret = (void *) LoadLibraryW (wpath);
|
||||
|
||||
#ifndef __x86_64__
|
||||
/* Restore original cxx_malloc pointer. */
|
||||
__cygwin_user_data.cxx_malloc = tmp_malloc;
|
||||
#endif
|
||||
|
||||
if (ret && gmheflags)
|
||||
GetModuleHandleExW (gmheflags, wpath, (HMODULE *) &ret);
|
||||
|
||||
if (!ret)
|
||||
__seterrno ();
|
||||
}
|
||||
else
|
||||
{
|
||||
/* handle for the named library */
|
||||
path_conv pc;
|
||||
if (get_full_path_of_dll (name, pc))
|
||||
{
|
||||
tmp_pathbuf tp;
|
||||
wchar_t *path = tp.w_get ();
|
||||
|
||||
pc.get_wide_win32_path (path);
|
||||
/* Check if the last path component contains a dot. If so,
|
||||
leave the filename alone. Otherwise add a trailing dot
|
||||
to override LoadLibrary's automatic adding of a ".dll" suffix. */
|
||||
wchar_t *last_bs = wcsrchr (path, L'\\') ?: path;
|
||||
if (last_bs && !wcschr (last_bs, L'.'))
|
||||
wcscat (last_bs, L".");
|
||||
|
||||
#ifndef __x86_64__
|
||||
/* Workaround for broken DLLs built against Cygwin versions 1.7.0-49
|
||||
up to 1.7.0-57. They override the cxx_malloc pointer in their
|
||||
DLL initialization code even if loaded dynamically. This is a
|
||||
no-no since a later dlclose lets cxx_malloc point into nirvana.
|
||||
The below kludge "fixes" that by reverting the original cxx_malloc
|
||||
pointer after LoadLibrary. This implies that their overrides
|
||||
won't be applied; that's OK. All overrides should be present at
|
||||
final link time, as Windows doesn't allow undefined references;
|
||||
it would actually be wrong for a dlopen'd DLL to opportunistically
|
||||
override functions in a way that wasn't known then. We're not
|
||||
going to try and reproduce the full ELF dynamic loader here! */
|
||||
|
||||
/* Store original cxx_malloc pointer. */
|
||||
struct per_process_cxx_malloc *tmp_malloc;
|
||||
tmp_malloc = __cygwin_user_data.cxx_malloc;
|
||||
#endif
|
||||
|
||||
if (flags & RTLD_NOLOAD)
|
||||
GetModuleHandleExW (0, path, (HMODULE *) &ret);
|
||||
else
|
||||
ret = (void *) LoadLibraryW (path);
|
||||
if (ret && (flags & RTLD_NODELETE))
|
||||
GetModuleHandleExW (GET_MODULE_HANDLE_EX_FLAG_PIN, path,
|
||||
(HMODULE *) &ret);
|
||||
|
||||
#ifndef __x86_64__
|
||||
/* Restore original cxx_malloc pointer. */
|
||||
__cygwin_user_data.cxx_malloc = tmp_malloc;
|
||||
#endif
|
||||
|
||||
if (!ret)
|
||||
__seterrno ();
|
||||
}
|
||||
}
|
||||
while (0);
|
||||
|
||||
if (!ret)
|
||||
set_dl_error ("dlopen");
|
||||
|
|
|
@ -0,0 +1,208 @@
|
|||
/* pathfinder.h: find one of multiple file names in path list
|
||||
|
||||
This file is part of Cygwin.
|
||||
|
||||
This software is a copyrighted work licensed under the terms of the
|
||||
Cygwin license. Please consult the file "CYGWIN_LICENSE" for
|
||||
details. */
|
||||
|
||||
#include "vstrlist.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
/* Search a list of directory names for first occurrence of a file,
|
||||
which's file name matches one out of a list of file names. */
|
||||
class pathfinder
|
||||
{
|
||||
public:
|
||||
typedef vstrlist searchdirlist;
|
||||
typedef vstrlist basenamelist;
|
||||
|
||||
private:
|
||||
pathfinder ();
|
||||
pathfinder (pathfinder const &);
|
||||
pathfinder & operator = (pathfinder const &);
|
||||
|
||||
basenamelist basenames_;
|
||||
size_t basenames_maxlen_;
|
||||
|
||||
/* Add to searchdirs_ with extra buffer for any basename we may search for.
|
||||
This is an optimization for the loops in check_path_access method. */
|
||||
searchdirlist searchdirs_;
|
||||
|
||||
public:
|
||||
~pathfinder () {}
|
||||
|
||||
/* We need the basenames to search for first, to allow for optimized
|
||||
memory allocation of each searchpath + longest basename combination.
|
||||
The incoming list of basenames is emptied (ownership take over). */
|
||||
pathfinder (allocator_interface & a, basenamelist & basenames)
|
||||
: basenames_ (a)
|
||||
, basenames_maxlen_ ()
|
||||
, searchdirs_(a)
|
||||
{
|
||||
basenames_.swap(basenames);
|
||||
|
||||
for (basenamelist::buffer_iterator basename (basenames_.begin ());
|
||||
basename != basenames_.end ();
|
||||
++ basename)
|
||||
{
|
||||
if (basenames_maxlen_ < basename->bufferlength ())
|
||||
basenames_maxlen_ = basename->bufferlength ();
|
||||
}
|
||||
}
|
||||
|
||||
void add_searchdir (const char *dir, int dirlen)
|
||||
{
|
||||
if (dirlen < 0)
|
||||
dirlen = strlen (dir);
|
||||
|
||||
if (!dirlen)
|
||||
return;
|
||||
|
||||
searchdirs_.appendv (dir, dirlen, "/", 1 + basenames_maxlen_, NULL);
|
||||
}
|
||||
|
||||
void add_searchpath (const char *path)
|
||||
{
|
||||
while (path && *path)
|
||||
{
|
||||
const char *next = strchr (path, ':');
|
||||
add_searchdir (path, next ? next - path : -1);
|
||||
path = next ? next + 1 : next;
|
||||
}
|
||||
}
|
||||
|
||||
void add_envsearchpath (const char *envpath)
|
||||
{
|
||||
add_searchpath (getenv (envpath));
|
||||
}
|
||||
|
||||
|
||||
/* pathfinder::criterion_interface
|
||||
Overload this test method when you need separate dir and basename. */
|
||||
struct criterion_interface
|
||||
{
|
||||
virtual char const * name () const { return NULL; }
|
||||
|
||||
virtual bool test (searchdirlist::iterator dir,
|
||||
basenamelist::iterator name) const = 0;
|
||||
};
|
||||
|
||||
|
||||
/* pathfinder::simple_criterion_interface
|
||||
Overload this test method when you need a single filename. */
|
||||
class simple_criterion_interface
|
||||
: public criterion_interface
|
||||
{
|
||||
virtual bool test (searchdirlist::iterator dir,
|
||||
basenamelist::iterator name) const
|
||||
{
|
||||
/* Complete the filename path to search for within dir,
|
||||
We have allocated enough memory above. */
|
||||
searchdirlist::buffer_iterator dirbuf (dir);
|
||||
memcpy (dirbuf->buffer () + dirbuf->stringlength (),
|
||||
name->string (), name->stringlength () + 1);
|
||||
bool ret = test (dirbuf->string ());
|
||||
/* reset original dir */
|
||||
dirbuf->buffer ()[dirbuf->stringlength ()] = '\0';
|
||||
return ret;
|
||||
}
|
||||
|
||||
public:
|
||||
virtual bool test (const char * filename) const = 0;
|
||||
};
|
||||
|
||||
|
||||
/* pathfinder::path_conv_criterion_interface
|
||||
Overload this test method when you need a path_conv. */
|
||||
class path_conv_criterion_interface
|
||||
: public simple_criterion_interface
|
||||
{
|
||||
path_conv mypc_;
|
||||
path_conv & pc_;
|
||||
unsigned opt_;
|
||||
|
||||
/* simple_criterion_interface */
|
||||
virtual bool test (const char * filename) const
|
||||
{
|
||||
pc_.check (filename, opt_);
|
||||
return test (pc_);
|
||||
}
|
||||
|
||||
public:
|
||||
path_conv_criterion_interface (unsigned opt = PC_SYM_FOLLOW)
|
||||
: mypc_ ()
|
||||
, pc_ (mypc_)
|
||||
, opt_ (opt)
|
||||
{}
|
||||
|
||||
path_conv_criterion_interface (path_conv & ret, unsigned opt = PC_SYM_FOLLOW)
|
||||
: mypc_ ()
|
||||
, pc_ (ret)
|
||||
, opt_ (opt)
|
||||
{}
|
||||
|
||||
virtual bool test (path_conv & pc) const = 0;
|
||||
};
|
||||
|
||||
|
||||
/* pathfinder::exists_and_not_dir
|
||||
Test if path_conv argument does exist and is not a directory. */
|
||||
struct exists_and_not_dir
|
||||
: public path_conv_criterion_interface
|
||||
{
|
||||
virtual char const * name () const { return "exists and not dir"; }
|
||||
|
||||
exists_and_not_dir (path_conv & pc, unsigned opt = PC_SYM_FOLLOW)
|
||||
: path_conv_criterion_interface (pc, opt)
|
||||
{}
|
||||
|
||||
/* path_conv_criterion_interface */
|
||||
virtual bool test (path_conv & pc) const
|
||||
{
|
||||
if (pc.exists () && !pc.isdir ())
|
||||
return true;
|
||||
|
||||
pc.error = ENOENT;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/* Find the single dir + basename that matches criterion.
|
||||
|
||||
Calls criterion.test method for each registered dir + basename
|
||||
until returning true:
|
||||
Returns true with found_dir + found_basename set.
|
||||
If criterion.test method never returns true:
|
||||
Returns false, not modifying found_dir nor found_basename. */
|
||||
bool find (criterion_interface const & criterion,
|
||||
searchdirlist::member const ** found_dir = NULL,
|
||||
basenamelist::member const ** found_basename = NULL)
|
||||
{
|
||||
char const * critname = criterion.name ();
|
||||
for (basenamelist::iterator name = basenames_.begin ();
|
||||
name != basenames_.end ();
|
||||
++name)
|
||||
for (searchdirlist::iterator dir(searchdirs_.begin ());
|
||||
dir != searchdirs_.end ();
|
||||
++dir)
|
||||
if (criterion.test (dir, name))
|
||||
{
|
||||
debug_printf ("(%s), take %s%s", critname,
|
||||
dir->string(), name->string ());
|
||||
if (found_dir)
|
||||
*found_dir = dir.operator -> ();
|
||||
if (found_basename)
|
||||
*found_basename = name.operator -> ();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
debug_printf ("not (%s), skip %s%s", critname,
|
||||
dir->string(), name->string ());
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* __cplusplus */
|
|
@ -0,0 +1,373 @@
|
|||
/* vstrlist.h: class vstrlist
|
||||
|
||||
This file is part of Cygwin.
|
||||
|
||||
This software is a copyrighted work licensed under the terms of the
|
||||
Cygwin license. Please consult the file "CYGWIN_LICENSE" for
|
||||
details. */
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
struct allocator_interface
|
||||
{
|
||||
virtual void * alloc (size_t) = 0;
|
||||
virtual void free (void *) = 0;
|
||||
};
|
||||
|
||||
|
||||
/* The allocated_type makes sure to use the free () method of the
|
||||
same allocator_interface than the alloc () method was used of.
|
||||
|
||||
Stores the allocator_interface address before the real object,
|
||||
to hide it from (construction & destruction of) real object. */
|
||||
class allocated_type
|
||||
{
|
||||
union allocator_store
|
||||
{
|
||||
allocator_interface * allocator_;
|
||||
char alignment_[8];
|
||||
|
||||
union pointer
|
||||
{
|
||||
void * vptr;
|
||||
allocator_store * real;
|
||||
};
|
||||
};
|
||||
|
||||
public:
|
||||
void * operator new (size_t class_size, allocator_interface & allocator)
|
||||
{
|
||||
allocator_store::pointer astore;
|
||||
astore.vptr = allocator.alloc (sizeof (allocator_store) + class_size);
|
||||
astore.real->allocator_ = &allocator;
|
||||
++ astore.real;
|
||||
return astore.vptr;
|
||||
}
|
||||
|
||||
void operator delete (void * p)
|
||||
{
|
||||
allocator_store::pointer astore;
|
||||
astore.vptr = p;
|
||||
-- astore.real;
|
||||
astore.real->allocator_->free (astore.vptr);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/* Double linked list of char arrays, each being a string buffer,
|
||||
which's final buffer size and initial string content is defined
|
||||
by a NULL terminated variable argument list of STRING+LEN pairs,
|
||||
where each STRING (up to LEN) is concatenated for the initial
|
||||
string buffer content, and each LEN is added to the final size
|
||||
of the allocated string buffer.
|
||||
If LEN is -1, strlen(STRING) is used for LEN.
|
||||
|
||||
Needs:
|
||||
An implementation of the allocator_interface.
|
||||
|
||||
Provides:
|
||||
iterator:
|
||||
short name for the string_iterator
|
||||
string_iterator:
|
||||
provides readonly access via member methods:
|
||||
string (): readonly string buffer
|
||||
stringlength (): length (readonly) of initial string
|
||||
buffer_iterator:
|
||||
extends string_iterator
|
||||
provides writeable access via member methods:
|
||||
buffer (): writeable string buffer
|
||||
bufferlength (): length (readonly) of allocated buffer
|
||||
|
||||
Usage sample:
|
||||
char * text = "snipe";
|
||||
vstrlist l;
|
||||
l.appendv (text, 4, text+3, 2, "", 2, NULL);
|
||||
buffer_iterator it (l.begin ());
|
||||
strcpy (it->buffer () + it->stringlength (), "ts");
|
||||
printf ("Sample result is: '%s'", it->string ());
|
||||
Sample result is: 'snippets' */
|
||||
class vstrlist
|
||||
{
|
||||
public:
|
||||
class member
|
||||
: public allocated_type
|
||||
{
|
||||
friend class vstrlist;
|
||||
friend class string_iterator;
|
||||
|
||||
member * prev_;
|
||||
member * next_;
|
||||
size_t bufferlength_;
|
||||
size_t stringlength_;
|
||||
char buffer_[1]; /* we always have space for the trailing zero */
|
||||
|
||||
/* no copy, just swap */
|
||||
member (member const &);
|
||||
member & operator = (member const &);
|
||||
|
||||
/* anchor */
|
||||
void * operator new (size_t class_size, allocator_interface & allocator)
|
||||
{
|
||||
return allocated_type::operator new (class_size, allocator);
|
||||
}
|
||||
|
||||
/* anchor */
|
||||
member ()
|
||||
: allocated_type ()
|
||||
, prev_ (this)
|
||||
, next_ (this)
|
||||
, bufferlength_ (0)
|
||||
, stringlength_ (0)
|
||||
, buffer_ ()
|
||||
{}
|
||||
|
||||
/* entry: determine memory size from args */
|
||||
void * operator new (size_t class_size, allocator_interface & allocator,
|
||||
char const * part0, va_list parts)
|
||||
{
|
||||
char const * part = part0;
|
||||
va_list partsdup;
|
||||
va_copy (partsdup, parts);
|
||||
while (part)
|
||||
{
|
||||
int partlen = va_arg (partsdup, int);
|
||||
if (partlen < 0)
|
||||
partlen = strlen (part);
|
||||
class_size += partlen;
|
||||
part = va_arg (partsdup, char const *);
|
||||
}
|
||||
va_end (partsdup);
|
||||
|
||||
return allocated_type::operator new (class_size, allocator);
|
||||
}
|
||||
|
||||
/* entry: instantly insert into list */
|
||||
member (member * before, char const * part0, va_list parts)
|
||||
: allocated_type ()
|
||||
, prev_ (NULL)
|
||||
, next_ (NULL)
|
||||
, bufferlength_ (0)
|
||||
, stringlength_ ()
|
||||
, buffer_ ()
|
||||
{
|
||||
prev_ = before->prev_;
|
||||
next_ = before;
|
||||
|
||||
prev_->next_ = this;
|
||||
next_->prev_ = this;
|
||||
|
||||
char * dest = buffer_;
|
||||
char const * part = part0;
|
||||
va_list partsdup;
|
||||
va_copy (partsdup, parts);
|
||||
while (part)
|
||||
{
|
||||
int partlen = va_arg (partsdup, int);
|
||||
if (partlen < 0)
|
||||
{
|
||||
char * old = dest;
|
||||
dest = stpcpy (old, part);
|
||||
partlen = dest - old;
|
||||
}
|
||||
else
|
||||
dest = stpncpy (dest, part, partlen);
|
||||
bufferlength_ += partlen;
|
||||
part = va_arg (partsdup, const char *);
|
||||
}
|
||||
va_end (partsdup);
|
||||
*dest = (char)0;
|
||||
stringlength_ = dest - buffer_;
|
||||
if (bufferlength_ > stringlength_)
|
||||
memset (++dest, 0, bufferlength_ - stringlength_);
|
||||
}
|
||||
|
||||
/* remove entry from list */
|
||||
~member ()
|
||||
{
|
||||
member * next = next_;
|
||||
member * prev = prev_;
|
||||
if (next)
|
||||
next->prev_ = prev;
|
||||
if (prev)
|
||||
prev->next_ = next;
|
||||
prev_ = NULL;
|
||||
next_ = NULL;
|
||||
}
|
||||
|
||||
public:
|
||||
member const * next () const { return next_; }
|
||||
member * next () { return next_; }
|
||||
member const * prev () const { return next_; }
|
||||
member * prev () { return next_; }
|
||||
|
||||
/* readonly access */
|
||||
char const * string () const { return buffer_; }
|
||||
size_t stringlength () const { return stringlength_; }
|
||||
|
||||
/* writeable access */
|
||||
char * buffer () { return buffer_; }
|
||||
size_t bufferlength () { return bufferlength_; }
|
||||
};
|
||||
|
||||
/* readonly access */
|
||||
class string_iterator
|
||||
{
|
||||
friend class vstrlist;
|
||||
friend class buffer_iterator;
|
||||
|
||||
member * current_;
|
||||
|
||||
string_iterator ();
|
||||
|
||||
string_iterator (member * current)
|
||||
: current_ (current)
|
||||
{}
|
||||
|
||||
public:
|
||||
string_iterator (string_iterator const & rhs)
|
||||
: current_ (rhs.current_)
|
||||
{}
|
||||
|
||||
string_iterator & operator = (string_iterator const & rhs)
|
||||
{
|
||||
current_ = rhs.current_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
string_iterator & operator ++ ()
|
||||
{
|
||||
current_ = current_->next ();
|
||||
return *this;
|
||||
}
|
||||
|
||||
string_iterator operator ++ (int)
|
||||
{
|
||||
string_iterator ret (*this);
|
||||
current_ = current_->next ();
|
||||
return ret;
|
||||
}
|
||||
|
||||
string_iterator & operator -- ()
|
||||
{
|
||||
current_ = current_->prev ();
|
||||
return *this;
|
||||
}
|
||||
|
||||
string_iterator operator -- (int)
|
||||
{
|
||||
string_iterator ret (*this);
|
||||
current_ = current_->prev ();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool operator == (string_iterator const & rhs) const
|
||||
{
|
||||
return current_ == rhs.current_;
|
||||
}
|
||||
|
||||
bool operator != (string_iterator const & rhs) const
|
||||
{
|
||||
return current_ != rhs.current_;
|
||||
}
|
||||
|
||||
/* readonly member access */
|
||||
member const & operator * () const { return *current_; }
|
||||
member const * operator -> () const { return current_; }
|
||||
|
||||
void remove ()
|
||||
{
|
||||
member * old = current_;
|
||||
++ *this;
|
||||
delete old;
|
||||
}
|
||||
};
|
||||
|
||||
/* writeable access */
|
||||
class buffer_iterator
|
||||
: public string_iterator
|
||||
{
|
||||
public:
|
||||
explicit /* can be used with vstrlist.begin () */
|
||||
buffer_iterator (string_iterator const & begin)
|
||||
: string_iterator (begin)
|
||||
{}
|
||||
|
||||
buffer_iterator (buffer_iterator const & rhs)
|
||||
: string_iterator (rhs)
|
||||
{}
|
||||
|
||||
buffer_iterator & operator = (buffer_iterator const & rhs)
|
||||
{
|
||||
string_iterator::operator = (rhs);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/* writeable member access */
|
||||
member & operator * () const { return *current_; }
|
||||
member * operator -> () const { return current_; }
|
||||
};
|
||||
|
||||
private:
|
||||
allocator_interface & allocator_;
|
||||
member * anchor_;
|
||||
|
||||
/* not without an allocator */
|
||||
vstrlist ();
|
||||
|
||||
/* no copy, just swap () */
|
||||
vstrlist (vstrlist const &);
|
||||
vstrlist & operator = (vstrlist const &);
|
||||
|
||||
public:
|
||||
/* iterator is the string_iterator */
|
||||
typedef class string_iterator iterator;
|
||||
|
||||
iterator begin () { return iterator (anchor_->next ()); }
|
||||
iterator end () { return iterator (anchor_ ); }
|
||||
iterator rbegin () { return iterator (anchor_->prev ()); }
|
||||
iterator rend () { return iterator (anchor_ ); }
|
||||
|
||||
vstrlist (allocator_interface & a)
|
||||
: allocator_ (a)
|
||||
, anchor_ (NULL) /* exception safety */
|
||||
{
|
||||
anchor_ = new (allocator_) member ();
|
||||
}
|
||||
|
||||
~vstrlist ()
|
||||
{
|
||||
if (anchor_ != NULL)
|
||||
{
|
||||
for (iterator it = begin (); it != end (); it.remove ());
|
||||
delete anchor_;
|
||||
}
|
||||
}
|
||||
|
||||
void swap (vstrlist & that)
|
||||
{
|
||||
allocator_interface & a = allocator_;
|
||||
member * m = anchor_;
|
||||
allocator_ = that.allocator_;
|
||||
anchor_ = that.anchor_;
|
||||
that.allocator_ = a;
|
||||
that.anchor_ = m;
|
||||
}
|
||||
|
||||
string_iterator appendv (char const * part0, va_list parts)
|
||||
{
|
||||
member * ret = new (allocator_, part0, parts)
|
||||
member (anchor_, part0, parts);
|
||||
return string_iterator (ret);
|
||||
}
|
||||
|
||||
string_iterator appendv (char const * part0, ...)
|
||||
{
|
||||
va_list parts;
|
||||
va_start (parts, part0);
|
||||
string_iterator ret = appendv (part0, parts);
|
||||
va_end (parts);
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* __cplusplus */
|
Loading…
Reference in New Issue