From 18ebbaf4f619e79231f5ad18a2ab8c135d22ef56 Mon Sep 17 00:00:00 2001 From: DongHun Kwak Date: Tue, 10 Sep 2019 15:38:54 +0900 Subject: [PATCH] Imported Upstream version 0.7.5 --- .appveyor.yml | 34 + .travis.yml | 26 +- CMakeLists.txt | 50 +- CREDITS | 3 + NEWS | 11 + README | 2 +- TODO_1.0 | 4 - VERSION.cmake | 2 +- bindings/solv.i | 10 +- doc/CMakeLists.txt | 4 +- examples/solv/CMakeLists.txt | 2 +- ext/CMakeLists.txt | 28 +- ext/libsolvext.ver | 1 + ext/repo_conda.c | 180 ++ ext/repo_conda.h | 8 + ext/repo_deb.c | 8 + ext/repo_rpmdb_bdb.h | 13 +- ext/repo_susetags.c | 4 + ext/repo_testcase.c | 716 +++++ ext/solv_jsonparser.c | 35 +- ext/solv_jsonparser.h | 6 +- ext/solv_xfopen.c | 56 + ext/testcase.c | 826 +----- ext/testcase.h | 1 + libsolv.pc.in | 4 +- libsolvext.pc.in | 4 +- package/libsolv.changes | 17 + src/CMakeLists.txt | 18 +- src/chksum.c | 4 + src/cleandeps.c | 2 +- src/conda.c | 242 +- src/conda.h | 2 + src/knownid.h | 15 +- src/libsolv.ver | 2 + src/policy.c | 30 +- src/pool.c | 18 +- src/qsort_r.c | 2 + src/repo.c | 13 +- src/repo_solv.c | 2 +- src/repodata.c | 32 +- src/repopage.c | 19 +- src/rules.c | 166 +- src/rules.h | 2 +- src/selection.c | 21 +- src/solvable.c | 74 +- src/solver.c | 78 +- src/solver.h | 1 - src/util.c | 22 +- src/util.h | 1 + test/CMakeLists.txt | 24 +- test/runtestcases.bat | 24 + test/{runtestcases => runtestcases.sh} | 0 test/testcases/focus/best.t | 8 + test/testcases/focus/installed.t | 6 + test/testcases/focus/normal.t | 7 + test/testcases/lock/best.t | 27 + test/testcases/lock/dup.t | 25 + test/testcases/lockstep/lockstep_install.t | 3 + test/testcases/lockstep/lockstep_update.t | 3 + .../supplements_implicitobsoleteusescolors.t | 9 +- tools/CMakeLists.txt | 25 +- tools/conda2solv.c | 71 + tools/deb2solv.c | 2 +- tools/diskusagexml2solv.c | 60 - tools/dumpsolv.c | 1 + tools/installcheck.c | 3 + tools/mergesolv.c | 1 + tools/testsolv.c | 1 + win32/CMakeLists.txt | 9 + win32/LICENSE | 70 + win32/fmemopen.c | 41 + win32/fnmatch.c | 320 +++ win32/fnmatch.h | 24 + win32/getopt.c | 97 + win32/getopt.h | 30 + win32/regcomp.c | 2953 ++++++++++++++++++++ win32/regerror.c | 37 + win32/regex.h | 65 + win32/regexec.c | 1028 +++++++ win32/strfncs.c | 35 + win32/strfncs.h | 8 + win32/tre-mem.c | 158 ++ win32/tre.h | 230 ++ win32/unistd.h | 4 + 84 files changed, 7238 insertions(+), 992 deletions(-) create mode 100644 .appveyor.yml create mode 100644 ext/repo_conda.c create mode 100644 ext/repo_conda.h create mode 100644 ext/repo_testcase.c create mode 100644 test/runtestcases.bat rename test/{runtestcases => runtestcases.sh} (100%) create mode 100644 test/testcases/lock/best.t create mode 100644 test/testcases/lock/dup.t create mode 100644 tools/conda2solv.c delete mode 100644 tools/diskusagexml2solv.c create mode 100644 win32/CMakeLists.txt create mode 100644 win32/LICENSE create mode 100644 win32/fmemopen.c create mode 100644 win32/fnmatch.c create mode 100644 win32/fnmatch.h create mode 100644 win32/getopt.c create mode 100644 win32/getopt.h create mode 100644 win32/regcomp.c create mode 100644 win32/regerror.c create mode 100644 win32/regex.h create mode 100644 win32/regexec.c create mode 100644 win32/strfncs.c create mode 100644 win32/strfncs.h create mode 100644 win32/tre-mem.c create mode 100644 win32/tre.h create mode 100644 win32/unistd.h diff --git a/.appveyor.yml b/.appveyor.yml new file mode 100644 index 0000000..2b91665 --- /dev/null +++ b/.appveyor.yml @@ -0,0 +1,34 @@ +build: false + +platform: + - x64 + +image: + - Visual Studio 2017 + +environment: + matrix: + - MINICONDA: C:\libsolv-conda + +init: + - "ECHO %MINICONDA%" + - if "%APPVEYOR_BUILD_WORKER_IMAGE%" == "Visual Studio 2015" set VCVARPATH="C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" + - if "%APPVEYOR_BUILD_WORKER_IMAGE%" == "Visual Studio 2015" set VCARGUMENT=%PLATFORM% + - if "%APPVEYOR_BUILD_WORKER_IMAGE%" == "Visual Studio 2017" set VCVARPATH="C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat" + - echo "%VCVARPATH% %VCARGUMENT%" + - "%VCVARPATH% %VCARGUMENT%" + - ps: if($env:Platform -eq "x64"){Start-FileDownload 'http://repo.continuum.io/miniconda/Miniconda3-latest-Windows-x86_64.exe' C:\Miniconda.exe; echo "Done"} + - ps: if($env:Platform -eq "x86"){Start-FileDownload 'http://repo.continuum.io/miniconda/Miniconda3-latest-Windows-x86.exe' C:\Miniconda.exe; echo "Done"} + - cmd: C:\Miniconda.exe /S /D=C:\libsolv-conda + - "set PATH=%MINICONDA%;%MINICONDA%\\Scripts;%MINICONDA%\\Library\\bin;%PATH%" + +install: + - conda config --set always_yes yes --set changeps1 no + - conda update -q conda + - conda info -a + - conda install cmake zlib xz -c conda-forge + - cmake -G "NMake Makefiles" -D CMAKE_INSTALL_PREFIX=%MINICONDA%\\LIBRARY -DDISABLE_SHARED=1 -DWITHOUT_COOKIEOPEN=1 -DMULTI_SEMANTICS=1 -DENABLE_COMPLEX_DEPS=1 -DENABLE_EXAMPLES=0 . + - nmake + +build_script: + - ctest diff --git a/.travis.yml b/.travis.yml index 8d7dda5..a115095 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,23 @@ language: C -before_script: sudo apt-get install cmake -script: mkdir build && cd build && cmake -DDEBIAN=1 -DMULTI_SEMANTICS=1 .. && make && make test - - +matrix: + fast_finish: true + include: + - os: linux + addons: + apt: + packages: + - cmake + - os: osx + osx_image: xcode9.4 + compiler: clang + addons: + homebrew: + packages: + - cmake + - zlib +script: +- mkdir build +- cd build +- cmake -DDEBIAN=1 -DMULTI_SEMANTICS=1 .. +- make +- make test diff --git a/CMakeLists.txt b/CMakeLists.txt index ab385f1..e331318 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ PROJECT (libsolv) -CMAKE_MINIMUM_REQUIRED (VERSION 2.4) +CMAKE_MINIMUM_REQUIRED (VERSION 2.8.5) OPTION (ENABLE_STATIC "Build a static version of the libraries?" OFF) OPTION (DISABLE_SHARED "Do not build a shared version of the libraries?" OFF) @@ -38,35 +38,16 @@ OPTION (ENABLE_ZSTD_COMPRESSION "Build with zstd compression support?" OFF) OPTION (ENABLE_ZCHUNK_COMPRESSION "Build with zchunk compression support?" OFF) OPTION (WITH_SYSTEM_ZCHUNK "Use system zchunk library?" OFF) OPTION (WITH_LIBXML2 "Build with libxml2 instead of libexpat?" OFF) +OPTION (WITHOUT_COOKIEOPEN "Disable the use of stdio cookie opens?" OFF) -# Library -IF (DEFINED LIB) - SET (LIB_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${LIB}") -ELSE (DEFINED LIB) - IF (CMAKE_SIZEOF_VOID_P MATCHES "8") - SET (LIB_SUFFIX "64") - ENDIF (CMAKE_SIZEOF_VOID_P MATCHES "8") - SET (LIB_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/lib${LIB_SUFFIX}") -ENDIF (DEFINED LIB) -MESSAGE (STATUS "Libraries will be installed in ${LIB_INSTALL_DIR}") -# Library -IF (DEFINED INCLUDE) - SET (INCLUDE_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${INCLUDE}") -else (DEFINED INCLUDE) - SET (INCLUDE_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/include") -ENDIF (DEFINED INCLUDE) -MESSAGE (STATUS "Header files will be installed in ${INCLUDE_INSTALL_DIR}") -SET (BIN_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/bin") -IF (NOT MAN_INSTALL_DIR) -SET (MAN_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/share/man") -IF (IS_DIRECTORY "${CMAKE_INSTALL_PREFIX}/man" AND NOT IS_DIRECTORY "${CMAKE_INSTALL_PREFIX}/share/man") - SET (MAN_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/man") -ENDIF (IS_DIRECTORY "${CMAKE_INSTALL_PREFIX}/man" AND NOT IS_DIRECTORY "${CMAKE_INSTALL_PREFIX}/share/man") -ENDIF (NOT MAN_INSTALL_DIR) -MESSAGE(STATUS "Man pages will be installed in ${MAN_INSTALL_DIR}") +include (GNUInstallDirs) +message (STATUS "Libraries will be installed in ${CMAKE_INSTALL_FULL_LIBDIR}") +message (STATUS "Header files will be installed in ${CMAKE_INSTALL_FULL_INCLUDEDIR}") +message (STATUS "Binaries will be installed in ${CMAKE_INSTALL_FULL_BINDIR}") +message (STATUS "Man pages will be installed in ${CMAKE_INSTALL_FULL_MANDIR}") IF (NOT PKGCONFIG_INSTALL_DIR) - SET (PKGCONFIG_INSTALL_DIR ${LIB_INSTALL_DIR}/pkgconfig) + SET (PKGCONFIG_INSTALL_DIR ${CMAKE_INSTALL_LIBDIR}/pkgconfig) ENDIF (NOT PKGCONFIG_INSTALL_DIR) #################################################################### # CONFIGURATION # @@ -287,7 +268,7 @@ ENDIF (${CMAKE_MAJOR_VERSION} GREATER 2) # should create config.h with #cmakedefine instead... FOREACH (VAR HAVE_STRCHRNUL HAVE_FOPENCOOKIE HAVE_FUNOPEN WORDS_BIGENDIAN - HAVE_RPM_DB_H HAVE_PGPDIGGETPARAMS WITH_LIBXML2 ) + HAVE_RPM_DB_H HAVE_PGPDIGGETPARAMS WITH_LIBXML2 WITHOUT_COOKIEOPEN) IF(${VAR}) ADD_DEFINITIONS (-D${VAR}=1) SET (SWIG_FLAGS ${SWIG_FLAGS} -D${VAR}) @@ -389,10 +370,15 @@ INCLUDE_DIRECTORIES (${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_SOURCE_DIR} ${CMAKE_SOU MESSAGE (STATUS "Looking for modules in ${CMAKE_MODULE_PATH}") -set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall") -set (CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS} -O3") -set (CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS} -g -O3") -set (CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS} -g3 -O0") +IF (MSVC) + set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /wd4244 /wd4267") + add_definitions(-D_CRT_SECURE_NO_WARNINGS) +ELSE () + set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall") + set (CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS} -O3") + set (CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS} -g -O3") + set (CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS} -g3 -O0") +ENDIF () # set system libraries SET (SYSTEM_LIBRARIES "") diff --git a/CREDITS b/CREDITS index 049bcae..38f89f9 100644 --- a/CREDITS +++ b/CREDITS @@ -23,3 +23,6 @@ Michael Schroeder Ingo Weinhold - haiku support +Wolf Vollprecht + - win32 port + diff --git a/NEWS b/NEWS index 198fca3..ede4a0e 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,17 @@ This file contains the major changes between libsolv versions: +Version 0.7.5 +- selected bug fixes: + * fix favorq leaking between solver runs if the solver is reused + * fix SOLVER_FLAG_FOCUS_BEST updateing packages without reason + * be more correct with multiversion packages that obsolete their + own name + * allow building with swig-4.0.0 + * lock jobs now take precedence over dup and forcebest jobs +- new features + * MSVC compilation support + Version 0.7.4 - selected bug fixes: * repo_add_rpmdb: do not copy bad solvables from the old solv file diff --git a/README b/README index 0242459..59ac1a1 100644 --- a/README +++ b/README @@ -37,7 +37,7 @@ Supported repository formats: Build instructions ================== -Requires: cmake 2.4.x +Requires: cmake 2.8.5 or later mkdir build cd build diff --git a/TODO_1.0 b/TODO_1.0 index d77df67..3e243d3 100644 --- a/TODO_1.0 +++ b/TODO_1.0 @@ -7,10 +7,6 @@ - deal with DIRSTR entries having dirid 0 (for source rpms) -- drop patchcheck - -- make FAVOR handling deal with versions - - write more manpages IDEAS: diff --git a/VERSION.cmake b/VERSION.cmake index 80d0e03..5c228a3 100644 --- a/VERSION.cmake +++ b/VERSION.cmake @@ -49,5 +49,5 @@ SET(LIBSOLVEXT_SOVERSION "1") SET(LIBSOLV_MAJOR "0") SET(LIBSOLV_MINOR "7") -SET(LIBSOLV_PATCH "4") +SET(LIBSOLV_PATCH "5") diff --git a/bindings/solv.i b/bindings/solv.i index ad265a5..107192f 100644 --- a/bindings/solv.i +++ b/bindings/solv.i @@ -629,8 +629,10 @@ SWIG_AsValDepId(void *obj, int *val) { %typemap(out) disown_helper { #if defined(SWIGRUBY) SWIG_ConvertPtr(self, &argp1,SWIGTYPE_p_Pool, SWIG_POINTER_DISOWN | 0 ); -#elif defined(SWIGPYTHON) +#elif defined(SWIGPYTHON) && SWIG_VERSION < 0x040000 SWIG_ConvertPtr(obj0, &argp1,SWIGTYPE_p_Pool, SWIG_POINTER_DISOWN | 0 ); +#elif defined(SWIGPYTHON) + SWIG_ConvertPtr(swig_obj[0], &argp1,SWIGTYPE_p_Pool, SWIG_POINTER_DISOWN | 0 ); #elif defined(SWIGPERL) SWIG_ConvertPtr(ST(0), &argp1,SWIGTYPE_p_Pool, SWIG_POINTER_DISOWN | 0 ); #elif defined(SWIGTCL) @@ -1121,7 +1123,7 @@ SolvFp *solvfp_xfopen_fd(const char *fn, int fd, const char *mode = 0); fd = dup(fd); if (fd == -1) return 0; - fcntl(fd, F_SETFD, FD_CLOEXEC); + solv_setcloexec(fd, 1); fp = solv_xfopen_fd(fn, fd, mode); if (!fp) { close(fd); @@ -1138,7 +1140,7 @@ SolvFp *solvfp_xfopen_fd(const char *fn, int fd, const char *mode = 0); if (!fp) return 0; if (fileno(fp) != -1) - fcntl(fileno(fp), F_SETFD, FD_CLOEXEC); + solv_setcloexec(fileno(fp), 1); sfp = solv_calloc(1, sizeof(SolvFp)); sfp->fp = fp; return sfp; @@ -1220,7 +1222,7 @@ typedef struct { void cloexec(bool state) { if (!$self->fp || fileno($self->fp) == -1) return; - fcntl(fileno($self->fp), F_SETFD, state ? FD_CLOEXEC : 0); + solv_setcloexec(fileno($self->fp), state); } } diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index 66011b4..ed38274 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -48,8 +48,8 @@ STRING(REGEX REPLACE "([^;]+)" "gen/\\1" libsolv_MANPAGES3 "${libsolv_MANPAGES3} INSTALL(FILES ${libsolv_MANPAGES3} - DESTINATION "${MAN_INSTALL_DIR}/man3") + DESTINATION "${CMAKE_INSTALL_MANDIR}/man3") INSTALL(FILES ${libsolv_MANPAGES1} - DESTINATION "${MAN_INSTALL_DIR}/man1") + DESTINATION "${CMAKE_INSTALL_MANDIR}/man1") diff --git a/examples/solv/CMakeLists.txt b/examples/solv/CMakeLists.txt index 41f45f7..0f3bd47 100644 --- a/examples/solv/CMakeLists.txt +++ b/examples/solv/CMakeLists.txt @@ -25,5 +25,5 @@ TARGET_LINK_LIBRARIES (solv libsolvext libsolv ${SYSTEM_LIBRARIES}) INSTALL(TARGETS solv - DESTINATION ${BIN_INSTALL_DIR}) + DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/ext/CMakeLists.txt b/ext/CMakeLists.txt index edc2b9f..7c25535 100644 --- a/ext/CMakeLists.txt +++ b/ext/CMakeLists.txt @@ -1,5 +1,5 @@ SET (libsolvext_SRCS - solv_xfopen.c testcase.c) + solv_xfopen.c testcase.c repo_testcase.c) SET (libsolvext_HEADERS tools_util.h solv_xfopen.h testcase.h) @@ -116,6 +116,13 @@ IF (ENABLE_APPDATA) repo_appdata.h) ENDIF (ENABLE_APPDATA) +IF (ENABLE_CONDA) + SET (libsolvext_SRCS ${libsolvext_SRCS} + repo_conda.c) + SET (libsolvext_HEADERS ${libsolvext_HEADERS} + repo_conda.h) +ENDIF (ENABLE_CONDA) + IF (ENABLE_RPMMD OR ENABLE_SUSEREPO) SET (libsolvext_SRCS ${libsolvext_SRCS} repodata_diskusage.c) @@ -126,12 +133,19 @@ IF (ENABLE_RPMMD OR ENABLE_SUSEREPO OR ENABLE_APPDATA OR ENABLE_COMPS OR ENABLE_ solv_xmlparser.c) ENDIF (ENABLE_RPMMD OR ENABLE_SUSEREPO OR ENABLE_APPDATA OR ENABLE_COMPS OR ENABLE_HELIXREPO OR ENABLE_MDKREPO) +IF (ENABLE_CONDA) + SET (libsolvext_SRCS ${libsolvext_SRCS} + solv_jsonparser.c) +ENDIF (ENABLE_CONDA) + IF (ENABLE_ZCHUNK_COMPRESSION) SET (libsolvext_SRCS ${libsolvext_SRCS} solv_zchunk.c) ENDIF (ENABLE_ZCHUNK_COMPRESSION) +IF (NOT MSVC) SET (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC") +ENDIF (NOT MSVC) IF (HAVE_LINKER_VERSION_SCRIPT) SET (CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${LINK_FLAGS} -Wl,--version-script=${CMAKE_SOURCE_DIR}/ext/libsolvext.ver") ENDIF (HAVE_LINKER_VERSION_SCRIPT) @@ -143,16 +157,20 @@ ADD_LIBRARY (libsolvext SHARED ${libsolvext_SRCS}) TARGET_LINK_LIBRARIES(libsolvext libsolv ${SYSTEM_LIBRARIES}) ENDIF (DISABLE_SHARED) +IF (WIN32) + INCLUDE_DIRECTORIES (${PROJECT_SOURCE_DIR}/win32/) +ENDIF () + SET_TARGET_PROPERTIES(libsolvext PROPERTIES OUTPUT_NAME "solvext") SET_TARGET_PROPERTIES(libsolvext PROPERTIES SOVERSION ${LIBSOLVEXT_SOVERSION}) -SET_TARGET_PROPERTIES(libsolvext PROPERTIES INSTALL_NAME_DIR ${LIB_INSTALL_DIR}) +SET_TARGET_PROPERTIES(libsolvext PROPERTIES INSTALL_NAME_DIR ${CMAKE_INSTALL_LIBDIR}) -INSTALL (FILES ${libsolvext_HEADERS} DESTINATION "${INCLUDE_INSTALL_DIR}/solv") -INSTALL (TARGETS libsolvext LIBRARY DESTINATION ${LIB_INSTALL_DIR} ARCHIVE DESTINATION ${LIB_INSTALL_DIR} RUNTIME DESTINATION bin) +INSTALL (FILES ${libsolvext_HEADERS} DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/solv") +INSTALL (TARGETS libsolvext LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) IF (ENABLE_STATIC AND NOT DISABLE_SHARED) ADD_LIBRARY (libsolvext_static STATIC ${libsolvext_SRCS}) SET_TARGET_PROPERTIES(libsolvext_static PROPERTIES OUTPUT_NAME "solvext") SET_TARGET_PROPERTIES(libsolvext_static PROPERTIES SOVERSION ${LIBSOLVEXT_SOVERSION}) -INSTALL (TARGETS libsolvext_static LIBRARY DESTINATION ${LIB_INSTALL_DIR} ARCHIVE DESTINATION ${LIB_INSTALL_DIR}) +INSTALL (TARGETS libsolvext_static LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) ENDIF (ENABLE_STATIC AND NOT DISABLE_SHARED) diff --git a/ext/libsolvext.ver b/ext/libsolvext.ver index 4896e85..ef028f0 100644 --- a/ext/libsolvext.ver +++ b/ext/libsolvext.ver @@ -10,6 +10,7 @@ SOLV_1.0 { repo_add_arch_repo; repo_add_autopattern; repo_add_code11_products; + repo_add_conda; repo_add_content; repo_add_comps; repo_add_cudf; diff --git a/ext/repo_conda.c b/ext/repo_conda.c new file mode 100644 index 0000000..0828aff --- /dev/null +++ b/ext/repo_conda.c @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2019, SUSE LLC. + * + * This program is licensed under the BSD license, read LICENSE.BSD + * for further information + */ + +#define _GNU_SOURCE +#include +#include +#include +#include + +#include "pool.h" +#include "repo.h" +#include "chksum.h" +#include "solv_jsonparser.h" +#include "conda.h" +#include "repo_conda.h" + +struct parsedata { + Pool *pool; + Repo *repo; + Repodata *data; +}; + +static int +parse_deps(struct parsedata *pd, struct solv_jsonparser *jp, Offset *depp) +{ + int type = JP_ARRAY; + while (type > 0 && (type = jsonparser_parse(jp)) > 0 && type != JP_ARRAY_END) + { + if (type == JP_STRING) + { + Id id = pool_conda_matchspec(pd->pool, jp->value); + if (id) + *depp = repo_addid_dep(pd->repo, *depp, id, 0); + } + else + type = jsonparser_skip(jp, type); + } + return type; +} + +static int +parse_package(struct parsedata *pd, struct solv_jsonparser *jp, char *kfn) +{ + int type = JP_OBJECT; + Pool *pool= pd->pool; + Repodata *data = pd->data; + Solvable *s; + Id handle = repo_add_solvable(pd->repo); + s = pool_id2solvable(pool, handle); + char *fn = 0; + char *subdir = 0; + + while (type > 0 && (type = jsonparser_parse(jp)) > 0 && type != JP_OBJECT_END) + { + if (type == JP_STRING && !strcmp(jp->key, "build")) + repodata_add_poolstr_array(data, handle, SOLVABLE_BUILDFLAVOR, jp->value); + else if (type == JP_NUMBER && !strcmp(jp->key, "build_number")) + repodata_set_str(data, handle, SOLVABLE_BUILDVERSION, jp->value); + else if (type == JP_ARRAY && !strcmp(jp->key, "depends")) + type = parse_deps(pd, jp, &s->requires); + else if (type == JP_ARRAY && !strcmp(jp->key, "requires")) + type = parse_deps(pd, jp, &s->requires); + else if (type == JP_STRING && !strcmp(jp->key, "license")) + repodata_add_poolstr_array(data, handle, SOLVABLE_LICENSE, jp->value); + else if (type == JP_STRING && !strcmp(jp->key, "md5")) + repodata_set_checksum(data, handle, SOLVABLE_PKGID, REPOKEY_TYPE_MD5, jp->value); + else if (type == JP_STRING && !strcmp(jp->key, "sha256")) + repodata_set_checksum(data, handle, SOLVABLE_CHECKSUM, REPOKEY_TYPE_SHA256, jp->value); + else if (type == JP_STRING && !strcmp(jp->key, "name")) + s->name = pool_str2id(pool, jp->value, 1); + else if (type == JP_STRING && !strcmp(jp->key, "version")) + s->evr= pool_str2id(pool, jp->value, 1); + else if (type == JP_STRING && !strcmp(jp->key, "fn") && !fn) + fn = solv_strdup(jp->value); + else if (type == JP_STRING && !strcmp(jp->key, "subdir") && !subdir) + subdir = solv_strdup(jp->value); + else if (type == JP_NUMBER && !strcmp(jp->key, "size")) + repodata_set_num(data, handle, SOLVABLE_DOWNLOADSIZE, strtoull(jp->value, 0, 10)); + else if (type == JP_NUMBER && !strcmp(jp->key, "timestamp")) + { + unsigned long long ts = strtoull(jp->value, 0, 10); + if (ts > 253402300799ULL) + ts /= 1000; + repodata_set_num(data, handle, SOLVABLE_BUILDTIME, ts); + } + else + type = jsonparser_skip(jp, type); + } + if (fn || kfn) + repodata_set_location(data, handle, 0, subdir, fn ? fn : kfn); + solv_free(fn); + solv_free(subdir); + if (!s->evr) + s->evr = 1; + if (s->name) + s->provides = repo_addid_dep(pd->repo, s->provides, pool_rel2id(pool, s->name, s->evr, REL_EQ, 1), 0); + return type; +} + +static int +parse_packages(struct parsedata *pd, struct solv_jsonparser *jp) +{ + int type = JP_OBJECT; + while (type > 0 && (type = jsonparser_parse(jp)) > 0 && type != JP_OBJECT_END) + { + if (type == JP_OBJECT) + { + char *fn = solv_strdup(jp->key); + type = parse_package(pd, jp, fn); + solv_free(fn); + } + else + type = jsonparser_skip(jp, type); + } + return type; +} + +static int +parse_packages2(struct parsedata *pd, struct solv_jsonparser *jp) +{ + int type = JP_ARRAY; + while (type > 0 && (type = jsonparser_parse(jp)) > 0 && type != JP_ARRAY_END) + { + if (type == JP_OBJECT) + type = parse_package(pd, jp, 0); + else + type = jsonparser_skip(jp, type); + } + return type; +} + +static int +parse_main(struct parsedata *pd, struct solv_jsonparser *jp) +{ + int type = JP_OBJECT; + while (type > 0 && (type = jsonparser_parse(jp)) > 0 && type != JP_OBJECT_END) + { + if (type == JP_OBJECT && !strcmp("packages", jp->key)) + type = parse_packages(pd, jp); + if (type == JP_ARRAY && !strcmp("packages", jp->key)) + type = parse_packages2(pd, jp); + else + type = jsonparser_skip(jp, type); + } + return type; +} + +int +repo_add_conda(Repo *repo, FILE *fp, int flags) +{ + Pool *pool = repo->pool; + struct solv_jsonparser jp; + struct parsedata pd; + Repodata *data; + int type, ret = 0; + + data = repo_add_repodata(repo, flags); + + memset(&pd, 0, sizeof(pd)); + pd.pool = pool; + pd.repo = repo; + pd.data = data; + + jsonparser_init(&jp, fp); + if ((type = jsonparser_parse(&jp)) != JP_OBJECT) + ret = pool_error(pool, -1, "repository does not start with an object"); + else if ((type = parse_main(&pd, &jp)) != JP_OBJECT_END) + ret = pool_error(pool, -1, "parse error line %d", jp.line); + jsonparser_free(&jp); + + if (!(flags & REPO_NO_INTERNALIZE)) + repodata_internalize(data); + + return ret; +} + diff --git a/ext/repo_conda.h b/ext/repo_conda.h new file mode 100644 index 0000000..7e90a3d --- /dev/null +++ b/ext/repo_conda.h @@ -0,0 +1,8 @@ +/* + * Copyright (c) 2019, SUSE LLC + * + * This program is licensed under the BSD license, read LICENSE.BSD + * for further information + */ + +extern int repo_add_conda(Repo *repo, FILE *fp, int flags); diff --git a/ext/repo_deb.c b/ext/repo_deb.c index 5dd79f4..8f63756 100644 --- a/ext/repo_deb.c +++ b/ext/repo_deb.c @@ -22,6 +22,14 @@ #include "chksum.h" #include "repo_deb.h" +#ifdef _WIN32 + #include "strfncs.h" +#endif + +#if !defined(S_ISREG) && defined(S_IFMT) && defined(S_IFREG) +#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) +#endif + static unsigned char * decompress_gz(unsigned char *in, int inl, int *outlp, int maxoutl) { diff --git a/ext/repo_rpmdb_bdb.h b/ext/repo_rpmdb_bdb.h index ed82a69..ae477f7 100644 --- a/ext/repo_rpmdb_bdb.h +++ b/ext/repo_rpmdb_bdb.h @@ -22,7 +22,13 @@ #endif #ifdef RPM5 -# define RPM_INDEX_SIZE 4 /* just the rpmdbid */ +# include +# if RPMLIB_VERSION < RPMLIB_VERSION_ENCODE(5,3,_,0,0,_) +# define RPM_INDEX_SIZE 8 /* rpmdbid + array index */ +# else +# define RPM_INDEX_SIZE 4 /* just the rpmdbid */ +# define RPM5_BIG_ENDIAN_ID +#endif #else # define RPM_INDEX_SIZE 8 /* rpmdbid + array index */ #endif @@ -66,11 +72,10 @@ stat_database(struct rpmdbstate *state, char *dbname, struct stat *statbuf, int return 0; } - static inline Id db2rpmdbid(unsigned char *db, int byteswapped) { -#ifdef RPM5 +#ifdef RPM5_BIG_ENDIAN_ID return db[0] << 24 | db[1] << 16 | db[2] << 8 | db[3]; #else # if defined(WORDS_BIGENDIAN) @@ -87,7 +92,7 @@ db2rpmdbid(unsigned char *db, int byteswapped) static inline void rpmdbid2db(unsigned char *db, Id id, int byteswapped) { -#ifdef RPM5 +#ifdef RPM5_BIG_ENDIAN_ID db[0] = id >> 24, db[1] = id >> 16, db[2] = id >> 8, db[3] = id; #else # if defined(WORDS_BIGENDIAN) diff --git a/ext/repo_susetags.c b/ext/repo_susetags.c index 561c789..dc60aa4 100644 --- a/ext/repo_susetags.c +++ b/ext/repo_susetags.c @@ -21,6 +21,10 @@ #endif #include "repodata_diskusage.h" +#ifdef _WIN32 +#include "strfncs.h" +#endif + struct datashare { Id name; Id evr; diff --git a/ext/repo_testcase.c b/ext/repo_testcase.c new file mode 100644 index 0000000..48d8a0e --- /dev/null +++ b/ext/repo_testcase.c @@ -0,0 +1,716 @@ +/* + * Copyright (c) 2019, SUSE LLC + * + * This program is licensed under the BSD license, read LICENSE.BSD + * for further information + */ + +#include +#include +#include +#include +#include +#include + +#include "pool.h" +#include "repo.h" +#include "testcase.h" + +#define DISABLE_JOIN2 +#include "tools_util.h" + +static const char * +testcase_id2str(Pool *pool, Id id, int isname) +{ + const char *s = pool_id2str(pool, id); + const char *ss; + char *s2, *s2p; + int bad = 0, paren = 0, parenbad = 0; + + if (id == 0) + return ""; + if (id == 1) + return "\\00"; + if (strchr("[(<=>!", *s)) + bad++; + if (!strncmp(s, "namespace:", 10)) + bad++; + for (ss = s + bad; *ss; ss++) + { + if (*ss == ' ' || *ss == '\\' || *(unsigned char *)ss < 32 || *ss == '(' || *ss == ')') + bad++; + if (*ss == '(') + paren = paren == 0 ? 1 : -1; + else if (*ss == ')') + { + paren = paren == 1 ? 0 : -1; + if (!paren) + parenbad += 2; + } + } + if (isname && ss - s > 4 && !strcmp(ss - 4, ":any")) + bad++; + if (!paren && !(bad - parenbad)) + return s; + + /* we need escaping! */ + s2 = s2p = pool_alloctmpspace(pool, strlen(s) + bad * 2 + 1); + if (!strncmp(s, "namespace:", 10)) + { + strcpy(s2p, "namespace\\3a"); + s2p += 12; + s += 10; + } + ss = s; + for (; *ss; ss++) + { + *s2p++ = *ss; + if ((ss == s && strchr("[(<=>!", *s)) || *ss == ' ' || *ss == '\\' || *(unsigned char *)ss < 32 || *ss == '(' || *ss == ')') + { + s2p[-1] = '\\'; + solv_bin2hex((unsigned char *)ss, 1, s2p); + s2p += 2; + } + } + *s2p = 0; + if (isname && s2p - s2 > 4 && !strcmp(s2p - 4, ":any")) + strcpy(s2p - 4, "\\3aany"); + return s2; +} + +struct oplist { + Id flags; + const char *opname; +} oplist[] = { + { REL_EQ, "=" }, + { REL_GT | REL_LT | REL_EQ, "<=>" }, + { REL_LT | REL_EQ, "<=" }, + { REL_GT | REL_EQ, ">=" }, + { REL_GT, ">" }, + { REL_GT | REL_LT, "<>" }, + { REL_AND, "&" }, + { REL_OR , "|" }, + { REL_WITH , "+" }, + { REL_WITHOUT , "-" }, + { REL_NAMESPACE , "" }, + { REL_ARCH, "." }, + { REL_MULTIARCH, "" }, + { REL_FILECONFLICT, "" }, + { REL_COND, "" }, + { REL_COMPAT, "compat >=" }, + { REL_KIND, "" }, + { REL_ELSE, "" }, + { REL_ERROR, "" }, + { REL_UNLESS, "" }, + { REL_CONDA, "" }, + { REL_LT, "<" }, + { 0, 0 } +}; + +static char * +testcase_dep2str_complex(Pool *pool, char *s, Id id, int addparens) +{ + Reldep *rd; + const char *s2; + int needparens; + struct oplist *op; + + if (!ISRELDEP(id)) + { + s2 = testcase_id2str(pool, id, 1); + s = pool_tmpappend(pool, s, s2, 0); + pool_freetmpspace(pool, s2); + return s; + } + rd = GETRELDEP(pool, id); + + /* check for special shortcuts */ + if (rd->flags == REL_NAMESPACE && !ISRELDEP(rd->name) && !strncmp(pool_id2str(pool, rd->name), "namespace:", 10)) + { + s = pool_tmpappend(pool, s, pool_id2str(pool, rd->name), "("); + s = testcase_dep2str_complex(pool, s, rd->evr, 0); + return pool_tmpappend(pool, s, ")", 0); + } + if (rd->flags == REL_MULTIARCH && !ISRELDEP(rd->name) && rd->evr == ARCH_ANY) + { + /* append special :any suffix */ + s2 = testcase_id2str(pool, rd->name, 1); + s = pool_tmpappend(pool, s, s2, ":any"); + pool_freetmpspace(pool, s2); + return s; + } + + needparens = 0; + if (ISRELDEP(rd->name)) + { + Reldep *rd2 = GETRELDEP(pool, rd->name); + needparens = 1; + if (rd->flags > 7 && rd->flags != REL_COMPAT && rd2->flags && rd2->flags <= 7) + needparens = 0; + } + + if (addparens) + s = pool_tmpappend(pool, s, "(", 0); + s = testcase_dep2str_complex(pool, s, rd->name, needparens); + + for (op = oplist; op->flags; op++) + if (rd->flags == op->flags) + break; + if (op->flags) + { + s = pool_tmpappend(pool, s, " ", op->opname); + s = pool_tmpappend(pool, s, " ", 0); + } + else + { + char buf[64]; + sprintf(buf, " <%u> ", rd->flags); + s = pool_tmpappend(pool, s, buf, 0); + } + + needparens = 0; + if (ISRELDEP(rd->evr)) + { + Reldep *rd2 = GETRELDEP(pool, rd->evr); + needparens = 1; + if (rd->flags > 7 && rd2->flags && rd2->flags <= 7) + needparens = 0; + if (rd->flags == REL_AND && rd2->flags == REL_AND) + needparens = 0; /* chain */ + if (rd->flags == REL_OR && rd2->flags == REL_OR) + needparens = 0; /* chain */ + if (rd->flags > 0 && rd->flags < 8 && rd2->flags == REL_COMPAT) + needparens = 0; /* chain */ + } + if (!ISRELDEP(rd->evr)) + { + s2 = testcase_id2str(pool, rd->evr, 0); + s = pool_tmpappend(pool, s, s2, 0); + pool_freetmpspace(pool, s2); + } + else + s = (char *)testcase_dep2str_complex(pool, s, rd->evr, needparens); + if (addparens) + s = pool_tmpappend(pool, s, ")", 0); + return s; +} + +const char * +testcase_dep2str(Pool *pool, Id id) +{ + char *s; + if (!ISRELDEP(id)) + return testcase_id2str(pool, id, 1); + s = pool_alloctmpspace(pool, 1); + *s = 0; + return testcase_dep2str_complex(pool, s, id, 0); +} + + +/* Convert a simple string. Also handle the :any suffix */ +static Id +testcase_str2dep_simple(Pool *pool, const char **sp, int isname) +{ + int haveesc = 0; + int paren = 0; + int isany = 0; + Id id; + const char *s; + for (s = *sp; *s; s++) + { + if (*s == '\\') + haveesc++; + if (*s == ' ' || *(unsigned char *)s < 32) + break; + if (*s == '(') + paren++; + if (*s == ')' && paren-- <= 0) + break; + } + if (isname && s - *sp > 4 && !strncmp(s - 4, ":any", 4)) + { + isany = 1; + s -= 4; + } + if (!haveesc) + { + if (s - *sp == 6 && !strncmp(*sp, "", 6)) + id = 0; + else + id = pool_strn2id(pool, *sp, s - *sp, 1); + } + else if (s - *sp == 3 && !strncmp(*sp, "\\00", 3)) + id = 1; + else + { + char buf[128], *bp, *bp2; + const char *sp2; + bp = s - *sp >= 128 ? solv_malloc(s - *sp + 1) : buf; + for (bp2 = bp, sp2 = *sp; sp2 < s;) + { + *bp2++ = *sp2++; + if (bp2[-1] == '\\') + solv_hex2bin(&sp2, (unsigned char *)bp2 - 1, 1); + } + *bp2 = 0; + id = pool_str2id(pool, bp, 1); + if (bp != buf) + solv_free(bp); + } + if (isany) + { + id = pool_rel2id(pool, id, ARCH_ANY, REL_MULTIARCH, 1); + s += 4; + } + *sp = s; + return id; +} + + +static Id +testcase_str2dep_complex(Pool *pool, const char **sp, int relop) +{ + const char *s = *sp; + Id flags, id, id2, namespaceid = 0; + struct oplist *op; + + if (!s) + return 0; + while (*s == ' ' || *s == '\t') + s++; + if (!strncmp(s, "namespace:", 10)) + { + /* special namespace hack */ + const char *s2; + for (s2 = s + 10; *s2 && *s2 != '('; s2++) + ; + if (*s2 == '(') + { + namespaceid = pool_strn2id(pool, s, s2 - s, 1); + s = s2; + } + } + if (*s == '(') + { + s++; + id = testcase_str2dep_complex(pool, &s, 0); + if (!s || *s != ')') + { + *sp = 0; + return 0; + } + s++; + } + else + id = testcase_str2dep_simple(pool, &s, relop ? 0 : 1); + if (namespaceid) + id = pool_rel2id(pool, namespaceid, id, REL_NAMESPACE, 1); + + for (;;) + { + while (*s == ' ' || *s == '\t') + s++; + if (!*s || *s == ')' || (relop && strncmp(s, "compat >= ", 10) != 0)) + { + *sp = s; + return id; + } + + /* we have an op! Find the end */ + flags = -1; + if (s[0] == '<' && (s[1] >= '0' && s[1] <= '9')) + { + const char *s2; + for (s2 = s + 1; *s2 >= '0' && *s2 <= '9'; s2++) + ; + if (*s2 == '>') + { + flags = strtoul(s + 1, 0, 10); + s = s2 + 1; + } + } + if (flags == -1) + { + for (op = oplist; op->flags; op++) + if (!strncmp(s, op->opname, strlen(op->opname))) + break; + if (!op->flags) + { + *sp = 0; + return 0; + } + flags = op->flags; + s += strlen(op->opname); + } + id2 = testcase_str2dep_complex(pool, &s, flags > 0 && flags < 8); + if (!s) + { + *sp = 0; + return 0; + } + id = pool_rel2id(pool, id, id2, flags, 1); + } +} + +Id +testcase_str2dep(Pool *pool, const char *s) +{ + Id id = testcase_str2dep_complex(pool, &s, 0); + return s && !*s ? id : 0; +} + +static void +writedeps(Repo *repo, FILE *fp, const char *tag, Id key, Solvable *s, Offset off) +{ + Pool *pool = repo->pool; + Id id, *dp; + int tagwritten = 0; + const char *idstr; + + if (!off) + return; + dp = repo->idarraydata + off; + while ((id = *dp++) != 0) + { + if (key == SOLVABLE_REQUIRES && id == SOLVABLE_PREREQMARKER) + { + if (tagwritten) + fprintf(fp, "-%s\n", tag); + tagwritten = 0; + tag = "Prq:"; + continue; + } + if (key == SOLVABLE_PROVIDES && id == SOLVABLE_FILEMARKER) + continue; + idstr = testcase_dep2str(pool, id); + if (!tagwritten) + { + fprintf(fp, "+%s\n", tag); + tagwritten = 1; + } + fprintf(fp, "%s\n", idstr); + } + if (tagwritten) + fprintf(fp, "-%s\n", tag); +} + +static void +writefilelist(Repo *repo, FILE *fp, const char *tag, Solvable *s) +{ + Pool *pool = repo->pool; + int tagwritten = 0; + Dataiterator di; + + dataiterator_init(&di, pool, repo, s - pool->solvables, SOLVABLE_FILELIST, 0, 0); + while (dataiterator_step(&di)) + { + const char *s = repodata_dir2str(di.data, di.kv.id, di.kv.str); + if (!tagwritten) + { + fprintf(fp, "+%s\n", tag); + tagwritten = 1; + } + fprintf(fp, "%s\n", s); + } + if (tagwritten) + fprintf(fp, "-%s\n", tag); + dataiterator_free(&di); +} + +int +testcase_write_testtags(Repo *repo, FILE *fp) +{ + Pool *pool = repo->pool; + Solvable *s; + Id p; + const char *name; + const char *evr; + const char *arch; + const char *release; + const char *tmp; + unsigned int ti; + Queue q; + + fprintf(fp, "=Ver: 3.0\n"); + queue_init(&q); + FOR_REPO_SOLVABLES(repo, p, s) + { + name = pool_id2str(pool, s->name); + evr = pool_id2str(pool, s->evr); + arch = s->arch ? pool_id2str(pool, s->arch) : "-"; + release = strrchr(evr, '-'); + if (!release) + release = evr + strlen(evr); + fprintf(fp, "=Pkg: %s %.*s %s %s\n", name, (int)(release - evr), evr, *release && release[1] ? release + 1 : "-", arch); + tmp = solvable_lookup_str(s, SOLVABLE_SUMMARY); + if (tmp) + fprintf(fp, "=Sum: %s\n", tmp); + writedeps(repo, fp, "Req:", SOLVABLE_REQUIRES, s, s->requires); + writedeps(repo, fp, "Prv:", SOLVABLE_PROVIDES, s, s->provides); + writedeps(repo, fp, "Obs:", SOLVABLE_OBSOLETES, s, s->obsoletes); + writedeps(repo, fp, "Con:", SOLVABLE_CONFLICTS, s, s->conflicts); + writedeps(repo, fp, "Rec:", SOLVABLE_RECOMMENDS, s, s->recommends); + writedeps(repo, fp, "Sup:", SOLVABLE_SUPPLEMENTS, s, s->supplements); + writedeps(repo, fp, "Sug:", SOLVABLE_SUGGESTS, s, s->suggests); + writedeps(repo, fp, "Enh:", SOLVABLE_ENHANCES, s, s->enhances); + if (solvable_lookup_idarray(s, SOLVABLE_PREREQ_IGNOREINST, &q)) + { + int i; + fprintf(fp, "+Ipr:\n"); + for (i = 0; i < q.count; i++) + fprintf(fp, "%s\n", testcase_dep2str(pool, q.elements[i])); + fprintf(fp, "-Ipr:\n"); + } + if (s->vendor) + fprintf(fp, "=Vnd: %s\n", pool_id2str(pool, s->vendor)); + if (solvable_lookup_idarray(s, SOLVABLE_BUILDFLAVOR, &q)) + { + int i; + for (i = 0; i < q.count; i++) + fprintf(fp, "=Flv: %s\n", pool_id2str(pool, q.elements[i])); + } + tmp = solvable_lookup_str(s, SOLVABLE_BUILDVERSION); + if (tmp) + fprintf(fp, "=Bvr: %s\n", tmp); + ti = solvable_lookup_num(s, SOLVABLE_BUILDTIME, 0); + if (ti) + fprintf(fp, "=Tim: %u\n", ti); + writefilelist(repo, fp, "Fls:", s); + } + queue_free(&q); + return 0; +} + +static inline Offset +adddep(Repo *repo, Offset olddeps, char *str, Id marker) +{ + Id id = *str == '/' ? pool_str2id(repo->pool, str, 1) : testcase_str2dep(repo->pool, str); + return repo_addid_dep(repo, olddeps, id, marker); +} + +static void +finish_v2_solvable(Pool *pool, Repodata *data, Solvable *s, char *filelist, int nfilelist) +{ + if (nfilelist) + { + int l; + Id did; + for (l = 0; l < nfilelist; l += strlen(filelist + l) + 1) + { + char *p = strrchr(filelist + l, '/'); + if (!p) + continue; + *p++ = 0; + did = repodata_str2dir(data, filelist + l, 1); + p[-1] = '/'; + if (!did) + did = repodata_str2dir(data, "/", 1); + repodata_add_dirstr(data, s - pool->solvables, SOLVABLE_FILELIST, did, p); + } + } + repo_rewrite_suse_deps(s, 0); +} + +/* stripped down version of susetags parser used for testcases */ +int +testcase_add_testtags(Repo *repo, FILE *fp, int flags) +{ + Pool *pool = repo->pool; + char *line, *linep; + int aline; + int tag; + Repodata *data; + Solvable *s; + char *sp[5]; + unsigned int t; + int intag; + char *filelist = 0; + int afilelist = 0; + int nfilelist = 0; + int tagsversion = 0; + int addselfprovides = 1; /* for compat reasons */ + + data = repo_add_repodata(repo, flags); + s = 0; + intag = 0; + + aline = 1024; + line = solv_malloc(aline); + linep = line; + for (;;) + { + if (linep - line + 16 > aline) + { + aline = linep - line; + line = solv_realloc(line, aline + 512); + linep = line + aline; + aline += 512; + } + if (!fgets(linep, aline - (linep - line), fp)) + break; + linep += strlen(linep); + if (linep == line || linep[-1] != '\n') + continue; + linep[-1] = 0; + linep = line + intag; + if (intag) + { + if (line[intag] == '-' && !strncmp(line + 1, line + intag + 1, intag - 2)) + { + intag = 0; + linep = line; + continue; + } + } + else if (line[0] == '+' && line[1] && line[1] != ':') + { + char *tagend = strchr(line, ':'); + if (!tagend) + continue; + line[0] = '='; + tagend[1] = ' '; + intag = tagend + 2 - line; + linep = line + intag; + continue; + } + if (*line != '=' || !line[1] || !line[2] || !line[3] || line[4] != ':') + continue; + tag = line[1] << 16 | line[2] << 8 | line[3]; + /* tags that do not need a solvable */ + switch(tag) + { + case 'V' << 16 | 'e' << 8 | 'r': + tagsversion = atoi(line + 6); + addselfprovides = tagsversion < 3 || strstr(line + 6, "addselfprovides") != 0; + continue; + case 'P' << 16 | 'k' << 8 | 'g': + if (s) + { + if (tagsversion == 2) + finish_v2_solvable(pool, data, s, filelist, nfilelist); + if (addselfprovides && s->name && s->arch != ARCH_SRC && s->arch != ARCH_NOSRC) + s->provides = repo_addid_dep(s->repo, s->provides, pool_rel2id(pool, s->name, s->evr, REL_EQ, 1), 0); + } + nfilelist = 0; + if (split(line + 5, sp, 5) != 4) + break; + s = pool_id2solvable(pool, repo_add_solvable(repo)); + s->name = pool_str2id(pool, sp[0], 1); + /* join back version and release */ + if (sp[2] && !(sp[2][0] == '-' && !sp[2][1])) + sp[2][-1] = '-'; + s->evr = makeevr(pool, sp[1]); + s->arch = strcmp(sp[3], "-") ? pool_str2id(pool, sp[3], 1) : 0; + continue; + default: + break; + } + if (!s) + continue; + /* tags that need a solvable */ + switch(tag) + { + case 'S' << 16 | 'u' << 8 | 'm': + repodata_set_str(data, s - pool->solvables, SOLVABLE_SUMMARY, line + 6); + break; + case 'V' << 16 | 'n' << 8 | 'd': + s->vendor = pool_str2id(pool, line + 6, 1); + break; + case 'T' << 16 | 'i' << 8 | 'm': + t = atoi(line + 6); + if (t) + repodata_set_num(data, s - pool->solvables, SOLVABLE_BUILDTIME, t); + break; + case 'R' << 16 | 'e' << 8 | 'q': + s->requires = adddep(repo, s->requires, line + 6, -SOLVABLE_PREREQMARKER); + break; + case 'P' << 16 | 'r' << 8 | 'q': + s->requires = adddep(repo, s->requires, line + 6, SOLVABLE_PREREQMARKER); + break; + case 'P' << 16 | 'r' << 8 | 'v': + /* version 2 had the file list at the end of the provides */ + if (tagsversion == 2) + { + if (line[6] == '/') + { + int l = strlen(line + 6) + 1; + if (nfilelist + l > afilelist) + { + afilelist = nfilelist + l + 512; + filelist = solv_realloc(filelist, afilelist); + } + memcpy(filelist + nfilelist, line + 6, l); + nfilelist += l; + break; + } + if (nfilelist) + { + int l; + for (l = 0; l < nfilelist; l += strlen(filelist + l) + 1) + s->provides = repo_addid_dep(repo, s->provides, pool_str2id(pool, filelist + l, 1), 0); + nfilelist = 0; + } + } + s->provides = adddep(repo, s->provides, line + 6, 0); + break; + case 'F' << 16 | 'l' << 8 | 's': + { + char *p = strrchr(line + 6, '/'); + Id did; + if (!p) + break; + *p++ = 0; + did = repodata_str2dir(data, line + 6, 1); + if (!did) + did = repodata_str2dir(data, "/", 1); + repodata_add_dirstr(data, s - pool->solvables, SOLVABLE_FILELIST, did, p); + break; + } + case 'O' << 16 | 'b' << 8 | 's': + s->obsoletes = adddep(repo, s->obsoletes, line + 6, 0); + break; + case 'C' << 16 | 'o' << 8 | 'n': + s->conflicts = adddep(repo, s->conflicts, line + 6, 0); + break; + case 'R' << 16 | 'e' << 8 | 'c': + s->recommends = adddep(repo, s->recommends, line + 6, 0); + break; + case 'S' << 16 | 'u' << 8 | 'p': + s->supplements = adddep(repo, s->supplements, line + 6, 0); + break; + case 'S' << 16 | 'u' << 8 | 'g': + s->suggests = adddep(repo, s->suggests, line + 6, 0); + break; + case 'E' << 16 | 'n' << 8 | 'h': + s->enhances = adddep(repo, s->enhances, line + 6, 0); + break; + case 'I' << 16 | 'p' << 8 | 'r': + { + Id id = line[6] == '/' ? pool_str2id(pool, line + 6, 1) : testcase_str2dep(pool, line + 6); + repodata_add_idarray(data, s - pool->solvables, SOLVABLE_PREREQ_IGNOREINST, id); + break; + } + case 'F' << 16 | 'l' << 8 | 'v': + repodata_add_poolstr_array(data, s - pool->solvables, SOLVABLE_BUILDFLAVOR, line + 6); + break; + case 'B' << 16 | 'v' << 8 | 'r': + repodata_set_str(data, s - pool->solvables, SOLVABLE_BUILDVERSION, line + 6); + break; + default: + break; + } + } + if (s) + { + if (tagsversion == 2) + finish_v2_solvable(pool, data, s, filelist, nfilelist); + if (addselfprovides && s->name && s->arch != ARCH_SRC && s->arch != ARCH_NOSRC) + s->provides = repo_addid_dep(s->repo, s->provides, pool_rel2id(pool, s->name, s->evr, REL_EQ, 1), 0); + } + solv_free(line); + solv_free(filelist); + repodata_free_dircache(data); + if (!(flags & REPO_NO_INTERNALIZE)) + repodata_internalize(data); + return 0; +} diff --git a/ext/solv_jsonparser.c b/ext/solv_jsonparser.c index 053ee6f..ea31923 100644 --- a/ext/solv_jsonparser.c +++ b/ext/solv_jsonparser.c @@ -15,7 +15,7 @@ #include "util.h" #include "solv_jsonparser.h" -struct solv_jsonparser * +void jsonparser_init(struct solv_jsonparser *jp, FILE *fp) { memset(jp, 0, sizeof(*jp)); @@ -24,14 +24,13 @@ jsonparser_init(struct solv_jsonparser *jp, FILE *fp) jp->line = jp->nextline = 1; jp->nextc = ' '; queue_init(&jp->stateq); - return jp; } -struct solv_jsonparser * +void jsonparser_free(struct solv_jsonparser *jp) { + solv_free(jp->space); queue_free(&jp->stateq); - return 0; } static void @@ -73,18 +72,18 @@ static int skipspace(struct solv_jsonparser *jp) { int c = jp->nextc; + jp->nextc = ' '; while (c == ' ' || c == '\t' || c == '\r' || c == '\n') c = nextc(jp); jp->line = jp->nextline; - return (jp->nextc = c); + return c; } static int -parseliteral(struct solv_jsonparser *jp) +parseliteral(struct solv_jsonparser *jp, int c) { size_t nspace = jp->nspace; - int c; - savec(jp, jp->nextc); + savec(jp, c); for (;;) { c = nextc(jp); @@ -104,10 +103,9 @@ parseliteral(struct solv_jsonparser *jp) } static int -parsenumber(struct solv_jsonparser *jp) +parsenumber(struct solv_jsonparser *jp, int c) { - int c; - savec(jp, jp->nextc); + savec(jp, c); for (;;) { c = nextc(jp); @@ -194,7 +192,6 @@ parsestring(struct solv_jsonparser *jp) } savec(jp, c); } - jp->nextc = ' '; savec(jp, 0); return JP_STRING; } @@ -206,10 +203,9 @@ parsevalue(struct solv_jsonparser *jp) if (c == '"') return parsestring(jp); if ((c >= '0' && c <= '9') || c == '+' || c == '-' || c == '.') - return parsenumber(jp); + return parsenumber(jp, c); if ((c >= 'a' && c <= 'z')) - return parseliteral(jp); - jp->nextc = ' '; + return parseliteral(jp, c); if (c == '[') return JP_ARRAY; if (c == '{') @@ -252,7 +248,6 @@ jsonparser_parse(struct solv_jsonparser *jp) return JP_ERROR; if (skipspace(jp) != ':') return JP_ERROR; - jp->nextc = ' '; type = parsevalue(jp); if (type == JP_OBJECT_END || type == JP_ARRAY_END) return JP_ERROR; @@ -264,7 +259,7 @@ jsonparser_parse(struct solv_jsonparser *jp) jp->value = jp->space + nspace; jp->valuelen = jp->nspace - nspace - 1; } - if (type == JP_ARRAY || type == JP_OBJECT) + if (type == JP_OBJECT || type == JP_ARRAY) { queue_push(&jp->stateq, jp->state); jp->state = type; @@ -272,9 +267,9 @@ jsonparser_parse(struct solv_jsonparser *jp) else if (jp->state == JP_OBJECT || jp->state == JP_ARRAY) { int c = skipspace(jp); - if (c == ',') - jp->nextc = ' '; - else if (c != (jp->state == JP_OBJECT ? '}' : ']')) + if (c == (jp->state == JP_OBJECT ? '}' : ']')) + jp->nextc = c; + else if (c != ',') return JP_ERROR; } return type; diff --git a/ext/solv_jsonparser.h b/ext/solv_jsonparser.h index d58d9e3..f728fb6 100644 --- a/ext/solv_jsonparser.h +++ b/ext/solv_jsonparser.h @@ -20,7 +20,7 @@ struct solv_jsonparser { char *value; size_t valuelen; - int state; + int state; /* START, END, OBJECT, ARRAY */ Queue stateq; int nextc; int nextline; @@ -41,8 +41,8 @@ struct solv_jsonparser { #define JP_ARRAY 8 #define JP_ARRAY_END 9 -struct solv_jsonparser *jsonparser_init(struct solv_jsonparser *jp, FILE *fp); -struct solv_jsonparser *jsonparser_free(struct solv_jsonparser *jp); +void jsonparser_init(struct solv_jsonparser *jp, FILE *fp); +void jsonparser_free(struct solv_jsonparser *jp); int jsonparser_parse(struct solv_jsonparser *jp); int jsonparser_skip(struct solv_jsonparser *jp, int type); diff --git a/ext/solv_xfopen.c b/ext/solv_xfopen.c index 343aed8..9aab68b 100644 --- a/ext/solv_xfopen.c +++ b/ext/solv_xfopen.c @@ -12,9 +12,14 @@ #include #include +#ifdef _WIN32 + #include "fmemopen.c" +#endif + #include "solv_xfopen.h" #include "util.h" +#ifndef WITHOUT_COOKIEOPEN static FILE *cookieopen(void *cookie, const char *mode, ssize_t (*cread)(void *, char *, size_t), @@ -602,6 +607,16 @@ static inline FILE *myzchunkfdopen(int fd, const char *mode) #endif /* ENABLE_ZCHUNK_COMPRESSION */ +#else +/* no cookies no compression */ +#undef ENABLE_ZLIB_COMPRESSION +#undef ENABLE_LZMA_COMPRESSION +#undef ENABLE_BZIP2_COMPRESSION +#undef ENABLE_ZSTD_COMPRESSION +#undef ENABLE_ZCHUNK_COMPRESSION +#endif + + FILE * solv_xfopen(const char *fn, const char *mode) @@ -664,7 +679,15 @@ solv_xfopen_fd(const char *fn, int fd, const char *mode) suf = fn ? strrchr(fn, '.') : 0; if (!mode) { + #ifndef _WIN32 int fl = fcntl(fd, F_GETFL, 0); + #else + HANDLE handle = (HANDLE) _get_osfhandle(fd); + BY_HANDLE_FILE_INFORMATION file_info; + if (!GetFileInformationByHandle(handle, &file_info)) + return 0; + int fl = file_info.dwFileAttributes; + #endif if (fl == -1) return 0; fl &= O_RDONLY|O_WRONLY|O_RDWR; @@ -759,6 +782,9 @@ solv_xfopen_iscompressed(const char *fn) return 0; } + +#ifndef WITHOUT_COOKIEOPEN + struct bufcookie { char **bufp; size_t *buflp; @@ -835,3 +861,33 @@ solv_xfopen_buf(const char *fn, char **bufp, size_t *buflp, const char *mode) } return fp; } + +#else + +FILE * +solv_xfopen_buf(const char *fn, char **bufp, size_t *buflp, const char *mode) +{ + FILE *fp; + size_t l; + if (*mode != 'r') + return 0; + l = buflp ? *buflp : strlen(*bufp); + if (!strcmp(mode, "rf")) + { + if (!(fp = fmemopen(0, l, "r+"))) + return 0; + if (l && fwrite(*bufp, l, 1, fp) != 1) + { + fclose(fp); + return 0; + } + solv_free(*bufp); + rewind(fp); + } + else + fp = fmemopen(*bufp, l, "r"); + return fp; +} + +#endif + diff --git a/ext/testcase.c b/ext/testcase.c index 8edf58f..fcebcfa 100644 --- a/ext/testcase.c +++ b/ext/testcase.c @@ -28,8 +28,17 @@ #include "ext/repo_helix.h" #endif -#define DISABLE_JOIN2 -#include "tools_util.h" +#ifdef _WIN32 + #include +#endif + +/* see repo_testcase.c */ +struct oplist { + Id flags; + const char *opname; +}; +extern struct oplist oplist[]; + static struct job2str { Id job; @@ -88,6 +97,7 @@ static struct resultflags2str { { TESTCASE_RESULT_REASON, "reason" }, { TESTCASE_RESULT_CLEANDEPS, "cleandeps" }, { TESTCASE_RESULT_JOBS, "jobs" }, + { TESTCASE_RESULT_USERINSTALLED, "userinstalled" }, { 0, 0 } }; @@ -322,347 +332,6 @@ strqueue_diff(Strqueue *sq1, Strqueue *sq2, Strqueue *osq) strqueue_pushjoin(osq, "+", sq2->str[j++], 0); } - -static const char * -testcase_id2str(Pool *pool, Id id, int isname) -{ - const char *s = pool_id2str(pool, id); - const char *ss; - char *s2, *s2p; - int bad = 0, paren = 0, parenbad = 0; - - if (id == 0) - return ""; - if (id == 1) - return "\\00"; - if (strchr("[(<=>!", *s)) - bad++; - if (!strncmp(s, "namespace:", 10)) - bad++; - for (ss = s + bad; *ss; ss++) - { - if (*ss == ' ' || *ss == '\\' || *(unsigned char *)ss < 32 || *ss == '(' || *ss == ')') - bad++; - if (*ss == '(') - paren = paren == 0 ? 1 : -1; - else if (*ss == ')') - { - paren = paren == 1 ? 0 : -1; - if (!paren) - parenbad += 2; - } - } - if (isname && ss - s > 4 && !strcmp(ss - 4, ":any")) - bad++; - if (!paren && !(bad - parenbad)) - return s; - - /* we need escaping! */ - s2 = s2p = pool_alloctmpspace(pool, strlen(s) + bad * 2 + 1); - if (!strncmp(s, "namespace:", 10)) - { - strcpy(s2p, "namespace\\3a"); - s2p += 12; - s += 10; - } - ss = s; - for (; *ss; ss++) - { - *s2p++ = *ss; - if ((ss == s && strchr("[(<=>!", *s)) || *ss == ' ' || *ss == '\\' || *(unsigned char *)ss < 32 || *ss == '(' || *ss == ')') - { - s2p[-1] = '\\'; - solv_bin2hex((unsigned char *)ss, 1, s2p); - s2p += 2; - } - } - *s2p = 0; - if (isname && s2p - s2 > 4 && !strcmp(s2p - 4, ":any")) - strcpy(s2p - 4, "\\3aany"); - return s2; -} - -struct oplist { - Id flags; - const char *opname; -} oplist[] = { - { REL_EQ, "=" }, - { REL_GT | REL_LT | REL_EQ, "<=>" }, - { REL_LT | REL_EQ, "<=" }, - { REL_GT | REL_EQ, ">=" }, - { REL_GT, ">" }, - { REL_GT | REL_LT, "<>" }, - { REL_AND, "&" }, - { REL_OR , "|" }, - { REL_WITH , "+" }, - { REL_WITHOUT , "-" }, - { REL_NAMESPACE , "" }, - { REL_ARCH, "." }, - { REL_MULTIARCH, "" }, - { REL_FILECONFLICT, "" }, - { REL_COND, "" }, - { REL_COMPAT, "compat >=" }, - { REL_KIND, "" }, - { REL_ELSE, "" }, - { REL_ERROR, "" }, - { REL_UNLESS, "" }, - { REL_CONDA, "" }, - { REL_LT, "<" }, - { 0, 0 } -}; - -static char * -testcase_dep2str_complex(Pool *pool, char *s, Id id, int addparens) -{ - Reldep *rd; - const char *s2; - int needparens; - struct oplist *op; - - if (!ISRELDEP(id)) - { - s2 = testcase_id2str(pool, id, 1); - s = pool_tmpappend(pool, s, s2, 0); - pool_freetmpspace(pool, s2); - return s; - } - rd = GETRELDEP(pool, id); - - /* check for special shortcuts */ - if (rd->flags == REL_NAMESPACE && !ISRELDEP(rd->name) && !strncmp(pool_id2str(pool, rd->name), "namespace:", 10)) - { - s = pool_tmpappend(pool, s, pool_id2str(pool, rd->name), "("); - s = testcase_dep2str_complex(pool, s, rd->evr, 0); - return pool_tmpappend(pool, s, ")", 0); - } - if (rd->flags == REL_MULTIARCH && !ISRELDEP(rd->name) && rd->evr == ARCH_ANY) - { - /* append special :any suffix */ - s2 = testcase_id2str(pool, rd->name, 1); - s = pool_tmpappend(pool, s, s2, ":any"); - pool_freetmpspace(pool, s2); - return s; - } - - needparens = 0; - if (ISRELDEP(rd->name)) - { - Reldep *rd2 = GETRELDEP(pool, rd->name); - needparens = 1; - if (rd->flags > 7 && rd->flags != REL_COMPAT && rd2->flags && rd2->flags <= 7) - needparens = 0; - } - - if (addparens) - s = pool_tmpappend(pool, s, "(", 0); - s = testcase_dep2str_complex(pool, s, rd->name, needparens); - - for (op = oplist; op->flags; op++) - if (rd->flags == op->flags) - break; - if (op->flags) - { - s = pool_tmpappend(pool, s, " ", op->opname); - s = pool_tmpappend(pool, s, " ", 0); - } - else - { - char buf[64]; - sprintf(buf, " <%u> ", rd->flags); - s = pool_tmpappend(pool, s, buf, 0); - } - - needparens = 0; - if (ISRELDEP(rd->evr)) - { - Reldep *rd2 = GETRELDEP(pool, rd->evr); - needparens = 1; - if (rd->flags > 7 && rd2->flags && rd2->flags <= 7) - needparens = 0; - if (rd->flags == REL_AND && rd2->flags == REL_AND) - needparens = 0; /* chain */ - if (rd->flags == REL_OR && rd2->flags == REL_OR) - needparens = 0; /* chain */ - if (rd->flags > 0 && rd->flags < 8 && rd2->flags == REL_COMPAT) - needparens = 0; /* chain */ - } - if (!ISRELDEP(rd->evr)) - { - s2 = testcase_id2str(pool, rd->evr, 0); - s = pool_tmpappend(pool, s, s2, 0); - pool_freetmpspace(pool, s2); - } - else - s = (char *)testcase_dep2str_complex(pool, s, rd->evr, needparens); - if (addparens) - s = pool_tmpappend(pool, s, ")", 0); - return s; -} - -const char * -testcase_dep2str(Pool *pool, Id id) -{ - char *s; - if (!ISRELDEP(id)) - return testcase_id2str(pool, id, 1); - s = pool_alloctmpspace(pool, 1); - *s = 0; - return testcase_dep2str_complex(pool, s, id, 0); -} - - -/* Convert a simple string. Also handle the :any suffix */ -static Id -testcase_str2dep_simple(Pool *pool, const char **sp, int isname) -{ - int haveesc = 0; - int paren = 0; - int isany = 0; - Id id; - const char *s; - for (s = *sp; *s; s++) - { - if (*s == '\\') - haveesc++; - if (*s == ' ' || *(unsigned char *)s < 32) - break; - if (*s == '(') - paren++; - if (*s == ')' && paren-- <= 0) - break; - } - if (isname && s - *sp > 4 && !strncmp(s - 4, ":any", 4)) - { - isany = 1; - s -= 4; - } - if (!haveesc) - { - if (s - *sp == 6 && !strncmp(*sp, "", 6)) - id = 0; - else - id = pool_strn2id(pool, *sp, s - *sp, 1); - } - else if (s - *sp == 3 && !strncmp(*sp, "\\00", 3)) - id = 1; - else - { - char buf[128], *bp, *bp2; - const char *sp2; - bp = s - *sp >= 128 ? solv_malloc(s - *sp + 1) : buf; - for (bp2 = bp, sp2 = *sp; sp2 < s;) - { - *bp2++ = *sp2++; - if (bp2[-1] == '\\') - solv_hex2bin(&sp2, (unsigned char *)bp2 - 1, 1); - } - *bp2 = 0; - id = pool_str2id(pool, bp, 1); - if (bp != buf) - solv_free(bp); - } - if (isany) - { - id = pool_rel2id(pool, id, ARCH_ANY, REL_MULTIARCH, 1); - s += 4; - } - *sp = s; - return id; -} - - -static Id -testcase_str2dep_complex(Pool *pool, const char **sp, int relop) -{ - const char *s = *sp; - Id flags, id, id2, namespaceid = 0; - struct oplist *op; - - if (!s) - return 0; - while (*s == ' ' || *s == '\t') - s++; - if (!strncmp(s, "namespace:", 10)) - { - /* special namespace hack */ - const char *s2; - for (s2 = s + 10; *s2 && *s2 != '('; s2++) - ; - if (*s2 == '(') - { - namespaceid = pool_strn2id(pool, s, s2 - s, 1); - s = s2; - } - } - if (*s == '(') - { - s++; - id = testcase_str2dep_complex(pool, &s, 0); - if (!s || *s != ')') - { - *sp = 0; - return 0; - } - s++; - } - else - id = testcase_str2dep_simple(pool, &s, relop ? 0 : 1); - if (namespaceid) - id = pool_rel2id(pool, namespaceid, id, REL_NAMESPACE, 1); - - for (;;) - { - while (*s == ' ' || *s == '\t') - s++; - if (!*s || *s == ')' || (relop && strncmp(s, "compat >= ", 10) != 0)) - { - *sp = s; - return id; - } - - /* we have an op! Find the end */ - flags = -1; - if (s[0] == '<' && (s[1] >= '0' && s[1] <= '9')) - { - const char *s2; - for (s2 = s + 1; *s2 >= '0' && *s2 <= '9'; s2++) - ; - if (*s2 == '>') - { - flags = strtoul(s + 1, 0, 10); - s = s2 + 1; - } - } - if (flags == -1) - { - for (op = oplist; op->flags; op++) - if (!strncmp(s, op->opname, strlen(op->opname))) - break; - if (!op->flags) - { - *sp = 0; - return 0; - } - flags = op->flags; - s += strlen(op->opname); - } - id2 = testcase_str2dep_complex(pool, &s, flags > 0 && flags < 8); - if (!s) - { - *sp = 0; - return 0; - } - id = pool_rel2id(pool, id, id2, flags, 1); - } -} - -Id -testcase_str2dep(Pool *pool, const char *s) -{ - Id id = testcase_str2dep_complex(pool, &s, 0); - return s && !*s ? id : 0; -} - /**********************************************************/ const char * @@ -699,17 +368,30 @@ testcase_solvid2str(Pool *pool, Id p) e = pool_id2str(pool, s->evr); a = pool_id2str(pool, s->arch); str = pool_alloctmpspace(pool, strlen(n) + strlen(e) + strlen(a) + 3); - sprintf(str, "%s-%s.%s", n, e, a); + sprintf(str, "%s-%s", n, e); + if (solvable_lookup_type(s, SOLVABLE_BUILDFLAVOR)) + { + Queue flavorq; + int i; + + queue_init(&flavorq); + solvable_lookup_idarray(s, SOLVABLE_BUILDFLAVOR, &flavorq); + for (i = 0; i < flavorq.count; i++) + str = pool_tmpappend(pool, str, "-", pool_id2str(pool, flavorq.elements[i])); + queue_free(&flavorq); + } + if (s->arch) + str = pool_tmpappend(pool, str, ".", a); if (!s->repo) return pool_tmpappend(pool, str, "@", 0); if (s->repo->name) { int l = strlen(str); - char *str2 = pool_tmpappend(pool, str, "@", s->repo->name); - for (; str2[l]; l++) - if (str2[l] == ' ' || str2[l] == '\t') - str2[l] = '_'; - return str2; + str = pool_tmpappend(pool, str, "@", s->repo->name); + for (; str[l]; l++) + if (str[l] == ' ' || str[l] == '\t') + str[l] = '_'; + return str; } sprintf(buf, "@#%d", s->repo->repoid); return pool_tmpappend(pool, str, buf, 0); @@ -754,6 +436,47 @@ testcase_str2repo(Pool *pool, const char *str) return repo; } +/* check evr and buildflavors */ +static int +str2solvid_check(Pool *pool, Solvable *s, const char *start, const char *end, Id evrid) +{ + if (!solvable_lookup_type(s, SOLVABLE_BUILDFLAVOR)) + { + /* just check the evr */ + return evrid && s->evr == evrid; + } + else + { + Queue flavorq; + int i; + + queue_init(&flavorq); + solvable_lookup_idarray(s, SOLVABLE_BUILDFLAVOR, &flavorq); + queue_unshift(&flavorq, s->evr); + for (i = 0; i < flavorq.count; i++) + { + const char *part = pool_id2str(pool, flavorq.elements[i]); + size_t partl = strlen(part); + if (start + partl > end || strncmp(start, part, partl) != 0) + break; + start += partl; + if (i + 1 < flavorq.count) + { + if (start >= end || *start != '-') + break; + start++; + } + } + if (i < flavorq.count) + { + queue_free(&flavorq); + return 0; + } + queue_free(&flavorq); + return start == end; + } +} + Id testcase_str2solvid(Pool *pool, const char *str) { @@ -793,19 +516,18 @@ testcase_str2solvid(Pool *pool, const char *str) if (!nid) continue; evrid = pool_strn2id(pool, str + i + 1, repostart - (i + 1), 0); - if (!evrid) - continue; /* first check whatprovides */ FOR_PROVIDES(p, pp, nid) { Solvable *s = pool->solvables + p; - if (s->name != nid || s->evr != evrid) + if (s->name != nid) continue; if (repo && s->repo != repo) continue; if (arch && s->arch != arch) continue; - return p; + if (str2solvid_check(pool, s, str + i + 1, str + repostart, evrid)) + return p; } /* maybe it's not installable and thus not in whatprovides. do a slow search */ if (repo) @@ -813,11 +535,12 @@ testcase_str2solvid(Pool *pool, const char *str) Solvable *s; FOR_REPO_SOLVABLES(repo, p, s) { - if (s->name != nid || s->evr != evrid) + if (s->name != nid) continue; if (arch && s->arch != arch) continue; - return p; + if (str2solvid_check(pool, s, str + i + 1, str + repostart, evrid)) + return p; } } else @@ -825,11 +548,12 @@ testcase_str2solvid(Pool *pool, const char *str) FOR_POOL_SOLVABLES(p) { Solvable *s = pool->solvables + p; - if (s->name != nid || s->evr != evrid) + if (s->name != nid) continue; if (arch && s->arch != arch) continue; - return p; + if (str2solvid_check(pool, s, str + i + 1, str + repostart, evrid)) + return p; } } } @@ -1180,347 +904,6 @@ addselectionjob(Pool *pool, char **pieces, int npieces, Queue *jobqueue, int typ return r; } -static void -writedeps(Repo *repo, FILE *fp, const char *tag, Id key, Solvable *s, Offset off) -{ - Pool *pool = repo->pool; - Id id, *dp; - int tagwritten = 0; - const char *idstr; - - if (!off) - return; - dp = repo->idarraydata + off; - while ((id = *dp++) != 0) - { - if (key == SOLVABLE_REQUIRES && id == SOLVABLE_PREREQMARKER) - { - if (tagwritten) - fprintf(fp, "-%s\n", tag); - tagwritten = 0; - tag = "Prq:"; - continue; - } - if (key == SOLVABLE_PROVIDES && id == SOLVABLE_FILEMARKER) - continue; - idstr = testcase_dep2str(pool, id); - if (!tagwritten) - { - fprintf(fp, "+%s\n", tag); - tagwritten = 1; - } - fprintf(fp, "%s\n", idstr); - } - if (tagwritten) - fprintf(fp, "-%s\n", tag); -} - -static void -writefilelist(Repo *repo, FILE *fp, const char *tag, Solvable *s) -{ - Pool *pool = repo->pool; - int tagwritten = 0; - Dataiterator di; - - dataiterator_init(&di, pool, repo, s - pool->solvables, SOLVABLE_FILELIST, 0, 0); - while (dataiterator_step(&di)) - { - const char *s = repodata_dir2str(di.data, di.kv.id, di.kv.str); - if (!tagwritten) - { - fprintf(fp, "+%s\n", tag); - tagwritten = 1; - } - fprintf(fp, "%s\n", s); - } - if (tagwritten) - fprintf(fp, "-%s\n", tag); - dataiterator_free(&di); -} - -int -testcase_write_testtags(Repo *repo, FILE *fp) -{ - Pool *pool = repo->pool; - Solvable *s; - Id p; - const char *name; - const char *evr; - const char *arch; - const char *release; - const char *tmp; - unsigned int ti; - Queue q; - - fprintf(fp, "=Ver: 3.0\n"); - queue_init(&q); - FOR_REPO_SOLVABLES(repo, p, s) - { - name = pool_id2str(pool, s->name); - evr = pool_id2str(pool, s->evr); - arch = pool_id2str(pool, s->arch); - release = strrchr(evr, '-'); - if (!release) - release = evr + strlen(evr); - fprintf(fp, "=Pkg: %s %.*s %s %s\n", name, (int)(release - evr), evr, *release && release[1] ? release + 1 : "-", arch); - tmp = solvable_lookup_str(s, SOLVABLE_SUMMARY); - if (tmp) - fprintf(fp, "=Sum: %s\n", tmp); - writedeps(repo, fp, "Req:", SOLVABLE_REQUIRES, s, s->requires); - writedeps(repo, fp, "Prv:", SOLVABLE_PROVIDES, s, s->provides); - writedeps(repo, fp, "Obs:", SOLVABLE_OBSOLETES, s, s->obsoletes); - writedeps(repo, fp, "Con:", SOLVABLE_CONFLICTS, s, s->conflicts); - writedeps(repo, fp, "Rec:", SOLVABLE_RECOMMENDS, s, s->recommends); - writedeps(repo, fp, "Sup:", SOLVABLE_SUPPLEMENTS, s, s->supplements); - writedeps(repo, fp, "Sug:", SOLVABLE_SUGGESTS, s, s->suggests); - writedeps(repo, fp, "Enh:", SOLVABLE_ENHANCES, s, s->enhances); - if (solvable_lookup_idarray(s, SOLVABLE_PREREQ_IGNOREINST, &q)) - { - int i; - fprintf(fp, "+Ipr:\n"); - for (i = 0; i < q.count; i++) - fprintf(fp, "%s\n", testcase_dep2str(pool, q.elements[i])); - fprintf(fp, "-Ipr:\n"); - } - if (s->vendor) - fprintf(fp, "=Vnd: %s\n", pool_id2str(pool, s->vendor)); - ti = solvable_lookup_num(s, SOLVABLE_BUILDTIME, 0); - if (ti) - fprintf(fp, "=Tim: %u\n", ti); - writefilelist(repo, fp, "Fls:", s); - } - queue_free(&q); - return 0; -} - -static inline Offset -adddep(Repo *repo, Offset olddeps, char *str, Id marker) -{ - Id id = *str == '/' ? pool_str2id(repo->pool, str, 1) : testcase_str2dep(repo->pool, str); - return repo_addid_dep(repo, olddeps, id, marker); -} - -static void -finish_v2_solvable(Pool *pool, Repodata *data, Solvable *s, char *filelist, int nfilelist) -{ - if (nfilelist) - { - int l; - Id did; - for (l = 0; l < nfilelist; l += strlen(filelist + l) + 1) - { - char *p = strrchr(filelist + l, '/'); - if (!p) - continue; - *p++ = 0; - did = repodata_str2dir(data, filelist + l, 1); - p[-1] = '/'; - if (!did) - did = repodata_str2dir(data, "/", 1); - repodata_add_dirstr(data, s - pool->solvables, SOLVABLE_FILELIST, did, p); - } - } - repo_rewrite_suse_deps(s, 0); -} - -/* stripped down version of susetags parser used for testcases */ -int -testcase_add_testtags(Repo *repo, FILE *fp, int flags) -{ - Pool *pool = repo->pool; - char *line, *linep; - int aline; - int tag; - Repodata *data; - Solvable *s; - char *sp[5]; - unsigned int t; - int intag; - char *filelist = 0; - int afilelist = 0; - int nfilelist = 0; - int tagsversion = 0; - int addselfprovides = 1; /* for compat reasons */ - - data = repo_add_repodata(repo, flags); - s = 0; - intag = 0; - - aline = 1024; - line = solv_malloc(aline); - linep = line; - for (;;) - { - if (linep - line + 16 > aline) - { - aline = linep - line; - line = solv_realloc(line, aline + 512); - linep = line + aline; - aline += 512; - } - if (!fgets(linep, aline - (linep - line), fp)) - break; - linep += strlen(linep); - if (linep == line || linep[-1] != '\n') - continue; - linep[-1] = 0; - linep = line + intag; - if (intag) - { - if (line[intag] == '-' && !strncmp(line + 1, line + intag + 1, intag - 2)) - { - intag = 0; - linep = line; - continue; - } - } - else if (line[0] == '+' && line[1] && line[1] != ':') - { - char *tagend = strchr(line, ':'); - if (!tagend) - continue; - line[0] = '='; - tagend[1] = ' '; - intag = tagend + 2 - line; - linep = line + intag; - continue; - } - if (*line != '=' || !line[1] || !line[2] || !line[3] || line[4] != ':') - continue; - tag = line[1] << 16 | line[2] << 8 | line[3]; - /* tags that do not need a solvable */ - switch(tag) - { - case 'V' << 16 | 'e' << 8 | 'r': - tagsversion = atoi(line + 6); - addselfprovides = tagsversion < 3 || strstr(line + 6, "addselfprovides") != 0; - continue; - case 'P' << 16 | 'k' << 8 | 'g': - if (s) - { - if (tagsversion == 2) - finish_v2_solvable(pool, data, s, filelist, nfilelist); - if (addselfprovides && s->name && s->arch != ARCH_SRC && s->arch != ARCH_NOSRC) - s->provides = repo_addid_dep(s->repo, s->provides, pool_rel2id(pool, s->name, s->evr, REL_EQ, 1), 0); - } - nfilelist = 0; - if (split(line + 5, sp, 5) != 4) - break; - s = pool_id2solvable(pool, repo_add_solvable(repo)); - s->name = pool_str2id(pool, sp[0], 1); - /* join back version and release */ - if (sp[2] && !(sp[2][0] == '-' && !sp[2][1])) - sp[2][-1] = '-'; - s->evr = makeevr(pool, sp[1]); - s->arch = pool_str2id(pool, sp[3], 1); - continue; - default: - break; - } - if (!s) - continue; - /* tags that need a solvable */ - switch(tag) - { - case 'S' << 16 | 'u' << 8 | 'm': - repodata_set_str(data, s - pool->solvables, SOLVABLE_SUMMARY, line + 6); - break; - case 'V' << 16 | 'n' << 8 | 'd': - s->vendor = pool_str2id(pool, line + 6, 1); - break; - case 'T' << 16 | 'i' << 8 | 'm': - t = atoi(line + 6); - if (t) - repodata_set_num(data, s - pool->solvables, SOLVABLE_BUILDTIME, t); - break; - case 'R' << 16 | 'e' << 8 | 'q': - s->requires = adddep(repo, s->requires, line + 6, -SOLVABLE_PREREQMARKER); - break; - case 'P' << 16 | 'r' << 8 | 'q': - s->requires = adddep(repo, s->requires, line + 6, SOLVABLE_PREREQMARKER); - break; - case 'P' << 16 | 'r' << 8 | 'v': - /* version 2 had the file list at the end of the provides */ - if (tagsversion == 2) - { - if (line[6] == '/') - { - int l = strlen(line + 6) + 1; - if (nfilelist + l > afilelist) - { - afilelist = nfilelist + l + 512; - filelist = solv_realloc(filelist, afilelist); - } - memcpy(filelist + nfilelist, line + 6, l); - nfilelist += l; - break; - } - if (nfilelist) - { - int l; - for (l = 0; l < nfilelist; l += strlen(filelist + l) + 1) - s->provides = repo_addid_dep(repo, s->provides, pool_str2id(pool, filelist + l, 1), 0); - nfilelist = 0; - } - } - s->provides = adddep(repo, s->provides, line + 6, 0); - break; - case 'F' << 16 | 'l' << 8 | 's': - { - char *p = strrchr(line + 6, '/'); - Id did; - if (!p) - break; - *p++ = 0; - did = repodata_str2dir(data, line + 6, 1); - if (!did) - did = repodata_str2dir(data, "/", 1); - repodata_add_dirstr(data, s - pool->solvables, SOLVABLE_FILELIST, did, p); - break; - } - case 'O' << 16 | 'b' << 8 | 's': - s->obsoletes = adddep(repo, s->obsoletes, line + 6, 0); - break; - case 'C' << 16 | 'o' << 8 | 'n': - s->conflicts = adddep(repo, s->conflicts, line + 6, 0); - break; - case 'R' << 16 | 'e' << 8 | 'c': - s->recommends = adddep(repo, s->recommends, line + 6, 0); - break; - case 'S' << 16 | 'u' << 8 | 'p': - s->supplements = adddep(repo, s->supplements, line + 6, 0); - break; - case 'S' << 16 | 'u' << 8 | 'g': - s->suggests = adddep(repo, s->suggests, line + 6, 0); - break; - case 'E' << 16 | 'n' << 8 | 'h': - s->enhances = adddep(repo, s->enhances, line + 6, 0); - break; - case 'I' << 16 | 'p' << 8 | 'r': - { - Id id = line[6] == '/' ? pool_str2id(pool, line + 6, 1) : testcase_str2dep(pool, line + 6); - repodata_add_idarray(data, s - pool->solvables, SOLVABLE_PREREQ_IGNOREINST, id); - break; - } - default: - break; - } - } - if (s) - { - if (tagsversion == 2) - finish_v2_solvable(pool, data, s, filelist, nfilelist); - if (addselfprovides && s->name && s->arch != ARCH_SRC && s->arch != ARCH_NOSRC) - s->provides = repo_addid_dep(s->repo, s->provides, pool_rel2id(pool, s->name, s->evr, REL_EQ, 1), 0); - } - solv_free(line); - solv_free(filelist); - repodata_free_dircache(data); - if (!(flags & REPO_NO_INTERNALIZE)) - repodata_internalize(data); - return 0; -} - const char * testcase_getpoolflags(Pool *pool) { @@ -1987,6 +1370,24 @@ testcase_solverresult(Solver *solv, int resultflags) queue_free(&q); queue_free(&qf); } + if ((resultflags & TESTCASE_RESULT_USERINSTALLED) != 0) + { + Queue q; + solver_get_userinstalled(solv, &q, 0); + for (i = 0; i < q.count; i++) + { + s = pool_tmpjoin(pool, "userinstalled pkg ", testcase_solvid2str(pool, q.elements[i]), 0); + strqueue_push(&sq, s); + } + queue_empty(&q); + solver_get_userinstalled(solv, &q, GET_USERINSTALLED_NAMES | GET_USERINSTALLED_INVERTED); + for (i = 0; i < q.count; i++) + { + s = pool_tmpjoin(pool, "autoinst name ", pool_id2str(pool, q.elements[i]), 0); + strqueue_push(&sq, s); + } + queue_free(&q); + } if ((resultflags & TESTCASE_RESULT_ALTERNATIVES) != 0) { char *altprefix; @@ -2023,7 +1424,7 @@ testcase_solverresult(Solver *solv, int resultflags) if ((rtype & SOLVER_RULE_TYPEMASK) == SOLVER_RULE_JOB) { const char *js = testcase_job2str(pool, rq.elements[i + 2], rq.elements[i + 3]); - char *s = pool_tmpjoin(pool, altprefix, num, " job "); + char *s = pool_tmpjoin(pool, altprefix, num, "job "); s = pool_tmpappend(pool, s, js, 0); strqueue_push(&sq, s); } @@ -2033,6 +1434,13 @@ testcase_solverresult(Solver *solv, int resultflags) s = pool_tmpappend(pool, s, " requires ", testcase_dep2str(pool, rq.elements[i + 3])); strqueue_push(&sq, s); } + else if (rtype == SOLVER_RULE_UPDATE || rtype == SOLVER_RULE_FEATURE) + { + const char *js = testcase_solvid2str(pool, rq.elements[i + 1]); + char *s = pool_tmpjoin(pool, altprefix, num, "update "); + s = pool_tmpappend(pool, s, js, 0); + strqueue_push(&sq, s); + } } } for (i = 0; i < q.count; i++) @@ -2188,7 +1596,11 @@ testcase_write_mangled(Solver *solv, const char *dir, int resultflags, const cha if (!resultname) resultname = "solver.result"; +#ifdef _WIN32 + if (mkdir(dir) && errno != EEXIST) +#else if (mkdir(dir, 0777) && errno != EEXIST) +#endif return pool_error(solv->pool, 0, "testcase_write: could not create directory '%s'", dir); strqueue_init(&sq); FOR_REPOS(repoid, repo) @@ -2199,7 +1611,11 @@ testcase_write_mangled(Solver *solv, const char *dir, int resultflags, const cha sprintf(priobuf, "%d.%d", repo->priority, repo->subpriority); else sprintf(priobuf, "%d", repo->priority); +#if !defined(WITHOUT_COOKIEOPEN) && defined(ENABLE_ZLIB_COMPRESSION) out = pool_tmpjoin(pool, name, ".repo", ".gz"); +#else + out = pool_tmpjoin(pool, name, ".repo", 0); +#endif for (i = 0; out[i]; i++) if (out[i] == '/') out[i] = '_'; @@ -2235,7 +1651,7 @@ testcase_write_mangled(Solver *solv, const char *dir, int resultflags, const cha lowscore = pool->id2arch[i]; } } - cmd = pool_tmpjoin(pool, "system ", pool->lastarch ? pool_id2str(pool, arch) : "unset", 0); + cmd = pool_tmpjoin(pool, "system ", pool->lastarch ? pool_id2str(pool, arch) : "-", 0); for (i = 0; disttype2str[i].str != 0; i++) if (pool->disttype == disttype2str[i].type) break; @@ -2569,7 +1985,13 @@ testcase_read(Pool *pool, FILE *fp, const char *testcase, Queue *job, char **res return 0; } testcasedir = solv_strdup(testcase); - if ((s = strrchr(testcasedir, '/')) != 0) + s = strrchr(testcasedir, '/'); +#ifdef _WIN32 + buf = strrchr(testcasedir, '\\'); + if (!s || (buf && buf > s)) + s = buf; +#endif + if (s) s[1] = 0; else *testcasedir = 0; @@ -2717,7 +2139,7 @@ testcase_read(Pool *pool, FILE *fp, const char *testcase, Queue *job, char **res missing_features = 1; } } - if (strcmp(pieces[1], "unset") == 0) + if (strcmp(pieces[1], "unset") == 0 || strcmp(pieces[1], "-") == 0) pool_setarch(pool, 0); else if (pieces[1][0] == ':') pool_setarchpolicy(pool, pieces[1] + 1); diff --git a/ext/testcase.h b/ext/testcase.h index 387a506..997feaf 100644 --- a/ext/testcase.h +++ b/ext/testcase.h @@ -20,6 +20,7 @@ #define TESTCASE_RESULT_REASON (1 << 8) #define TESTCASE_RESULT_CLEANDEPS (1 << 9) #define TESTCASE_RESULT_JOBS (1 << 10) +#define TESTCASE_RESULT_USERINSTALLED (1 << 11) /* reuse solver hack, testsolv use only */ #define TESTCASE_RESULT_REUSE_SOLVER (1 << 31) diff --git a/libsolv.pc.in b/libsolv.pc.in index 40a8623..766146c 100644 --- a/libsolv.pc.in +++ b/libsolv.pc.in @@ -1,5 +1,5 @@ -libdir=@LIB_INSTALL_DIR@ -includedir=@INCLUDE_INSTALL_DIR@ +libdir=@CMAKE_INSTALL_FULL_LIBDIR@ +includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@ Name: libsolv Description: Library for solving packages diff --git a/libsolvext.pc.in b/libsolvext.pc.in index d48b6fa..d007846 100644 --- a/libsolvext.pc.in +++ b/libsolvext.pc.in @@ -1,5 +1,5 @@ -libdir=@LIB_INSTALL_DIR@ -includedir=@INCLUDE_INSTALL_DIR@ +libdir=@CMAKE_INSTALL_FULL_LIBDIR@ +includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@ Name: libsolvext Description: Library for reading repositories diff --git a/package/libsolv.changes b/package/libsolv.changes index 897cc04..6393734 100644 --- a/package/libsolv.changes +++ b/package/libsolv.changes @@ -1,4 +1,20 @@ ------------------------------------------------------------------- +Wed Jun 12 13:22:40 CEST 2019 - mls@suse.de + +- fix favorq leaking between solver runs if the solver is reused +- fix SOLVER_FLAG_FOCUS_BEST updateing packages without reason +- be more correct with multiversion packages that obsolete their + own name [bnc#1127155] +- allow building with swig-4.0.0 [bnc#1135749] +- bump version to 0.7.5 + +------------------------------------------------------------------- +Wed Apr 24 15:34:44 CEST 2019 - mls@suse.de + +- always prefer to stay with the same package name if there are + multiple alternatives [bnc#1131823] + +------------------------------------------------------------------- Fri Mar 29 15:58:54 CET 2019 - mls@suse.de - repo_add_rpmdb: do not copy bad solvables from the old solv file @@ -11,6 +27,7 @@ Wed Jan 30 15:51:36 CET 2019 - mls@suse.de - fixed a couple of null pointer derefs [bnc#1120629] [bnc#1120630] [bnc#1120631] + [CVE-2018-20532] [CVE-2018-20533] [CVE-2018-20534] - do favor evaluation before pruning allowing to (dis)favor specific package versions - no longer disable infarch rules when they don't conflict with diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index be487a7..f91c9c0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -31,10 +31,18 @@ SET (libsolv_HEADERS IF (ENABLE_CONDA) SET (libsolv_SRCS ${libsolv_SRCS} conda.c) + SET (libsolv_HEADERS ${libsolv_HEADERS} conda.h) ENDIF (ENABLE_CONDA) +IF (NOT MSVC) + SET (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC") +ENDIF (NOT MSVC) + +IF (WIN32) + INCLUDE (${PROJECT_SOURCE_DIR}/win32/CMakeLists.txt) + LIST (APPEND libsolv_SRCS ${WIN32_COMPAT_SOURCES}) +ENDIF (WIN32) -SET (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC") IF (HAVE_LINKER_VERSION_SCRIPT) SET (CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${LINK_FLAGS} -Wl,--version-script=${CMAKE_SOURCE_DIR}/src/libsolv.ver") ENDIF (HAVE_LINKER_VERSION_SCRIPT) @@ -47,14 +55,14 @@ ENDIF (DISABLE_SHARED) SET_TARGET_PROPERTIES(libsolv PROPERTIES OUTPUT_NAME "solv") SET_TARGET_PROPERTIES(libsolv PROPERTIES SOVERSION ${LIBSOLV_SOVERSION}) -SET_TARGET_PROPERTIES(libsolv PROPERTIES INSTALL_NAME_DIR ${LIB_INSTALL_DIR}) +SET_TARGET_PROPERTIES(libsolv PROPERTIES INSTALL_NAME_DIR ${CMAKE_INSTALL_LIBDIR}) -INSTALL (FILES ${libsolv_HEADERS} DESTINATION "${INCLUDE_INSTALL_DIR}/solv") -INSTALL (TARGETS libsolv LIBRARY DESTINATION ${LIB_INSTALL_DIR} ARCHIVE DESTINATION ${LIB_INSTALL_DIR} RUNTIME DESTINATION bin) +INSTALL (FILES ${libsolv_HEADERS} DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/solv") +INSTALL (TARGETS libsolv LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) IF (ENABLE_STATIC AND NOT DISABLE_SHARED) ADD_LIBRARY (libsolv_static STATIC ${libsolv_SRCS}) SET_TARGET_PROPERTIES(libsolv_static PROPERTIES OUTPUT_NAME "solv") SET_TARGET_PROPERTIES(libsolv_static PROPERTIES SOVERSION ${LIBSOLV_SOVERSION}) -INSTALL (TARGETS libsolv_static LIBRARY DESTINATION ${LIB_INSTALL_DIR} ARCHIVE DESTINATION ${LIB_INSTALL_DIR}) +INSTALL (TARGETS libsolv_static LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) ENDIF (ENABLE_STATIC AND NOT DISABLE_SHARED) diff --git a/src/chksum.c b/src/chksum.c index df46145..1f8ab47 100644 --- a/src/chksum.c +++ b/src/chksum.c @@ -19,6 +19,10 @@ #include "sha1.h" #include "sha2.h" +#ifdef _WIN32 + #include "strfncs.h" +#endif + struct s_Chksum { Id type; int done; diff --git a/src/cleandeps.c b/src/cleandeps.c index 8ef6fd3..aa83c10 100644 --- a/src/cleandeps.c +++ b/src/cleandeps.c @@ -836,7 +836,7 @@ solver_createcleandepsmap(Solver *solv, Map *cleandepsmap, int unneeded) if (MAPTST(&solv->multiversion, p)) break; if (p) - continue; + continue; /* found a multiversion package that will not obsolate anything */ } om.size = 0; diff --git a/src/conda.c b/src/conda.c index 5b134dd..7f2538a 100644 --- a/src/conda.c +++ b/src/conda.c @@ -24,6 +24,10 @@ #include "util.h" #include "conda.h" +#ifdef _WIN32 +#include "strfncs.h" +#endif + static const char * endseg(const char *seg, const char *end) { @@ -226,7 +230,7 @@ pool_evrcmp_conda(const Pool *pool, const char *evr1, const char *evr2, int mode } static int -regexmatch(const char *evr, const char *version, size_t versionlen) +regexmatch(const char *evr, const char *version, size_t versionlen, int icase) { regex_t reg; char *buf = solv_malloc(versionlen + 1); @@ -234,15 +238,19 @@ regexmatch(const char *evr, const char *version, size_t versionlen) memcpy(buf, version, versionlen); buf[versionlen] = 0; - if (regcomp(®, buf, REG_EXTENDED | REG_NOSUB)) - return 0; + if (regcomp(®, buf, REG_EXTENDED | REG_NOSUB | (icase ? REG_ICASE : 0))) + { + solv_free(buf); + return 0; + } r = regexec(®, evr, 0, NULL, 0); regfree(®); + solv_free(buf); return r == 0; } static int -globmatch(const char *evr, const char *version, size_t versionlen) +globmatch(const char *evr, const char *version, size_t versionlen, int icase) { regex_t reg; char *buf = solv_malloc(2 * versionlen + 3); @@ -259,10 +267,14 @@ globmatch(const char *evr, const char *version, size_t versionlen) } buf[j++] = '$'; buf[j] = 0; - if (regcomp(®, buf, REG_EXTENDED | REG_NOSUB)) - return 0; + if (regcomp(®, buf, REG_EXTENDED | REG_NOSUB | (icase ? REG_ICASE : 0))) + { + solv_free(buf); + return 0; + } r = regexec(®, evr, 0, NULL, 0); regfree(®); + solv_free(buf); return r == 0; } @@ -279,7 +291,7 @@ solvable_conda_matchversion_single(Solvable *s, const char *version, size_t vers return 1; /* matches every version */ evr = pool_id2str(s->repo->pool, s->evr); if (versionlen >= 2 && version[0] == '^' && version[versionlen - 1] == '$') - return regexmatch(evr, version, versionlen); + return regexmatch(evr, version, versionlen, 0); if (version[0] == '=' || version[0] == '<' || version[0] == '>' || version[0] == '!' || version[0] == '~') { int flags = 0; @@ -361,7 +373,7 @@ solvable_conda_matchversion_single(Solvable *s, const char *version, size_t vers if (version[i] != '*') break; if (i < versionlen) - return globmatch(evr, version, versionlen); + return globmatch(evr, version, versionlen, 1); } if (versionlen > 1 && version[versionlen - 1] == '*') @@ -430,6 +442,26 @@ solvable_conda_matchversion_rec(Solvable *s, const char **versionp, const char * } } +static int +solvable_conda_matchbuild(Solvable *s, const char *build, const char *buildend) +{ + const char *bp; + const char *flavor = solvable_lookup_str(s, SOLVABLE_BUILDFLAVOR); + + if (!flavor) + flavor = ""; + if (build == buildend) + return *flavor ? 0 : 1; + if (build + 1 == buildend && *build == '*') + return 1; + if (*build == '^' && buildend[-1] == '$') + return regexmatch(flavor, build, buildend - build, 0); + for (bp = build; bp < buildend; bp++) + if (*bp == '*') + return globmatch(flavor, build, buildend - build, 0); + return strncmp(flavor, build, buildend - build) == 0 && flavor[buildend - build] == 0 ? 1 : 0; +} + /* return true if solvable s matches the version */ /* see conda/models/match_spec.py */ int @@ -449,6 +481,200 @@ solvable_conda_matchversion(Solvable *s, const char *version) r = solvable_conda_matchversion_rec(s, &version, versionend); if (r != 1 || version != versionend) return 0; + if (build && !solvable_conda_matchbuild(s, build, build + strlen(build))) + return 0; return r; } +static Id +pool_addrelproviders_conda_slow(Pool *pool, const char *namestr, Id evr, Queue *plist, int mode) +{ + size_t namestrlen = strlen(namestr); + const char *evrstr = evr == 0 || evr == 1 ? 0 : pool_id2str(pool, evr); + Id p; + + FOR_POOL_SOLVABLES(p) + { + Solvable *s = pool->solvables + p; + if (!pool_installable(pool, s)) + continue; + if (mode == 1 && !globmatch(pool_id2str(pool, s->name), namestr, namestrlen, 1)) + continue; + if (mode == 2 && !regexmatch(pool_id2str(pool, s->name), namestr, namestrlen, 1)) + continue; + if (!evrstr || solvable_conda_matchversion(s, evrstr)) + queue_push(plist, p); + } + return 0; +} + +/* called from pool_addrelproviders, plist is an empty queue + * it can either return an offset into the whatprovides array + * or fill the plist queue and return zero */ +Id +pool_addrelproviders_conda(Pool *pool, Id name, Id evr, Queue *plist) +{ + const char *namestr = pool_id2str(pool, name), *np; + size_t l, nuc = 0; + Id wp, p, *pp; + + /* is this a regex? */ + if (*namestr && *namestr == '^') + { + l = strlen(namestr); + if (namestr[l - 1] == '$') + return pool_addrelproviders_conda_slow(pool, namestr, evr, plist, 2); + } + /* is this '*'? */ + if (*namestr && *namestr == '*' && namestr[1] == 0) + return pool_addrelproviders_conda_slow(pool, namestr, evr, plist, 0); + /* does the string contain '*' or uppercase? */ + for (np = namestr; *np; np++) + { + if (*np == '*') + return pool_addrelproviders_conda_slow(pool, namestr, evr, plist, 1); + else if (*np >= 'A' && *np <= 'Z') + nuc++; + } + if (nuc) + { + char *nbuf = solv_strdup(namestr), *nbufp; + for (nbufp = nbuf; *nbufp; nbufp++) + *nbufp = *nbufp >= 'A' && *nbufp <= 'Z' ? *nbufp + ('a' - 'A') : *nbufp; + name = pool_str2id(pool, nbuf, 0); + wp = name ? pool_whatprovides(pool, name) : 0; + solv_free(nbuf); + } + else + wp = pool_whatprovides(pool, name); + if (wp && evr && evr != 1) + { + const char *evrstr = pool_id2str(pool, evr); + pp = pool->whatprovidesdata + wp; + while ((p = *pp++) != 0) + { + if (solvable_conda_matchversion(pool->solvables + p, evrstr)) + queue_push(plist, p); + else + wp = 0; + } + } + return wp; +} + +/* create a CONDA_REL relation from a matchspec */ +Id +pool_conda_matchspec(Pool *pool, const char *name) +{ + const char *p2; + char *name2; + char *p, *pp; + char *build, *buildend, *version, *versionend; + Id nameid, evrid; + int haveglob = 0; + + /* ignore channel and namespace for now */ + if ((p2 = strrchr(name, ':'))) + name = p2 + 1; + name2 = solv_strdup(name); + /* find end of name */ + for (p = name2; *p && *p != ' ' && *p != '=' && *p != '<' && *p != '>' && *p != '!' && *p != '~'; p++) + { + if (*p >= 'A' && *p <= 'Z') + *(char *)p += 'a' - 'A'; /* lower case the name */ + else if (*p == '*') + haveglob = 1; + } + if (p == name2) + { + solv_free(name2); + return 0; /* error: empty package name */ + } + nameid = pool_strn2id(pool, name2, p - name2, 1); + while (*p == ' ') + p++; + if (!*p) + { + if (*name2 != '^' && !haveglob) + { + solv_free(name2); + return nameid; /* return a simple dependency if no glob/regex */ + } + evrid = pool_str2id(pool, "*", 1); + solv_free(name2); + return pool_rel2id(pool, nameid, evrid, REL_CONDA, 1); + } + /* have version */ + version = p; + versionend = p + strlen(p); + while (versionend > version && versionend[-1] == ' ') + versionend--; + build = buildend = 0; + /* split of build */ + p = versionend; + for (;;) + { + while (p > version && p[-1] != ' ' && p[-1] != '-' && p[-1] != '=' && p[-1] != ',' && p[-1] != '|' && p[-1] != '<' && p[-1] != '>' && p[-1] != '~') + p--; + if (p <= version + 1 || (p[-1] != ' ' && p[-1] != '=')) + break; /* no build */ + /* check char before delimiter */ + if (p[-2] == '=' || p[-2] == '!' || p[-2] == '|' || p[-2] == ',' || p[-2] == '<' || p[-2] == '>' || p[-2] == '~') + { + /* illegal combination */ + if (p[-1] == ' ') + { + p--; + continue; /* special case space: it may be in the build */ + } + break; /* no build */ + } + if (p < versionend) + { + build = p; + buildend = versionend; + versionend = p - 1; + } + break; + } + /* do weird version translation */ + if (versionend > version && version[0] == '=') + { + if (versionend - version >= 2 && version[1] == '=') + { + if (!build) + version += 2; + } + else if (build) + version += 1; + else + { + for (p = version + 1; p < versionend; p++) + if (*p == '=' || *p == ',' || *p == '|') + break; + if (p == versionend) + { + memmove(version, version + 1, versionend - version - 1); + versionend[-1] = '*'; + } + } + } +#if 0 + printf("version: >%.*s<\n", (int)(versionend - version), version); + if (build) printf("build: >%.*s<\n", (int)(buildend - build), build); +#endif + /* strip spaces from version */ + for (p = pp = version; pp < versionend; pp++) + if (*pp != ' ') + *p++ = *pp; + if (build) + { + *p++ = ' '; + memcpy(p, build, buildend - build); + p += buildend - build; + } + evrid = pool_strn2id(pool, version, p - version, 1); + solv_free(name2); + return pool_rel2id(pool, nameid, evrid, REL_CONDA, 1); +} + diff --git a/src/conda.h b/src/conda.h index 7233f17..3bcfa2d 100644 --- a/src/conda.h +++ b/src/conda.h @@ -15,6 +15,8 @@ int pool_evrcmp_conda(const Pool *pool, const char *evr1, const char *evr2, int mode); int solvable_conda_matchversion(Solvable *s, const char *version); +Id pool_addrelproviders_conda(Pool *pool, Id name, Id evr, Queue *plist); +Id pool_conda_matchspec(Pool *pool, const char *name); #endif /* LIBSOLV_CONDA_H */ diff --git a/src/knownid.h b/src/knownid.h index b5b41d6..9d7f157 100644 --- a/src/knownid.h +++ b/src/knownid.h @@ -251,12 +251,15 @@ KNOWNID(SIGNATURE_EXPIRES, "signature:expires"), KNOWNID(SIGNATURE_DATA, "signature:data"), /* 'content' of patch, usually list of modules */ -KNOWNID(UPDATE_MODULE, "update:module"), /* "name stream version context arch" */ -KNOWNID(UPDATE_MODULE_NAME, "update:module:name"), /* name */ -KNOWNID(UPDATE_MODULE_STREAM, "update:module:stream"), /* stream */ -KNOWNID(UPDATE_MODULE_VERSION, "update:module:version"), /* version */ -KNOWNID(UPDATE_MODULE_CONTEXT, "update:module:context"), /* context */ -KNOWNID(UPDATE_MODULE_ARCH, "update:module:arch"), /* architecture */ +KNOWNID(UPDATE_MODULE, "update:module"), /* "name stream version context arch" */ +KNOWNID(UPDATE_MODULE_NAME, "update:module:name"), /* name */ +KNOWNID(UPDATE_MODULE_STREAM, "update:module:stream"), /* stream */ +KNOWNID(UPDATE_MODULE_VERSION, "update:module:version"), /* version */ +KNOWNID(UPDATE_MODULE_CONTEXT, "update:module:context"), /* context */ +KNOWNID(UPDATE_MODULE_ARCH, "update:module:arch"), /* architecture */ + +KNOWNID(SOLVABLE_BUILDVERSION, "solvable:buildversion"), /* conda */ +KNOWNID(SOLVABLE_BUILDFLAVOR, "solvable:buildflavor"), /* conda */ KNOWNID(ID_NUM_INTERNAL, 0) diff --git a/src/libsolv.ver b/src/libsolv.ver index f0e86ff..eafe3e6 100644 --- a/src/libsolv.ver +++ b/src/libsolv.ver @@ -448,4 +448,6 @@ SOLV_1.2 { SOLV_1.3 { repodata_set_kv; + solv_setcloexec; + pool_conda_matchspec; } SOLV_1.2; diff --git a/src/policy.c b/src/policy.c index 5f61115..823a008 100644 --- a/src/policy.c +++ b/src/policy.c @@ -798,6 +798,8 @@ move_installed_to_front(Pool *pool, Queue *plist) Solvable *s; Id p, pp; + if (!pool->installed) + return; for (i = j = 0; i < plist->count; i++) { s = pool->solvables + plist->elements[i]; @@ -831,6 +833,26 @@ move_installed_to_front(Pool *pool, Queue *plist) } } +static int +pool_buildversioncmp(Pool *pool, Solvable *s1, Solvable *s2) +{ + const char *bv1 = solvable_lookup_str(s1, SOLVABLE_BUILDVERSION); + const char *bv2 = solvable_lookup_str(s2, SOLVABLE_BUILDVERSION); + if (!bv1 && !bv2) + return 0; + return pool_evrcmp_str(pool, bv1 ? bv1 : "" , bv2 ? bv2 : "", EVRCMP_COMPARE); +} + +static int +pool_buildflavorcmp(Pool *pool, Solvable *s1, Solvable *s2) +{ + const char *f1 = solvable_lookup_str(s1, SOLVABLE_BUILDFLAVOR); + const char *f2 = solvable_lookup_str(s2, SOLVABLE_BUILDFLAVOR); + if (!f1 && !f2) + return 0; + return pool_evrcmp_str(pool, f1 ? f1 : "" , f2 ? f2 : "", EVRCMP_COMPARE); +} + /* * prune_to_best_version * @@ -878,6 +900,10 @@ prune_to_best_version(Pool *pool, Queue *plist) if (r == 0 && has_package_link(pool, s)) r = pool_link_evrcmp(pool, best, s); #endif + if (r == 0 && pool->disttype == DISTTYPE_CONDA) + r = pool_buildversioncmp(pool, best, s); + if (r == 0 && pool->disttype == DISTTYPE_CONDA) + r = pool_buildflavorcmp(pool, best, s); if (r < 0) best = s; } @@ -893,8 +919,6 @@ prune_to_best_version(Pool *pool, Queue *plist) else prune_obsoleted(pool, plist); } - if (plist->count > 1 && pool->installed) - move_installed_to_front(pool, plist); } @@ -1321,6 +1345,7 @@ policy_filter_unwanted(Solver *solv, Queue *plist, int mode) #endif dislike_old_versions(pool, plist); sort_by_common_dep(pool, plist); + move_installed_to_front(pool, plist); if (solv->urpmreorder) urpm_reorder(solv, plist); prefer_suggested(solv, plist); @@ -1342,6 +1367,7 @@ pool_best_solvables(Pool *pool, Queue *plist, int flags) { dislike_old_versions(pool, plist); sort_by_common_dep(pool, plist); + move_installed_to_front(pool, plist); } } diff --git a/src/pool.c b/src/pool.c index 09354ef..a554453 100644 --- a/src/pool.c +++ b/src/pool.c @@ -1289,19 +1289,7 @@ pool_addrelproviders(Pool *pool, Id d) break; #ifdef ENABLE_CONDA case REL_CONDA: - wp = pool_whatprovides(pool, name); - if (evr) - { - const char *evrstr = pool_id2str(pool, evr); - pp = pool->whatprovidesdata + wp; - while ((p = *pp++) != 0) - { - if (solvable_conda_matchversion(pool->solvables + p, evrstr)) - queue_push(&plist, p); - else - wp = 0; - } - } + wp = pool_addrelproviders_conda(pool, name, evr, &plist); break; #endif default: @@ -1324,7 +1312,7 @@ pool_addrelproviders(Pool *pool, Id d) POOL_DEBUG(SOLV_DEBUG_STATS, "addrelproviders: what provides %s?\n", pool_dep2str(pool, name)); #endif pp = pool_whatprovides_ptr(pool, name); - if (!ISRELDEP(name) && name < pool->whatprovidesauxoff) + if (!ISRELDEP(name) && (Offset)name < pool->whatprovidesauxoff) ppaux = pool->whatprovidesaux[name] ? pool->whatprovidesauxdata + pool->whatprovidesaux[name] : 0; while (ISRELDEP(name)) { @@ -1938,7 +1926,7 @@ pool_set_whatprovides(Pool *pool, Id id, Id providers) else { pool->whatprovides[id] = providers; - if (id < pool->whatprovidesauxoff) + if ((Offset)id < pool->whatprovidesauxoff) pool->whatprovidesaux[id] = 0; /* sorry */ d = 1; } diff --git a/src/qsort_r.c b/src/qsort_r.c index d49049a..ffc09dc 100644 --- a/src/qsort_r.c +++ b/src/qsort_r.c @@ -37,7 +37,9 @@ #if defined(LIBC_SCCS) && !defined(lint) static char sccsid[] = "@(#)qsort.c 8.1 (Berkeley) 6/4/93"; #endif /* LIBC_SCCS and not lint */ +#ifndef _WIN32 #include +#endif /* $FreeBSD: src/lib/libc/stdlib/qsort.c,v 1.13.2.1.8.1 2010/12/21 17:10:29 kensmith Exp $ */ diff --git a/src/repo.c b/src/repo.c index d163f13..da40219 100644 --- a/src/repo.c +++ b/src/repo.c @@ -391,7 +391,7 @@ repo_addid_dep_hash(Repo *repo, Offset olddeps, Id id, Id marker, int size) if (repo->lastidhash_idarraysize != repo->idarraysize || (Hashval)size * 2 > repo->lastidhash_mask || repo->lastmarker != marker) { repo->lastmarkerpos = 0; - if (size * 2 > (Hashval)repo->lastidhash_mask) + if ((Hashval)size * 2 > repo->lastidhash_mask) { repo->lastidhash_mask = mkmask(size < REPO_ADDID_DEP_HASHMIN ? REPO_ADDID_DEP_HASHMIN : size); repo->lastidhash = solv_realloc2(repo->lastidhash, repo->lastidhash_mask + 1, sizeof(Id)); @@ -844,7 +844,7 @@ repo_search_md(Repo *repo, Id p, Id keyname, struct matchdata *md) case RPM_RPMDBID: if (repo->rpmdbid) { - kv.num = repo->rpmdbid[p - repo->start]; + kv.num = (unsigned int)repo->rpmdbid[p - repo->start]; kv.num2 = 0; repo_matchvalue(md, s, 0, repo_solvablekeys + (RPM_RPMDBID - SOLVABLE_NAME), &kv); } @@ -1097,7 +1097,7 @@ repo_lookup_num(Repo *repo, Id entry, Id keyname, unsigned long long notfound) if (keyname == RPM_RPMDBID) { if (repo->rpmdbid && entry >= repo->start && entry < repo->end) - return repo->rpmdbid[entry - repo->start]; + return (unsigned int)repo->rpmdbid[entry - repo->start]; return notfound; } } @@ -1250,7 +1250,10 @@ Id repo_lookup_type(Repo *repo, Id entry, Id keyname) { Id type; - Repodata *data = repo_lookup_repodata_opt(repo, entry, keyname); + Repodata *data; + if (keyname >= SOLVABLE_NAME && keyname <= RPM_RPMDBID) + return repo_solvablekeys[keyname - SOLVABLE_NAME].type; + data = repo_lookup_repodata_opt(repo, entry, keyname); if (data && (type = repodata_lookup_type(data, entry, keyname)) != 0 && type != REPOKEY_TYPE_DELETED) return type; return 0; @@ -1363,7 +1366,7 @@ repo_set_num(Repo *repo, Id p, Id keyname, unsigned long long num) { if (!repo->rpmdbid) repo->rpmdbid = repo_sidedata_create(repo, sizeof(Id)); - repo->rpmdbid[p - repo->start] = num; + repo->rpmdbid[p - repo->start] = (Id)num; return; } } diff --git a/src/repo_solv.c b/src/repo_solv.c index be33967..761d06e 100644 --- a/src/repo_solv.c +++ b/src/repo_solv.c @@ -1220,7 +1220,7 @@ printf("=> %s %s %p\n", pool_id2str(pool, keys[key].name), pool_id2str(pool, key default: if (id == RPM_RPMDBID && s && keys[key].type == REPOKEY_TYPE_NUM) { - dp = data_read_id_max(dp, &id, 0, 0, &data); + dp = data_read_id(dp, &id); if (!repo->rpmdbid) repo->rpmdbid = repo_sidedata_create(repo, sizeof(Id)); repo->rpmdbid[(s - pool->solvables) - repo->start] = id; diff --git a/src/repodata.c b/src/repodata.c index ad5aaea..0c7a51f 100644 --- a/src/repodata.c +++ b/src/repodata.c @@ -34,6 +34,10 @@ #include "repopack.h" #include "repopage.h" +#ifdef _WIN32 + #include "strfncs.h" +#endif + #define REPODATA_BLOCK 255 static unsigned char *data_skip_key(Repodata *data, unsigned char *dp, Repokey *key); @@ -791,14 +795,28 @@ repodata_lookup_idarray(Repodata *data, Id solvid, Id keyname, Queue *q) queue_empty(q); dp = find_key_data(data, solvid, keyname, &key); - if (!dp || key->type != REPOKEY_TYPE_IDARRAY) + if (!dp) return 0; - for (;;) + switch (key->type) { - dp = data_read_ideof(dp, &id, &eof); + case REPOKEY_TYPE_CONSTANTID: + queue_push(q, key->size); + break; + case REPOKEY_TYPE_ID: + dp = data_read_id(dp, &id); queue_push(q, id); - if (eof) - break; + break; + case REPOKEY_TYPE_IDARRAY: + for (;;) + { + dp = data_read_ideof(dp, &id, &eof); + queue_push(q, id); + if (eof) + break; + } + break; + default: + return 0; } return 1; } @@ -1124,7 +1142,7 @@ repodata_search_array(Repodata *data, Id solvid, Id keyname, int flags, Repokey if (!dp || kv->entry != -1) return 0; - while (++kv->entry < kv->num) + while (++kv->entry < (int)kv->num) { if (kv->entry) dp = data_skip_schema(data, dp, schema); @@ -3142,7 +3160,7 @@ compact_attrdata(Repodata *data, int entry, int nentry) break; case REPOKEY_TYPE_DIRSTRARRAY: for (v = attrs[1]; data->attriddata[v] ; v += 2) - if (data->attriddata[v + 1] < attrdatastart) + if ((unsigned int)data->attriddata[v + 1] < attrdatastart) attrdatastart = data->attriddata[v + 1]; /* FALLTHROUGH */ case REPOKEY_TYPE_IDARRAY: diff --git a/src/repopage.c b/src/repopage.c index 85d53eb..9e9694f 100644 --- a/src/repopage.c +++ b/src/repopage.c @@ -30,6 +30,12 @@ #include #include +#ifdef _WIN32 + #include + #include + #include +#endif + #include "repo.h" #include "repopage.h" @@ -704,11 +710,22 @@ repopagestore_load_page_range(Repopagestore *store, unsigned int pstart, unsigne #ifdef DEBUG_PAGING fprintf(stderr, "PAGEIN: %d to %d", pnum, i); #endif +#ifndef _WIN32 if (pread(store->pagefd, compressed ? buf : dest, in_len, store->file_offset + p->page_offset) != in_len) { perror("mapping pread"); return 0; } +#else + DWORD read_len; + OVERLAPPED ovlp = {0}; + ovlp.Offset = store->file_offset + p->page_offset; + if (!ReadFile((HANDLE) _get_osfhandle(store->pagefd), compressed ? buf : dest, in_len, &read_len, &ovlp) || read_len != in_len) + { + perror("mapping ReadFile"); + return 0; + } +#endif if (compressed) { unsigned int out_len; @@ -785,7 +802,7 @@ repopagestore_read_or_setup_pages(Repopagestore *store, FILE *fp, unsigned int p if (store->pagefd == -1) can_seek = 0; else - fcntl(store->pagefd, F_SETFD, FD_CLOEXEC); + solv_setcloexec(store->pagefd, 1); #ifdef DEBUG_PAGING fprintf(stderr, "can %sseek\n", can_seek ? "" : "NOT "); diff --git a/src/rules.c b/src/rules.c index b6cd582..3aef6ee 100644 --- a/src/rules.c +++ b/src/rules.c @@ -1328,6 +1328,31 @@ solver_addfeaturerule(Solver *solv, Solvable *s) } } +/* check if multiversion solvable s2 has an obsoletes for installed solvable s */ +static int +is_multiversion_obsoleteed(Pool *pool, Solvable *s, Solvable *s2) +{ + Id *wp, obs, *obsp; + + if (pool->obsoleteusescolors && !pool_colormatch(pool, s, s2)) + return 0; + obsp = s2->repo->idarraydata + s2->obsoletes; + if (!pool->obsoleteusesprovides) + { + while ((obs = *obsp++) != 0) + if (pool_match_nevr(pool, s, obs)) + return 1; + } + else + { + while ((obs = *obsp++) != 0) + for (wp = pool_whatprovides_ptr(pool, obs); *wp; wp++) + if (pool->solvables + *wp == s) + return 1; + } + return 0; +} + /*------------------------------------------------------------------- * * add rule for update @@ -1389,9 +1414,8 @@ solver_addupdaterule(Solver *solv, Solvable *s) if (MAPTST(&solv->multiversion, qs.elements[i])) { Solvable *ps = pool->solvables + qs.elements[i]; - /* if keepexplicitobsoletes is set and the name is different, - * we assume that there is an obsoletes. XXX: not 100% correct */ - if (solv->keepexplicitobsoletes && ps->name != s->name) + /* check if there is an explicit obsoletes */ + if (solv->keepexplicitobsoletes && ps->obsoletes && is_multiversion_obsoleteed(pool, s, ps)) { qs.elements[j++] = qs.elements[i]; continue; @@ -2189,7 +2213,7 @@ jobtodisablelist(Solver *solv, Id how, Id what, Queue *q) if (pool->solvables[p].repo == installed) return; if (solv->multiversion.size && MAPTST(&solv->multiversion, p) && !solv->keepexplicitobsoletes) - return; + return; /* will not obsolete anything, so just return */ } omap.size = 0; qstart = q->count; @@ -2273,6 +2297,34 @@ jobtodisablelist(Solver *solv, Id how, Id what, Queue *q) #endif } return; + + case SOLVER_LOCK: + if (!installed) + break; + qstart = q->count; + if (select == SOLVER_SOLVABLE_ALL || (select == SOLVER_SOLVABLE_REPO && what == installed->repoid)) + { + FOR_REPO_SOLVABLES(installed, p, s) + { + for (i = qstart; i < q->count; i += 2) + if (q->elements[i] == DISABLE_DUP && q->elements[i + 1] == pool->solvables[p].name) + break; + if (i == q->count) + queue_push2(q, DISABLE_DUP, pool->solvables[p].name); + } + } + FOR_JOB_SELECT(p, pp, select, what) + { + if (pool->solvables[p].repo != installed) + continue; + for (i = qstart; i < q->count; i += 2) + if (q->elements[i] == DISABLE_DUP && q->elements[i + 1] == pool->solvables[p].name) + break; + if (i == q->count) + queue_push2(q, DISABLE_DUP, pool->solvables[p].name); + } + break; + default: return; } @@ -3260,8 +3312,19 @@ prune_to_dup_packages(Solver *solv, Id p, Queue *q) queue_truncate(q, j); } +static void +prune_best_update(Solver *solv, Id p, Queue *q) +{ + if (solv->update_targets && solv->update_targets->elements[p - solv->installed->start]) + prune_to_update_targets(solv, solv->update_targets->elements + solv->update_targets->elements[p - solv->installed->start], q); + if (solv->dupinvolvedmap.size && MAPTST(&solv->dupinvolvedmap, p)) + prune_to_dup_packages(solv, p, q); + /* select best packages, just look at prio and version */ + policy_filter_unwanted(solv, q, POLICY_MODE_RECOMMEND); +} + void -solver_addbestrules(Solver *solv, int havebestinstalljobs) +solver_addbestrules(Solver *solv, int havebestinstalljobs, int haslockjob) { Pool *pool = solv->pool; Id p; @@ -3271,12 +3334,26 @@ solver_addbestrules(Solver *solv, int havebestinstalljobs) Rule *r; Queue infoq; int i, oldcnt; + Map *lockedmap = 0; solv->bestrules = solv->nrules; queue_init(&q); queue_init(&q2); queue_init(&infoq); + if (haslockjob) + { + int i; + lockedmap = solv_calloc(1, sizeof(Map)); + map_init(lockedmap, pool->nsolvables); + for (i = 0, r = solv->rules + solv->jobrules; i < solv->ruletojob.count; i++, r++) + { + if (r->w2 || (solv->job.elements[solv->ruletojob.elements[i]] & SOLVER_JOBMASK) != SOLVER_LOCK) + continue; + p = r->p > 0 ? r->p : -r->p; + MAPSET(lockedmap, p); + } + } if (havebestinstalljobs) { for (i = 0; i < solv->job.count; i += 2) @@ -3284,7 +3361,7 @@ solver_addbestrules(Solver *solv, int havebestinstalljobs) Id how = solv->job.elements[i]; if ((how & (SOLVER_JOBMASK | SOLVER_FORCEBEST)) == (SOLVER_INSTALL | SOLVER_FORCEBEST)) { - int j; + int j, k; Id p2, pp2; for (j = 0; j < solv->ruletojob.count; j++) { @@ -3307,6 +3384,25 @@ solver_addbestrules(Solver *solv, int havebestinstalljobs) policy_filter_unwanted(solv, &q, POLICY_MODE_RECOMMEND); if (q.count == oldcnt) continue; /* nothing filtered */ + if (lockedmap) + { + FOR_RULELITERALS(p2, pp2, r) + { + if (p2 <= 0) + continue; + if (installed && pool->solvables[p2].repo == installed) + { + if (MAPTST(lockedmap, p2)) + queue_pushunique(&q, p2); /* we always want that package */ + } + else if (MAPTST(lockedmap, p2)) + continue; + queue_push(&q2, p2); + } + policy_filter_unwanted(solv, &q2, POLICY_MODE_RECOMMEND); + for (k = 0; k < q2.count; k++) + queue_pushunique(&q, q2.elements[k]); + } if (q2.count) queue_insertn(&q, 0, q2.count, q2.elements); p2 = queue_shift(&q); @@ -3361,14 +3457,38 @@ solver_addbestrules(Solver *solv, int havebestinstalljobs) if (p2 > 0) queue_push(&q, p2); } - if (solv->update_targets && solv->update_targets->elements[p - installed->start]) - prune_to_update_targets(solv, solv->update_targets->elements + solv->update_targets->elements[p - installed->start], &q); - if (solv->dupinvolvedmap.size && MAPTST(&solv->dupinvolvedmap, p)) - prune_to_dup_packages(solv, p, &q); - /* select best packages, just look at prio and version */ - policy_filter_unwanted(solv, &q, POLICY_MODE_RECOMMEND); + if (lockedmap) + { + queue_empty(&q2); + queue_insertn(&q2, 0, q.count, q.elements); + } + prune_best_update(solv, p, &q); if (!q.count) continue; /* orphaned */ + if (lockedmap) + { + int j; + /* always ok to keep installed locked packages */ + if (MAPTST(lockedmap, p)) + queue_pushunique(&q2, p); + for (j = 0; j < q2.count; j++) + { + Id p2 = q2.elements[j]; + if (pool->solvables[p2].repo == installed && MAPTST(lockedmap, p2)) + queue_pushunique(&q, p2); + } + /* filter out locked packages */ + for (i = j = 0; j < q2.count; j++) + { + Id p2 = q2.elements[j]; + if (pool->solvables[p2].repo == installed || !MAPTST(lockedmap, p2)) + q2.elements[i++] = p2; + } + queue_truncate(&q2, i); + prune_best_update(solv, p, &q2); + for (j = 0; j < q2.count; j++) + queue_pushunique(&q, q2.elements[j]); + } if (solv->bestobeypolicy) { /* also filter the best of the feature rule packages and add them */ @@ -3380,13 +3500,20 @@ solver_addbestrules(Solver *solv, int havebestinstalljobs) FOR_RULELITERALS(p2, pp2, r) if (p2 > 0) queue_push(&q2, p2); - if (solv->update_targets && solv->update_targets->elements[p - installed->start]) - prune_to_update_targets(solv, solv->update_targets->elements + solv->update_targets->elements[p - installed->start], &q2); - if (solv->dupinvolvedmap.size && MAPTST(&solv->dupinvolvedmap, p)) - prune_to_dup_packages(solv, p, &q2); - policy_filter_unwanted(solv, &q2, POLICY_MODE_RECOMMEND); + prune_best_update(solv, p, &q2); for (j = 0; j < q2.count; j++) queue_pushunique(&q, q2.elements[j]); + if (lockedmap) + { + queue_empty(&q2); + FOR_RULELITERALS(p2, pp2, r) + if (p2 > 0) + if (pool->solvables[p2].repo == installed || !MAPTST(lockedmap, p2)) + queue_push(&q2, p2); + prune_best_update(solv, p, &q2); + for (j = 0; j < q2.count; j++) + queue_pushunique(&q, q2.elements[j]); + } } } if (solv->allowuninstall || solv->allowuninstall_all || (solv->allowuninstallmap.size && MAPTST(&solv->allowuninstallmap, p - installed->start))) @@ -3427,6 +3554,11 @@ solver_addbestrules(Solver *solv, int havebestinstalljobs) queue_free(&q); queue_free(&q2); queue_free(&infoq); + if (lockedmap) + { + map_free(lockedmap); + solv_free(lockedmap); + } } diff --git a/src/rules.h b/src/rules.h index 1db0551..6b8511f 100644 --- a/src/rules.h +++ b/src/rules.h @@ -129,7 +129,7 @@ extern void solver_addchoicerules(struct s_Solver *solv); extern void solver_disablechoicerules(struct s_Solver *solv, Rule *r); /* best rules */ -extern void solver_addbestrules(struct s_Solver *solv, int havebestinstalljobs); +extern void solver_addbestrules(struct s_Solver *solv, int havebestinstalljobs, int haslockjob); /* yumobs rules */ extern void solver_addyumobsrules(struct s_Solver *solv); diff --git a/src/selection.c b/src/selection.c index a160122..e58d731 100644 --- a/src/selection.c +++ b/src/selection.c @@ -21,7 +21,13 @@ #include "selection.h" #include "solver.h" #include "evr.h" +#ifdef ENABLE_CONDA +#include "conda.h" +#endif +#ifdef _WIN32 +#include "strfncs.h" +#endif static int str2archid(Pool *pool, const char *arch) @@ -1149,6 +1155,19 @@ selection_canon(Pool *pool, Queue *selection, const char *name, int flags) flags |= SELECTION_NAME; flags &= ~SELECTION_PROVIDES; +#ifdef ENABLE_CONDA + if (pool->disttype == DISTTYPE_CONDA) + { + Id *wp, id = pool_conda_matchspec(pool, name); + if (!id) + return 0; + wp = pool_whatprovides_ptr(pool, id); /* check if there is a match */ + if (!wp || !*wp) + return 0; + queue_push2(selection, SOLVER_SOLVABLE_PROVIDES, id); + return SELECTION_CANON; + } +#endif if (pool->disttype == DISTTYPE_DEB) { if ((r = strchr(name, '_')) == 0) @@ -1552,7 +1571,7 @@ selection_make_matchdeps_common(Pool *pool, Queue *selection, const char *name, revr = pool_str2id(pool, r, 1); ret |= SELECTION_REL; } - if ((flags & SELECTION_GLOB) != 0 && !strpbrk(rname, "[*?") != 0) + if ((flags & SELECTION_GLOB) != 0 && strpbrk(rname, "[*?") == 0) flags &= ~SELECTION_GLOB; if ((flags & SELECTION_GLOB) == 0 && (flags & SELECTION_NOCASE) == 0 && (flags & SELECTION_MATCH_DEPSTR) == 0) diff --git a/src/solvable.c b/src/solvable.c index 331f290..d3d2d31 100644 --- a/src/solvable.c +++ b/src/solvable.c @@ -32,9 +32,9 @@ pool_solvable2str(Pool *pool, Solvable *s) int nl, el, al; char *p; n = pool_id2str(pool, s->name); - e = pool_id2str(pool, s->evr); + e = s->evr ? pool_id2str(pool, s->evr) : ""; /* XXX: may want to skip the epoch here */ - a = pool_id2str(pool, s->arch); + a = s->arch ? pool_id2str(pool, s->arch) : ""; nl = strlen(n); el = strlen(e); al = strlen(a); @@ -58,6 +58,16 @@ pool_solvable2str(Pool *pool, Solvable *s) p[nl + el] = pool->disttype == DISTTYPE_HAIKU ? '-' : '.'; strcpy(p + nl + el + 1, a); } + if (pool->disttype == DISTTYPE_CONDA && solvable_lookup_type(s, SOLVABLE_BUILDFLAVOR)) + { + Queue flavorq; + int i; + queue_init(&flavorq); + solvable_lookup_idarray(s, SOLVABLE_BUILDFLAVOR, &flavorq); + for (i = 0; i < flavorq.count; i++) + p = pool_tmpappend(pool, p, "-", pool_id2str(pool, flavorq.elements[i])); + queue_free(&flavorq); + } return p; } @@ -110,10 +120,15 @@ solvable_lookup_str_joinarray(Solvable *s, Id keyname, const char *joinstr) if (solvable_lookup_idarray(s, keyname, &q) && q.count) { Pool *pool = s->repo->pool; - int i; - str = pool_tmpjoin(pool, pool_id2str(pool, q.elements[0]), 0, 0); - for (i = 1; i < q.count; i++) - str = pool_tmpappend(pool, str, joinstr, pool_id2str(pool, q.elements[i])); + if (q.count == 1) + str = (char *)pool_id2str(pool, q.elements[0]); + else + { + int i; + str = pool_tmpjoin(pool, pool_id2str(pool, q.elements[0]), 0, 0); + for (i = 1; i < q.count; i++) + str = pool_tmpappend(pool, str, joinstr, pool_id2str(pool, q.elements[i])); + } } queue_free(&q); return str; @@ -126,7 +141,7 @@ solvable_lookup_str(Solvable *s, Id keyname) if (!s->repo) return 0; str = repo_lookup_str(s->repo, s - s->repo->pool->solvables, keyname); - if (!str && (keyname == SOLVABLE_LICENSE || keyname == SOLVABLE_GROUP)) + if (!str && (keyname == SOLVABLE_LICENSE || keyname == SOLVABLE_GROUP || keyname == SOLVABLE_BUILDFLAVOR)) str = solvable_lookup_str_joinarray(s, keyname, ", "); return str; } @@ -238,14 +253,14 @@ solvable_lookup_str_poollang(Solvable *s, Id keyname) const char * solvable_lookup_str_lang(Solvable *s, Id keyname, const char *lang, int usebase) { - if (s->repo) - { - Id id = pool_id2langid(s->repo->pool, keyname, lang, 0); - if (id) - return solvable_lookup_str_base(s, id, keyname, usebase); - if (!usebase) - return 0; - } + Id id; + if (!s->repo) + return 0; + id = pool_id2langid(s->repo->pool, keyname, lang, 0); + if (id) + return solvable_lookup_str_base(s, id, keyname, usebase); + if (!usebase) + return 0; return solvable_lookup_str(s, keyname); } @@ -278,25 +293,27 @@ solvable_lookup_void(Solvable *s, Id keyname) int solvable_lookup_bool(Solvable *s, Id keyname) { + Id type; if (!s->repo) return 0; /* historic nonsense: there are two ways of storing a bool, as num == 1 or void. test both. */ - if (repo_lookup_type(s->repo, s - s->repo->pool->solvables, keyname) == REPOKEY_TYPE_VOID) + type = repo_lookup_type(s->repo, s - s->repo->pool->solvables, keyname); + if (type == REPOKEY_TYPE_VOID) return 1; - return repo_lookup_num(s->repo, s - s->repo->pool->solvables, keyname, 0) == 1; + if (type == REPOKEY_TYPE_NUM || type == REPOKEY_TYPE_CONSTANT) + return repo_lookup_num(s->repo, s - s->repo->pool->solvables, keyname, 0) == 1; + return 0; } const unsigned char * solvable_lookup_bin_checksum(Solvable *s, Id keyname, Id *typep) { - Repo *repo = s->repo; - - if (!repo) + if (!s->repo) { *typep = 0; return 0; } - return repo_lookup_bin_checksum(repo, s - repo->pool->solvables, keyname, typep); + return repo_lookup_bin_checksum(s->repo, s - s->repo->pool->solvables, keyname, typep); } const char * @@ -456,7 +473,7 @@ pool_create_state_maps(Pool *pool, Queue *installed, Map *installedmap, Map *con int solvable_identical(Solvable *s1, Solvable *s2) { - unsigned int bt1, bt2; + unsigned long long bt1, bt2; Id rq1, rq2; Id *reqp; if (s1->name != s2->name) @@ -504,6 +521,19 @@ solvable_identical(Solvable *s1, Solvable *s2) if (rq1 != rq2) return 0; } + if (s1->repo && s1->repo->pool->disttype == DISTTYPE_CONDA) + { + /* check buildflavor and buildversion */ + const char *str1, *str2; + str1 = solvable_lookup_str(s1, SOLVABLE_BUILDFLAVOR); + str2 = solvable_lookup_str(s2, SOLVABLE_BUILDFLAVOR); + if (str1 != str2 && (!str1 || !str2 || strcmp(str1, str2) != 0)) + return 0; + str1 = solvable_lookup_str(s1, SOLVABLE_BUILDVERSION); + str2 = solvable_lookup_str(s2, SOLVABLE_BUILDVERSION); + if (str1 != str2 && (!str1 || !str2 || strcmp(str1, str2) != 0)) + return 0; + } return 1; } diff --git a/src/solver.c b/src/solver.c index b335e6a..e7a9dc0 100644 --- a/src/solver.c +++ b/src/solver.c @@ -1382,7 +1382,6 @@ solver_free(Solver *solv) queuep_free(&solv->recommendscplxq); queuep_free(&solv->suggestscplxq); queuep_free(&solv->brokenorphanrules); - queuep_free(&solv->favorq); queuep_free(&solv->recommendsruleq); map_free(&solv->recommendsmap); @@ -1914,6 +1913,8 @@ resolve_dependencies(Solver *solv, int level, int disablerules, Queue *dq) } if (i == solv->nrules) i = 1; + if (solv->focus_best && solv->do_extra_reordering && i >= solv->featurerules) + continue; r = solv->rules + i; if (r->d < 0) /* ignore disabled rules */ continue; @@ -3281,36 +3282,44 @@ add_complex_jobrules(Solver *solv, Id dep, int flags, int jobidx, int weak) } #endif -/* sort by package id, last entry wins */ -static int -setup_favormap_cmp(const void *ap, const void *bp, void *dp) -{ - const Id *a = ap, *b = bp; - if ((*a - *b) != 0) - return *a - *b; - return (b[1] < 0 ? -b[1] : b[1]) - (a[1] < 0 ? -a[1] : a[1]); -} - static void setup_favormap(Solver *solv) { - Queue *q = solv->favorq; + Queue *job = &solv->job; Pool *pool = solv->pool; - int i; - Id oldp = 0; - if (q->count > 2) - solv_sort(q->elements, q->count / 2, 2 * sizeof(Id), setup_favormap_cmp, solv); + int i, idx; + Id p, pp, how, what, select; + + solv_free(solv->favormap); solv->favormap = solv_calloc(pool->nsolvables, sizeof(Id)); - solv->havedisfavored = 0; - for (i = 0; i < q->count; i += 2) + for (i = 0; i < job->count; i += 2) { - Id p = q->elements[i]; - if (p == oldp) + how = job->elements[i]; + if ((how & SOLVER_JOBMASK) != SOLVER_FAVOR && (how & SOLVER_JOBMASK) != SOLVER_DISFAVOR) continue; - oldp = p; - solv->favormap[p] = q->elements[i + 1]; - if (q->elements[i + 1] < 0) - solv->havedisfavored = 1; + what = job->elements[i + 1]; + select = how & SOLVER_SELECTMASK; + idx = (how & SOLVER_JOBMASK) == SOLVER_FAVOR ? i + 1 : -(i + 1); + if (select == SOLVER_SOLVABLE_REPO) + { + Repo *repo = pool_id2repo(pool, what); + if (repo) + { + Solvable *s; + FOR_REPO_SOLVABLES(repo, p, s) + { + solv->favormap[p] = idx; + if (idx < 0) + solv->havedisfavored = 1; + } + } + } + FOR_JOB_SELECT(p, pp, select, what) + { + solv->favormap[p] = idx; + if (idx < 0) + solv->havedisfavored = 1; + } } } @@ -3336,6 +3345,8 @@ solver_solve(Solver *solv, Queue *job) int now, solve_start; int needduprules = 0; int hasbestinstalljob = 0; + int hasfavorjob = 0; + int haslockjob = 0; solve_start = solv_timems(0); @@ -3343,6 +3354,7 @@ solver_solve(Solver *solv, Queue *job) POOL_DEBUG(SOLV_DEBUG_STATS, "solver started\n"); POOL_DEBUG(SOLV_DEBUG_STATS, "dosplitprovides=%d, noupdateprovide=%d, noinfarchcheck=%d\n", solv->dosplitprovides, solv->noupdateprovide, solv->noinfarchcheck); POOL_DEBUG(SOLV_DEBUG_STATS, "allowuninstall=%d, allowdowngrade=%d, allownamechange=%d, allowarchchange=%d, allowvendorchange=%d\n", solv->allowuninstall, solv->allowdowngrade, solv->allownamechange, solv->allowarchchange, solv->allowvendorchange); + POOL_DEBUG(SOLV_DEBUG_STATS, "dupallowdowngrade=%d, dupallownamechange=%d, dupallowarchchange=%d, dupallowvendorchange=%d\n", solv->dup_allowdowngrade, solv->dup_allownamechange, solv->dup_allowarchchange, solv->dup_allowvendorchange); POOL_DEBUG(SOLV_DEBUG_STATS, "promoteepoch=%d, forbidselfconflicts=%d\n", pool->promoteepoch, pool->forbidselfconflicts); POOL_DEBUG(SOLV_DEBUG_STATS, "obsoleteusesprovides=%d, implicitobsoleteusesprovides=%d, obsoleteusescolors=%d, implicitobsoleteusescolors=%d\n", pool->obsoleteusesprovides, pool->implicitobsoleteusesprovides, pool->obsoleteusescolors, pool->implicitobsoleteusescolors); POOL_DEBUG(SOLV_DEBUG_STATS, "dontinstallrecommended=%d, addalreadyrecommended=%d onlynamespacerecommended=%d\n", solv->dontinstallrecommended, solv->addalreadyrecommended, solv->only_namespace_recommended); @@ -3914,6 +3926,8 @@ solver_solve(Solver *solv, Queue *job) } FOR_JOB_SELECT(p, pp, select, what) solver_addjobrule(solv, installed && pool->solvables[p].repo == installed ? p : -p, 0, 0, i, weak); + if (solv->nrules != oldnrules) + haslockjob = 1; break; case SOLVER_DISTUPGRADE: POOL_DEBUG(SOLV_DEBUG_JOB, "job: distupgrade %s\n", solver_select2str(pool, select, what)); @@ -3930,15 +3944,7 @@ solver_solve(Solver *solv, Queue *job) case SOLVER_FAVOR: case SOLVER_DISFAVOR: POOL_DEBUG(SOLV_DEBUG_JOB, "job: %s %s\n", (how & SOLVER_JOBMASK) == SOLVER_FAVOR ? "favor" : "disfavor", solver_select2str(pool, select, what)); - FOR_JOB_SELECT(p, pp, select, what) - { - if (!solv->favorq) - { - solv->favorq = solv_calloc(1, sizeof(Queue)); - queue_init(solv->favorq); - } - queue_push2(solv->favorq, p, (how & SOLVER_JOBMASK) == SOLVER_FAVOR ? i + 1 : -(i + 1)); - } + hasfavorjob = 1; break; default: POOL_DEBUG(SOLV_DEBUG_JOB, "job: unknown job\n"); @@ -3960,8 +3966,8 @@ solver_solve(Solver *solv, Queue *job) assert(solv->ruletojob.count == solv->nrules - solv->jobrules); solv->jobrules_end = solv->nrules; - /* sort favorq and transform it into two maps */ - if (solv->favorq) + /* create favormap if we have favor jobs */ + if (hasfavorjob) setup_favormap(solv); /* now create infarch and dup rules */ @@ -3981,7 +3987,7 @@ solver_solve(Solver *solv, Queue *job) #endif if (solv->bestupdatemap_all || solv->bestupdatemap.size || hasbestinstalljob) - solver_addbestrules(solv, hasbestinstalljob); + solver_addbestrules(solv, hasbestinstalljob, haslockjob); else solv->bestrules = solv->bestrules_end = solv->bestrules_up = solv->nrules; diff --git a/src/solver.h b/src/solver.h index 93baa34..daf4f63 100644 --- a/src/solver.h +++ b/src/solver.h @@ -205,7 +205,6 @@ struct s_Solver { Map allowuninstallmap; /* ok to uninstall those */ int allowuninstall_all; - Queue *favorq; Id *favormap; /* favor job index, > 0: favored, < 0: disfavored */ int havedisfavored; /* do we have disfavored packages? */ diff --git a/src/util.c b/src/util.c index 65c8629..72426e0 100644 --- a/src/util.c +++ b/src/util.c @@ -11,7 +11,13 @@ #include #include #include -#include +#include +#ifdef _WIN32 + #include + #include +#else + #include +#endif #include "util.h" @@ -123,6 +129,9 @@ solv_strdup(const char *s) unsigned int solv_timems(unsigned int subtract) { +#ifdef _WIN32 + return GetTickCount() - subtract; +#else struct timeval tv; unsigned int r; @@ -132,6 +141,17 @@ solv_timems(unsigned int subtract) r += ((unsigned int)tv.tv_sec & 0xffff) * 1000; r += (unsigned int)tv.tv_usec / 1000; return r - subtract; +#endif +} + +int +solv_setcloexec(int fd, int state) +{ + #ifdef _WIN32 + return SetHandleInformation((HANDLE) _get_osfhandle(fd), HANDLE_FLAG_INHERIT, state ? 0 : HANDLE_FLAG_INHERIT); + #else + return fcntl(fd, F_SETFD, state ? FD_CLOEXEC : 0) == 0; + #endif } /* bsd's qsort_r has different arguments, so we define our diff --git a/src/util.h b/src/util.h index 5f7a93a..1dc5f32 100644 --- a/src/util.h +++ b/src/util.h @@ -34,6 +34,7 @@ extern void *solv_free(void *); extern char *solv_strdup(const char *); extern void solv_oom(size_t, size_t); extern unsigned int solv_timems(unsigned int subtract); +extern int solv_setcloexec(int fd, int state); extern void solv_sort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *, void *), void *compard); extern char *solv_dupjoin(const char *str1, const char *str2, const char *str3); extern char *solv_dupappend(const char *str1, const char *str2, const char *str3); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 92a5e7a..8a5cd8a 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,11 +1,21 @@ +IF (NOT WIN32) + SET (RUNTESTCASES_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/runtestcases.sh) +ELSE () + SET (RUNTESTCASES_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/runtestcases.bat) +ENDIF () + FOREACH(tcdir testcases libsolv-zypptestcases) IF(IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/${tcdir}") FILE(GLOB dirs "${CMAKE_CURRENT_SOURCE_DIR}/${tcdir}/[_a-zA-Z0-9]*") FOREACH(dir ${dirs}) - IF(IS_DIRECTORY ${dir}) - FILE(RELATIVE_PATH myname "${CMAKE_CURRENT_SOURCE_DIR}/${tcdir}" ${dir}) - ADD_TEST(${myname} ${CMAKE_CURRENT_SOURCE_DIR}/runtestcases ${CMAKE_BINARY_DIR}/tools/testsolv ${dir}) - ENDIF(IS_DIRECTORY ${dir}) - ENDFOREACH(dir) - ENDIF(IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/${tcdir}") -ENDFOREACH(tcdir) + IF(IS_DIRECTORY ${dir}) + FILE(RELATIVE_PATH myname "${CMAKE_CURRENT_SOURCE_DIR}/${tcdir}" ${dir}) + FILE(TO_NATIVE_PATH "${CMAKE_BINARY_DIR}/tools/testsolv" TESTSOLV_BINARY) + FILE(TO_NATIVE_PATH "${dir}" dir) + STRING(REPLACE \\ \\\\ dir ${dir}) + STRING(REPLACE \\ \\\\ TESTSOLV_BINARY ${TESTSOLV_BINARY}) + ADD_TEST(${myname} ${RUNTESTCASES_SCRIPT} "${TESTSOLV_BINARY}" "${dir}") + ENDIF () + ENDFOREACH () + ENDIF () +ENDFOREACH () \ No newline at end of file diff --git a/test/runtestcases.bat b/test/runtestcases.bat new file mode 100644 index 0000000..4d4b5e0 --- /dev/null +++ b/test/runtestcases.bat @@ -0,0 +1,24 @@ +@ECHO OFF +SETLOCAL EnableDelayedExpansion +SET cmd=%1 +SET dir=%2 + +SET ex=0 + +FOR /f "tokens=*" %%G IN ('dir /b %dir%\*.t') DO ( + ECHO "%dir%\%%G" + CALL %cmd% "%dir%\%%G" + + IF !ERRORLEVEL! EQU 0 ( + ECHO "PASSED"; + ) ELSE ( + IF !ERRORLEVEL! EQU 77 ( + ECHO "SKIPPED"; + ) ELSE ( + ECHO "FAILED"; + SET ex=1; + ) + ) +) + +exit /B %ex% \ No newline at end of file diff --git a/test/runtestcases b/test/runtestcases.sh similarity index 100% rename from test/runtestcases rename to test/runtestcases.sh diff --git a/test/testcases/focus/best.t b/test/testcases/focus/best.t index a7a46b9..7527285 100644 --- a/test/testcases/focus/best.t +++ b/test/testcases/focus/best.t @@ -1,6 +1,7 @@ repo system 0 testtags #>=Pkg: A 1 1 noarch #>=Pkg: A2 1 1 noarch +#>=Pkg: D 1 1 noarch repo available 0 testtags #>=Pkg: A 1 1 noarch #>=Pkg: A 2 1 noarch @@ -16,8 +17,15 @@ repo available 0 testtags #>=Req: A2 = 2 #>=Pkg: C 1 1 noarch #>=Req: B2 +#>=Pkg: D 2 1 noarch system i686 rpm system solverflags focusbest job install name B job install name C +result transaction,problems +#>install B-2-1.noarch@available +#>install B2-2-1.noarch@available +#>install C-1-1.noarch@available +#>upgrade A-1-1.noarch@system A-2-1.noarch@available +#>upgrade A2-1-1.noarch@system A2-2-1.noarch@available diff --git a/test/testcases/focus/installed.t b/test/testcases/focus/installed.t index 4f49d35..f272ee0 100644 --- a/test/testcases/focus/installed.t +++ b/test/testcases/focus/installed.t @@ -1,6 +1,7 @@ repo system 0 testtags #>=Pkg: A 1 1 noarch #>=Pkg: A2 1 1 noarch +#>=Pkg: D 1 1 noarch repo available 0 testtags #>=Pkg: A 1 1 noarch #>=Pkg: A 2 1 noarch @@ -16,8 +17,13 @@ repo available 0 testtags #>=Req: A2 = 2 #>=Pkg: C 1 1 noarch #>=Req: B2 +#>=Pkg: D 2 1 noarch system i686 rpm system solverflags focusinstalled job install name B job install name C +result transaction,problems +#>install B-1-1.noarch@available +#>install B2-1-1.noarch@available +#>install C-1-1.noarch@available diff --git a/test/testcases/focus/normal.t b/test/testcases/focus/normal.t index 6e562a3..0056b8a 100644 --- a/test/testcases/focus/normal.t +++ b/test/testcases/focus/normal.t @@ -1,6 +1,7 @@ repo system 0 testtags #>=Pkg: A 1 1 noarch #>=Pkg: A2 1 1 noarch +#>=Pkg: D 1 1 noarch repo available 0 testtags #>=Pkg: A 1 1 noarch #>=Pkg: A 2 1 noarch @@ -16,7 +17,13 @@ repo available 0 testtags #>=Req: A2 = 2 #>=Pkg: C 1 1 noarch #>=Req: B2 +#>=Pkg: D 2 1 noarch system i686 rpm system job install name B job install name C +result transaction,problems +#>install B-2-1.noarch@available +#>install B2-1-1.noarch@available +#>install C-1-1.noarch@available +#>upgrade A-1-1.noarch@system A-2-1.noarch@available diff --git a/test/testcases/lock/best.t b/test/testcases/lock/best.t new file mode 100644 index 0000000..5c992d6 --- /dev/null +++ b/test/testcases/lock/best.t @@ -0,0 +1,27 @@ +# test that locked packages trump best rules + +repo system 0 testtags +#>=Pkg: b 1 1 i686 +repo available 0 testtags +#>=Pkg: a 2 1 i686 +#>=Pkg: a 3 1 i686 +#>=Pkg: b 2 1 i686 +#>=Pkg: b 3 1 i686 + +system i686 * system + +job install name a [forcebest] +job lock name a = 3-1 +result transaction,problems +#>install a-2-1.i686@available + +nextjob +job update name b [forcebest] +job lock name b = 3-1 +result transaction,problems +#>upgrade b-1-1.i686@system b-2-1.i686@available + +nextjob +job update name b [forcebest] +job lock name b = 1-1 +result transaction,problems diff --git a/test/testcases/lock/dup.t b/test/testcases/lock/dup.t new file mode 100644 index 0000000..3c400da --- /dev/null +++ b/test/testcases/lock/dup.t @@ -0,0 +1,25 @@ +# test that locked packages trump dup rules + +repo system 0 testtags +#>=Pkg: a 1 1 i686 +repo available 0 testtags +#>=Pkg: a 2 1 i686 + +system i686 * system + +job distupgrade all packages +job lock name a +result transaction,problems + +# but we still get a problem if only the available packages +# are locked +# +nextjob +job distupgrade all packages +job lock name a = 2-1 +result transaction,problems +#>problem 1889163e info problem with installed package a-1-1.i686 +#>problem 1889163e solution 25ae2253 allow a-1-1.i686@system +#>problem 1889163e solution 06ec856f deljob lock name a = 2-1 +#>problem 1889163e solution e5fc66c9 erase a-1-1.i686@system +#>upgrade a-1-1.i686@system a-2-1.i686@available diff --git a/test/testcases/lockstep/lockstep_install.t b/test/testcases/lockstep/lockstep_install.t index f626da0..6e0a5e5 100644 --- a/test/testcases/lockstep/lockstep_install.t +++ b/test/testcases/lockstep/lockstep_install.t @@ -7,3 +7,6 @@ repo test 0 testtags system x86_64 rpm system poolflags implicitobsoleteusescolors job install provides A(x32) +result transaction,problems +#>install A-1-1.i586@test +#>install A-1-1.x86_64@test diff --git a/test/testcases/lockstep/lockstep_update.t b/test/testcases/lockstep/lockstep_update.t index 128bcc3..a70a339 100644 --- a/test/testcases/lockstep/lockstep_update.t +++ b/test/testcases/lockstep/lockstep_update.t @@ -9,3 +9,6 @@ repo test 0 testtags system x86_64 rpm system poolflags implicitobsoleteusescolors job update all packages +result transaction,problems +#>upgrade A-1-1.i586@system A-2-1.i586@test +#>upgrade A-1-1.x86_64@system A-2-1.x86_64@test diff --git a/test/testcases/weakdeps/supplements_implicitobsoleteusescolors.t b/test/testcases/weakdeps/supplements_implicitobsoleteusescolors.t index 6de4544..46a1139 100644 --- a/test/testcases/weakdeps/supplements_implicitobsoleteusescolors.t +++ b/test/testcases/weakdeps/supplements_implicitobsoleteusescolors.t @@ -12,9 +12,16 @@ repo test 0 testtags #>=Req: XX #>=Pkg: B2 1 1 i686 #>=Sup: A2 -system x86_64 * system +system x86_64 rpm system poolflags implicitobsoleteusescolors job install name A +result transaction,problems +#>install A-1-1.noarch@test +#>install B-1-1.x86_64@test nextjob job install name A2 +result transaction,problems +#>install A2-1-1.noarch@test + + diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 802dc50..f19030e 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -4,7 +4,11 @@ ADD_LIBRARY (toolstuff STATIC common_write.c) -SET (tools_list mergesolv dumpsolv installcheck testsolv repo2solv) +IF (WIN32) + INCLUDE_DIRECTORIES (${PROJECT_SOURCE_DIR}/win32/) +ENDIF () + +SET (tools_list testsolv mergesolv dumpsolv installcheck testsolv) IF (ENABLE_RPMDB) ADD_EXECUTABLE (rpmdb2solv rpmdb2solv.c) @@ -98,6 +102,19 @@ TARGET_LINK_LIBRARIES (appdata2solv toolstuff libsolvext libsolv ${SYSTEM_LIBRAR SET (tools_list ${tools_list} appdata2solv) ENDIF (ENABLE_APPDATA) +IF (ENABLE_CONDA) +ADD_EXECUTABLE (conda2solv conda2solv.c) +TARGET_LINK_LIBRARIES (conda2solv toolstuff libsolvext libsolv ${SYSTEM_LIBRARIES}) + +SET (tools_list ${tools_list} conda2solv) +ENDIF (ENABLE_CONDA) + +IF (NOT WIN32) +ADD_EXECUTABLE (repo2solv repo2solv.c ) +TARGET_LINK_LIBRARIES (repo2solv toolstuff libsolvext libsolv ${SYSTEM_LIBRARIES}) +SET(tools_list ${tools_list} repo2solv) +ENDIF (NOT WIN32) + ADD_EXECUTABLE (dumpsolv dumpsolv.c ) TARGET_LINK_LIBRARIES (dumpsolv libsolv) @@ -107,8 +124,4 @@ TARGET_LINK_LIBRARIES (mergesolv toolstuff libsolvext libsolv ${SYSTEM_LIBRARIES ADD_EXECUTABLE (testsolv testsolv.c) TARGET_LINK_LIBRARIES (testsolv libsolvext libsolv ${SYSTEM_LIBRARIES}) -ADD_EXECUTABLE (repo2solv repo2solv.c ) -TARGET_LINK_LIBRARIES (repo2solv toolstuff libsolvext libsolv ${SYSTEM_LIBRARIES}) - -INSTALL (TARGETS ${tools_list} DESTINATION ${BIN_INSTALL_DIR}) - +INSTALL (TARGETS ${tools_list} DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/tools/conda2solv.c b/tools/conda2solv.c new file mode 100644 index 0000000..2b8f3c4 --- /dev/null +++ b/tools/conda2solv.c @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2019, SUSE LLC + * + * This program is licensed under the BSD license, read LICENSE.BSD + * for further information + */ + +/* + * conda2solv.c + * + * parse a conda repository file + * + * reads from stdin + * writes to stdout + */ + +#include +#include +#include +#include +#include + +#include "pool.h" +#include "repo.h" +#include "repo_conda.h" +#include "solv_xfopen.h" +#include "common_write.h" + + +static void +usage(int status) +{ + fprintf(stderr, "\nUsage:\n" + "conda2solv\n" + " reads a 'synthesis' repository from and writes a .solv file to \n" + " -h : print help & exit\n" + ); + exit(status); +} + +int +main(int argc, char **argv) +{ + Pool *pool; + Repo *repo; + int c; + + while ((c = getopt(argc, argv, "h")) >= 0) + { + switch(c) + { + case 'h': + usage(0); + break; + default: + usage(1); + break; + } + } + pool = pool_create(); + repo = repo_create(pool, ""); + if (repo_add_conda(repo, stdin, 0)) + { + fprintf(stderr, "conda2solv: %s\n", pool_errstr(pool)); + exit(1); + } + repo_internalize(repo); + tool_write(repo, stdout); + pool_free(pool); + exit(0); +} diff --git a/tools/deb2solv.c b/tools/deb2solv.c index ad27541..4fac3f0 100644 --- a/tools/deb2solv.c +++ b/tools/deb2solv.c @@ -15,6 +15,7 @@ #include #include #include +#include #include "util.h" #include "pool.h" @@ -57,7 +58,6 @@ main(int argc, char **argv) Repo *repo; FILE *fp; char buf[4096], *p; - const char *basefile = 0; int is_repo = 0; while ((c = getopt(argc, argv, "0:m:r")) >= 0) diff --git a/tools/diskusagexml2solv.c b/tools/diskusagexml2solv.c deleted file mode 100644 index 850b02c..0000000 --- a/tools/diskusagexml2solv.c +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (c) 2007, Novell Inc. - * - * This program is licensed under the BSD license, read LICENSE.BSD - * for further information - */ - -#include -#include -#include -#include -#include -#include -#include - -#include "pool.h" -#include "repo.h" -#include "repo_diskusagexml.h" -#include "common_write.h" - -static void -usage(int status) -{ - fprintf(stderr, "\nUsage:\n" - "diskusagexml2solv [-h]\n" - " reads a 'diskusage.xml' file from and writes a .solv file to \n" - " -h : print help & exit\n" - ); - exit(status); -} - -int -main(int argc, char **argv) -{ - int c, flags = 0; - - Pool *pool = pool_create(); - Repo *repo = repo_create(pool, ""); - - while ((c = getopt(argc, argv, "h")) >= 0) - { - switch(c) - { - case 'h': - usage(0); - break; - default: - usage(1); - break; - } - } - if (repo_add_diskusagexml(repo, stdin, flags)) - { - fprintf(stderr, "diskusagexml2solv: %s\n", pool_errstr(pool)); - exit(1); - } - tool_write(repo, stdout); - pool_free(pool); - exit(0); -} diff --git a/tools/dumpsolv.c b/tools/dumpsolv.c index bdd521e..1307657 100644 --- a/tools/dumpsolv.c +++ b/tools/dumpsolv.c @@ -9,6 +9,7 @@ #include #include #include +#include static int with_attr; static int dump_json; diff --git a/tools/installcheck.c b/tools/installcheck.c index 6c090d8..4eb66a0 100644 --- a/tools/installcheck.c +++ b/tools/installcheck.c @@ -34,6 +34,9 @@ #include "solver.h" #include "solv_xfopen.h" +#ifdef _WIN32 +#include "strfncs.h" +#endif void usage(char** argv) diff --git a/tools/mergesolv.c b/tools/mergesolv.c index 8746ac6..a10c686 100644 --- a/tools/mergesolv.c +++ b/tools/mergesolv.c @@ -16,6 +16,7 @@ #include #include #include +#include #include "pool.h" #include "repo_solv.h" diff --git a/tools/testsolv.c b/tools/testsolv.c index 3331fae..18dfcfe 100644 --- a/tools/testsolv.c +++ b/tools/testsolv.c @@ -1,6 +1,7 @@ #include #include #include +#include #include "pool.h" #include "repo.h" diff --git a/win32/CMakeLists.txt b/win32/CMakeLists.txt new file mode 100644 index 0000000..9a87af7 --- /dev/null +++ b/win32/CMakeLists.txt @@ -0,0 +1,9 @@ +INCLUDE_DIRECTORIES (${PROJECT_SOURCE_DIR}/win32) +SET (WIN32_COMPAT_SOURCES + ${PROJECT_SOURCE_DIR}/win32/fnmatch.c + ${PROJECT_SOURCE_DIR}/win32/getopt.c + ${PROJECT_SOURCE_DIR}/win32/regcomp.c + ${PROJECT_SOURCE_DIR}/win32/regexec.c + ${PROJECT_SOURCE_DIR}/win32/strfncs.c + ${PROJECT_SOURCE_DIR}/win32/tre-mem.c +) \ No newline at end of file diff --git a/win32/LICENSE b/win32/LICENSE new file mode 100644 index 0000000..c9ad4f4 --- /dev/null +++ b/win32/LICENSE @@ -0,0 +1,70 @@ +All files for the Windows compatibility layer are taken from musl, +except for unistd.h and fmemopen.c. + +MUSL + +musl as a whole is licensed under the following standard MIT license: + +---------------------------------------------------------------------- +Copyright © 2005-2014 Rich Felker, et al. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +---------------------------------------------------------------------- + + +TRE (tre.h, tre-mem.c) + +The TRE regular expression implementation (reg* and +tre*) is Copyright © 2001-2008 Ville Laurikari and licensed +under a 2-clause BSD license (license text in the source files). The +included version has been heavily modified by Rich Felker in 2012, in +the interests of size, simplicity, and namespace cleanliness. + +---------------------------------------------------------------------- +This is the license, copyright notice, and disclaimer for TRE, a regex +matching package (library and tools) with support for approximate +matching. + +Copyright (c) 2001-2009 Ville Laurikari +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 COPYRIGHT HOLDER AND CONTRIBUTORS +``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 COPYRIGHT +HOLDER OR CONTRIBUTORS 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. +---------------------------------------------------------------------- diff --git a/win32/fmemopen.c b/win32/fmemopen.c new file mode 100644 index 0000000..ea88b08 --- /dev/null +++ b/win32/fmemopen.c @@ -0,0 +1,41 @@ +#include +#include +#include +#include + +FILE * +fmemopen(void *buf, size_t size, const char *mode) +{ + char temppath[MAX_PATH + 1]; + char tempnam[MAX_PATH + 1]; + DWORD l; + HANDLE fh; + FILE *fp; + + if (strcmp(mode, "r") != 0 && strcmp(mode, "r+") != 0) + return 0; + l = GetTempPath(MAX_PATH, temppath); + if (!l || l >= MAX_PATH) + return 0; + if (!GetTempFileName(temppath, "solvtmp", 0, tempnam)) + return 0; + fh = CreateFile(tempnam, DELETE | GENERIC_READ | GENERIC_WRITE, 0, + NULL, CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, + NULL); + if (fh == INVALID_HANDLE_VALUE) + return 0; + fp = _fdopen(_open_osfhandle((intptr_t)fh, 0), "w+b"); + if (!fp) + { + CloseHandle(fh); + return 0; + } + if (buf && size && fwrite(buf, size, 1, fp) != 1) + { + fclose(fp); + return 0; + } + rewind(fp); + return fp; +} diff --git a/win32/fnmatch.c b/win32/fnmatch.c new file mode 100644 index 0000000..9e90852 --- /dev/null +++ b/win32/fnmatch.c @@ -0,0 +1,320 @@ +/* + * An implementation of what I call the "Sea of Stars" algorithm for + * POSIX fnmatch(). The basic idea is that we factor the pattern into + * a head component (which we match first and can reject without ever + * measuring the length of the string), an optional tail component + * (which only exists if the pattern contains at least one star), and + * an optional "sea of stars", a set of star-separated components + * between the head and tail. After the head and tail matches have + * been removed from the input string, the components in the "sea of + * stars" are matched sequentially by searching for their first + * occurrence past the end of the previous match. + * + * - Rich Felker, April 2012 + */ + +#include +#include +#include +#include +#include + +#define END 0 +#define UNMATCHABLE -2 +#define BRACKET -3 +#define QUESTION -4 +#define STAR -5 + +static int str_next(const char *str, size_t n, size_t *step) +{ + if (!n) { + *step = 0; + return 0; + } + if (str[0] >= 128U) { + wchar_t wc; + int k = mbtowc(&wc, str, n); + if (k<0) { + *step = 1; + return -1; + } + *step = k; + return wc; + } + *step = 1; + return str[0]; +} + +static int pat_next(const char *pat, size_t m, size_t *step, int flags) +{ + int esc = 0; + if (!m || !*pat) { + *step = 0; + return END; + } + *step = 1; + if (pat[0]=='\\' && pat[1] && !(flags & FNM_NOESCAPE)) { + *step = 2; + pat++; + esc = 1; + goto escaped; + } + if (pat[0]=='[') { + size_t k = 1; + if (k= 128U) { + wchar_t wc; + int k = mbtowc(&wc, pat, m); + if (k<0) { + *step = 0; + return UNMATCHABLE; + } + *step = k + esc; + return wc; + } + return pat[0]; +} + +static int casefold(int k) +{ + int c = towupper(k); + return c == k ? towlower(k) : c; +} + +static int match_bracket(const char *p, int k, int kfold) +{ + wchar_t wc; + int inv = 0; + p++; + if (*p=='^' || *p=='!') { + inv = 1; + p++; + } + if (*p==']') { + if (k==']') return !inv; + p++; + } else if (*p=='-') { + if (k=='-') return !inv; + p++; + } + wc = p[-1]; + for (; *p != ']'; p++) { + if (p[0]=='-' && p[1]!=']') { + wchar_t wc2; + int l = mbtowc(&wc2, p+1, 4); + if (l < 0) return 0; + if (wc <= wc2) + if ((unsigned)k-wc <= wc2-wc || + (unsigned)kfold-wc <= wc2-wc) + return !inv; + p += l-1; + continue; + } + if (p[0]=='[' && (p[1]==':' || p[1]=='.' || p[1]=='=')) { + const char *p0 = p+2; + int z = p[1]; + p+=3; + while (p[-1]!=z || p[0]!=']') p++; + if (z == ':' && p-1-p0 < 16) { + char buf[16]; + memcpy(buf, p0, p-1-p0); + buf[p-1-p0] = 0; + if (iswctype(k, wctype(buf)) || + iswctype(kfold, wctype(buf))) + return !inv; + } + continue; + } + if (*p < 128U) { + wc = (unsigned char)*p; + } else { + int l = mbtowc(&wc, p, 4); + if (l < 0) return 0; + p += l-1; + } + if (wc==k || wc==kfold) return !inv; + } + return inv; +} + +static int fnmatch_internal(const char *pat, size_t m, const char *str, size_t n, int flags) +{ + const char *p, *ptail, *endpat; + const char *s, *stail, *endstr; + size_t pinc, sinc, tailcnt=0; + int c, k, kfold; + + if (flags & FNM_PERIOD) { + if (*str == '.' && *pat != '.') + return FNM_NOMATCH; + } + for (;;) { + switch ((c = pat_next(pat, m, &pinc, flags))) { + case UNMATCHABLE: + return FNM_NOMATCH; + case STAR: + pat++; + m--; + break; + default: + k = str_next(str, n, &sinc); + if (k <= 0) + return (c==END) ? 0 : FNM_NOMATCH; + str += sinc; + n -= sinc; + kfold = flags & FNM_CASEFOLD ? casefold(k) : k; + if (c == BRACKET) { + if (!match_bracket(pat, k, kfold)) + return FNM_NOMATCH; + } else if (c != QUESTION && k != c && kfold != c) { + return FNM_NOMATCH; + } + pat+=pinc; + m-=pinc; + continue; + } + break; + } + + /* Compute real pat length if it was initially unknown/-1 */ + m = strnlen(pat, m); + endpat = pat + m; + + /* Find the last * in pat and count chars needed after it */ + for (p=ptail=pat; pstr && tailcnt; tailcnt--) { + if (s[-1] < 128U || MB_CUR_MAX==1) s--; + else while ((unsigned char)*--s-0x80U<0x40 && s>str); + } + if (tailcnt) return FNM_NOMATCH; + stail = s; + + /* Check that the pat and str tails match */ + p = ptail; + for (;;) { + c = pat_next(p, endpat-p, &pinc, flags); + p += pinc; + if ((k = str_next(s, endstr-s, &sinc)) <= 0) { + if (c != END) return FNM_NOMATCH; + break; + } + s += sinc; + kfold = flags & FNM_CASEFOLD ? casefold(k) : k; + if (c == BRACKET) { + if (!match_bracket(p-pinc, k, kfold)) + return FNM_NOMATCH; + } else if (c != QUESTION && k != c && kfold != c) { + return FNM_NOMATCH; + } + } + + /* We're all done with the tails now, so throw them out */ + endstr = stail; + endpat = ptail; + + /* Match pattern components until there are none left */ + while (pat 0) str += sinc; + else for (str++; str_next(str, endstr-str, &sinc)<0; str++); + } + + return 0; +} + +int fnmatch(const char *pat, const char *str, int flags) +{ + const char *s, *p; + size_t inc; + int c; + if (flags & FNM_PATHNAME) for (;;) { + for (s=str; *s && *s!='/'; s++); + for (p=pat; (c=pat_next(p, -1, &inc, flags))!=END && c!='/'; p+=inc); + if (c!=*s && (!*s || !(flags & FNM_LEADING_DIR))) + return FNM_NOMATCH; + if (fnmatch_internal(pat, p-pat, str, s-str, flags)) + return FNM_NOMATCH; + if (!c) return 0; + str = s+1; + pat = p+inc; + } else if (flags & FNM_LEADING_DIR) { + for (s=str; *s; s++) { + if (*s != '/') continue; + if (!fnmatch_internal(pat, -1, str, s-str, flags)) + return 0; + } + } + return fnmatch_internal(pat, -1, str, -1, flags); +} diff --git a/win32/fnmatch.h b/win32/fnmatch.h new file mode 100644 index 0000000..f959321 --- /dev/null +++ b/win32/fnmatch.h @@ -0,0 +1,24 @@ +#ifndef _FNMATCH_H +#define _FNMATCH_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define FNM_PATHNAME 0x1 +#define FNM_NOESCAPE 0x2 +#define FNM_PERIOD 0x4 +#define FNM_LEADING_DIR 0x8 +#define FNM_CASEFOLD 0x10 +#define FNM_FILE_NAME FNM_PATHNAME + +#define FNM_NOMATCH 1 +#define FNM_NOSYS (-1) + +int fnmatch(const char *, const char *, int); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/win32/getopt.c b/win32/getopt.c new file mode 100644 index 0000000..1e7e451 --- /dev/null +++ b/win32/getopt.c @@ -0,0 +1,97 @@ +#include +#include +#include +#include +#include +#include + +char *optarg; +int optind=1, opterr=1, optopt, __optpos, __optreset=0; + +#define optpos __optpos + +void __getopt_msg(const char *a, const char *b, const char *c, size_t l) +{ + FILE *f = stderr; + fputs(a, f)>=0 + && fwrite(b, strlen(b), 1, f) + && fwrite(c, 1, l, f)==l + && putc('\n', f); +} + +int getopt(int argc, char * const argv[], const char *optstring) +{ + int i; + wchar_t c, d; + int k, l; + char *optchar; + + if (!optind || __optreset) { + __optreset = 0; + __optpos = 0; + optind = 1; + } + + if (optind >= argc || !argv[optind]) + return -1; + + if (argv[optind][0] != '-') { + if (optstring[0] == '-') { + optarg = argv[optind++]; + return 1; + } + return -1; + } + + if (!argv[optind][1]) + return -1; + + if (argv[optind][1] == '-' && !argv[optind][2]) + return optind++, -1; + + if (!optpos) optpos++; + if ((k = mbtowc(&c, argv[optind]+optpos, MB_LEN_MAX)) < 0) { + k = 1; + c = 0xfffd; /* replacement char */ + } + optchar = argv[optind]+optpos; + optpos += k; + + if (!argv[optind][optpos]) { + optind++; + optpos = 0; + } + + if (optstring[0] == '-' || optstring[0] == '+') + optstring++; + + i = 0; + d = 0; + do { + l = mbtowc(&d, optstring+i, MB_LEN_MAX); + if (l>0) i+=l; else i++; + } while (l && d != c); + + if (d != c || c == ':') { + optopt = c; + if (optstring[0] != ':' && opterr) + __getopt_msg(argv[0], ": unrecognized option: ", optchar, k); + return '?'; + } + if (optstring[i] == ':') { + optarg = 0; + if (optstring[i+1] != ':' || optpos) { + optarg = argv[optind++] + optpos; + optpos = 0; + } + if (optind > argc) { + optopt = c; + if (optstring[0] == ':') return ':'; + if (opterr) __getopt_msg(argv[0], + ": option requires an argument: ", + optchar, k); + return '?'; + } + } + return c; +} diff --git a/win32/getopt.h b/win32/getopt.h new file mode 100644 index 0000000..35cbd35 --- /dev/null +++ b/win32/getopt.h @@ -0,0 +1,30 @@ +#ifndef _GETOPT_H +#define _GETOPT_H + +#ifdef __cplusplus +extern "C" { +#endif + +int getopt(int, char * const [], const char *); +extern char *optarg; +extern int optind, opterr, optopt, optreset; + +struct option { + const char *name; + int has_arg; + int *flag; + int val; +}; + +int getopt_long(int, char *const *, const char *, const struct option *, int *); +int getopt_long_only(int, char *const *, const char *, const struct option *, int *); + +#define no_argument 0 +#define required_argument 1 +#define optional_argument 2 + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/win32/regcomp.c b/win32/regcomp.c new file mode 100644 index 0000000..7ef8327 --- /dev/null +++ b/win32/regcomp.c @@ -0,0 +1,2953 @@ +/* + regcomp.c - TRE POSIX compatible regex compilation functions. + + Copyright (c) 2001-2009 Ville Laurikari + 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 COPYRIGHT HOLDER AND CONTRIBUTORS + ``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 COPYRIGHT + HOLDER OR CONTRIBUTORS 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. + +*/ + +#include +#include +#include +#include +#include +#include + +#include "tre.h" + +#include + +/*********************************************************************** + from tre-compile.h +***********************************************************************/ + +typedef struct { + int position; + int code_min; + int code_max; + int *tags; + int assertions; + tre_ctype_t class; + tre_ctype_t *neg_classes; + int backref; +} tre_pos_and_tags_t; + + +/*********************************************************************** + from tre-ast.c and tre-ast.h +***********************************************************************/ + +/* The different AST node types. */ +typedef enum { + LITERAL, + CATENATION, + ITERATION, + UNION +} tre_ast_type_t; + +/* Special subtypes of TRE_LITERAL. */ +#define EMPTY -1 /* Empty leaf (denotes empty string). */ +#define ASSERTION -2 /* Assertion leaf. */ +#define TAG -3 /* Tag leaf. */ +#define BACKREF -4 /* Back reference leaf. */ + +#define IS_SPECIAL(x) ((x)->code_min < 0) +#define IS_EMPTY(x) ((x)->code_min == EMPTY) +#define IS_ASSERTION(x) ((x)->code_min == ASSERTION) +#define IS_TAG(x) ((x)->code_min == TAG) +#define IS_BACKREF(x) ((x)->code_min == BACKREF) + + +/* A generic AST node. All AST nodes consist of this node on the top + level with `obj' pointing to the actual content. */ +typedef struct { + tre_ast_type_t type; /* Type of the node. */ + void *obj; /* Pointer to actual node. */ + int nullable; + int submatch_id; + int num_submatches; + int num_tags; + tre_pos_and_tags_t *firstpos; + tre_pos_and_tags_t *lastpos; +} tre_ast_node_t; + + +/* A "literal" node. These are created for assertions, back references, + tags, matching parameter settings, and all expressions that match one + character. */ +typedef struct { + long code_min; + long code_max; + int position; + tre_ctype_t class; + tre_ctype_t *neg_classes; +} tre_literal_t; + +/* A "catenation" node. These are created when two regexps are concatenated. + If there are more than one subexpressions in sequence, the `left' part + holds all but the last, and `right' part holds the last subexpression + (catenation is left associative). */ +typedef struct { + tre_ast_node_t *left; + tre_ast_node_t *right; +} tre_catenation_t; + +/* An "iteration" node. These are created for the "*", "+", "?", and "{m,n}" + operators. */ +typedef struct { + /* Subexpression to match. */ + tre_ast_node_t *arg; + /* Minimum number of consecutive matches. */ + int min; + /* Maximum number of consecutive matches. */ + int max; + /* If 0, match as many characters as possible, if 1 match as few as + possible. Note that this does not always mean the same thing as + matching as many/few repetitions as possible. */ + unsigned int minimal:1; +} tre_iteration_t; + +/* An "union" node. These are created for the "|" operator. */ +typedef struct { + tre_ast_node_t *left; + tre_ast_node_t *right; +} tre_union_t; + + +static tre_ast_node_t * +tre_ast_new_node(tre_mem_t mem, int type, void *obj) +{ + tre_ast_node_t *node = tre_mem_calloc(mem, sizeof *node); + if (!node || !obj) + return 0; + node->obj = obj; + node->type = type; + node->nullable = -1; + node->submatch_id = -1; + return node; +} + +static tre_ast_node_t * +tre_ast_new_literal(tre_mem_t mem, int code_min, int code_max, int position) +{ + tre_ast_node_t *node; + tre_literal_t *lit; + + lit = tre_mem_calloc(mem, sizeof *lit); + node = tre_ast_new_node(mem, LITERAL, lit); + if (!node) + return 0; + lit->code_min = code_min; + lit->code_max = code_max; + lit->position = position; + return node; +} + +static tre_ast_node_t * +tre_ast_new_iter(tre_mem_t mem, tre_ast_node_t *arg, int min, int max, int minimal) +{ + tre_ast_node_t *node; + tre_iteration_t *iter; + + iter = tre_mem_calloc(mem, sizeof *iter); + node = tre_ast_new_node(mem, ITERATION, iter); + if (!node) + return 0; + iter->arg = arg; + iter->min = min; + iter->max = max; + iter->minimal = minimal; + node->num_submatches = arg->num_submatches; + return node; +} + +static tre_ast_node_t * +tre_ast_new_union(tre_mem_t mem, tre_ast_node_t *left, tre_ast_node_t *right) +{ + tre_ast_node_t *node; + tre_union_t *un; + + if (!left) + return right; + un = tre_mem_calloc(mem, sizeof *un); + node = tre_ast_new_node(mem, UNION, un); + if (!node || !right) + return 0; + un->left = left; + un->right = right; + node->num_submatches = left->num_submatches + right->num_submatches; + return node; +} + +static tre_ast_node_t * +tre_ast_new_catenation(tre_mem_t mem, tre_ast_node_t *left, tre_ast_node_t *right) +{ + tre_ast_node_t *node; + tre_catenation_t *cat; + + if (!left) + return right; + cat = tre_mem_calloc(mem, sizeof *cat); + node = tre_ast_new_node(mem, CATENATION, cat); + if (!node) + return 0; + cat->left = left; + cat->right = right; + node->num_submatches = left->num_submatches + right->num_submatches; + return node; +} + + +/*********************************************************************** + from tre-stack.c and tre-stack.h +***********************************************************************/ + +typedef struct tre_stack_rec tre_stack_t; + +/* Creates a new stack object. `size' is initial size in bytes, `max_size' + is maximum size, and `increment' specifies how much more space will be + allocated with realloc() if all space gets used up. Returns the stack + object or NULL if out of memory. */ +static tre_stack_t * +tre_stack_new(int size, int max_size, int increment); + +/* Frees the stack object. */ +static void +tre_stack_destroy(tre_stack_t *s); + +/* Returns the current number of objects in the stack. */ +static int +tre_stack_num_objects(tre_stack_t *s); + +/* Each tre_stack_push_*(tre_stack_t *s, value) function pushes + `value' on top of stack `s'. Returns REG_ESPACE if out of memory. + This tries to realloc() more space before failing if maximum size + has not yet been reached. Returns REG_OK if successful. */ +#define declare_pushf(typetag, type) \ + static reg_errcode_t tre_stack_push_ ## typetag(tre_stack_t *s, type value) + +declare_pushf(voidptr, void *); +declare_pushf(int, int); + +/* Each tre_stack_pop_*(tre_stack_t *s) function pops the topmost + element off of stack `s' and returns it. The stack must not be + empty. */ +#define declare_popf(typetag, type) \ + static type tre_stack_pop_ ## typetag(tre_stack_t *s) + +declare_popf(voidptr, void *); +declare_popf(int, int); + +/* Just to save some typing. */ +#define STACK_PUSH(s, typetag, value) \ + do \ + { \ + status = tre_stack_push_ ## typetag(s, value); \ + } \ + while (/*CONSTCOND*/0) + +#define STACK_PUSHX(s, typetag, value) \ + { \ + status = tre_stack_push_ ## typetag(s, value); \ + if (status != REG_OK) \ + break; \ + } + +#define STACK_PUSHR(s, typetag, value) \ + { \ + reg_errcode_t _status; \ + _status = tre_stack_push_ ## typetag(s, value); \ + if (_status != REG_OK) \ + return _status; \ + } + +union tre_stack_item { + void *voidptr_value; + int int_value; +}; + +struct tre_stack_rec { + int size; + int max_size; + int increment; + int ptr; + union tre_stack_item *stack; +}; + + +static tre_stack_t * +tre_stack_new(int size, int max_size, int increment) +{ + tre_stack_t *s; + + s = xmalloc(sizeof(*s)); + if (s != NULL) + { + s->stack = xmalloc(sizeof(*s->stack) * size); + if (s->stack == NULL) + { + xfree(s); + return NULL; + } + s->size = size; + s->max_size = max_size; + s->increment = increment; + s->ptr = 0; + } + return s; +} + +static void +tre_stack_destroy(tre_stack_t *s) +{ + xfree(s->stack); + xfree(s); +} + +static int +tre_stack_num_objects(tre_stack_t *s) +{ + return s->ptr; +} + +static reg_errcode_t +tre_stack_push(tre_stack_t *s, union tre_stack_item value) +{ + if (s->ptr < s->size) + { + s->stack[s->ptr] = value; + s->ptr++; + } + else + { + if (s->size >= s->max_size) + { + return REG_ESPACE; + } + else + { + union tre_stack_item *new_buffer; + int new_size; + new_size = s->size + s->increment; + if (new_size > s->max_size) + new_size = s->max_size; + new_buffer = xrealloc(s->stack, sizeof(*new_buffer) * new_size); + if (new_buffer == NULL) + { + return REG_ESPACE; + } + assert(new_size > s->size); + s->size = new_size; + s->stack = new_buffer; + tre_stack_push(s, value); + } + } + return REG_OK; +} + +#define define_pushf(typetag, type) \ + declare_pushf(typetag, type) { \ + union tre_stack_item item; \ + item.typetag ## _value = value; \ + return tre_stack_push(s, item); \ +} + +define_pushf(int, int) +define_pushf(voidptr, void *) + +#define define_popf(typetag, type) \ + declare_popf(typetag, type) { \ + return s->stack[--s->ptr].typetag ## _value; \ + } + +define_popf(int, int) +define_popf(voidptr, void *) + + +/*********************************************************************** + from tre-parse.c and tre-parse.h +***********************************************************************/ + +/* Parse context. */ +typedef struct { + /* Memory allocator. The AST is allocated using this. */ + tre_mem_t mem; + /* Stack used for keeping track of regexp syntax. */ + tre_stack_t *stack; + /* The parsed node after a parse function returns. */ + tre_ast_node_t *n; + /* Position in the regexp pattern after a parse function returns. */ + const char *s; + /* The first character of the last subexpression parsed. */ + const char *start; + /* Current submatch ID. */ + int submatch_id; + /* Current position (number of literal). */ + int position; + /* The highest back reference or -1 if none seen so far. */ + int max_backref; + /* Compilation flags. */ + int cflags; +} tre_parse_ctx_t; + +/* Some macros for expanding \w, \s, etc. */ +static const struct { + char c; + const char *expansion; +} tre_macros[] = { + {'t', "\t"}, {'n', "\n"}, {'r', "\r"}, + {'f', "\f"}, {'a', "\a"}, {'e', "\033"}, + {'w', "[[:alnum:]_]"}, {'W', "[^[:alnum:]_]"}, {'s', "[[:space:]]"}, + {'S', "[^[:space:]]"}, {'d', "[[:digit:]]"}, {'D', "[^[:digit:]]"}, + { 0, 0 } +}; + +/* Expands a macro delimited by `regex' and `regex_end' to `buf', which + must have at least `len' items. Sets buf[0] to zero if the there + is no match in `tre_macros'. */ +static const char *tre_expand_macro(const char *s) +{ + int i; + for (i = 0; tre_macros[i].c && tre_macros[i].c != *s; i++); + return tre_macros[i].expansion; +} + +static int +tre_compare_lit(const void *a, const void *b) +{ + const tre_literal_t *const *la = a; + const tre_literal_t *const *lb = b; + /* assumes the range of valid code_min is < INT_MAX */ + return la[0]->code_min - lb[0]->code_min; +} + +struct literals { + tre_mem_t mem; + tre_literal_t **a; + int len; + int cap; +}; + +static tre_literal_t *tre_new_lit(struct literals *p) +{ + tre_literal_t **a; + if (p->len >= p->cap) { + if (p->cap >= 1<<15) + return 0; + p->cap *= 2; + a = xrealloc(p->a, p->cap * sizeof *p->a); + if (!a) + return 0; + p->a = a; + } + a = p->a + p->len++; + *a = tre_mem_calloc(p->mem, sizeof **a); + return *a; +} + +static int add_icase_literals(struct literals *ls, int min, int max) +{ + tre_literal_t *lit; + int b, e, c; + for (c=min; c<=max; ) { + /* assumes islower(c) and isupper(c) are exclusive + and toupper(c)!=c if islower(c). + multiple opposite case characters are not supported */ + if (tre_islower(c)) { + b = e = tre_toupper(c); + for (c++, e++; c<=max; c++, e++) + if (tre_toupper(c) != e) break; + } else if (tre_isupper(c)) { + b = e = tre_tolower(c); + for (c++, e++; c<=max; c++, e++) + if (tre_tolower(c) != e) break; + } else { + c++; + continue; + } + lit = tre_new_lit(ls); + if (!lit) + return -1; + lit->code_min = b; + lit->code_max = e-1; + lit->position = -1; + } + return 0; +} + + +/* Maximum number of character classes in a negated bracket expression. */ +#define MAX_NEG_CLASSES 64 + +struct neg { + int negate; + int len; + tre_ctype_t a[MAX_NEG_CLASSES]; +}; + +// TODO: parse bracket into a set of non-overlapping [lo,hi] ranges + +/* +bracket grammar: +Bracket = '[' List ']' | '[^' List ']' +List = Term | List Term +Term = Char | Range | Chclass | Eqclass +Range = Char '-' Char | Char '-' '-' +Char = Coll | coll_single +Meta = ']' | '-' +Coll = '[.' coll_single '.]' | '[.' coll_multi '.]' | '[.' Meta '.]' +Eqclass = '[=' coll_single '=]' | '[=' coll_multi '=]' +Chclass = '[:' class ':]' + +coll_single is a single char collating element but it can be + '-' only at the beginning or end of a List and + ']' only at the beginning of a List and + '^' anywhere except after the openning '[' +*/ + +static reg_errcode_t parse_bracket_terms(tre_parse_ctx_t *ctx, const char *s, struct literals *ls, struct neg *neg) +{ + const char *start = s; + tre_ctype_t class; + int min, max; + wchar_t wc; + int len; + + for (;;) { + class = 0; + len = mbtowc(&wc, s, -1); + if (len <= 0) + return *s ? REG_BADPAT : REG_EBRACK; + if (*s == ']' && s != start) { + ctx->s = s+1; + return REG_OK; + } + if (*s == '-' && s != start && s[1] != ']' && + /* extension: [a-z--@] is accepted as [a-z]|[--@] */ + (s[1] != '-' || s[2] == ']')) + return REG_ERANGE; + if (*s == '[' && (s[1] == '.' || s[1] == '=')) + /* collating symbols and equivalence classes are not supported */ + return REG_ECOLLATE; + if (*s == '[' && s[1] == ':') { + char tmp[CHARCLASS_NAME_MAX+1]; + s += 2; + for (len=0; len < CHARCLASS_NAME_MAX && s[len]; len++) { + if (s[len] == ':') { + memcpy(tmp, s, len); + tmp[len] = 0; + class = tre_ctype(tmp); + break; + } + } + if (!class || s[len+1] != ']') + return REG_ECTYPE; + min = 0; + max = TRE_CHAR_MAX; + s += len+2; + } else { + min = max = wc; + s += len; + if (*s == '-' && s[1] != ']') { + s++; + len = mbtowc(&wc, s, -1); + max = wc; + /* XXX - Should use collation order instead of + encoding values in character ranges. */ + if (len <= 0 || min > max) + return REG_ERANGE; + s += len; + } + } + + if (class && neg->negate) { + if (neg->len >= MAX_NEG_CLASSES) + return REG_ESPACE; + neg->a[neg->len++] = class; + } else { + tre_literal_t *lit = tre_new_lit(ls); + if (!lit) + return REG_ESPACE; + lit->code_min = min; + lit->code_max = max; + lit->class = class; + lit->position = -1; + + /* Add opposite-case codepoints if REG_ICASE is present. + It seems that POSIX requires that bracket negation + should happen before case-folding, but most practical + implementations do it the other way around. Changing + the order would need efficient representation of + case-fold ranges and bracket range sets even with + simple patterns so this is ok for now. */ + if (ctx->cflags & REG_ICASE && !class) + if (add_icase_literals(ls, min, max)) + return REG_ESPACE; + } + } +} + +static reg_errcode_t parse_bracket(tre_parse_ctx_t *ctx, const char *s) +{ + int i, max, min, negmax, negmin; + tre_ast_node_t *node = 0, *n; + tre_ctype_t *nc = 0; + tre_literal_t *lit; + struct literals ls; + struct neg neg; + reg_errcode_t err; + + ls.mem = ctx->mem; + ls.len = 0; + ls.cap = 32; + ls.a = xmalloc(ls.cap * sizeof *ls.a); + if (!ls.a) + return REG_ESPACE; + neg.len = 0; + neg.negate = *s == '^'; + if (neg.negate) + s++; + + err = parse_bracket_terms(ctx, s, &ls, &neg); + if (err != REG_OK) + goto parse_bracket_done; + + if (neg.negate) { + /* + * With REG_NEWLINE, POSIX requires that newlines are not matched by + * any form of a non-matching list. + */ + if (ctx->cflags & REG_NEWLINE) { + lit = tre_new_lit(&ls); + if (!lit) { + err = REG_ESPACE; + goto parse_bracket_done; + } + lit->code_min = '\n'; + lit->code_max = '\n'; + lit->position = -1; + } + /* Sort the array if we need to negate it. */ + qsort(ls.a, ls.len, sizeof *ls.a, tre_compare_lit); + /* extra lit for the last negated range */ + lit = tre_new_lit(&ls); + if (!lit) { + err = REG_ESPACE; + goto parse_bracket_done; + } + lit->code_min = TRE_CHAR_MAX+1; + lit->code_max = TRE_CHAR_MAX+1; + lit->position = -1; + /* negated classes */ + if (neg.len) { + nc = tre_mem_alloc(ctx->mem, (neg.len+1)*sizeof *neg.a); + if (!nc) { + err = REG_ESPACE; + goto parse_bracket_done; + } + memcpy(nc, neg.a, neg.len*sizeof *neg.a); + nc[neg.len] = 0; + } + } + + /* Build a union of the items in the array, negated if necessary. */ + negmax = negmin = 0; + for (i = 0; i < ls.len; i++) { + lit = ls.a[i]; + min = lit->code_min; + max = lit->code_max; + if (neg.negate) { + if (min <= negmin) { + /* Overlap. */ + negmin = MAX(max + 1, negmin); + continue; + } + negmax = min - 1; + lit->code_min = negmin; + lit->code_max = negmax; + negmin = max + 1; + } + lit->position = ctx->position; + lit->neg_classes = nc; + n = tre_ast_new_node(ctx->mem, LITERAL, lit); + node = tre_ast_new_union(ctx->mem, node, n); + if (!node) { + err = REG_ESPACE; + break; + } + } + +parse_bracket_done: + xfree(ls.a); + ctx->position++; + ctx->n = node; + return err; +} + +static const char *parse_dup_count(const char *s, int *n) +{ + *n = -1; + if (!isdigit(*s)) + return s; + *n = 0; + for (;;) { + *n = 10 * *n + (*s - '0'); + s++; + if (!isdigit(*s) || *n > RE_DUP_MAX) + break; + } + return s; +} + +static const char *parse_dup(const char *s, int ere, int *pmin, int *pmax) +{ + int min, max; + + s = parse_dup_count(s, &min); + if (*s == ',') + s = parse_dup_count(s+1, &max); + else + max = min; + + if ( + (max < min && max >= 0) || + max > RE_DUP_MAX || + min > RE_DUP_MAX || + min < 0 || + (!ere && *s++ != '\\') || + *s++ != '}' + ) + return 0; + *pmin = min; + *pmax = max; + return s; +} + +static int hexval(unsigned c) +{ + if (c-'0'<10) return c-'0'; + c |= 32; + if (c-'a'<6) return c-'a'+10; + return -1; +} + +static reg_errcode_t marksub(tre_parse_ctx_t *ctx, tre_ast_node_t *node, int subid) +{ + if (node->submatch_id >= 0) { + tre_ast_node_t *n = tre_ast_new_literal(ctx->mem, EMPTY, -1, -1); + if (!n) + return REG_ESPACE; + n = tre_ast_new_catenation(ctx->mem, n, node); + if (!n) + return REG_ESPACE; + n->num_submatches = node->num_submatches; + node = n; + } + node->submatch_id = subid; + node->num_submatches++; + ctx->n = node; + return REG_OK; +} + +/* +BRE grammar: +Regex = Branch | '^' | '$' | '^$' | '^' Branch | Branch '$' | '^' Branch '$' +Branch = Atom | Branch Atom +Atom = char | quoted_char | '.' | Bracket | Atom Dup | '\(' Branch '\)' | back_ref +Dup = '*' | '\{' Count '\}' | '\{' Count ',\}' | '\{' Count ',' Count '\}' + +(leading ^ and trailing $ in a sub expr may be an anchor or literal as well) + +ERE grammar: +Regex = Branch | Regex '|' Branch +Branch = Atom | Branch Atom +Atom = char | quoted_char | '.' | Bracket | Atom Dup | '(' Regex ')' | '^' | '$' +Dup = '*' | '+' | '?' | '{' Count '}' | '{' Count ',}' | '{' Count ',' Count '}' + +(a*+?, ^*, $+, \X, {, (|a) are unspecified) +*/ + +static reg_errcode_t parse_atom(tre_parse_ctx_t *ctx, const char *s) +{ + int len, ere = ctx->cflags & REG_EXTENDED; + const char *p; + tre_ast_node_t *node; + wchar_t wc; + switch (*s) { + case '[': + return parse_bracket(ctx, s+1); + case '\\': + p = tre_expand_macro(s+1); + if (p) { + /* assume \X expansion is a single atom */ + reg_errcode_t err = parse_atom(ctx, p); + ctx->s = s+2; + return err; + } + /* extensions: \b, \B, \<, \>, \xHH \x{HHHH} */ + switch (*++s) { + case 0: + return REG_EESCAPE; + case 'b': + node = tre_ast_new_literal(ctx->mem, ASSERTION, ASSERT_AT_WB, -1); + break; + case 'B': + node = tre_ast_new_literal(ctx->mem, ASSERTION, ASSERT_AT_WB_NEG, -1); + break; + case '<': + node = tre_ast_new_literal(ctx->mem, ASSERTION, ASSERT_AT_BOW, -1); + break; + case '>': + node = tre_ast_new_literal(ctx->mem, ASSERTION, ASSERT_AT_EOW, -1); + break; + case 'x': + s++; + int i, v = 0, c; + len = 2; + if (*s == '{') { + len = 8; + s++; + } + for (i=0; imem, v, v, ctx->position++); + s--; + break; + case '{': + case '+': + case '?': + /* extension: treat \+, \? as repetitions in BRE */ + /* reject repetitions after empty expression in BRE */ + if (!ere) + return REG_BADRPT; + case '|': + /* extension: treat \| as alternation in BRE */ + if (!ere) { + node = tre_ast_new_literal(ctx->mem, EMPTY, -1, -1); + s--; + goto end; + } + /* fallthrough */ + default: + if (!ere && (unsigned)*s-'1' < 9) { + /* back reference */ + int val = *s - '0'; + node = tre_ast_new_literal(ctx->mem, BACKREF, val, ctx->position++); + ctx->max_backref = MAX(val, ctx->max_backref); + } else { + /* extension: accept unknown escaped char + as a literal */ + goto parse_literal; + } + } + s++; + break; + case '.': + if (ctx->cflags & REG_NEWLINE) { + tre_ast_node_t *tmp1, *tmp2; + tmp1 = tre_ast_new_literal(ctx->mem, 0, '\n'-1, ctx->position++); + tmp2 = tre_ast_new_literal(ctx->mem, '\n'+1, TRE_CHAR_MAX, ctx->position++); + if (tmp1 && tmp2) + node = tre_ast_new_union(ctx->mem, tmp1, tmp2); + else + node = 0; + } else { + node = tre_ast_new_literal(ctx->mem, 0, TRE_CHAR_MAX, ctx->position++); + } + s++; + break; + case '^': + /* '^' has a special meaning everywhere in EREs, and at beginning of BRE. */ + if (!ere && s != ctx->start) + goto parse_literal; + node = tre_ast_new_literal(ctx->mem, ASSERTION, ASSERT_AT_BOL, -1); + s++; + break; + case '$': + /* '$' is special everywhere in EREs, and at the end of a BRE subexpression. */ + if (!ere && s[1] && (s[1]!='\\'|| (s[2]!=')' && s[2]!='|'))) + goto parse_literal; + node = tre_ast_new_literal(ctx->mem, ASSERTION, ASSERT_AT_EOL, -1); + s++; + break; + case '*': + case '{': + case '+': + case '?': + /* reject repetitions after empty expression in ERE */ + if (ere) + return REG_BADRPT; + case '|': + if (!ere) + goto parse_literal; + case 0: + node = tre_ast_new_literal(ctx->mem, EMPTY, -1, -1); + break; + default: +parse_literal: + len = mbtowc(&wc, s, -1); + if (len < 0) + return REG_BADPAT; + if (ctx->cflags & REG_ICASE && (tre_isupper(wc) || tre_islower(wc))) { + tre_ast_node_t *tmp1, *tmp2; + /* multiple opposite case characters are not supported */ + tmp1 = tre_ast_new_literal(ctx->mem, tre_toupper(wc), tre_toupper(wc), ctx->position); + tmp2 = tre_ast_new_literal(ctx->mem, tre_tolower(wc), tre_tolower(wc), ctx->position); + if (tmp1 && tmp2) + node = tre_ast_new_union(ctx->mem, tmp1, tmp2); + else + node = 0; + } else { + node = tre_ast_new_literal(ctx->mem, wc, wc, ctx->position); + } + ctx->position++; + s += len; + break; + } +end: + if (!node) + return REG_ESPACE; + ctx->n = node; + ctx->s = s; + return REG_OK; +} + +#define PUSHPTR(err, s, v) do { \ + if ((err = tre_stack_push_voidptr(s, v)) != REG_OK) \ + return err; \ +} while(0) + +#define PUSHINT(err, s, v) do { \ + if ((err = tre_stack_push_int(s, v)) != REG_OK) \ + return err; \ +} while(0) + +static reg_errcode_t tre_parse(tre_parse_ctx_t *ctx) +{ + tre_ast_node_t *nbranch=0, *nunion=0; + int ere = ctx->cflags & REG_EXTENDED; + const char *s = ctx->start; + int subid = 0; + int depth = 0; + reg_errcode_t err; + tre_stack_t *stack = ctx->stack; + + PUSHINT(err, stack, subid++); + for (;;) { + if ((!ere && *s == '\\' && s[1] == '(') || + (ere && *s == '(')) { + PUSHPTR(err, stack, nunion); + PUSHPTR(err, stack, nbranch); + PUSHINT(err, stack, subid++); + s++; + if (!ere) + s++; + depth++; + nbranch = nunion = 0; + ctx->start = s; + continue; + } + if ((!ere && *s == '\\' && s[1] == ')') || + (ere && *s == ')' && depth)) { + ctx->n = tre_ast_new_literal(ctx->mem, EMPTY, -1, -1); + if (!ctx->n) + return REG_ESPACE; + } else { + err = parse_atom(ctx, s); + if (err != REG_OK) + return err; + s = ctx->s; + } + + parse_iter: + for (;;) { + int min, max; + + if (*s!='\\' && *s!='*') { + if (!ere) + break; + if (*s!='+' && *s!='?' && *s!='{') + break; + } + if (*s=='\\' && ere) + break; + /* extension: treat \+, \? as repetitions in BRE */ + if (*s=='\\' && s[1]!='+' && s[1]!='?' && s[1]!='{') + break; + if (*s=='\\') + s++; + + /* handle ^* at the start of a BRE. */ + if (!ere && s==ctx->start+1 && s[-1]=='^') + break; + + /* extension: multiple consecutive *+?{,} is unspecified, + but (a+)+ has to be supported so accepting a++ makes + sense, note however that the RE_DUP_MAX limit can be + circumvented: (a{255}){255} uses a lot of memory.. */ + if (*s=='{') { + s = parse_dup(s+1, ere, &min, &max); + if (!s) + return REG_BADBR; + } else { + min=0; + max=-1; + if (*s == '+') + min = 1; + if (*s == '?') + max = 1; + s++; + } + if (max == 0) + ctx->n = tre_ast_new_literal(ctx->mem, EMPTY, -1, -1); + else + ctx->n = tre_ast_new_iter(ctx->mem, ctx->n, min, max, 0); + if (!ctx->n) + return REG_ESPACE; + } + + nbranch = tre_ast_new_catenation(ctx->mem, nbranch, ctx->n); + if ((ere && *s == '|') || + (ere && *s == ')' && depth) || + (!ere && *s == '\\' && s[1] == ')') || + /* extension: treat \| as alternation in BRE */ + (!ere && *s == '\\' && s[1] == '|') || + !*s) { + /* extension: empty branch is unspecified (), (|a), (a|) + here they are not rejected but match on empty string */ + int c = *s; + nunion = tre_ast_new_union(ctx->mem, nunion, nbranch); + nbranch = 0; + + if (c == '\\' && s[1] == '|') { + s+=2; + ctx->start = s; + } else if (c == '|') { + s++; + ctx->start = s; + } else { + if (c == '\\') { + if (!depth) return REG_EPAREN; + s+=2; + } else if (c == ')') + s++; + depth--; + err = marksub(ctx, nunion, tre_stack_pop_int(stack)); + if (err != REG_OK) + return err; + if (!c && depth<0) { + ctx->submatch_id = subid; + return REG_OK; + } + if (!c || depth<0) + return REG_EPAREN; + nbranch = tre_stack_pop_voidptr(stack); + nunion = tre_stack_pop_voidptr(stack); + goto parse_iter; + } + } + } +} + + +/*********************************************************************** + from tre-compile.c +***********************************************************************/ + + +/* + TODO: + - Fix tre_ast_to_tnfa() to recurse using a stack instead of recursive + function calls. +*/ + +/* + Algorithms to setup tags so that submatch addressing can be done. +*/ + + +/* Inserts a catenation node to the root of the tree given in `node'. + As the left child a new tag with number `tag_id' to `node' is added, + and the right child is the old root. */ +static reg_errcode_t +tre_add_tag_left(tre_mem_t mem, tre_ast_node_t *node, int tag_id) +{ + tre_catenation_t *c; + + c = tre_mem_alloc(mem, sizeof(*c)); + if (c == NULL) + return REG_ESPACE; + c->left = tre_ast_new_literal(mem, TAG, tag_id, -1); + if (c->left == NULL) + return REG_ESPACE; + c->right = tre_mem_alloc(mem, sizeof(tre_ast_node_t)); + if (c->right == NULL) + return REG_ESPACE; + + c->right->obj = node->obj; + c->right->type = node->type; + c->right->nullable = -1; + c->right->submatch_id = -1; + c->right->firstpos = NULL; + c->right->lastpos = NULL; + c->right->num_tags = 0; + c->right->num_submatches = 0; + node->obj = c; + node->type = CATENATION; + return REG_OK; +} + +/* Inserts a catenation node to the root of the tree given in `node'. + As the right child a new tag with number `tag_id' to `node' is added, + and the left child is the old root. */ +static reg_errcode_t +tre_add_tag_right(tre_mem_t mem, tre_ast_node_t *node, int tag_id) +{ + tre_catenation_t *c; + + c = tre_mem_alloc(mem, sizeof(*c)); + if (c == NULL) + return REG_ESPACE; + c->right = tre_ast_new_literal(mem, TAG, tag_id, -1); + if (c->right == NULL) + return REG_ESPACE; + c->left = tre_mem_alloc(mem, sizeof(tre_ast_node_t)); + if (c->left == NULL) + return REG_ESPACE; + + c->left->obj = node->obj; + c->left->type = node->type; + c->left->nullable = -1; + c->left->submatch_id = -1; + c->left->firstpos = NULL; + c->left->lastpos = NULL; + c->left->num_tags = 0; + c->left->num_submatches = 0; + node->obj = c; + node->type = CATENATION; + return REG_OK; +} + +typedef enum { + ADDTAGS_RECURSE, + ADDTAGS_AFTER_ITERATION, + ADDTAGS_AFTER_UNION_LEFT, + ADDTAGS_AFTER_UNION_RIGHT, + ADDTAGS_AFTER_CAT_LEFT, + ADDTAGS_AFTER_CAT_RIGHT, + ADDTAGS_SET_SUBMATCH_END +} tre_addtags_symbol_t; + + +typedef struct { + int tag; + int next_tag; +} tre_tag_states_t; + + +/* Go through `regset' and set submatch data for submatches that are + using this tag. */ +static void +tre_purge_regset(int *regset, tre_tnfa_t *tnfa, int tag) +{ + int i; + + for (i = 0; regset[i] >= 0; i++) + { + int id = regset[i] / 2; + int start = !(regset[i] % 2); + if (start) + tnfa->submatch_data[id].so_tag = tag; + else + tnfa->submatch_data[id].eo_tag = tag; + } + regset[0] = -1; +} + + +/* Adds tags to appropriate locations in the parse tree in `tree', so that + subexpressions marked for submatch addressing can be traced. */ +static reg_errcode_t +tre_add_tags(tre_mem_t mem, tre_stack_t *stack, tre_ast_node_t *tree, + tre_tnfa_t *tnfa) +{ + reg_errcode_t status = REG_OK; + tre_addtags_symbol_t symbol; + tre_ast_node_t *node = tree; /* Tree node we are currently looking at. */ + int bottom = tre_stack_num_objects(stack); + /* True for first pass (counting number of needed tags) */ + int first_pass = (mem == NULL || tnfa == NULL); + int *regset, *orig_regset; + int num_tags = 0; /* Total number of tags. */ + int num_minimals = 0; /* Number of special minimal tags. */ + int tag = 0; /* The tag that is to be added next. */ + int next_tag = 1; /* Next tag to use after this one. */ + int *parents; /* Stack of submatches the current submatch is + contained in. */ + int minimal_tag = -1; /* Tag that marks the beginning of a minimal match. */ + tre_tag_states_t *saved_states; + + tre_tag_direction_t direction = TRE_TAG_MINIMIZE; + if (!first_pass) + { + tnfa->end_tag = 0; + tnfa->minimal_tags[0] = -1; + } + + regset = xmalloc(sizeof(*regset) * ((tnfa->num_submatches + 1) * 2)); + if (regset == NULL) + return REG_ESPACE; + regset[0] = -1; + orig_regset = regset; + + parents = xmalloc(sizeof(*parents) * (tnfa->num_submatches + 1)); + if (parents == NULL) + { + xfree(regset); + return REG_ESPACE; + } + parents[0] = -1; + + saved_states = xmalloc(sizeof(*saved_states) * (tnfa->num_submatches + 1)); + if (saved_states == NULL) + { + xfree(regset); + xfree(parents); + return REG_ESPACE; + } + else + { + unsigned int i; + for (i = 0; i <= tnfa->num_submatches; i++) + saved_states[i].tag = -1; + } + + STACK_PUSH(stack, voidptr, node); + STACK_PUSH(stack, int, ADDTAGS_RECURSE); + + while (tre_stack_num_objects(stack) > bottom) + { + if (status != REG_OK) + break; + + symbol = (tre_addtags_symbol_t)tre_stack_pop_int(stack); + switch (symbol) + { + + case ADDTAGS_SET_SUBMATCH_END: + { + int id = tre_stack_pop_int(stack); + int i; + + /* Add end of this submatch to regset. */ + for (i = 0; regset[i] >= 0; i++); + regset[i] = id * 2 + 1; + regset[i + 1] = -1; + + /* Pop this submatch from the parents stack. */ + for (i = 0; parents[i] >= 0; i++); + parents[i - 1] = -1; + break; + } + + case ADDTAGS_RECURSE: + node = tre_stack_pop_voidptr(stack); + + if (node->submatch_id >= 0) + { + int id = node->submatch_id; + int i; + + + /* Add start of this submatch to regset. */ + for (i = 0; regset[i] >= 0; i++); + regset[i] = id * 2; + regset[i + 1] = -1; + + if (!first_pass) + { + for (i = 0; parents[i] >= 0; i++); + tnfa->submatch_data[id].parents = NULL; + if (i > 0) + { + int *p = xmalloc(sizeof(*p) * (i + 1)); + if (p == NULL) + { + status = REG_ESPACE; + break; + } + assert(tnfa->submatch_data[id].parents == NULL); + tnfa->submatch_data[id].parents = p; + for (i = 0; parents[i] >= 0; i++) + p[i] = parents[i]; + p[i] = -1; + } + } + + /* Add end of this submatch to regset after processing this + node. */ + STACK_PUSHX(stack, int, node->submatch_id); + STACK_PUSHX(stack, int, ADDTAGS_SET_SUBMATCH_END); + } + + switch (node->type) + { + case LITERAL: + { + tre_literal_t *lit = node->obj; + + if (!IS_SPECIAL(lit) || IS_BACKREF(lit)) + { + int i; + if (regset[0] >= 0) + { + /* Regset is not empty, so add a tag before the + literal or backref. */ + if (!first_pass) + { + status = tre_add_tag_left(mem, node, tag); + tnfa->tag_directions[tag] = direction; + if (minimal_tag >= 0) + { + for (i = 0; tnfa->minimal_tags[i] >= 0; i++); + tnfa->minimal_tags[i] = tag; + tnfa->minimal_tags[i + 1] = minimal_tag; + tnfa->minimal_tags[i + 2] = -1; + minimal_tag = -1; + num_minimals++; + } + tre_purge_regset(regset, tnfa, tag); + } + else + { + node->num_tags = 1; + } + + regset[0] = -1; + tag = next_tag; + num_tags++; + next_tag++; + } + } + else + { + assert(!IS_TAG(lit)); + } + break; + } + case CATENATION: + { + tre_catenation_t *cat = node->obj; + tre_ast_node_t *left = cat->left; + tre_ast_node_t *right = cat->right; + int reserved_tag = -1; + + + /* After processing right child. */ + STACK_PUSHX(stack, voidptr, node); + STACK_PUSHX(stack, int, ADDTAGS_AFTER_CAT_RIGHT); + + /* Process right child. */ + STACK_PUSHX(stack, voidptr, right); + STACK_PUSHX(stack, int, ADDTAGS_RECURSE); + + /* After processing left child. */ + STACK_PUSHX(stack, int, next_tag + left->num_tags); + if (left->num_tags > 0 && right->num_tags > 0) + { + /* Reserve the next tag to the right child. */ + reserved_tag = next_tag; + next_tag++; + } + STACK_PUSHX(stack, int, reserved_tag); + STACK_PUSHX(stack, int, ADDTAGS_AFTER_CAT_LEFT); + + /* Process left child. */ + STACK_PUSHX(stack, voidptr, left); + STACK_PUSHX(stack, int, ADDTAGS_RECURSE); + + } + break; + case ITERATION: + { + tre_iteration_t *iter = node->obj; + + if (first_pass) + { + STACK_PUSHX(stack, int, regset[0] >= 0 || iter->minimal); + } + else + { + STACK_PUSHX(stack, int, tag); + STACK_PUSHX(stack, int, iter->minimal); + } + STACK_PUSHX(stack, voidptr, node); + STACK_PUSHX(stack, int, ADDTAGS_AFTER_ITERATION); + + STACK_PUSHX(stack, voidptr, iter->arg); + STACK_PUSHX(stack, int, ADDTAGS_RECURSE); + + /* Regset is not empty, so add a tag here. */ + if (regset[0] >= 0 || iter->minimal) + { + if (!first_pass) + { + int i; + status = tre_add_tag_left(mem, node, tag); + if (iter->minimal) + tnfa->tag_directions[tag] = TRE_TAG_MAXIMIZE; + else + tnfa->tag_directions[tag] = direction; + if (minimal_tag >= 0) + { + for (i = 0; tnfa->minimal_tags[i] >= 0; i++); + tnfa->minimal_tags[i] = tag; + tnfa->minimal_tags[i + 1] = minimal_tag; + tnfa->minimal_tags[i + 2] = -1; + minimal_tag = -1; + num_minimals++; + } + tre_purge_regset(regset, tnfa, tag); + } + + regset[0] = -1; + tag = next_tag; + num_tags++; + next_tag++; + } + direction = TRE_TAG_MINIMIZE; + } + break; + case UNION: + { + tre_union_t *uni = node->obj; + tre_ast_node_t *left = uni->left; + tre_ast_node_t *right = uni->right; + int left_tag; + int right_tag; + + if (regset[0] >= 0) + { + left_tag = next_tag; + right_tag = next_tag + 1; + } + else + { + left_tag = tag; + right_tag = next_tag; + } + + /* After processing right child. */ + STACK_PUSHX(stack, int, right_tag); + STACK_PUSHX(stack, int, left_tag); + STACK_PUSHX(stack, voidptr, regset); + STACK_PUSHX(stack, int, regset[0] >= 0); + STACK_PUSHX(stack, voidptr, node); + STACK_PUSHX(stack, voidptr, right); + STACK_PUSHX(stack, voidptr, left); + STACK_PUSHX(stack, int, ADDTAGS_AFTER_UNION_RIGHT); + + /* Process right child. */ + STACK_PUSHX(stack, voidptr, right); + STACK_PUSHX(stack, int, ADDTAGS_RECURSE); + + /* After processing left child. */ + STACK_PUSHX(stack, int, ADDTAGS_AFTER_UNION_LEFT); + + /* Process left child. */ + STACK_PUSHX(stack, voidptr, left); + STACK_PUSHX(stack, int, ADDTAGS_RECURSE); + + /* Regset is not empty, so add a tag here. */ + if (regset[0] >= 0) + { + if (!first_pass) + { + int i; + status = tre_add_tag_left(mem, node, tag); + tnfa->tag_directions[tag] = direction; + if (minimal_tag >= 0) + { + for (i = 0; tnfa->minimal_tags[i] >= 0; i++); + tnfa->minimal_tags[i] = tag; + tnfa->minimal_tags[i + 1] = minimal_tag; + tnfa->minimal_tags[i + 2] = -1; + minimal_tag = -1; + num_minimals++; + } + tre_purge_regset(regset, tnfa, tag); + } + + regset[0] = -1; + tag = next_tag; + num_tags++; + next_tag++; + } + + if (node->num_submatches > 0) + { + /* The next two tags are reserved for markers. */ + next_tag++; + tag = next_tag; + next_tag++; + } + + break; + } + } + + if (node->submatch_id >= 0) + { + int i; + /* Push this submatch on the parents stack. */ + for (i = 0; parents[i] >= 0; i++); + parents[i] = node->submatch_id; + parents[i + 1] = -1; + } + + break; /* end case: ADDTAGS_RECURSE */ + + case ADDTAGS_AFTER_ITERATION: + { + int minimal = 0; + int enter_tag; + node = tre_stack_pop_voidptr(stack); + if (first_pass) + { + node->num_tags = ((tre_iteration_t *)node->obj)->arg->num_tags + + tre_stack_pop_int(stack); + minimal_tag = -1; + } + else + { + minimal = tre_stack_pop_int(stack); + enter_tag = tre_stack_pop_int(stack); + if (minimal) + minimal_tag = enter_tag; + } + + if (!first_pass) + { + if (minimal) + direction = TRE_TAG_MINIMIZE; + else + direction = TRE_TAG_MAXIMIZE; + } + break; + } + + case ADDTAGS_AFTER_CAT_LEFT: + { + int new_tag = tre_stack_pop_int(stack); + next_tag = tre_stack_pop_int(stack); + if (new_tag >= 0) + { + tag = new_tag; + } + break; + } + + case ADDTAGS_AFTER_CAT_RIGHT: + node = tre_stack_pop_voidptr(stack); + if (first_pass) + node->num_tags = ((tre_catenation_t *)node->obj)->left->num_tags + + ((tre_catenation_t *)node->obj)->right->num_tags; + break; + + case ADDTAGS_AFTER_UNION_LEFT: + /* Lift the bottom of the `regset' array so that when processing + the right operand the items currently in the array are + invisible. The original bottom was saved at ADDTAGS_UNION and + will be restored at ADDTAGS_AFTER_UNION_RIGHT below. */ + while (*regset >= 0) + regset++; + break; + + case ADDTAGS_AFTER_UNION_RIGHT: + { + int added_tags, tag_left, tag_right; + tre_ast_node_t *left = tre_stack_pop_voidptr(stack); + tre_ast_node_t *right = tre_stack_pop_voidptr(stack); + node = tre_stack_pop_voidptr(stack); + added_tags = tre_stack_pop_int(stack); + if (first_pass) + { + node->num_tags = ((tre_union_t *)node->obj)->left->num_tags + + ((tre_union_t *)node->obj)->right->num_tags + added_tags + + ((node->num_submatches > 0) ? 2 : 0); + } + regset = tre_stack_pop_voidptr(stack); + tag_left = tre_stack_pop_int(stack); + tag_right = tre_stack_pop_int(stack); + + /* Add tags after both children, the left child gets a smaller + tag than the right child. This guarantees that we prefer + the left child over the right child. */ + /* XXX - This is not always necessary (if the children have + tags which must be seen for every match of that child). */ + /* XXX - Check if this is the only place where tre_add_tag_right + is used. If so, use tre_add_tag_left (putting the tag before + the child as opposed after the child) and throw away + tre_add_tag_right. */ + if (node->num_submatches > 0) + { + if (!first_pass) + { + status = tre_add_tag_right(mem, left, tag_left); + tnfa->tag_directions[tag_left] = TRE_TAG_MAXIMIZE; + if (status == REG_OK) + status = tre_add_tag_right(mem, right, tag_right); + tnfa->tag_directions[tag_right] = TRE_TAG_MAXIMIZE; + } + num_tags += 2; + } + direction = TRE_TAG_MAXIMIZE; + break; + } + + default: + assert(0); + break; + + } /* end switch(symbol) */ + } /* end while(tre_stack_num_objects(stack) > bottom) */ + + if (!first_pass) + tre_purge_regset(regset, tnfa, tag); + + if (!first_pass && minimal_tag >= 0) + { + int i; + for (i = 0; tnfa->minimal_tags[i] >= 0; i++); + tnfa->minimal_tags[i] = tag; + tnfa->minimal_tags[i + 1] = minimal_tag; + tnfa->minimal_tags[i + 2] = -1; + minimal_tag = -1; + num_minimals++; + } + + assert(tree->num_tags == num_tags); + tnfa->end_tag = num_tags; + tnfa->num_tags = num_tags; + tnfa->num_minimals = num_minimals; + xfree(orig_regset); + xfree(parents); + xfree(saved_states); + return status; +} + + + +/* + AST to TNFA compilation routines. +*/ + +typedef enum { + COPY_RECURSE, + COPY_SET_RESULT_PTR +} tre_copyast_symbol_t; + +/* Flags for tre_copy_ast(). */ +#define COPY_REMOVE_TAGS 1 +#define COPY_MAXIMIZE_FIRST_TAG 2 + +static reg_errcode_t +tre_copy_ast(tre_mem_t mem, tre_stack_t *stack, tre_ast_node_t *ast, + int flags, int *pos_add, tre_tag_direction_t *tag_directions, + tre_ast_node_t **copy, int *max_pos) +{ + reg_errcode_t status = REG_OK; + int bottom = tre_stack_num_objects(stack); + int num_copied = 0; + int first_tag = 1; + tre_ast_node_t **result = copy; + tre_copyast_symbol_t symbol; + + STACK_PUSH(stack, voidptr, ast); + STACK_PUSH(stack, int, COPY_RECURSE); + + while (status == REG_OK && tre_stack_num_objects(stack) > bottom) + { + tre_ast_node_t *node; + if (status != REG_OK) + break; + + symbol = (tre_copyast_symbol_t)tre_stack_pop_int(stack); + switch (symbol) + { + case COPY_SET_RESULT_PTR: + result = tre_stack_pop_voidptr(stack); + break; + case COPY_RECURSE: + node = tre_stack_pop_voidptr(stack); + switch (node->type) + { + case LITERAL: + { + tre_literal_t *lit = node->obj; + int pos = lit->position; + int min = lit->code_min; + int max = lit->code_max; + if (!IS_SPECIAL(lit) || IS_BACKREF(lit)) + { + /* XXX - e.g. [ab] has only one position but two + nodes, so we are creating holes in the state space + here. Not fatal, just wastes memory. */ + pos += *pos_add; + num_copied++; + } + else if (IS_TAG(lit) && (flags & COPY_REMOVE_TAGS)) + { + /* Change this tag to empty. */ + min = EMPTY; + max = pos = -1; + } + else if (IS_TAG(lit) && (flags & COPY_MAXIMIZE_FIRST_TAG) + && first_tag) + { + /* Maximize the first tag. */ + tag_directions[max] = TRE_TAG_MAXIMIZE; + first_tag = 0; + } + *result = tre_ast_new_literal(mem, min, max, pos); + if (*result == NULL) + status = REG_ESPACE; + else { + tre_literal_t *p = (*result)->obj; + p->class = lit->class; + p->neg_classes = lit->neg_classes; + } + + if (pos > *max_pos) + *max_pos = pos; + break; + } + case UNION: + { + tre_union_t *uni = node->obj; + tre_union_t *tmp; + *result = tre_ast_new_union(mem, uni->left, uni->right); + if (*result == NULL) + { + status = REG_ESPACE; + break; + } + tmp = (*result)->obj; + result = &tmp->left; + STACK_PUSHX(stack, voidptr, uni->right); + STACK_PUSHX(stack, int, COPY_RECURSE); + STACK_PUSHX(stack, voidptr, &tmp->right); + STACK_PUSHX(stack, int, COPY_SET_RESULT_PTR); + STACK_PUSHX(stack, voidptr, uni->left); + STACK_PUSHX(stack, int, COPY_RECURSE); + break; + } + case CATENATION: + { + tre_catenation_t *cat = node->obj; + tre_catenation_t *tmp; + *result = tre_ast_new_catenation(mem, cat->left, cat->right); + if (*result == NULL) + { + status = REG_ESPACE; + break; + } + tmp = (*result)->obj; + tmp->left = NULL; + tmp->right = NULL; + result = &tmp->left; + + STACK_PUSHX(stack, voidptr, cat->right); + STACK_PUSHX(stack, int, COPY_RECURSE); + STACK_PUSHX(stack, voidptr, &tmp->right); + STACK_PUSHX(stack, int, COPY_SET_RESULT_PTR); + STACK_PUSHX(stack, voidptr, cat->left); + STACK_PUSHX(stack, int, COPY_RECURSE); + break; + } + case ITERATION: + { + tre_iteration_t *iter = node->obj; + STACK_PUSHX(stack, voidptr, iter->arg); + STACK_PUSHX(stack, int, COPY_RECURSE); + *result = tre_ast_new_iter(mem, iter->arg, iter->min, + iter->max, iter->minimal); + if (*result == NULL) + { + status = REG_ESPACE; + break; + } + iter = (*result)->obj; + result = &iter->arg; + break; + } + default: + assert(0); + break; + } + break; + } + } + *pos_add += num_copied; + return status; +} + +typedef enum { + EXPAND_RECURSE, + EXPAND_AFTER_ITER +} tre_expand_ast_symbol_t; + +/* Expands each iteration node that has a finite nonzero minimum or maximum + iteration count to a catenated sequence of copies of the node. */ +static reg_errcode_t +tre_expand_ast(tre_mem_t mem, tre_stack_t *stack, tre_ast_node_t *ast, + int *position, tre_tag_direction_t *tag_directions) +{ + reg_errcode_t status = REG_OK; + int bottom = tre_stack_num_objects(stack); + int pos_add = 0; + int pos_add_total = 0; + int max_pos = 0; + int iter_depth = 0; + + STACK_PUSHR(stack, voidptr, ast); + STACK_PUSHR(stack, int, EXPAND_RECURSE); + while (status == REG_OK && tre_stack_num_objects(stack) > bottom) + { + tre_ast_node_t *node; + tre_expand_ast_symbol_t symbol; + + if (status != REG_OK) + break; + + symbol = (tre_expand_ast_symbol_t)tre_stack_pop_int(stack); + node = tre_stack_pop_voidptr(stack); + switch (symbol) + { + case EXPAND_RECURSE: + switch (node->type) + { + case LITERAL: + { + tre_literal_t *lit= node->obj; + if (!IS_SPECIAL(lit) || IS_BACKREF(lit)) + { + lit->position += pos_add; + if (lit->position > max_pos) + max_pos = lit->position; + } + break; + } + case UNION: + { + tre_union_t *uni = node->obj; + STACK_PUSHX(stack, voidptr, uni->right); + STACK_PUSHX(stack, int, EXPAND_RECURSE); + STACK_PUSHX(stack, voidptr, uni->left); + STACK_PUSHX(stack, int, EXPAND_RECURSE); + break; + } + case CATENATION: + { + tre_catenation_t *cat = node->obj; + STACK_PUSHX(stack, voidptr, cat->right); + STACK_PUSHX(stack, int, EXPAND_RECURSE); + STACK_PUSHX(stack, voidptr, cat->left); + STACK_PUSHX(stack, int, EXPAND_RECURSE); + break; + } + case ITERATION: + { + tre_iteration_t *iter = node->obj; + STACK_PUSHX(stack, int, pos_add); + STACK_PUSHX(stack, voidptr, node); + STACK_PUSHX(stack, int, EXPAND_AFTER_ITER); + STACK_PUSHX(stack, voidptr, iter->arg); + STACK_PUSHX(stack, int, EXPAND_RECURSE); + /* If we are going to expand this node at EXPAND_AFTER_ITER + then don't increase the `pos' fields of the nodes now, it + will get done when expanding. */ + if (iter->min > 1 || iter->max > 1) + pos_add = 0; + iter_depth++; + break; + } + default: + assert(0); + break; + } + break; + case EXPAND_AFTER_ITER: + { + tre_iteration_t *iter = node->obj; + int pos_add_last; + pos_add = tre_stack_pop_int(stack); + pos_add_last = pos_add; + if (iter->min > 1 || iter->max > 1) + { + tre_ast_node_t *seq1 = NULL, *seq2 = NULL; + int j; + int pos_add_save = pos_add; + + /* Create a catenated sequence of copies of the node. */ + for (j = 0; j < iter->min; j++) + { + tre_ast_node_t *copy; + /* Remove tags from all but the last copy. */ + int flags = ((j + 1 < iter->min) + ? COPY_REMOVE_TAGS + : COPY_MAXIMIZE_FIRST_TAG); + pos_add_save = pos_add; + status = tre_copy_ast(mem, stack, iter->arg, flags, + &pos_add, tag_directions, ©, + &max_pos); + if (status != REG_OK) + return status; + if (seq1 != NULL) + seq1 = tre_ast_new_catenation(mem, seq1, copy); + else + seq1 = copy; + if (seq1 == NULL) + return REG_ESPACE; + } + + if (iter->max == -1) + { + /* No upper limit. */ + pos_add_save = pos_add; + status = tre_copy_ast(mem, stack, iter->arg, 0, + &pos_add, NULL, &seq2, &max_pos); + if (status != REG_OK) + return status; + seq2 = tre_ast_new_iter(mem, seq2, 0, -1, 0); + if (seq2 == NULL) + return REG_ESPACE; + } + else + { + for (j = iter->min; j < iter->max; j++) + { + tre_ast_node_t *tmp, *copy; + pos_add_save = pos_add; + status = tre_copy_ast(mem, stack, iter->arg, 0, + &pos_add, NULL, ©, &max_pos); + if (status != REG_OK) + return status; + if (seq2 != NULL) + seq2 = tre_ast_new_catenation(mem, copy, seq2); + else + seq2 = copy; + if (seq2 == NULL) + return REG_ESPACE; + tmp = tre_ast_new_literal(mem, EMPTY, -1, -1); + if (tmp == NULL) + return REG_ESPACE; + seq2 = tre_ast_new_union(mem, tmp, seq2); + if (seq2 == NULL) + return REG_ESPACE; + } + } + + pos_add = pos_add_save; + if (seq1 == NULL) + seq1 = seq2; + else if (seq2 != NULL) + seq1 = tre_ast_new_catenation(mem, seq1, seq2); + if (seq1 == NULL) + return REG_ESPACE; + node->obj = seq1->obj; + node->type = seq1->type; + } + + iter_depth--; + pos_add_total += pos_add - pos_add_last; + if (iter_depth == 0) + pos_add = pos_add_total; + + break; + } + default: + assert(0); + break; + } + } + + *position += pos_add_total; + + /* `max_pos' should never be larger than `*position' if the above + code works, but just an extra safeguard let's make sure + `*position' is set large enough so enough memory will be + allocated for the transition table. */ + if (max_pos > *position) + *position = max_pos; + + return status; +} + +static tre_pos_and_tags_t * +tre_set_empty(tre_mem_t mem) +{ + tre_pos_and_tags_t *new_set; + + new_set = tre_mem_calloc(mem, sizeof(*new_set)); + if (new_set == NULL) + return NULL; + + new_set[0].position = -1; + new_set[0].code_min = -1; + new_set[0].code_max = -1; + + return new_set; +} + +static tre_pos_and_tags_t * +tre_set_one(tre_mem_t mem, int position, int code_min, int code_max, + tre_ctype_t class, tre_ctype_t *neg_classes, int backref) +{ + tre_pos_and_tags_t *new_set; + + new_set = tre_mem_calloc(mem, sizeof(*new_set) * 2); + if (new_set == NULL) + return NULL; + + new_set[0].position = position; + new_set[0].code_min = code_min; + new_set[0].code_max = code_max; + new_set[0].class = class; + new_set[0].neg_classes = neg_classes; + new_set[0].backref = backref; + new_set[1].position = -1; + new_set[1].code_min = -1; + new_set[1].code_max = -1; + + return new_set; +} + +static tre_pos_and_tags_t * +tre_set_union(tre_mem_t mem, tre_pos_and_tags_t *set1, tre_pos_and_tags_t *set2, + int *tags, int assertions) +{ + int s1, s2, i, j; + tre_pos_and_tags_t *new_set; + int *new_tags; + int num_tags; + + for (num_tags = 0; tags != NULL && tags[num_tags] >= 0; num_tags++); + for (s1 = 0; set1[s1].position >= 0; s1++); + for (s2 = 0; set2[s2].position >= 0; s2++); + new_set = tre_mem_calloc(mem, sizeof(*new_set) * (s1 + s2 + 1)); + if (!new_set ) + return NULL; + + for (s1 = 0; set1[s1].position >= 0; s1++) + { + new_set[s1].position = set1[s1].position; + new_set[s1].code_min = set1[s1].code_min; + new_set[s1].code_max = set1[s1].code_max; + new_set[s1].assertions = set1[s1].assertions | assertions; + new_set[s1].class = set1[s1].class; + new_set[s1].neg_classes = set1[s1].neg_classes; + new_set[s1].backref = set1[s1].backref; + if (set1[s1].tags == NULL && tags == NULL) + new_set[s1].tags = NULL; + else + { + for (i = 0; set1[s1].tags != NULL && set1[s1].tags[i] >= 0; i++); + new_tags = tre_mem_alloc(mem, (sizeof(*new_tags) + * (i + num_tags + 1))); + if (new_tags == NULL) + return NULL; + for (j = 0; j < i; j++) + new_tags[j] = set1[s1].tags[j]; + for (i = 0; i < num_tags; i++) + new_tags[j + i] = tags[i]; + new_tags[j + i] = -1; + new_set[s1].tags = new_tags; + } + } + + for (s2 = 0; set2[s2].position >= 0; s2++) + { + new_set[s1 + s2].position = set2[s2].position; + new_set[s1 + s2].code_min = set2[s2].code_min; + new_set[s1 + s2].code_max = set2[s2].code_max; + /* XXX - why not | assertions here as well? */ + new_set[s1 + s2].assertions = set2[s2].assertions; + new_set[s1 + s2].class = set2[s2].class; + new_set[s1 + s2].neg_classes = set2[s2].neg_classes; + new_set[s1 + s2].backref = set2[s2].backref; + if (set2[s2].tags == NULL) + new_set[s1 + s2].tags = NULL; + else + { + for (i = 0; set2[s2].tags[i] >= 0; i++); + new_tags = tre_mem_alloc(mem, sizeof(*new_tags) * (i + 1)); + if (new_tags == NULL) + return NULL; + for (j = 0; j < i; j++) + new_tags[j] = set2[s2].tags[j]; + new_tags[j] = -1; + new_set[s1 + s2].tags = new_tags; + } + } + new_set[s1 + s2].position = -1; + return new_set; +} + +/* Finds the empty path through `node' which is the one that should be + taken according to POSIX.2 rules, and adds the tags on that path to + `tags'. `tags' may be NULL. If `num_tags_seen' is not NULL, it is + set to the number of tags seen on the path. */ +static reg_errcode_t +tre_match_empty(tre_stack_t *stack, tre_ast_node_t *node, int *tags, + int *assertions, int *num_tags_seen) +{ + tre_literal_t *lit; + tre_union_t *uni; + tre_catenation_t *cat; + tre_iteration_t *iter; + int i; + int bottom = tre_stack_num_objects(stack); + reg_errcode_t status = REG_OK; + if (num_tags_seen) + *num_tags_seen = 0; + + status = tre_stack_push_voidptr(stack, node); + + /* Walk through the tree recursively. */ + while (status == REG_OK && tre_stack_num_objects(stack) > bottom) + { + node = tre_stack_pop_voidptr(stack); + + switch (node->type) + { + case LITERAL: + lit = (tre_literal_t *)node->obj; + switch (lit->code_min) + { + case TAG: + if (lit->code_max >= 0) + { + if (tags != NULL) + { + /* Add the tag to `tags'. */ + for (i = 0; tags[i] >= 0; i++) + if (tags[i] == lit->code_max) + break; + if (tags[i] < 0) + { + tags[i] = lit->code_max; + tags[i + 1] = -1; + } + } + if (num_tags_seen) + (*num_tags_seen)++; + } + break; + case ASSERTION: + assert(lit->code_max >= 1 + || lit->code_max <= ASSERT_LAST); + if (assertions != NULL) + *assertions |= lit->code_max; + break; + case EMPTY: + break; + default: + assert(0); + break; + } + break; + + case UNION: + /* Subexpressions starting earlier take priority over ones + starting later, so we prefer the left subexpression over the + right subexpression. */ + uni = (tre_union_t *)node->obj; + if (uni->left->nullable) + STACK_PUSHX(stack, voidptr, uni->left) + else if (uni->right->nullable) + STACK_PUSHX(stack, voidptr, uni->right) + else + assert(0); + break; + + case CATENATION: + /* The path must go through both children. */ + cat = (tre_catenation_t *)node->obj; + assert(cat->left->nullable); + assert(cat->right->nullable); + STACK_PUSHX(stack, voidptr, cat->left); + STACK_PUSHX(stack, voidptr, cat->right); + break; + + case ITERATION: + /* A match with an empty string is preferred over no match at + all, so we go through the argument if possible. */ + iter = (tre_iteration_t *)node->obj; + if (iter->arg->nullable) + STACK_PUSHX(stack, voidptr, iter->arg); + break; + + default: + assert(0); + break; + } + } + + return status; +} + + +typedef enum { + NFL_RECURSE, + NFL_POST_UNION, + NFL_POST_CATENATION, + NFL_POST_ITERATION +} tre_nfl_stack_symbol_t; + + +/* Computes and fills in the fields `nullable', `firstpos', and `lastpos' for + the nodes of the AST `tree'. */ +static reg_errcode_t +tre_compute_nfl(tre_mem_t mem, tre_stack_t *stack, tre_ast_node_t *tree) +{ + int bottom = tre_stack_num_objects(stack); + + STACK_PUSHR(stack, voidptr, tree); + STACK_PUSHR(stack, int, NFL_RECURSE); + + while (tre_stack_num_objects(stack) > bottom) + { + tre_nfl_stack_symbol_t symbol; + tre_ast_node_t *node; + + symbol = (tre_nfl_stack_symbol_t)tre_stack_pop_int(stack); + node = tre_stack_pop_voidptr(stack); + switch (symbol) + { + case NFL_RECURSE: + switch (node->type) + { + case LITERAL: + { + tre_literal_t *lit = (tre_literal_t *)node->obj; + if (IS_BACKREF(lit)) + { + /* Back references: nullable = false, firstpos = {i}, + lastpos = {i}. */ + node->nullable = 0; + node->firstpos = tre_set_one(mem, lit->position, 0, + TRE_CHAR_MAX, 0, NULL, -1); + if (!node->firstpos) + return REG_ESPACE; + node->lastpos = tre_set_one(mem, lit->position, 0, + TRE_CHAR_MAX, 0, NULL, + (int)lit->code_max); + if (!node->lastpos) + return REG_ESPACE; + } + else if (lit->code_min < 0) + { + /* Tags, empty strings, params, and zero width assertions: + nullable = true, firstpos = {}, and lastpos = {}. */ + node->nullable = 1; + node->firstpos = tre_set_empty(mem); + if (!node->firstpos) + return REG_ESPACE; + node->lastpos = tre_set_empty(mem); + if (!node->lastpos) + return REG_ESPACE; + } + else + { + /* Literal at position i: nullable = false, firstpos = {i}, + lastpos = {i}. */ + node->nullable = 0; + node->firstpos = + tre_set_one(mem, lit->position, (int)lit->code_min, + (int)lit->code_max, 0, NULL, -1); + if (!node->firstpos) + return REG_ESPACE; + node->lastpos = tre_set_one(mem, lit->position, + (int)lit->code_min, + (int)lit->code_max, + lit->class, lit->neg_classes, + -1); + if (!node->lastpos) + return REG_ESPACE; + } + break; + } + + case UNION: + /* Compute the attributes for the two subtrees, and after that + for this node. */ + STACK_PUSHR(stack, voidptr, node); + STACK_PUSHR(stack, int, NFL_POST_UNION); + STACK_PUSHR(stack, voidptr, ((tre_union_t *)node->obj)->right); + STACK_PUSHR(stack, int, NFL_RECURSE); + STACK_PUSHR(stack, voidptr, ((tre_union_t *)node->obj)->left); + STACK_PUSHR(stack, int, NFL_RECURSE); + break; + + case CATENATION: + /* Compute the attributes for the two subtrees, and after that + for this node. */ + STACK_PUSHR(stack, voidptr, node); + STACK_PUSHR(stack, int, NFL_POST_CATENATION); + STACK_PUSHR(stack, voidptr, ((tre_catenation_t *)node->obj)->right); + STACK_PUSHR(stack, int, NFL_RECURSE); + STACK_PUSHR(stack, voidptr, ((tre_catenation_t *)node->obj)->left); + STACK_PUSHR(stack, int, NFL_RECURSE); + break; + + case ITERATION: + /* Compute the attributes for the subtree, and after that for + this node. */ + STACK_PUSHR(stack, voidptr, node); + STACK_PUSHR(stack, int, NFL_POST_ITERATION); + STACK_PUSHR(stack, voidptr, ((tre_iteration_t *)node->obj)->arg); + STACK_PUSHR(stack, int, NFL_RECURSE); + break; + } + break; /* end case: NFL_RECURSE */ + + case NFL_POST_UNION: + { + tre_union_t *uni = (tre_union_t *)node->obj; + node->nullable = uni->left->nullable || uni->right->nullable; + node->firstpos = tre_set_union(mem, uni->left->firstpos, + uni->right->firstpos, NULL, 0); + if (!node->firstpos) + return REG_ESPACE; + node->lastpos = tre_set_union(mem, uni->left->lastpos, + uni->right->lastpos, NULL, 0); + if (!node->lastpos) + return REG_ESPACE; + break; + } + + case NFL_POST_ITERATION: + { + tre_iteration_t *iter = (tre_iteration_t *)node->obj; + + if (iter->min == 0 || iter->arg->nullable) + node->nullable = 1; + else + node->nullable = 0; + node->firstpos = iter->arg->firstpos; + node->lastpos = iter->arg->lastpos; + break; + } + + case NFL_POST_CATENATION: + { + int num_tags, *tags, assertions; + reg_errcode_t status; + tre_catenation_t *cat = node->obj; + node->nullable = cat->left->nullable && cat->right->nullable; + + /* Compute firstpos. */ + if (cat->left->nullable) + { + /* The left side matches the empty string. Make a first pass + with tre_match_empty() to get the number of tags and + parameters. */ + status = tre_match_empty(stack, cat->left, + NULL, NULL, &num_tags); + if (status != REG_OK) + return status; + /* Allocate arrays for the tags and parameters. */ + tags = xmalloc(sizeof(*tags) * (num_tags + 1)); + if (!tags) + return REG_ESPACE; + tags[0] = -1; + assertions = 0; + /* Second pass with tre_mach_empty() to get the list of + tags and parameters. */ + status = tre_match_empty(stack, cat->left, tags, + &assertions, NULL); + if (status != REG_OK) + { + xfree(tags); + return status; + } + node->firstpos = + tre_set_union(mem, cat->right->firstpos, cat->left->firstpos, + tags, assertions); + xfree(tags); + if (!node->firstpos) + return REG_ESPACE; + } + else + { + node->firstpos = cat->left->firstpos; + } + + /* Compute lastpos. */ + if (cat->right->nullable) + { + /* The right side matches the empty string. Make a first pass + with tre_match_empty() to get the number of tags and + parameters. */ + status = tre_match_empty(stack, cat->right, + NULL, NULL, &num_tags); + if (status != REG_OK) + return status; + /* Allocate arrays for the tags and parameters. */ + tags = xmalloc(sizeof(int) * (num_tags + 1)); + if (!tags) + return REG_ESPACE; + tags[0] = -1; + assertions = 0; + /* Second pass with tre_mach_empty() to get the list of + tags and parameters. */ + status = tre_match_empty(stack, cat->right, tags, + &assertions, NULL); + if (status != REG_OK) + { + xfree(tags); + return status; + } + node->lastpos = + tre_set_union(mem, cat->left->lastpos, cat->right->lastpos, + tags, assertions); + xfree(tags); + if (!node->lastpos) + return REG_ESPACE; + } + else + { + node->lastpos = cat->right->lastpos; + } + break; + } + + default: + assert(0); + break; + } + } + + return REG_OK; +} + + +/* Adds a transition from each position in `p1' to each position in `p2'. */ +static reg_errcode_t +tre_make_trans(tre_pos_and_tags_t *p1, tre_pos_and_tags_t *p2, + tre_tnfa_transition_t *transitions, + int *counts, int *offs) +{ + tre_pos_and_tags_t *orig_p2 = p2; + tre_tnfa_transition_t *trans; + int i, j, k, l, dup, prev_p2_pos; + + if (transitions != NULL) + while (p1->position >= 0) + { + p2 = orig_p2; + prev_p2_pos = -1; + while (p2->position >= 0) + { + /* Optimization: if this position was already handled, skip it. */ + if (p2->position == prev_p2_pos) + { + p2++; + continue; + } + prev_p2_pos = p2->position; + /* Set `trans' to point to the next unused transition from + position `p1->position'. */ + trans = transitions + offs[p1->position]; + while (trans->state != NULL) + { +#if 0 + /* If we find a previous transition from `p1->position' to + `p2->position', it is overwritten. This can happen only + if there are nested loops in the regexp, like in "((a)*)*". + In POSIX.2 repetition using the outer loop is always + preferred over using the inner loop. Therefore the + transition for the inner loop is useless and can be thrown + away. */ + /* XXX - The same position is used for all nodes in a bracket + expression, so this optimization cannot be used (it will + break bracket expressions) unless I figure out a way to + detect it here. */ + if (trans->state_id == p2->position) + { + break; + } +#endif + trans++; + } + + if (trans->state == NULL) + (trans + 1)->state = NULL; + /* Use the character ranges, assertions, etc. from `p1' for + the transition from `p1' to `p2'. */ + trans->code_min = p1->code_min; + trans->code_max = p1->code_max; + trans->state = transitions + offs[p2->position]; + trans->state_id = p2->position; + trans->assertions = p1->assertions | p2->assertions + | (p1->class ? ASSERT_CHAR_CLASS : 0) + | (p1->neg_classes != NULL ? ASSERT_CHAR_CLASS_NEG : 0); + if (p1->backref >= 0) + { + assert((trans->assertions & ASSERT_CHAR_CLASS) == 0); + assert(p2->backref < 0); + trans->u.backref = p1->backref; + trans->assertions |= ASSERT_BACKREF; + } + else + trans->u.class = p1->class; + if (p1->neg_classes != NULL) + { + for (i = 0; p1->neg_classes[i] != (tre_ctype_t)0; i++); + trans->neg_classes = + xmalloc(sizeof(*trans->neg_classes) * (i + 1)); + if (trans->neg_classes == NULL) + return REG_ESPACE; + for (i = 0; p1->neg_classes[i] != (tre_ctype_t)0; i++) + trans->neg_classes[i] = p1->neg_classes[i]; + trans->neg_classes[i] = (tre_ctype_t)0; + } + else + trans->neg_classes = NULL; + + /* Find out how many tags this transition has. */ + i = 0; + if (p1->tags != NULL) + while(p1->tags[i] >= 0) + i++; + j = 0; + if (p2->tags != NULL) + while(p2->tags[j] >= 0) + j++; + + /* If we are overwriting a transition, free the old tag array. */ + if (trans->tags != NULL) + xfree(trans->tags); + trans->tags = NULL; + + /* If there were any tags, allocate an array and fill it. */ + if (i + j > 0) + { + trans->tags = xmalloc(sizeof(*trans->tags) * (i + j + 1)); + if (!trans->tags) + return REG_ESPACE; + i = 0; + if (p1->tags != NULL) + while(p1->tags[i] >= 0) + { + trans->tags[i] = p1->tags[i]; + i++; + } + l = i; + j = 0; + if (p2->tags != NULL) + while (p2->tags[j] >= 0) + { + /* Don't add duplicates. */ + dup = 0; + for (k = 0; k < i; k++) + if (trans->tags[k] == p2->tags[j]) + { + dup = 1; + break; + } + if (!dup) + trans->tags[l++] = p2->tags[j]; + j++; + } + trans->tags[l] = -1; + } + + p2++; + } + p1++; + } + else + /* Compute a maximum limit for the number of transitions leaving + from each state. */ + while (p1->position >= 0) + { + p2 = orig_p2; + while (p2->position >= 0) + { + counts[p1->position]++; + p2++; + } + p1++; + } + return REG_OK; +} + +/* Converts the syntax tree to a TNFA. All the transitions in the TNFA are + labelled with one character range (there are no transitions on empty + strings). The TNFA takes O(n^2) space in the worst case, `n' is size of + the regexp. */ +static reg_errcode_t +tre_ast_to_tnfa(tre_ast_node_t *node, tre_tnfa_transition_t *transitions, + int *counts, int *offs) +{ + tre_union_t *uni; + tre_catenation_t *cat; + tre_iteration_t *iter; + reg_errcode_t errcode = REG_OK; + + /* XXX - recurse using a stack!. */ + switch (node->type) + { + case LITERAL: + break; + case UNION: + uni = (tre_union_t *)node->obj; + errcode = tre_ast_to_tnfa(uni->left, transitions, counts, offs); + if (errcode != REG_OK) + return errcode; + errcode = tre_ast_to_tnfa(uni->right, transitions, counts, offs); + break; + + case CATENATION: + cat = (tre_catenation_t *)node->obj; + /* Add a transition from each position in cat->left->lastpos + to each position in cat->right->firstpos. */ + errcode = tre_make_trans(cat->left->lastpos, cat->right->firstpos, + transitions, counts, offs); + if (errcode != REG_OK) + return errcode; + errcode = tre_ast_to_tnfa(cat->left, transitions, counts, offs); + if (errcode != REG_OK) + return errcode; + errcode = tre_ast_to_tnfa(cat->right, transitions, counts, offs); + break; + + case ITERATION: + iter = (tre_iteration_t *)node->obj; + assert(iter->max == -1 || iter->max == 1); + + if (iter->max == -1) + { + assert(iter->min == 0 || iter->min == 1); + /* Add a transition from each last position in the iterated + expression to each first position. */ + errcode = tre_make_trans(iter->arg->lastpos, iter->arg->firstpos, + transitions, counts, offs); + if (errcode != REG_OK) + return errcode; + } + errcode = tre_ast_to_tnfa(iter->arg, transitions, counts, offs); + break; + } + return errcode; +} + + +#define ERROR_EXIT(err) \ + do \ + { \ + errcode = err; \ + if (/*CONSTCOND*/1) \ + goto error_exit; \ + } \ + while (/*CONSTCOND*/0) + + +int +regcomp(regex_t *__restrict preg, const char *__restrict regex, int cflags) +{ + tre_stack_t *stack; + tre_ast_node_t *tree, *tmp_ast_l, *tmp_ast_r; + tre_pos_and_tags_t *p; + int *counts = NULL, *offs = NULL; + int i, add = 0; + tre_tnfa_transition_t *transitions, *initial; + tre_tnfa_t *tnfa = NULL; + tre_submatch_data_t *submatch_data; + tre_tag_direction_t *tag_directions = NULL; + reg_errcode_t errcode; + tre_mem_t mem; + + /* Parse context. */ + tre_parse_ctx_t parse_ctx; + + /* Allocate a stack used throughout the compilation process for various + purposes. */ + stack = tre_stack_new(512, 1024000, 128); + if (!stack) + return REG_ESPACE; + /* Allocate a fast memory allocator. */ + mem = tre_mem_new(); + if (!mem) + { + tre_stack_destroy(stack); + return REG_ESPACE; + } + + /* Parse the regexp. */ + memset(&parse_ctx, 0, sizeof(parse_ctx)); + parse_ctx.mem = mem; + parse_ctx.stack = stack; + parse_ctx.start = regex; + parse_ctx.cflags = cflags; + parse_ctx.max_backref = -1; + errcode = tre_parse(&parse_ctx); + if (errcode != REG_OK) + ERROR_EXIT(errcode); + preg->re_nsub = parse_ctx.submatch_id - 1; + tree = parse_ctx.n; + +#ifdef TRE_DEBUG + tre_ast_print(tree); +#endif /* TRE_DEBUG */ + + /* Referring to nonexistent subexpressions is illegal. */ + if (parse_ctx.max_backref > (int)preg->re_nsub) + ERROR_EXIT(REG_ESUBREG); + + /* Allocate the TNFA struct. */ + tnfa = xcalloc(1, sizeof(tre_tnfa_t)); + if (tnfa == NULL) + ERROR_EXIT(REG_ESPACE); + tnfa->have_backrefs = parse_ctx.max_backref >= 0; + tnfa->have_approx = 0; + tnfa->num_submatches = parse_ctx.submatch_id; + + /* Set up tags for submatch addressing. If REG_NOSUB is set and the + regexp does not have back references, this can be skipped. */ + if (tnfa->have_backrefs || !(cflags & REG_NOSUB)) + { + + /* Figure out how many tags we will need. */ + errcode = tre_add_tags(NULL, stack, tree, tnfa); + if (errcode != REG_OK) + ERROR_EXIT(errcode); + + if (tnfa->num_tags > 0) + { + tag_directions = xmalloc(sizeof(*tag_directions) + * (tnfa->num_tags + 1)); + if (tag_directions == NULL) + ERROR_EXIT(REG_ESPACE); + tnfa->tag_directions = tag_directions; + memset(tag_directions, -1, + sizeof(*tag_directions) * (tnfa->num_tags + 1)); + } + tnfa->minimal_tags = xcalloc((unsigned)tnfa->num_tags * 2 + 1, + sizeof(*tnfa->minimal_tags)); + if (tnfa->minimal_tags == NULL) + ERROR_EXIT(REG_ESPACE); + + submatch_data = xcalloc((unsigned)parse_ctx.submatch_id, + sizeof(*submatch_data)); + if (submatch_data == NULL) + ERROR_EXIT(REG_ESPACE); + tnfa->submatch_data = submatch_data; + + errcode = tre_add_tags(mem, stack, tree, tnfa); + if (errcode != REG_OK) + ERROR_EXIT(errcode); + + } + + /* Expand iteration nodes. */ + errcode = tre_expand_ast(mem, stack, tree, &parse_ctx.position, + tag_directions); + if (errcode != REG_OK) + ERROR_EXIT(errcode); + + /* Add a dummy node for the final state. + XXX - For certain patterns this dummy node can be optimized away, + for example "a*" or "ab*". Figure out a simple way to detect + this possibility. */ + tmp_ast_l = tree; + tmp_ast_r = tre_ast_new_literal(mem, 0, 0, parse_ctx.position++); + if (tmp_ast_r == NULL) + ERROR_EXIT(REG_ESPACE); + + tree = tre_ast_new_catenation(mem, tmp_ast_l, tmp_ast_r); + if (tree == NULL) + ERROR_EXIT(REG_ESPACE); + + errcode = tre_compute_nfl(mem, stack, tree); + if (errcode != REG_OK) + ERROR_EXIT(errcode); + + counts = xmalloc(sizeof(int) * parse_ctx.position); + if (counts == NULL) + ERROR_EXIT(REG_ESPACE); + + offs = xmalloc(sizeof(int) * parse_ctx.position); + if (offs == NULL) + ERROR_EXIT(REG_ESPACE); + + for (i = 0; i < parse_ctx.position; i++) + counts[i] = 0; + tre_ast_to_tnfa(tree, NULL, counts, NULL); + + add = 0; + for (i = 0; i < parse_ctx.position; i++) + { + offs[i] = add; + add += counts[i] + 1; + counts[i] = 0; + } + transitions = xcalloc((unsigned)add + 1, sizeof(*transitions)); + if (transitions == NULL) + ERROR_EXIT(REG_ESPACE); + tnfa->transitions = transitions; + tnfa->num_transitions = add; + + errcode = tre_ast_to_tnfa(tree, transitions, counts, offs); + if (errcode != REG_OK) + ERROR_EXIT(errcode); + + tnfa->firstpos_chars = NULL; + + p = tree->firstpos; + i = 0; + while (p->position >= 0) + { + i++; + p++; + } + + initial = xcalloc((unsigned)i + 1, sizeof(tre_tnfa_transition_t)); + if (initial == NULL) + ERROR_EXIT(REG_ESPACE); + tnfa->initial = initial; + + i = 0; + for (p = tree->firstpos; p->position >= 0; p++) + { + initial[i].state = transitions + offs[p->position]; + initial[i].state_id = p->position; + initial[i].tags = NULL; + /* Copy the arrays p->tags, and p->params, they are allocated + from a tre_mem object. */ + if (p->tags) + { + int j; + for (j = 0; p->tags[j] >= 0; j++); + initial[i].tags = xmalloc(sizeof(*p->tags) * (j + 1)); + if (!initial[i].tags) + ERROR_EXIT(REG_ESPACE); + memcpy(initial[i].tags, p->tags, sizeof(*p->tags) * (j + 1)); + } + initial[i].assertions = p->assertions; + i++; + } + initial[i].state = NULL; + + tnfa->num_transitions = add; + tnfa->final = transitions + offs[tree->lastpos[0].position]; + tnfa->num_states = parse_ctx.position; + tnfa->cflags = cflags; + + tre_mem_destroy(mem); + tre_stack_destroy(stack); + xfree(counts); + xfree(offs); + + preg->TRE_REGEX_T_FIELD = (void *)tnfa; + return REG_OK; + + error_exit: + /* Free everything that was allocated and return the error code. */ + tre_mem_destroy(mem); + if (stack != NULL) + tre_stack_destroy(stack); + if (counts != NULL) + xfree(counts); + if (offs != NULL) + xfree(offs); + preg->TRE_REGEX_T_FIELD = (void *)tnfa; + regfree(preg); + return errcode; +} + + + + +void +regfree(regex_t *preg) +{ + tre_tnfa_t *tnfa; + unsigned int i; + tre_tnfa_transition_t *trans; + + tnfa = (void *)preg->TRE_REGEX_T_FIELD; + if (!tnfa) + return; + + for (i = 0; i < tnfa->num_transitions; i++) + if (tnfa->transitions[i].state) + { + if (tnfa->transitions[i].tags) + xfree(tnfa->transitions[i].tags); + if (tnfa->transitions[i].neg_classes) + xfree(tnfa->transitions[i].neg_classes); + } + if (tnfa->transitions) + xfree(tnfa->transitions); + + if (tnfa->initial) + { + for (trans = tnfa->initial; trans->state; trans++) + { + if (trans->tags) + xfree(trans->tags); + } + xfree(tnfa->initial); + } + + if (tnfa->submatch_data) + { + for (i = 0; i < tnfa->num_submatches; i++) + if (tnfa->submatch_data[i].parents) + xfree(tnfa->submatch_data[i].parents); + xfree(tnfa->submatch_data); + } + + if (tnfa->tag_directions) + xfree(tnfa->tag_directions); + if (tnfa->firstpos_chars) + xfree(tnfa->firstpos_chars); + if (tnfa->minimal_tags) + xfree(tnfa->minimal_tags); + xfree(tnfa); +} diff --git a/win32/regerror.c b/win32/regerror.c new file mode 100644 index 0000000..bc7ae31 --- /dev/null +++ b/win32/regerror.c @@ -0,0 +1,37 @@ +#include +#include +#include +// #include "locale_impl.h" + +/* Error message strings for error codes listed in `regex.h'. This list + needs to be in sync with the codes listed there, naturally. */ + +/* Converted to single string by Rich Felker to remove the need for + * data relocations at runtime, 27 Feb 2006. */ + +static const char messages[] = { + "No error\0" + "No match\0" + "Invalid regexp\0" + "Unknown collating element\0" + "Unknown character class name\0" + "Trailing backslash\0" + "Invalid back reference\0" + "Missing ']'\0" + "Missing ')'\0" + "Missing '}'\0" + "Invalid contents of {}\0" + "Invalid character range\0" + "Out of memory\0" + "Repetition not preceded by valid expression\0" + "\0Unknown error" +}; + +size_t regerror(int e, const regex_t *__restrict preg, char *__restrict buf, size_t size) +{ + const char *s; + for (s=messages; e && *s; e--, s+=strlen(s)+1); + if (!*s) s++; + // s = LCTRANS_CUR(s); + return 1+snprintf(buf, size, "%s", s); +} diff --git a/win32/regex.h b/win32/regex.h new file mode 100644 index 0000000..7996b58 --- /dev/null +++ b/win32/regex.h @@ -0,0 +1,65 @@ +#ifndef _REGEX_H +#define _REGEX_H + +#ifdef __cplusplus +extern "C" { +#endif + +// #include + +// #define __NEED_regoff_t +// #define __NEED_size_t +#define CHARCLASS_NAME_MAX 14 +#define RE_DUP_MAX 255 + +typedef size_t regoff_t; +// #include + +typedef struct re_pattern_buffer { + size_t re_nsub; + void *__opaque, *__padding[4]; + size_t __nsub2; + char __padding2; +} regex_t; + +typedef struct { + regoff_t rm_so; + regoff_t rm_eo; +} regmatch_t; + +#define REG_EXTENDED 1 +#define REG_ICASE 2 +#define REG_NEWLINE 4 +#define REG_NOSUB 8 + +#define REG_NOTBOL 1 +#define REG_NOTEOL 2 + +#define REG_OK 0 +#define REG_NOMATCH 1 +#define REG_BADPAT 2 +#define REG_ECOLLATE 3 +#define REG_ECTYPE 4 +#define REG_EESCAPE 5 +#define REG_ESUBREG 6 +#define REG_EBRACK 7 +#define REG_EPAREN 8 +#define REG_EBRACE 9 +#define REG_BADBR 10 +#define REG_ERANGE 11 +#define REG_ESPACE 12 +#define REG_BADRPT 13 + +#define REG_ENOSYS -1 + +int regcomp(regex_t *__restrict, const char *__restrict, int); +int regexec(const regex_t *__restrict, const char *__restrict, size_t, regmatch_t *__restrict, int); +void regfree(regex_t *); + +size_t regerror(int, const regex_t *__restrict, char *__restrict, size_t); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/win32/regexec.c b/win32/regexec.c new file mode 100644 index 0000000..f22586e --- /dev/null +++ b/win32/regexec.c @@ -0,0 +1,1028 @@ +/* + regexec.c - TRE POSIX compatible matching functions (and more). + + Copyright (c) 2001-2009 Ville Laurikari + 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 COPYRIGHT HOLDER AND CONTRIBUTORS + ``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 COPYRIGHT + HOLDER OR CONTRIBUTORS 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. + +*/ + +#include +#include +#include +#include +#include +#include + +#include + +#include "tre.h" + +#include + +static void +tre_fill_pmatch(size_t nmatch, regmatch_t pmatch[], int cflags, + const tre_tnfa_t *tnfa, regoff_t *tags, regoff_t match_eo); + +/*********************************************************************** + from tre-match-utils.h +***********************************************************************/ + +#define GET_NEXT_WCHAR() do { \ + prev_c = next_c; pos += pos_add_next; \ + if ((pos_add_next = mbtowc(&next_c, str_byte, MB_LEN_MAX)) <= 0) { \ + if (pos_add_next < 0) { ret = REG_NOMATCH; goto error_exit; } \ + else pos_add_next++; \ + } \ + str_byte += pos_add_next; \ + } while (0) + +#define IS_WORD_CHAR(c) ((c) == L'_' || tre_isalnum(c)) + +#define CHECK_ASSERTIONS(assertions) \ + (((assertions & ASSERT_AT_BOL) \ + && (pos > 0 || reg_notbol) \ + && (prev_c != L'\n' || !reg_newline)) \ + || ((assertions & ASSERT_AT_EOL) \ + && (next_c != L'\0' || reg_noteol) \ + && (next_c != L'\n' || !reg_newline)) \ + || ((assertions & ASSERT_AT_BOW) \ + && (IS_WORD_CHAR(prev_c) || !IS_WORD_CHAR(next_c))) \ + || ((assertions & ASSERT_AT_EOW) \ + && (!IS_WORD_CHAR(prev_c) || IS_WORD_CHAR(next_c))) \ + || ((assertions & ASSERT_AT_WB) \ + && (pos != 0 && next_c != L'\0' \ + && IS_WORD_CHAR(prev_c) == IS_WORD_CHAR(next_c))) \ + || ((assertions & ASSERT_AT_WB_NEG) \ + && (pos == 0 || next_c == L'\0' \ + || IS_WORD_CHAR(prev_c) != IS_WORD_CHAR(next_c)))) + +#define CHECK_CHAR_CLASSES(trans_i, tnfa, eflags) \ + (((trans_i->assertions & ASSERT_CHAR_CLASS) \ + && !(tnfa->cflags & REG_ICASE) \ + && !tre_isctype((tre_cint_t)prev_c, trans_i->u.class)) \ + || ((trans_i->assertions & ASSERT_CHAR_CLASS) \ + && (tnfa->cflags & REG_ICASE) \ + && !tre_isctype(tre_tolower((tre_cint_t)prev_c),trans_i->u.class) \ + && !tre_isctype(tre_toupper((tre_cint_t)prev_c),trans_i->u.class)) \ + || ((trans_i->assertions & ASSERT_CHAR_CLASS_NEG) \ + && tre_neg_char_classes_match(trans_i->neg_classes,(tre_cint_t)prev_c,\ + tnfa->cflags & REG_ICASE))) + + + + +/* Returns 1 if `t1' wins `t2', 0 otherwise. */ +static int +tre_tag_order(int num_tags, tre_tag_direction_t *tag_directions, + regoff_t *t1, regoff_t *t2) +{ + int i; + for (i = 0; i < num_tags; i++) + { + if (tag_directions[i] == TRE_TAG_MINIMIZE) + { + if (t1[i] < t2[i]) + return 1; + if (t1[i] > t2[i]) + return 0; + } + else + { + if (t1[i] > t2[i]) + return 1; + if (t1[i] < t2[i]) + return 0; + } + } + /* assert(0);*/ + return 0; +} + +static int +tre_neg_char_classes_match(tre_ctype_t *classes, tre_cint_t wc, int icase) +{ + while (*classes != (tre_ctype_t)0) + if ((!icase && tre_isctype(wc, *classes)) + || (icase && (tre_isctype(tre_toupper(wc), *classes) + || tre_isctype(tre_tolower(wc), *classes)))) + return 1; /* Match. */ + else + classes++; + return 0; /* No match. */ +} + + +/*********************************************************************** + from tre-match-parallel.c +***********************************************************************/ + +/* + This algorithm searches for matches basically by reading characters + in the searched string one by one, starting at the beginning. All + matching paths in the TNFA are traversed in parallel. When two or + more paths reach the same state, exactly one is chosen according to + tag ordering rules; if returning submatches is not required it does + not matter which path is chosen. + + The worst case time required for finding the leftmost and longest + match, or determining that there is no match, is always linearly + dependent on the length of the text being searched. + + This algorithm cannot handle TNFAs with back referencing nodes. + See `tre-match-backtrack.c'. +*/ + +typedef struct { + tre_tnfa_transition_t *state; + regoff_t *tags; +} tre_tnfa_reach_t; + +typedef struct { + regoff_t pos; + regoff_t **tags; +} tre_reach_pos_t; + + +static reg_errcode_t +tre_tnfa_run_parallel(const tre_tnfa_t *tnfa, const void *string, + regoff_t *match_tags, int eflags, + regoff_t *match_end_ofs) +{ + /* State variables required by GET_NEXT_WCHAR. */ + tre_char_t prev_c = 0, next_c = 0; + const char *str_byte = string; + regoff_t pos = -1; + regoff_t pos_add_next = 1; +#ifdef TRE_MBSTATE + mbstate_t mbstate; +#endif /* TRE_MBSTATE */ + int reg_notbol = eflags & REG_NOTBOL; + int reg_noteol = eflags & REG_NOTEOL; + int reg_newline = tnfa->cflags & REG_NEWLINE; + reg_errcode_t ret; + + char *buf; + tre_tnfa_transition_t *trans_i; + tre_tnfa_reach_t *reach, *reach_next, *reach_i, *reach_next_i; + tre_reach_pos_t *reach_pos; + int *tag_i; + int num_tags, i; + + regoff_t match_eo = -1; /* end offset of match (-1 if no match found yet) */ + int new_match = 0; + regoff_t *tmp_tags = NULL; + regoff_t *tmp_iptr; + +#ifdef TRE_MBSTATE + memset(&mbstate, '\0', sizeof(mbstate)); +#endif /* TRE_MBSTATE */ + + if (!match_tags) + num_tags = 0; + else + num_tags = tnfa->num_tags; + + /* Allocate memory for temporary data required for matching. This needs to + be done for every matching operation to be thread safe. This allocates + everything in a single large block with calloc(). */ + { + size_t tbytes, rbytes, pbytes, xbytes, total_bytes; + char *tmp_buf; + + /* Ensure that tbytes and xbytes*num_states cannot overflow, and that + * they don't contribute more than 1/8 of SIZE_MAX to total_bytes. */ + if (num_tags > SIZE_MAX/(8 * sizeof(regoff_t) * tnfa->num_states)) + return REG_ESPACE; + + /* Likewise check rbytes. */ + if (tnfa->num_states+1 > SIZE_MAX/(8 * sizeof(*reach_next))) + return REG_ESPACE; + + /* Likewise check pbytes. */ + if (tnfa->num_states > SIZE_MAX/(8 * sizeof(*reach_pos))) + return REG_ESPACE; + + /* Compute the length of the block we need. */ + tbytes = sizeof(*tmp_tags) * num_tags; + rbytes = sizeof(*reach_next) * (tnfa->num_states + 1); + pbytes = sizeof(*reach_pos) * tnfa->num_states; + xbytes = sizeof(regoff_t) * num_tags; + total_bytes = + (sizeof(long) - 1) * 4 /* for alignment paddings */ + + (rbytes + xbytes * tnfa->num_states) * 2 + tbytes + pbytes; + + /* Allocate the memory. */ + buf = calloc(total_bytes, 1); + if (buf == NULL) + return REG_ESPACE; + + /* Get the various pointers within tmp_buf (properly aligned). */ + tmp_tags = (void *)buf; + tmp_buf = buf + tbytes; + tmp_buf += ALIGN(tmp_buf, long); + reach_next = (void *)tmp_buf; + tmp_buf += rbytes; + tmp_buf += ALIGN(tmp_buf, long); + reach = (void *)tmp_buf; + tmp_buf += rbytes; + tmp_buf += ALIGN(tmp_buf, long); + reach_pos = (void *)tmp_buf; + tmp_buf += pbytes; + tmp_buf += ALIGN(tmp_buf, long); + for (i = 0; i < tnfa->num_states; i++) + { + reach[i].tags = (void *)tmp_buf; + tmp_buf += xbytes; + reach_next[i].tags = (void *)tmp_buf; + tmp_buf += xbytes; + } + } + + for (i = 0; i < tnfa->num_states; i++) + reach_pos[i].pos = -1; + + GET_NEXT_WCHAR(); + pos = 0; + + reach_next_i = reach_next; + while (1) + { + /* If no match found yet, add the initial states to `reach_next'. */ + if (match_eo < 0) + { + trans_i = tnfa->initial; + while (trans_i->state != NULL) + { + if (reach_pos[trans_i->state_id].pos < pos) + { + if (trans_i->assertions + && CHECK_ASSERTIONS(trans_i->assertions)) + { + trans_i++; + continue; + } + + reach_next_i->state = trans_i->state; + for (i = 0; i < num_tags; i++) + reach_next_i->tags[i] = -1; + tag_i = trans_i->tags; + if (tag_i) + while (*tag_i >= 0) + { + if (*tag_i < num_tags) + reach_next_i->tags[*tag_i] = pos; + tag_i++; + } + if (reach_next_i->state == tnfa->final) + { + match_eo = pos; + new_match = 1; + for (i = 0; i < num_tags; i++) + match_tags[i] = reach_next_i->tags[i]; + } + reach_pos[trans_i->state_id].pos = pos; + reach_pos[trans_i->state_id].tags = &reach_next_i->tags; + reach_next_i++; + } + trans_i++; + } + reach_next_i->state = NULL; + } + else + { + if (num_tags == 0 || reach_next_i == reach_next) + /* We have found a match. */ + break; + } + + /* Check for end of string. */ + if (!next_c) break; + + GET_NEXT_WCHAR(); + + /* Swap `reach' and `reach_next'. */ + reach_i = reach; + reach = reach_next; + reach_next = reach_i; + + /* For each state in `reach', weed out states that don't fulfill the + minimal matching conditions. */ + if (tnfa->num_minimals && new_match) + { + new_match = 0; + reach_next_i = reach_next; + for (reach_i = reach; reach_i->state; reach_i++) + { + int skip = 0; + for (i = 0; tnfa->minimal_tags[i] >= 0; i += 2) + { + int end = tnfa->minimal_tags[i]; + int start = tnfa->minimal_tags[i + 1]; + if (end >= num_tags) + { + skip = 1; + break; + } + else if (reach_i->tags[start] == match_tags[start] + && reach_i->tags[end] < match_tags[end]) + { + skip = 1; + break; + } + } + if (!skip) + { + reach_next_i->state = reach_i->state; + tmp_iptr = reach_next_i->tags; + reach_next_i->tags = reach_i->tags; + reach_i->tags = tmp_iptr; + reach_next_i++; + } + } + reach_next_i->state = NULL; + + /* Swap `reach' and `reach_next'. */ + reach_i = reach; + reach = reach_next; + reach_next = reach_i; + } + + /* For each state in `reach' see if there is a transition leaving with + the current input symbol to a state not yet in `reach_next', and + add the destination states to `reach_next'. */ + reach_next_i = reach_next; + for (reach_i = reach; reach_i->state; reach_i++) + { + for (trans_i = reach_i->state; trans_i->state; trans_i++) + { + /* Does this transition match the input symbol? */ + if (trans_i->code_min <= (tre_cint_t)prev_c && + trans_i->code_max >= (tre_cint_t)prev_c) + { + if (trans_i->assertions + && (CHECK_ASSERTIONS(trans_i->assertions) + || CHECK_CHAR_CLASSES(trans_i, tnfa, eflags))) + { + continue; + } + + /* Compute the tags after this transition. */ + for (i = 0; i < num_tags; i++) + tmp_tags[i] = reach_i->tags[i]; + tag_i = trans_i->tags; + if (tag_i != NULL) + while (*tag_i >= 0) + { + if (*tag_i < num_tags) + tmp_tags[*tag_i] = pos; + tag_i++; + } + + if (reach_pos[trans_i->state_id].pos < pos) + { + /* Found an unvisited node. */ + reach_next_i->state = trans_i->state; + tmp_iptr = reach_next_i->tags; + reach_next_i->tags = tmp_tags; + tmp_tags = tmp_iptr; + reach_pos[trans_i->state_id].pos = pos; + reach_pos[trans_i->state_id].tags = &reach_next_i->tags; + + if (reach_next_i->state == tnfa->final + && (match_eo == -1 + || (num_tags > 0 + && reach_next_i->tags[0] <= match_tags[0]))) + { + match_eo = pos; + new_match = 1; + for (i = 0; i < num_tags; i++) + match_tags[i] = reach_next_i->tags[i]; + } + reach_next_i++; + + } + else + { + assert(reach_pos[trans_i->state_id].pos == pos); + /* Another path has also reached this state. We choose + the winner by examining the tag values for both + paths. */ + if (tre_tag_order(num_tags, tnfa->tag_directions, + tmp_tags, + *reach_pos[trans_i->state_id].tags)) + { + /* The new path wins. */ + tmp_iptr = *reach_pos[trans_i->state_id].tags; + *reach_pos[trans_i->state_id].tags = tmp_tags; + if (trans_i->state == tnfa->final) + { + match_eo = pos; + new_match = 1; + for (i = 0; i < num_tags; i++) + match_tags[i] = tmp_tags[i]; + } + tmp_tags = tmp_iptr; + } + } + } + } + } + reach_next_i->state = NULL; + } + + *match_end_ofs = match_eo; + ret = match_eo >= 0 ? REG_OK : REG_NOMATCH; +error_exit: + xfree(buf); + return ret; +} + + + +/*********************************************************************** + from tre-match-backtrack.c +***********************************************************************/ + +/* + This matcher is for regexps that use back referencing. Regexp matching + with back referencing is an NP-complete problem on the number of back + references. The easiest way to match them is to use a backtracking + routine which basically goes through all possible paths in the TNFA + and chooses the one which results in the best (leftmost and longest) + match. This can be spectacularly expensive and may run out of stack + space, but there really is no better known generic algorithm. Quoting + Henry Spencer from comp.compilers: + + + POSIX.2 REs require longest match, which is really exciting to + implement since the obsolete ("basic") variant also includes + \. I haven't found a better way of tackling this than doing + a preliminary match using a DFA (or simulation) on a modified RE + that just replicates subREs for \, and then doing a + backtracking match to determine whether the subRE matches were + right. This can be rather slow, but I console myself with the + thought that people who use \ deserve very slow execution. + (Pun unintentional but very appropriate.) + +*/ + +typedef struct { + regoff_t pos; + const char *str_byte; + tre_tnfa_transition_t *state; + int state_id; + int next_c; + regoff_t *tags; +#ifdef TRE_MBSTATE + mbstate_t mbstate; +#endif /* TRE_MBSTATE */ +} tre_backtrack_item_t; + +typedef struct tre_backtrack_struct { + tre_backtrack_item_t item; + struct tre_backtrack_struct *prev; + struct tre_backtrack_struct *next; +} *tre_backtrack_t; + +#ifdef TRE_MBSTATE +#define BT_STACK_MBSTATE_IN stack->item.mbstate = (mbstate) +#define BT_STACK_MBSTATE_OUT (mbstate) = stack->item.mbstate +#else /* !TRE_MBSTATE */ +#define BT_STACK_MBSTATE_IN +#define BT_STACK_MBSTATE_OUT +#endif /* !TRE_MBSTATE */ + +#define tre_bt_mem_new tre_mem_new +#define tre_bt_mem_alloc tre_mem_alloc +#define tre_bt_mem_destroy tre_mem_destroy + + +#define BT_STACK_PUSH(_pos, _str_byte, _str_wide, _state, _state_id, _next_c, _tags, _mbstate) \ + do \ + { \ + int i; \ + if (!stack->next) \ + { \ + tre_backtrack_t s; \ + s = tre_bt_mem_alloc(mem, sizeof(*s)); \ + if (!s) \ + { \ + tre_bt_mem_destroy(mem); \ + if (tags) \ + xfree(tags); \ + if (pmatch) \ + xfree(pmatch); \ + if (states_seen) \ + xfree(states_seen); \ + return REG_ESPACE; \ + } \ + s->prev = stack; \ + s->next = NULL; \ + s->item.tags = tre_bt_mem_alloc(mem, \ + sizeof(*tags) * tnfa->num_tags); \ + if (!s->item.tags) \ + { \ + tre_bt_mem_destroy(mem); \ + if (tags) \ + xfree(tags); \ + if (pmatch) \ + xfree(pmatch); \ + if (states_seen) \ + xfree(states_seen); \ + return REG_ESPACE; \ + } \ + stack->next = s; \ + stack = s; \ + } \ + else \ + stack = stack->next; \ + stack->item.pos = (_pos); \ + stack->item.str_byte = (_str_byte); \ + stack->item.state = (_state); \ + stack->item.state_id = (_state_id); \ + stack->item.next_c = (_next_c); \ + for (i = 0; i < tnfa->num_tags; i++) \ + stack->item.tags[i] = (_tags)[i]; \ + BT_STACK_MBSTATE_IN; \ + } \ + while (0) + +#define BT_STACK_POP() \ + do \ + { \ + int i; \ + assert(stack->prev); \ + pos = stack->item.pos; \ + str_byte = stack->item.str_byte; \ + state = stack->item.state; \ + next_c = stack->item.next_c; \ + for (i = 0; i < tnfa->num_tags; i++) \ + tags[i] = stack->item.tags[i]; \ + BT_STACK_MBSTATE_OUT; \ + stack = stack->prev; \ + } \ + while (0) + +#undef MIN +#define MIN(a, b) ((a) <= (b) ? (a) : (b)) + +static reg_errcode_t +tre_tnfa_run_backtrack(const tre_tnfa_t *tnfa, const void *string, + regoff_t *match_tags, int eflags, regoff_t *match_end_ofs) +{ + /* State variables required by GET_NEXT_WCHAR. */ + tre_char_t prev_c = 0, next_c = 0; + const char *str_byte = string; + regoff_t pos = 0; + regoff_t pos_add_next = 1; +#ifdef TRE_MBSTATE + mbstate_t mbstate; +#endif /* TRE_MBSTATE */ + int reg_notbol = eflags & REG_NOTBOL; + int reg_noteol = eflags & REG_NOTEOL; + int reg_newline = tnfa->cflags & REG_NEWLINE; + + /* These are used to remember the necessary values of the above + variables to return to the position where the current search + started from. */ + int next_c_start; + const char *str_byte_start; + regoff_t pos_start = -1; +#ifdef TRE_MBSTATE + mbstate_t mbstate_start; +#endif /* TRE_MBSTATE */ + + /* End offset of best match so far, or -1 if no match found yet. */ + regoff_t match_eo = -1; + /* Tag arrays. */ + int *next_tags; + regoff_t *tags = NULL; + /* Current TNFA state. */ + tre_tnfa_transition_t *state; + int *states_seen = NULL; + + /* Memory allocator to for allocating the backtracking stack. */ + tre_mem_t mem = tre_bt_mem_new(); + + /* The backtracking stack. */ + tre_backtrack_t stack; + + tre_tnfa_transition_t *trans_i; + regmatch_t *pmatch = NULL; + int ret; + +#ifdef TRE_MBSTATE + memset(&mbstate, '\0', sizeof(mbstate)); +#endif /* TRE_MBSTATE */ + + if (!mem) + return REG_ESPACE; + stack = tre_bt_mem_alloc(mem, sizeof(*stack)); + if (!stack) + { + ret = REG_ESPACE; + goto error_exit; + } + stack->prev = NULL; + stack->next = NULL; + + if (tnfa->num_tags) + { + tags = xmalloc(sizeof(*tags) * tnfa->num_tags); + if (!tags) + { + ret = REG_ESPACE; + goto error_exit; + } + } + if (tnfa->num_submatches) + { + pmatch = xmalloc(sizeof(*pmatch) * tnfa->num_submatches); + if (!pmatch) + { + ret = REG_ESPACE; + goto error_exit; + } + } + if (tnfa->num_states) + { + states_seen = xmalloc(sizeof(*states_seen) * tnfa->num_states); + if (!states_seen) + { + ret = REG_ESPACE; + goto error_exit; + } + } + + retry: + { + int i; + for (i = 0; i < tnfa->num_tags; i++) + { + tags[i] = -1; + if (match_tags) + match_tags[i] = -1; + } + for (i = 0; i < tnfa->num_states; i++) + states_seen[i] = 0; + } + + state = NULL; + pos = pos_start; + GET_NEXT_WCHAR(); + pos_start = pos; + next_c_start = next_c; + str_byte_start = str_byte; +#ifdef TRE_MBSTATE + mbstate_start = mbstate; +#endif /* TRE_MBSTATE */ + + /* Handle initial states. */ + next_tags = NULL; + for (trans_i = tnfa->initial; trans_i->state; trans_i++) + { + if (trans_i->assertions && CHECK_ASSERTIONS(trans_i->assertions)) + { + continue; + } + if (state == NULL) + { + /* Start from this state. */ + state = trans_i->state; + next_tags = trans_i->tags; + } + else + { + /* Backtrack to this state. */ + BT_STACK_PUSH(pos, str_byte, 0, trans_i->state, + trans_i->state_id, next_c, tags, mbstate); + { + int *tmp = trans_i->tags; + if (tmp) + while (*tmp >= 0) + stack->item.tags[*tmp++] = pos; + } + } + } + + if (next_tags) + for (; *next_tags >= 0; next_tags++) + tags[*next_tags] = pos; + + + if (state == NULL) + goto backtrack; + + while (1) + { + tre_tnfa_transition_t *next_state; + int empty_br_match; + + if (state == tnfa->final) + { + if (match_eo < pos + || (match_eo == pos + && match_tags + && tre_tag_order(tnfa->num_tags, tnfa->tag_directions, + tags, match_tags))) + { + int i; + /* This match wins the previous match. */ + match_eo = pos; + if (match_tags) + for (i = 0; i < tnfa->num_tags; i++) + match_tags[i] = tags[i]; + } + /* Our TNFAs never have transitions leaving from the final state, + so we jump right to backtracking. */ + goto backtrack; + } + + /* Go to the next character in the input string. */ + empty_br_match = 0; + trans_i = state; + if (trans_i->state && trans_i->assertions & ASSERT_BACKREF) + { + /* This is a back reference state. All transitions leaving from + this state have the same back reference "assertion". Instead + of reading the next character, we match the back reference. */ + regoff_t so, eo; + int bt = trans_i->u.backref; + regoff_t bt_len; + int result; + + /* Get the substring we need to match against. Remember to + turn off REG_NOSUB temporarily. */ + tre_fill_pmatch(bt + 1, pmatch, tnfa->cflags & ~REG_NOSUB, + tnfa, tags, pos); + so = pmatch[bt].rm_so; + eo = pmatch[bt].rm_eo; + bt_len = eo - so; + + result = strncmp((const char*)string + so, str_byte - 1, + (size_t)bt_len); + + if (result == 0) + { + /* Back reference matched. Check for infinite loop. */ + if (bt_len == 0) + empty_br_match = 1; + if (empty_br_match && states_seen[trans_i->state_id]) + { + goto backtrack; + } + + states_seen[trans_i->state_id] = empty_br_match; + + /* Advance in input string and resync `prev_c', `next_c' + and pos. */ + str_byte += bt_len - 1; + pos += bt_len - 1; + GET_NEXT_WCHAR(); + } + else + { + goto backtrack; + } + } + else + { + /* Check for end of string. */ + if (next_c == L'\0') + goto backtrack; + + /* Read the next character. */ + GET_NEXT_WCHAR(); + } + + next_state = NULL; + for (trans_i = state; trans_i->state; trans_i++) + { + if (trans_i->code_min <= (tre_cint_t)prev_c + && trans_i->code_max >= (tre_cint_t)prev_c) + { + if (trans_i->assertions + && (CHECK_ASSERTIONS(trans_i->assertions) + || CHECK_CHAR_CLASSES(trans_i, tnfa, eflags))) + { + continue; + } + + if (next_state == NULL) + { + /* First matching transition. */ + next_state = trans_i->state; + next_tags = trans_i->tags; + } + else + { + /* Second matching transition. We may need to backtrack here + to take this transition instead of the first one, so we + push this transition in the backtracking stack so we can + jump back here if needed. */ + BT_STACK_PUSH(pos, str_byte, 0, trans_i->state, + trans_i->state_id, next_c, tags, mbstate); + { + int *tmp; + for (tmp = trans_i->tags; tmp && *tmp >= 0; tmp++) + stack->item.tags[*tmp] = pos; + } +#if 0 /* XXX - it's important not to look at all transitions here to keep + the stack small! */ + break; +#endif + } + } + } + + if (next_state != NULL) + { + /* Matching transitions were found. Take the first one. */ + state = next_state; + + /* Update the tag values. */ + if (next_tags) + while (*next_tags >= 0) + tags[*next_tags++] = pos; + } + else + { + backtrack: + /* A matching transition was not found. Try to backtrack. */ + if (stack->prev) + { + if (stack->item.state->assertions & ASSERT_BACKREF) + { + states_seen[stack->item.state_id] = 0; + } + + BT_STACK_POP(); + } + else if (match_eo < 0) + { + /* Try starting from a later position in the input string. */ + /* Check for end of string. */ + if (next_c == L'\0') + { + break; + } + next_c = next_c_start; +#ifdef TRE_MBSTATE + mbstate = mbstate_start; +#endif /* TRE_MBSTATE */ + str_byte = str_byte_start; + goto retry; + } + else + { + break; + } + } + } + + ret = match_eo >= 0 ? REG_OK : REG_NOMATCH; + *match_end_ofs = match_eo; + + error_exit: + tre_bt_mem_destroy(mem); +#ifndef TRE_USE_ALLOCA + if (tags) + xfree(tags); + if (pmatch) + xfree(pmatch); + if (states_seen) + xfree(states_seen); +#endif /* !TRE_USE_ALLOCA */ + + return ret; +} + +/*********************************************************************** + from regexec.c +***********************************************************************/ + +/* Fills the POSIX.2 regmatch_t array according to the TNFA tag and match + endpoint values. */ +static void +tre_fill_pmatch(size_t nmatch, regmatch_t pmatch[], int cflags, + const tre_tnfa_t *tnfa, regoff_t *tags, regoff_t match_eo) +{ + tre_submatch_data_t *submatch_data; + unsigned int i, j; + int *parents; + + i = 0; + if (match_eo >= 0 && !(cflags & REG_NOSUB)) + { + /* Construct submatch offsets from the tags. */ + submatch_data = tnfa->submatch_data; + while (i < tnfa->num_submatches && i < nmatch) + { + if (submatch_data[i].so_tag == tnfa->end_tag) + pmatch[i].rm_so = match_eo; + else + pmatch[i].rm_so = tags[submatch_data[i].so_tag]; + + if (submatch_data[i].eo_tag == tnfa->end_tag) + pmatch[i].rm_eo = match_eo; + else + pmatch[i].rm_eo = tags[submatch_data[i].eo_tag]; + + /* If either of the endpoints were not used, this submatch + was not part of the match. */ + if (pmatch[i].rm_so == -1 || pmatch[i].rm_eo == -1) + pmatch[i].rm_so = pmatch[i].rm_eo = -1; + + i++; + } + /* Reset all submatches that are not within all of their parent + submatches. */ + i = 0; + while (i < tnfa->num_submatches && i < nmatch) + { + if (pmatch[i].rm_eo == -1) + assert(pmatch[i].rm_so == -1); + assert(pmatch[i].rm_so <= pmatch[i].rm_eo); + + parents = submatch_data[i].parents; + if (parents != NULL) + for (j = 0; parents[j] >= 0; j++) + { + if (pmatch[i].rm_so < pmatch[parents[j]].rm_so + || pmatch[i].rm_eo > pmatch[parents[j]].rm_eo) + pmatch[i].rm_so = pmatch[i].rm_eo = -1; + } + i++; + } + } + + while (i < nmatch) + { + pmatch[i].rm_so = -1; + pmatch[i].rm_eo = -1; + i++; + } +} + + +/* + Wrapper functions for POSIX compatible regexp matching. +*/ + +int +regexec(const regex_t *__restrict preg, const char *__restrict string, + size_t nmatch, regmatch_t * __restrict pmatch, int eflags) +{ + tre_tnfa_t *tnfa = (void *)preg->TRE_REGEX_T_FIELD; + reg_errcode_t status; + regoff_t *tags = NULL, eo; + if (tnfa->cflags & REG_NOSUB) nmatch = 0; + if (tnfa->num_tags > 0 && nmatch > 0) + { + tags = xmalloc(sizeof(*tags) * tnfa->num_tags); + if (tags == NULL) + return REG_ESPACE; + } + + /* Dispatch to the appropriate matcher. */ + if (tnfa->have_backrefs) + { + /* The regex has back references, use the backtracking matcher. */ + status = tre_tnfa_run_backtrack(tnfa, string, tags, eflags, &eo); + } + else + { + /* Exact matching, no back references, use the parallel matcher. */ + status = tre_tnfa_run_parallel(tnfa, string, tags, eflags, &eo); + } + + if (status == REG_OK) + /* A match was found, so fill the submatch registers. */ + tre_fill_pmatch(nmatch, pmatch, tnfa->cflags, tnfa, tags, eo); + if (tags) + xfree(tags); + return status; +} \ No newline at end of file diff --git a/win32/strfncs.c b/win32/strfncs.c new file mode 100644 index 0000000..2f7c011 --- /dev/null +++ b/win32/strfncs.c @@ -0,0 +1,35 @@ +#include +#include + +int strcasecmp(const char *_l, const char *_r) +{ + const unsigned char *l=(void *)_l, *r=(void *)_r; + for (; *l && *r && (*l == *r || tolower(*l) == tolower(*r)); l++, r++); + return tolower(*l) - tolower(*r); +} + +int strncasecmp(const char *_l, const char *_r, size_t n) +{ + const unsigned char *l=(void *)_l, *r=(void *)_r; + if (!n--) return 0; + for (; *l && *r && n && (*l == *r || tolower(*l) == tolower(*r)); l++, r++, n--); + return tolower(*l) - tolower(*r); +} + +char *strcasestr(const char *h, const char *n) +{ + size_t l = strlen(n); + for (; *h; h++) if (!strncasecmp(h, n, l)) return (char *)h; + return 0; +} + +char *strtok_r(char *s, const char *sep, char **p) +{ + if (!s && !(s = *p)) return NULL; + s += strspn(s, sep); + if (!*s) return *p = 0; + *p = s + strcspn(s, sep); + if (**p) *(*p)++ = 0; + else *p = 0; + return s; +} diff --git a/win32/strfncs.h b/win32/strfncs.h new file mode 100644 index 0000000..733bee7 --- /dev/null +++ b/win32/strfncs.h @@ -0,0 +1,8 @@ +#include + +int strcasecmp(const char *_l, const char *_r); +int strncasecmp(const char *_l, const char *_r, size_t n); + +char *strcasestr(const char *h, const char *n); + +char *strtok_r(char *s, const char *sep, char **p); diff --git a/win32/tre-mem.c b/win32/tre-mem.c new file mode 100644 index 0000000..0fbf7b2 --- /dev/null +++ b/win32/tre-mem.c @@ -0,0 +1,158 @@ +/* + tre-mem.c - TRE memory allocator + + Copyright (c) 2001-2009 Ville Laurikari + 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 COPYRIGHT HOLDER AND CONTRIBUTORS + ``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 COPYRIGHT + HOLDER OR CONTRIBUTORS 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. + +*/ + +/* + This memory allocator is for allocating small memory blocks efficiently + in terms of memory overhead and execution speed. The allocated blocks + cannot be freed individually, only all at once. There can be multiple + allocators, though. +*/ + +#include +#include + +#include "tre.h" + +/* + This memory allocator is for allocating small memory blocks efficiently + in terms of memory overhead and execution speed. The allocated blocks + cannot be freed individually, only all at once. There can be multiple + allocators, though. +*/ + +/* Returns a new memory allocator or NULL if out of memory. */ +tre_mem_t +tre_mem_new_impl(int provided, void *provided_block) +{ + tre_mem_t mem; + if (provided) + { + mem = provided_block; + memset(mem, 0, sizeof(*mem)); + } + else + mem = xcalloc(1, sizeof(*mem)); + if (mem == NULL) + return NULL; + return mem; +} + + +/* Frees the memory allocator and all memory allocated with it. */ +void +tre_mem_destroy(tre_mem_t mem) +{ + tre_list_t *tmp, *l = mem->blocks; + + while (l != NULL) + { + xfree(l->data); + tmp = l->next; + xfree(l); + l = tmp; + } + xfree(mem); +} + + +/* Allocates a block of `size' bytes from `mem'. Returns a pointer to the + allocated block or NULL if an underlying malloc() failed. */ +void * +tre_mem_alloc_impl(tre_mem_t mem, int provided, void *provided_block, + int zero, size_t size) +{ + void *ptr; + + if (mem->failed) + { + return NULL; + } + + if (mem->n < size) + { + /* We need more memory than is available in the current block. + Allocate a new block. */ + tre_list_t *l; + if (provided) + { + if (provided_block == NULL) + { + mem->failed = 1; + return NULL; + } + mem->ptr = provided_block; + mem->n = TRE_MEM_BLOCK_SIZE; + } + else + { + int block_size; + if (size * 8 > TRE_MEM_BLOCK_SIZE) + block_size = size * 8; + else + block_size = TRE_MEM_BLOCK_SIZE; + l = xmalloc(sizeof(*l)); + if (l == NULL) + { + mem->failed = 1; + return NULL; + } + l->data = xmalloc(block_size); + if (l->data == NULL) + { + xfree(l); + mem->failed = 1; + return NULL; + } + l->next = NULL; + if (mem->current != NULL) + mem->current->next = l; + if (mem->blocks == NULL) + mem->blocks = l; + mem->current = l; + mem->ptr = l->data; + mem->n = block_size; + } + } + + /* Make sure the next pointer will be aligned. */ + size += ALIGN(mem->ptr + size, long); + + /* Allocate from current block. */ + ptr = mem->ptr; + mem->ptr += size; + mem->n -= size; + + /* Set to zero if needed. */ + if (zero) + memset(ptr, 0, size); + + return ptr; +} \ No newline at end of file diff --git a/win32/tre.h b/win32/tre.h new file mode 100644 index 0000000..c07c959 --- /dev/null +++ b/win32/tre.h @@ -0,0 +1,230 @@ +/* + tre-internal.h - TRE internal definitions + + Copyright (c) 2001-2009 Ville Laurikari + 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 COPYRIGHT HOLDER AND CONTRIBUTORS + ``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 COPYRIGHT + HOLDER OR CONTRIBUTORS 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. + +*/ + +#include +#include +#include + +#undef TRE_MBSTATE + +#define NDEBUG + +#define TRE_REGEX_T_FIELD __opaque +typedef int reg_errcode_t; + +typedef wchar_t tre_char_t; + +#define DPRINT(msg) do { } while(0) + +#define elementsof(x) ( sizeof(x) / sizeof(x[0]) ) + +#define tre_mbrtowc(pwc, s, n, ps) (mbtowc((pwc), (s), (n))) + +/* Wide characters. */ +typedef wint_t tre_cint_t; +#define TRE_CHAR_MAX 0x10ffff + +#define tre_isalnum iswalnum +#define tre_isalpha iswalpha +#define tre_isblank iswblank +#define tre_iscntrl iswcntrl +#define tre_isdigit iswdigit +#define tre_isgraph iswgraph +#define tre_islower iswlower +#define tre_isprint iswprint +#define tre_ispunct iswpunct +#define tre_isspace iswspace +#define tre_isupper iswupper +#define tre_isxdigit iswxdigit + +#define tre_tolower towlower +#define tre_toupper towupper +#define tre_strlen wcslen + +/* Use system provided iswctype() and wctype(). */ +typedef wctype_t tre_ctype_t; +#define tre_isctype iswctype +#define tre_ctype wctype + +/* Returns number of bytes to add to (char *)ptr to make it + properly aligned for the type. */ +#define ALIGN(ptr, type) \ + ((((long)ptr) % sizeof(type)) \ + ? (sizeof(type) - (((long)ptr) % sizeof(type))) \ + : 0) + +#undef MAX +#undef MIN +#define MAX(a, b) (((a) >= (b)) ? (a) : (b)) +#define MIN(a, b) (((a) <= (b)) ? (a) : (b)) + +/* TNFA transition type. A TNFA state is an array of transitions, + the terminator is a transition with NULL `state'. */ +typedef struct tnfa_transition tre_tnfa_transition_t; + +struct tnfa_transition { + /* Range of accepted characters. */ + tre_cint_t code_min; + tre_cint_t code_max; + /* Pointer to the destination state. */ + tre_tnfa_transition_t *state; + /* ID number of the destination state. */ + int state_id; + /* -1 terminated array of tags (or NULL). */ + int *tags; + /* Assertion bitmap. */ + int assertions; + /* Assertion parameters. */ + union { + /* Character class assertion. */ + tre_ctype_t class; + /* Back reference assertion. */ + int backref; + } u; + /* Negative character class assertions. */ + tre_ctype_t *neg_classes; +}; + + +/* Assertions. */ +#define ASSERT_AT_BOL 1 /* Beginning of line. */ +#define ASSERT_AT_EOL 2 /* End of line. */ +#define ASSERT_CHAR_CLASS 4 /* Character class in `class'. */ +#define ASSERT_CHAR_CLASS_NEG 8 /* Character classes in `neg_classes'. */ +#define ASSERT_AT_BOW 16 /* Beginning of word. */ +#define ASSERT_AT_EOW 32 /* End of word. */ +#define ASSERT_AT_WB 64 /* Word boundary. */ +#define ASSERT_AT_WB_NEG 128 /* Not a word boundary. */ +#define ASSERT_BACKREF 256 /* A back reference in `backref'. */ +#define ASSERT_LAST 256 + +/* Tag directions. */ +typedef enum { + TRE_TAG_MINIMIZE = 0, + TRE_TAG_MAXIMIZE = 1 +} tre_tag_direction_t; + +/* Instructions to compute submatch register values from tag values + after a successful match. */ +struct tre_submatch_data { + /* Tag that gives the value for rm_so (submatch start offset). */ + int so_tag; + /* Tag that gives the value for rm_eo (submatch end offset). */ + int eo_tag; + /* List of submatches this submatch is contained in. */ + int *parents; +}; + +typedef struct tre_submatch_data tre_submatch_data_t; + + +/* TNFA definition. */ +typedef struct tnfa tre_tnfa_t; + +struct tnfa { + tre_tnfa_transition_t *transitions; + unsigned int num_transitions; + tre_tnfa_transition_t *initial; + tre_tnfa_transition_t *final; + tre_submatch_data_t *submatch_data; + char *firstpos_chars; + int first_char; + unsigned int num_submatches; + tre_tag_direction_t *tag_directions; + int *minimal_tags; + int num_tags; + int num_minimals; + int end_tag; + int num_states; + int cflags; + int have_backrefs; + int have_approx; +}; + +/* from tre-mem.h: */ + +#define TRE_MEM_BLOCK_SIZE 1024 + +typedef struct tre_list { + void *data; + struct tre_list *next; +} tre_list_t; + +typedef struct tre_mem_struct { + tre_list_t *blocks; + tre_list_t *current; + char *ptr; + size_t n; + int failed; + void **provided; +} *tre_mem_t; + +#define tre_mem_new_impl __tre_mem_new_impl +#define tre_mem_alloc_impl __tre_mem_alloc_impl +#define tre_mem_destroy __tre_mem_destroy + +tre_mem_t tre_mem_new_impl(int provided, void *provided_block); +void *tre_mem_alloc_impl(tre_mem_t mem, int provided, void *provided_block, + int zero, size_t size); + +/* Returns a new memory allocator or NULL if out of memory. */ +#define tre_mem_new() tre_mem_new_impl(0, NULL) + +/* Allocates a block of `size' bytes from `mem'. Returns a pointer to the + allocated block or NULL if an underlying malloc() failed. */ +#define tre_mem_alloc(mem, size) tre_mem_alloc_impl(mem, 0, NULL, 0, size) + +/* Allocates a block of `size' bytes from `mem'. Returns a pointer to the + allocated block or NULL if an underlying malloc() failed. The memory + is set to zero. */ +#define tre_mem_calloc(mem, size) tre_mem_alloc_impl(mem, 0, NULL, 1, size) + +#ifdef TRE_USE_ALLOCA +/* alloca() versions. Like above, but memory is allocated with alloca() + instead of malloc(). */ + +#define tre_mem_newa() \ + tre_mem_new_impl(1, alloca(sizeof(struct tre_mem_struct))) + +#define tre_mem_alloca(mem, size) \ + ((mem)->n >= (size) \ + ? tre_mem_alloc_impl((mem), 1, NULL, 0, (size)) \ + : tre_mem_alloc_impl((mem), 1, alloca(TRE_MEM_BLOCK_SIZE), 0, (size))) +#endif /* TRE_USE_ALLOCA */ + + +/* Frees the memory allocator and all memory allocated with it. */ +void tre_mem_destroy(tre_mem_t mem); + +#define xmalloc malloc +#define xcalloc calloc +#define xfree free +#define xrealloc realloc diff --git a/win32/unistd.h b/win32/unistd.h new file mode 100644 index 0000000..41b121c --- /dev/null +++ b/win32/unistd.h @@ -0,0 +1,4 @@ +#ifndef _UNISTD_H +#define _UNISTD_H + +#endif \ No newline at end of file -- 2.7.4