qse/lib/si/glob-imp.h

592 lines
13 KiB
C

/*
* $Id$
*
Copyright (c) 2006-2019 Chung, Hyung-Hwan. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#if defined(NO_RECURSION)
typedef struct stack_node_t stack_node_t;
#endif
struct glob_t
{
cbimpl_t cbimpl;
void* cbctx;
qse_mmgr_t* mmgr;
qse_cmgr_t* cmgr;
int flags;
str_t path;
str_t tbuf; /* temporary buffer */
#if defined(INCLUDE_MBUF)
qse_mbs_t mbuf;
#endif
int expanded;
int fnmat_flags;
#if defined(NO_RECURSION)
stack_node_t* stack;
stack_node_t* free;
#endif
};
typedef struct glob_t glob_t;
struct segment_t
{
segment_type_t type;
const char_t* ptr;
qse_size_t len;
char_t sep; /* preceeding separator */
unsigned int wild: 1; /* indicate that it contains wildcards */
unsigned int esc: 1; /* indicate that it contains escaped letters */
unsigned int next: 1; /* indicate that it has the following segment */
};
typedef struct segment_t segment_t;
#if defined(NO_RECURSION)
struct stack_node_t
{
qse_size_t tmp;
qse_size_t tmp2;
qse_dir_t* dp;
segment_t seg;
stack_node_t* next;
};
#endif
#if defined(INCLUDE_MBUF)
static qse_mchar_t* wcs_to_mbuf (glob_t* g, const qse_wchar_t* wcs, qse_mbs_t* mbs)
{
qse_size_t ml, wl;
if (qse_wcstombswithcmgr (wcs, &wl, QSE_NULL, &ml, g->cmgr) <= -1 ||
qse_mbs_setlen (mbs, ml) == (qse_size_t)-1) return QSE_NULL;
qse_wcstombswithcmgr (wcs, &wl, QSE_MBS_PTR(mbs), &ml, g->cmgr);
return QSE_MBS_PTR(mbs);
}
#endif
static int path_exists (glob_t* g, const char_t* name)
{
#if defined(_WIN32)
/* ------------------------------------------------------------------- */
#if !defined(INVALID_FILE_ATTRIBUTES)
#define INVALID_FILE_ATTRIBUTES ((DWORD)-1)
#endif
#if defined(CHAR_IS_MCHAR)
return (GetFileAttributesA(name) != INVALID_FILE_ATTRIBUTES)? 1: 0;
#else
return (GetFileAttributesW(name) != INVALID_FILE_ATTRIBUTES)? 1: 0;
#endif
/* ------------------------------------------------------------------- */
#elif defined(__OS2__)
/* ------------------------------------------------------------------- */
FILESTATUS3 fs;
APIRET rc;
const qse_mchar_t* mptr;
#if defined(CHAR_IS_MCHAR)
mptr = name;
#else
mptr = wcs_to_mbuf (g, name, &g->mbuf);
if (mptr == QSE_NULL) return -1;
#endif
rc = DosQueryPathInfo (mptr, FIL_STANDARD, &fs, QSE_SIZEOF(fs));
return (rc == NO_ERROR)? 1:
(rc == ERROR_PATH_NOT_FOUND)? 0: -1;
/* ------------------------------------------------------------------- */
#elif defined(__DOS__)
/* ------------------------------------------------------------------- */
unsigned int x, attr;
const qse_mchar_t* mptr;
#if defined(CHAR_IS_MCHAR)
mptr = name;
#else
mptr = wcs_to_mbuf (g, name, &g->mbuf);
if (mptr == QSE_NULL) return -1;
#endif
x = _dos_getfileattr (mptr, &attr);
return (x == 0)? 1:
(errno == ENOENT)? 0: -1;
/* ------------------------------------------------------------------- */
#elif defined(macintosh)
HFileInfo fpb;
const qse_mchar_t* mptr;
#if defined(CHAR_IS_MCHAR)
mptr = name;
#else
mptr = wcs_to_mbuf (g, name, &g->mbuf);
if (mptr == QSE_NULL) return -1;
#endif
QSE_MEMSET (&fpb, 0, QSE_SIZEOF(fpb));
fpb.ioNamePtr = (unsigned char*)mptr;
return (PBGetCatInfoSync ((CInfoPBRec*)&fpb) == noErr)? 1: 0;
#else
/* ------------------------------------------------------------------- */
#if defined(HAVE_LSTAT)
qse_lstat_t st;
#else
qse_stat_t st;
#endif
const qse_mchar_t* mptr;
#if defined(CHAR_IS_MCHAR)
mptr = name;
#else
mptr = wcs_to_mbuf (g, name, &g->mbuf);
if (mptr == QSE_NULL) return -1;
#endif
#if defined(HAVE_LSTAT)
return (QSE_LSTAT (mptr, &st) <= -1)? 0: 1;
#else
/* use stat() if no lstat() is available. */
return (QSE_STAT (mptr, &st) <= -1)? 0: 1;
#endif
/* ------------------------------------------------------------------- */
#endif
}
static int get_next_segment (glob_t* g, segment_t* seg)
{
if (seg->type == NONE)
{
/* seg->ptr must point to the beginning of the pattern
* and seg->len must be zero when seg->type is NONE. */
if (IS_NIL(seg->ptr[0]))
{
/* nothing to do */
}
else if (IS_SEP(seg->ptr[0]))
{
seg->type = ROOT;
seg->len = 1;
seg->next = IS_NIL(seg->ptr[1])? 0: 1;
seg->sep = T('\0');
seg->wild = 0;
seg->esc = 0;
}
#if defined(_WIN32) || defined(__OS2__) || defined(__DOS__)
else if (IS_DRIVE(seg->ptr))
{
seg->type = ROOT;
seg->len = 2;
if (IS_SEP(seg->ptr[2])) seg->len++;
seg->next = IS_NIL(seg->ptr[seg->len])? 0: 1;
seg->sep = T('\0');
seg->wild = 0;
seg->esc = 0;
}
#endif
else
{
int escaped = 0;
seg->type = NORMAL;
seg->sep = T('\0');
seg->wild = 0;
seg->esc = 0;
do
{
if (escaped) escaped = 0;
else
{
if (IS_ESC(seg->ptr[seg->len]))
{
escaped = 1;
seg->esc = 1;
}
else if (IS_WILD(seg->ptr[seg->len])) seg->wild = 1;
}
seg->len++;
}
while (!IS_SEP_OR_NIL(seg->ptr[seg->len]));
seg->next = IS_NIL(seg->ptr[seg->len])? 0: 1;
}
}
else if (seg->type == ROOT)
{
int escaped = 0;
seg->type = NORMAL;
seg->ptr = &seg->ptr[seg->len];
seg->len = 0;
seg->sep = T('\0');
seg->wild = 0;
seg->esc = 0;
while (!IS_SEP_OR_NIL(seg->ptr[seg->len]))
{
if (escaped) escaped = 0;
else
{
if (IS_ESC(seg->ptr[seg->len]))
{
escaped = 1;
seg->esc = 1;
}
else if (IS_WILD(seg->ptr[seg->len])) seg->wild = 1;
}
seg->len++;
}
seg->next = IS_NIL(seg->ptr[seg->len])? 0: 1;
}
else
{
QSE_ASSERT (seg->type == NORMAL);
seg->ptr = &seg->ptr[seg->len + 1];
seg->len = 0;
seg->wild = 0;
seg->esc = 0;
if (IS_NIL(seg->ptr[-1]))
{
seg->type = NONE;
seg->next = 0;
seg->sep = T('\0');
}
else
{
int escaped = 0;
seg->sep = seg->ptr[-1];
while (!IS_SEP_OR_NIL(seg->ptr[seg->len]))
{
if (escaped) escaped = 0;
else
{
if (IS_ESC(seg->ptr[seg->len]))
{
escaped = 1;
seg->esc = 1;
}
else if (IS_WILD(seg->ptr[seg->len])) seg->wild = 1;
}
seg->len++;
}
seg->next = IS_NIL(seg->ptr[seg->len])? 0: 1;
}
}
return seg->type;
}
static int handle_non_wild_segments (glob_t* g, segment_t* seg)
{
while (get_next_segment(g, seg) != NONE && !seg->wild)
{
QSE_ASSERT (seg->type != NONE && !seg->wild);
if (seg->sep && str_ccat (&g->path, seg->sep) == (qse_size_t)-1) return -1;
if (seg->esc)
{
/* if the segment contains escape sequences,
* strip the escape letters off the segment */
cstr_t tmp;
qse_size_t i;
int escaped = 0;
if (STR_CAPA(&g->tbuf) < seg->len &&
str_setcapa (&g->tbuf, seg->len) == (qse_size_t)-1) return -1;
tmp.ptr = STR_PTR(&g->tbuf);
tmp.len = 0;
/* the following loop drops the last character
* if it is the escape character */
for (i = 0; i < seg->len; i++)
{
if (escaped)
{
escaped = 0;
tmp.ptr[tmp.len++] = seg->ptr[i];
}
else
{
if (IS_ESC(seg->ptr[i]))
escaped = 1;
else
tmp.ptr[tmp.len++] = seg->ptr[i];
}
}
if (str_ncat (&g->path, tmp.ptr, tmp.len) == (qse_size_t)-1) return -1;
}
else
{
/* if the segmetn doesn't contain escape sequences,
* append the segment to the path without special handling */
if (str_ncat (&g->path, seg->ptr, seg->len) == (qse_size_t)-1) return -1;
}
if (!seg->next && path_exists (g, STR_PTR(&g->path)) > 0)
{
/* reached the last segment. match if the path exists */
if (g->cbimpl (STR_XSTR(&g->path), g->cbctx) <= -1) return -1;
g->expanded = 1;
}
}
return 0;
}
static int search (glob_t* g, segment_t* seg)
{
qse_dir_t* dp;
qse_size_t tmp, tmp2;
qse_dir_ent_t ent;
int x;
#if defined(NO_RECURSION)
stack_node_t* r;
entry:
#endif
dp = QSE_NULL;
if (handle_non_wild_segments (g, seg) <= -1) goto oops;
if (seg->wild)
{
int dir_flags = DIR_CHAR_FLAGS;
if (g->flags & QSE_GLOB_SKIPSPCDIR) dir_flags |= QSE_DIR_SKIPSPCDIR;
dp = qse_dir_open (
g->mmgr, 0, (const qse_char_t*)STR_PTR(&g->path),
dir_flags, QSE_NULL);
if (dp)
{
tmp = STR_LEN(&g->path);
if (seg->sep && str_ccat (&g->path, seg->sep) == (qse_size_t)-1) goto oops;
tmp2 = STR_LEN(&g->path);
while (1)
{
str_setlen (&g->path, tmp2);
x = qse_dir_read (dp, &ent);
if (x <= -1)
{
if (g->flags & QSE_GLOB_TOLERANT) break;
else goto oops;
}
if (x == 0) break;
if (str_cat (&g->path, (const char_t*)ent.name) == (qse_size_t)-1) goto oops;
if (strnfnmat (STR_CPTR(&g->path,tmp2), seg->ptr, seg->len, g->fnmat_flags) > 0)
{
if (seg->next)
{
#if defined(NO_RECURSION)
if (g->free)
{
r = g->free;
g->free = r->next;
}
else
{
r = QSE_MMGR_ALLOC (g->mmgr, QSE_SIZEOF(*r));
if (r == QSE_NULL) goto oops;
}
/* push key variables that must be restored
* into the stack. */
r->tmp = tmp;
r->tmp2 = tmp2;
r->dp = dp;
r->seg = *seg;
r->next = g->stack;
g->stack = r;
/* move to the function entry point as if
* a recursive call has been made */
goto entry;
resume:
;
#else
segment_t save;
int x;
save = *seg;
x = search (g, seg);
*seg = save;
if (x <= -1) goto oops;
#endif
}
else
{
if (g->cbimpl (STR_XSTR(&g->path), g->cbctx) <= -1) goto oops;
g->expanded = 1;
}
}
}
str_setlen (&g->path, tmp);
qse_dir_close (dp); dp = QSE_NULL;
}
}
QSE_ASSERT (dp == QSE_NULL);
#if defined(NO_RECURSION)
if (g->stack)
{
/* the stack is not empty. the emulated recusive call
* must have been made. restore the variables pushed
* and jump to the resumption point */
r = g->stack;
g->stack = r->next;
tmp = r->tmp;
tmp2 = r->tmp2;
dp = r->dp;
*seg = r->seg;
/* link the stack node to the free list
* instead of freeing it here */
r->next = g->free;
g->free = r;
goto resume;
}
while (g->free)
{
/* destory the free list */
r = g->free;
g->free = r->next;
QSE_MMGR_FREE (g->mmgr, r);
}
#endif
return 0;
oops:
if (dp) qse_dir_close (dp);
#if defined(NO_RECURSION)
while (g->stack)
{
r = g->stack;
g->stack = r->next;
qse_dir_close (r->dp);
QSE_MMGR_FREE (g->mmgr, r);
}
while (g->free)
{
r = g->stack;
g->free = r->next;
QSE_MMGR_FREE (g->mmgr, r);
}
#endif
return -1;
}
int glob (const char_t* pattern, cbimpl_t cbimpl, void* cbctx, int flags, qse_mmgr_t* mmgr, qse_cmgr_t* cmgr)
{
segment_t seg;
glob_t g;
int x;
QSE_MEMSET (&g, 0, QSE_SIZEOF(g));
g.cbimpl = cbimpl;
g.cbctx = cbctx;
g.mmgr = mmgr;
g.cmgr = cmgr;
g.flags = flags;
#if defined(_WIN32) || defined(__OS2__) || defined(__DOS__)
g.fnmat_flags |= QSE_STRFNMAT_IGNORECASE;
g.fnmat_flags |= QSE_STRFNMAT_NOESCAPE;
#else
if (flags & QSE_GLOB_IGNORECASE) g.fnmat_flags |= QSE_STRFNMAT_IGNORECASE;
if (flags & QSE_GLOB_NOESCAPE) g.fnmat_flags |= QSE_STRFNMAT_NOESCAPE;
#endif
if (flags & QSE_GLOB_PERIOD) g.fnmat_flags |= QSE_STRFNMAT_PERIOD;
if (str_init (&g.path, mmgr, 512) <= -1) return -1;
if (str_init (&g.tbuf, mmgr, 256) <= -1)
{
str_fini (&g.path);
return -1;
}
#if defined(INCLUDE_MBUF)
if (qse_mbs_init (&g.mbuf, mmgr, 512) <= -1)
{
str_fini (&g.path);
str_fini (&g.path);
return -1;
}
#endif
QSE_MEMSET (&seg, 0, QSE_SIZEOF(seg));
seg.type = NONE;
seg.ptr = pattern;
seg.len = 0;
x = search (&g, &seg);
#if defined(INCLUDE_MBUF)
qse_mbs_fini (&g.mbuf);
#endif
str_fini (&g.tbuf);
str_fini (&g.path);
if (x <= -1) return -1;
return g.expanded;
}