strongswan/src/libstrongswan/collections/enumerator.c

814 lines
15 KiB
C

/*
* Copyright (C) 2008-2017 Tobias Brunner
* Copyright (C) 2007 Martin Willi
* HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include "enumerator.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <limits.h>
#include <stdio.h>
#include <dirent.h>
#include <errno.h>
#include <string.h>
#ifdef HAVE_GLOB_H
#include <glob.h>
#elif defined(WIN32)
#include <fileapi.h>
#endif /* HAVE_GLOB_H */
#include <utils/debug.h>
/*
* Described in header.
*/
bool enumerator_enumerate_default(enumerator_t *enumerator, ...)
{
va_list args;
bool result;
if (!enumerator->venumerate)
{
DBG1(DBG_LIB, "!!! ENUMERATE DEFAULT: venumerate() missing !!!");
return FALSE;
}
va_start(args, enumerator);
result = enumerator->venumerate(enumerator, args);
va_end(args);
return result;
}
METHOD(enumerator_t, enumerate_empty, bool,
enumerator_t *enumerator, va_list args)
{
return FALSE;
}
/*
* Described in header
*/
enumerator_t* enumerator_create_empty()
{
enumerator_t *this;
INIT(this,
.enumerate = enumerator_enumerate_default,
.venumerate = _enumerate_empty,
.destroy = (void*)free,
);
return this;
}
/**
* Enumerator implementation for directory enumerator
*/
typedef struct {
/** implements enumerator_t */
enumerator_t public;
/** directory handle */
DIR *dir;
/** absolute path of current file */
char full[PATH_MAX];
/** where directory part of full ends and relative file gets written */
char *full_end;
} dir_enum_t;
METHOD(enumerator_t, destroy_dir_enum, void,
dir_enum_t *this)
{
closedir(this->dir);
free(this);
}
METHOD(enumerator_t, enumerate_dir_enum, bool,
dir_enum_t *this, va_list args)
{
struct dirent *entry = readdir(this->dir);
struct stat *st;
size_t remaining;
char **relative, **absolute;
int len;
VA_ARGS_VGET(args, relative, absolute, st);
if (!entry)
{
return FALSE;
}
if (streq(entry->d_name, ".") || streq(entry->d_name, ".."))
{
return this->public.enumerate(&this->public, relative, absolute, st);
}
if (relative)
{
*relative = entry->d_name;
}
if (absolute || st)
{
remaining = sizeof(this->full) - (this->full_end - this->full);
len = snprintf(this->full_end, remaining, "%s", entry->d_name);
if (len < 0 || len >= remaining)
{
DBG1(DBG_LIB, "buffer too small to enumerate file '%s'",
entry->d_name);
return FALSE;
}
if (absolute)
{
*absolute = this->full;
}
if (st && stat(this->full, st))
{
/* try lstat() e.g. if a symlink is not valid anymore */
if ((errno != ENOENT && errno != ENOTDIR) || lstat(this->full, st))
{
DBG1(DBG_LIB, "stat() on '%s' failed: %s", this->full,
strerror(errno));
return FALSE;
}
}
}
return TRUE;
}
/*
* Described in header
*/
enumerator_t* enumerator_create_directory(const char *path)
{
dir_enum_t *this;
int len;
INIT(this,
.public = {
.enumerate = enumerator_enumerate_default,
.venumerate = _enumerate_dir_enum,
.destroy = _destroy_dir_enum,
},
);
if (*path == '\0')
{
path = "./";
}
len = snprintf(this->full, sizeof(this->full)-1, "%s", path);
if (len < 0 || len >= sizeof(this->full)-1)
{
DBG1(DBG_LIB, "path string '%s' too long", path);
free(this);
return NULL;
}
/* append a '/' if not already done */
if (!path_is_separator(this->full[len-1]))
{
this->full[len++] = DIRECTORY_SEPARATOR[0];
this->full[len] = '\0';
}
this->full_end = &this->full[len];
this->dir = opendir(path);
if (!this->dir)
{
DBG1(DBG_LIB, "opening directory '%s' failed: %s", path,
strerror(errno));
free(this);
return NULL;
}
return &this->public;
}
#ifdef HAVE_GLOB_H
/**
* Enumerator implementation for glob enumerator
*/
typedef struct {
/** implements enumerator_t */
enumerator_t public;
/** glob data */
glob_t glob;
/** iteration count */
u_int pos;
} glob_enum_t;
METHOD(enumerator_t, destroy_glob_enum, void,
glob_enum_t *this)
{
globfree(&this->glob);
free(this);
}
METHOD(enumerator_t, enumerate_glob_enum, bool,
glob_enum_t *this, va_list args)
{
struct stat *st;
char *match;
char **file;
VA_ARGS_VGET(args, file, st);
if (this->pos >= this->glob.gl_pathc)
{
return FALSE;
}
match = this->glob.gl_pathv[this->pos++];
if (file)
{
*file = match;
}
if (st && stat(match, st))
{
DBG1(DBG_LIB, "stat() on '%s' failed: %s", match,
strerror(errno));
return FALSE;
}
return TRUE;
}
/*
* Described in header
*/
enumerator_t* enumerator_create_glob(const char *pattern)
{
glob_enum_t *this;
int status;
if (!pattern)
{
return enumerator_create_empty();
}
INIT(this,
.public = {
.enumerate = enumerator_enumerate_default,
.venumerate = _enumerate_glob_enum,
.destroy = _destroy_glob_enum,
},
);
status = glob(pattern, GLOB_ERR, NULL, &this->glob);
if (status == GLOB_NOMATCH)
{
DBG1(DBG_LIB, "no files found matching '%s'", pattern);
}
else if (status != 0)
{
DBG1(DBG_LIB, "expanding file pattern '%s' failed: %s", pattern,
strerror(errno));
}
return &this->public;
}
#elif defined(WIN32) /* HAVE_GLOB_H */
/**
* Enumerator implementation for glob enumerator on Windows
*/
typedef struct {
/** implements enumerator_t */
enumerator_t public;
/** search handle */
HANDLE handle;
/** current file path */
char path[PATH_MAX];
/** base path */
char *base;
} glob_enum_t;
METHOD(enumerator_t, destroy_glob_enum, void,
glob_enum_t *this)
{
if (this->handle != INVALID_HANDLE_VALUE)
{
FindClose(this->handle);
}
free(this->base);
free(this);
}
/**
* Create the combined path from the given file data
*/
static bool combine_glob_path(glob_enum_t *this, WIN32_FIND_DATA *data)
{
if (snprintf(this->path, sizeof(this->path), "%s%s%s", this->base,
DIRECTORY_SEPARATOR, data->cFileName) >= sizeof(this->path))
{
DBG1(DBG_LIB, "path for '%s' too long, ignored", data->cFileName);
return FALSE;
}
return TRUE;
}
/**
* Return the path and stat data for the current file
*/
static bool enumerate_glob_enum_data(glob_enum_t *this, va_list args)
{
struct stat *st;
char **file;
VA_ARGS_VGET(args, file, st);
if (file)
{
*file = this->path;
}
if (st && stat(this->path, st))
{
DBG1(DBG_LIB, "stat() on '%s' failed: %s", this->path,
strerror(errno));
return FALSE;
}
return TRUE;
}
METHOD(enumerator_t, enumerate_glob_enum, bool,
glob_enum_t *this, va_list args)
{
WIN32_FIND_DATA data;
do
{
if (!FindNextFile(this->handle, &data))
{
return FALSE;
}
}
while (!combine_glob_path(this, &data));
return enumerate_glob_enum_data(this, args);
}
METHOD(enumerator_t, enumerate_glob_enum_first, bool,
glob_enum_t *this, va_list args)
{
if (enumerate_glob_enum_data(this, args))
{
this->public.venumerate = _enumerate_glob_enum;
return TRUE;
}
return FALSE;
}
/*
* Described in header
*/
enumerator_t *enumerator_create_glob(const char *pattern)
{
glob_enum_t *this;
WIN32_FIND_DATA data;
if (!pattern)
{
return enumerator_create_empty();
}
INIT(this,
.public = {
.enumerate = enumerator_enumerate_default,
.venumerate = _enumerate_glob_enum_first,
.destroy = _destroy_glob_enum,
},
.base = path_dirname(pattern),
);
this->handle = FindFirstFile(pattern, &data);
if (this->handle == INVALID_HANDLE_VALUE)
{
if (GetLastError() == ERROR_FILE_NOT_FOUND ||
GetLastError() == ERROR_PATH_NOT_FOUND)
{
DBG1(DBG_LIB, "no files found matching '%s'", pattern);
}
else
{
DBG1(DBG_LIB, "FindFirstFile failed for pattern '%s' (%d)", pattern,
GetLastError());
}
destroy_glob_enum(this);
return enumerator_create_empty();
}
else if (!combine_glob_path(this, &data))
{ /* check the next file if we can't combine the path for the first one */
this->public.venumerate = _enumerate_glob_enum;
}
return &this->public;
}
#else /* HAVE_GLOB_H */
enumerator_t* enumerator_create_glob(const char *pattern)
{
return NULL;
}
#endif /* HAVE_GLOB_H */
/**
* Enumerator implementation for token enumerator
*/
typedef struct {
/** implements enumerator_t */
enumerator_t public;
/** string to parse */
char *string;
/** current position */
char *pos;
/** separator chars */
const char *sep;
/** trim chars */
const char *trim;
} token_enum_t;
METHOD(enumerator_t, destroy_token_enum, void,
token_enum_t *this)
{
free(this->string);
free(this);
}
METHOD(enumerator_t, enumerate_token_enum, bool,
token_enum_t *this, va_list args)
{
const char *sep, *trim;
char *pos = NULL, *tmp, **token;
bool last = FALSE;
VA_ARGS_VGET(args, token);
/* trim leading characters/separators */
while (*this->pos)
{
trim = this->trim;
while (*trim)
{
if (*trim == *this->pos)
{
this->pos++;
break;
}
trim++;
}
sep = this->sep;
while (*sep)
{
if (*sep == *this->pos)
{
this->pos++;
break;
}
sep++;
}
if (!*trim && !*sep)
{
break;
}
}
switch (*this->pos)
{
case '"':
case '\'':
{
/* read quoted token */
tmp = strchr(this->pos + 1, *this->pos);
if (tmp)
{
*token = this->pos + 1;
*tmp = '\0';
this->pos = tmp + 1;
return TRUE;
}
/* unterminated string, FALL-THROUGH */
}
default:
{
/* find nearest separator */
sep = this->sep;
while (*sep)
{
tmp = strchr(this->pos, *sep);
if (tmp && (pos == NULL || tmp < pos))
{
pos = tmp;
}
sep++;
}
*token = this->pos;
if (pos)
{
*pos = '\0';
this->pos = pos + 1;
}
else
{
last = TRUE;
pos = this->pos = strchr(this->pos, '\0');
}
break;
}
}
/* trim trailing characters */
pos--;
while (pos >= *token)
{
trim = this->trim;
while (*trim)
{
if (*trim == *pos)
{
*(pos--) = '\0';
break;
}
trim++;
}
if (!*trim)
{
break;
}
}
if (!last || pos >= *token)
{
return TRUE;
}
return FALSE;
}
/*
* Described in header
*/
enumerator_t* enumerator_create_token(const char *string, const char *sep,
const char *trim)
{
token_enum_t *this;
INIT(this,
.public = {
.enumerate = enumerator_enumerate_default,
.venumerate = _enumerate_token_enum,
.destroy = _destroy_token_enum,
},
.string = strdup(string),
.sep = sep,
.trim = trim,
);
this->pos = this->string;
return &this->public;
}
/**
* Enumerator for nested enumerations
*/
typedef struct {
enumerator_t public;
enumerator_t *outer;
enumerator_t *inner;
enumerator_t *(*create_inner)(void *outer, void *data);
void *data;
void (*destructor)(void *data);
} nested_enumerator_t;
METHOD(enumerator_t, enumerate_nested, bool,
nested_enumerator_t *this, va_list args)
{
while (TRUE)
{
while (!this->inner)
{
void *outer;
if (!this->outer->enumerate(this->outer, &outer))
{
return FALSE;
}
this->inner = this->create_inner(outer, this->data);
if (this->inner && !this->inner->venumerate)
{
DBG1(DBG_LIB, "!!! ENUMERATE NESTED: venumerate() missing !!!");
return FALSE;
}
}
if (this->inner->venumerate(this->inner, args))
{
return TRUE;
}
this->inner->destroy(this->inner);
this->inner = NULL;
}
}
METHOD(enumerator_t, destroy_nested, void,
nested_enumerator_t *this)
{
if (this->destructor)
{
this->destructor(this->data);
}
DESTROY_IF(this->inner);
this->outer->destroy(this->outer);
free(this);
}
/*
* Described in header
*/
enumerator_t *enumerator_create_nested(enumerator_t *outer,
enumerator_t *(inner_constructor)(void *outer, void *data),
void *data, void (*destructor)(void *data))
{
nested_enumerator_t *this;
INIT(this,
.public = {
.enumerate = enumerator_enumerate_default,
.venumerate = _enumerate_nested,
.destroy = _destroy_nested,
},
.outer = outer,
.create_inner = inner_constructor,
.data = data,
.destructor = destructor,
);
return &this->public;
}
/**
* Enumerator for filtered enumerator
*/
typedef struct {
enumerator_t public;
enumerator_t *orig;
void *data;
bool (*filter)(void*,enumerator_t*,va_list);
void (*destructor)(void *data);
} filter_enumerator_t;
METHOD(enumerator_t, destroy_filter, void,
filter_enumerator_t *this)
{
if (this->destructor)
{
this->destructor(this->data);
}
this->orig->destroy(this->orig);
free(this);
}
METHOD(enumerator_t, enumerate_filter, bool,
filter_enumerator_t *this, va_list args)
{
bool result = FALSE;
if (this->filter(this->data, this->orig, args))
{
result = TRUE;
}
return result;
}
/*
* Described in header
*/
enumerator_t *enumerator_create_filter(enumerator_t *orig,
bool (*filter)(void *data, enumerator_t *orig, va_list args),
void *data, void (*destructor)(void *data))
{
filter_enumerator_t *this;
INIT(this,
.public = {
.enumerate = enumerator_enumerate_default,
.venumerate = _enumerate_filter,
.destroy = _destroy_filter,
},
.orig = orig,
.filter = filter,
.data = data,
.destructor = destructor,
);
return &this->public;
}
/**
* Enumerator for cleaner enumerator
*/
typedef struct {
enumerator_t public;
enumerator_t *wrapped;
void (*cleanup)(void *data);
void *data;
} cleaner_enumerator_t;
METHOD(enumerator_t, destroy_cleaner, void,
cleaner_enumerator_t *this)
{
this->cleanup(this->data);
this->wrapped->destroy(this->wrapped);
free(this);
}
METHOD(enumerator_t, enumerate_cleaner, bool,
cleaner_enumerator_t *this, va_list args)
{
if (!this->wrapped->venumerate)
{
DBG1(DBG_LIB, "!!! CLEANER ENUMERATOR: venumerate() missing !!!");
return FALSE;
}
return this->wrapped->venumerate(this->wrapped, args);
}
/*
* Described in header
*/
enumerator_t *enumerator_create_cleaner(enumerator_t *wrapped,
void (*cleanup)(void *data), void *data)
{
cleaner_enumerator_t *this;
INIT(this,
.public = {
.enumerate = enumerator_enumerate_default,
.venumerate = _enumerate_cleaner,
.destroy = _destroy_cleaner,
},
.wrapped = wrapped,
.cleanup = cleanup,
.data = data,
);
return &this->public;
}
/**
* Enumerator for single enumerator
*/
typedef struct {
enumerator_t public;
void *item;
void (*cleanup)(void *item);
bool done;
} single_enumerator_t;
METHOD(enumerator_t, destroy_single, void,
single_enumerator_t *this)
{
if (this->cleanup)
{
this->cleanup(this->item);
}
free(this);
}
METHOD(enumerator_t, enumerate_single, bool,
single_enumerator_t *this, va_list args)
{
void **item;
VA_ARGS_VGET(args, item);
if (this->done)
{
return FALSE;
}
*item = this->item;
this->done = TRUE;
return TRUE;
}
/*
* Described in header
*/
enumerator_t *enumerator_create_single(void *item, void (*cleanup)(void *item))
{
single_enumerator_t *this;
INIT(this,
.public = {
.enumerate = enumerator_enumerate_default,
.venumerate = _enumerate_single,
.destroy = _destroy_single,
},
.item = item,
.cleanup = cleanup,
);
return &this->public;
}