Imported Upstream version 0.7.5 upstream/0.7.5
authorDongHun Kwak <dh0128.kwak@samsung.com>
Tue, 10 Sep 2019 06:38:54 +0000 (15:38 +0900)
committerDongHun Kwak <dh0128.kwak@samsung.com>
Tue, 10 Sep 2019 06:38:54 +0000 (15:38 +0900)
84 files changed:
.appveyor.yml [new file with mode: 0644]
.travis.yml
CMakeLists.txt
CREDITS
NEWS
README
TODO_1.0
VERSION.cmake
bindings/solv.i
doc/CMakeLists.txt
examples/solv/CMakeLists.txt
ext/CMakeLists.txt
ext/libsolvext.ver
ext/repo_conda.c [new file with mode: 0644]
ext/repo_conda.h [new file with mode: 0644]
ext/repo_deb.c
ext/repo_rpmdb_bdb.h
ext/repo_susetags.c
ext/repo_testcase.c [new file with mode: 0644]
ext/solv_jsonparser.c
ext/solv_jsonparser.h
ext/solv_xfopen.c
ext/testcase.c
ext/testcase.h
libsolv.pc.in
libsolvext.pc.in
package/libsolv.changes
src/CMakeLists.txt
src/chksum.c
src/cleandeps.c
src/conda.c
src/conda.h
src/knownid.h
src/libsolv.ver
src/policy.c
src/pool.c
src/qsort_r.c
src/repo.c
src/repo_solv.c
src/repodata.c
src/repopage.c
src/rules.c
src/rules.h
src/selection.c
src/solvable.c
src/solver.c
src/solver.h
src/util.c
src/util.h
test/CMakeLists.txt
test/runtestcases.bat [new file with mode: 0644]
test/runtestcases.sh [moved from test/runtestcases with 100% similarity]
test/testcases/focus/best.t
test/testcases/focus/installed.t
test/testcases/focus/normal.t
test/testcases/lock/best.t [new file with mode: 0644]
test/testcases/lock/dup.t [new file with mode: 0644]
test/testcases/lockstep/lockstep_install.t
test/testcases/lockstep/lockstep_update.t
test/testcases/weakdeps/supplements_implicitobsoleteusescolors.t
tools/CMakeLists.txt
tools/conda2solv.c [new file with mode: 0644]
tools/deb2solv.c
tools/diskusagexml2solv.c [deleted file]
tools/dumpsolv.c
tools/installcheck.c
tools/mergesolv.c
tools/testsolv.c
win32/CMakeLists.txt [new file with mode: 0644]
win32/LICENSE [new file with mode: 0644]
win32/fmemopen.c [new file with mode: 0644]
win32/fnmatch.c [new file with mode: 0644]
win32/fnmatch.h [new file with mode: 0644]
win32/getopt.c [new file with mode: 0644]
win32/getopt.h [new file with mode: 0644]
win32/regcomp.c [new file with mode: 0644]
win32/regerror.c [new file with mode: 0644]
win32/regex.h [new file with mode: 0644]
win32/regexec.c [new file with mode: 0644]
win32/strfncs.c [new file with mode: 0644]
win32/strfncs.h [new file with mode: 0644]
win32/tre-mem.c [new file with mode: 0644]
win32/tre.h [new file with mode: 0644]
win32/unistd.h [new file with mode: 0644]

diff --git a/.appveyor.yml b/.appveyor.yml
new file mode 100644 (file)
index 0000000..2b91665
--- /dev/null
@@ -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
index 8d7dda5..a115095 100644 (file)
@@ -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
index ab385f1..e331318 100644 (file)
@@ -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 (file)
--- 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 (file)
--- 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 (file)
--- 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
index d77df67..3e243d3 100644 (file)
--- 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:
index 80d0e03..5c228a3 100644 (file)
@@ -49,5 +49,5 @@ SET(LIBSOLVEXT_SOVERSION "1")
 
 SET(LIBSOLV_MAJOR "0")
 SET(LIBSOLV_MINOR "7")
-SET(LIBSOLV_PATCH "4")
+SET(LIBSOLV_PATCH "5")
 
index ad265a5..107192f 100644 (file)
@@ -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);
   }
 }
 
index 66011b4..ed38274 100644 (file)
@@ -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")
index 41f45f7..0f3bd47 100644 (file)
@@ -25,5 +25,5 @@ TARGET_LINK_LIBRARIES (solv libsolvext libsolv ${SYSTEM_LIBRARIES})
 
 INSTALL(TARGETS
     solv
-    DESTINATION ${BIN_INSTALL_DIR})
+    DESTINATION ${CMAKE_INSTALL_BINDIR})
 
index edc2b9f..7c25535 100644 (file)
@@ -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)
index 4896e85..ef028f0 100644 (file)
@@ -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 (file)
index 0000000..0828aff
--- /dev/null
@@ -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 <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 (file)
index 0000000..7e90a3d
--- /dev/null
@@ -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);
index 5dd79f4..8f63756 100644 (file)
 #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)
 {
index ed82a69..ae477f7 100644 (file)
 #endif
 
 #ifdef RPM5
-# define RPM_INDEX_SIZE 4      /* just the rpmdbid */
+# include <rpm/rpmversion.h>
+# 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)
index 561c789..dc60aa4 100644 (file)
 #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 (file)
index 0000000..48d8a0e
--- /dev/null
@@ -0,0 +1,716 @@
+/*
+ * Copyright (c) 2019, SUSE LLC
+ *
+ * This program is licensed under the BSD license, read LICENSE.BSD
+ * for further information
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#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 "<NULL>";
+  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 , "<NAMESPACE>" },
+  { REL_ARCH,       "." },
+  { REL_MULTIARCH,  "<MULTIARCH>" },
+  { REL_FILECONFLICT,  "<FILECONFLICT>" },
+  { REL_COND,  "<IF>" },
+  { REL_COMPAT,  "compat >=" },
+  { REL_KIND,  "<KIND>" },
+  { REL_ELSE, "<ELSE>" },
+  { REL_ERROR, "<ERROR>" },
+  { REL_UNLESS, "<UNLESS>" },
+  { REL_CONDA, "<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, "<NULL>", 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;
+}
index 053ee6f..ea31923 100644 (file)
@@ -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;
index d58d9e3..f728fb6 100644 (file)
@@ -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);
 
index 343aed8..9aab68b 100644 (file)
 #include <string.h>
 #include <fcntl.h>
 
+#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
+
index 8edf58f..fcebcfa 100644 (file)
 #include "ext/repo_helix.h"
 #endif
 
-#define DISABLE_JOIN2
-#include "tools_util.h"
+#ifdef _WIN32
+  #include <direct.h>
+#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 "<NULL>";
-  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 , "<NAMESPACE>" },
-  { REL_ARCH,       "." },
-  { REL_MULTIARCH,  "<MULTIARCH>" },
-  { REL_FILECONFLICT,  "<FILECONFLICT>" },
-  { REL_COND,  "<IF>" },
-  { REL_COMPAT,  "compat >=" },
-  { REL_KIND,  "<KIND>" },
-  { REL_ELSE, "<ELSE>" },
-  { REL_ERROR, "<ERROR>" },
-  { REL_UNLESS, "<UNLESS>" },
-  { REL_CONDA, "<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, "<NULL>", 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);
index 387a506..997feaf 100644 (file)
@@ -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)
index 40a8623..766146c 100644 (file)
@@ -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
index d48b6fa..d007846 100644 (file)
@@ -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
index 897cc04..6393734 100644 (file)
@@ -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
index be487a7..f91c9c0 100644 (file)
@@ -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)
index df46145..1f8ab47 100644 (file)
 #include "sha1.h"
 #include "sha2.h"
 
+#ifdef _WIN32
+  #include "strfncs.h"
+#endif
+
 struct s_Chksum {
   Id type;
   int done;
index 8ef6fd3..aa83c10 100644 (file)
@@ -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;
index 5b134dd..7f2538a 100644 (file)
 #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(&reg, buf, REG_EXTENDED | REG_NOSUB))
-    return 0;
+  if (regcomp(&reg, buf, REG_EXTENDED | REG_NOSUB | (icase ? REG_ICASE : 0)))
+    {
+      solv_free(buf);
+      return 0;
+    }
   r = regexec(&reg, evr, 0, NULL, 0);
   regfree(&reg);
+  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(&reg, buf, REG_EXTENDED | REG_NOSUB))
-    return 0;
+  if (regcomp(&reg, buf, REG_EXTENDED | REG_NOSUB | (icase ? REG_ICASE : 0)))
+    {
+      solv_free(buf);
+      return 0;
+    }
   r = regexec(&reg, evr, 0, NULL, 0);
   regfree(&reg);
+  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);
+}
+
index 7233f17..3bcfa2d 100644 (file)
@@ -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 */
 
index b5b41d6..9d7f157 100644 (file)
@@ -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)
 
index f0e86ff..eafe3e6 100644 (file)
@@ -448,4 +448,6 @@ SOLV_1.2 {
 
 SOLV_1.3 {
                repodata_set_kv;
+               solv_setcloexec;
+               pool_conda_matchspec;
 } SOLV_1.2;
index 5f61115..823a008 100644 (file)
@@ -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);
     }
 }
 
index 09354ef..a554453 100644 (file)
@@ -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;
     }
index d49049a..ffc09dc 100644 (file)
@@ -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 <sys/cdefs.h>
+#endif
 
 /* $FreeBSD: src/lib/libc/stdlib/qsort.c,v 1.13.2.1.8.1 2010/12/21 17:10:29 kensmith Exp $ */
 
index d163f13..da40219 100644 (file)
@@ -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;
        }
     }
index be33967..761d06e 100644 (file)
@@ -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;
index ad5aaea..0c7a51f 100644 (file)
 #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:
index 85d53eb..9e9694f 100644 (file)
 #include <fcntl.h>
 #include <time.h>
 
+#ifdef _WIN32
+  #include <windows.h>
+  #include <fileapi.h>
+  #include <io.h>
+#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 ");
index b6cd582..3aef6ee 100644 (file)
@@ -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);
+    }
 }
 
 
index 1db0551..6b8511f 100644 (file)
@@ -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);
index a160122..e58d731 100644 (file)
 #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)
index 331f290..d3d2d31 100644 (file)
@@ -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;
 }
 
index b335e6a..e7a9dc0 100644 (file)
@@ -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;
 
index 93baa34..daf4f63 100644 (file)
@@ -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? */
 
index 65c8629..72426e0 100644 (file)
 #include <stdlib.h>
 #include <unistd.h>
 #include <string.h>
-#include <sys/time.h>
+#include <fcntl.h>
+#ifdef _WIN32
+  #include <windows.h>
+  #include <io.h>
+#else
+  #include <sys/time.h>
+#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
index 5f7a93a..1dc5f32 100644 (file)
@@ -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);
index 92a5e7a..8a5cd8a 100644 (file)
@@ -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 (file)
index 0000000..4d4b5e0
--- /dev/null
@@ -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
similarity index 100%
rename from test/runtestcases
rename to test/runtestcases.sh
index a7a46b9..7527285 100644 (file)
@@ -1,6 +1,7 @@
 repo system 0 testtags <inline>
 #>=Pkg: A 1 1 noarch
 #>=Pkg: A2 1 1 noarch
+#>=Pkg: D 1 1 noarch
 repo available 0 testtags <inline>
 #>=Pkg: A 1 1 noarch
 #>=Pkg: A 2 1 noarch
@@ -16,8 +17,15 @@ repo available 0 testtags <inline>
 #>=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 <inline>
+#>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
index 4f49d35..f272ee0 100644 (file)
@@ -1,6 +1,7 @@
 repo system 0 testtags <inline>
 #>=Pkg: A 1 1 noarch
 #>=Pkg: A2 1 1 noarch
+#>=Pkg: D 1 1 noarch
 repo available 0 testtags <inline>
 #>=Pkg: A 1 1 noarch
 #>=Pkg: A 2 1 noarch
@@ -16,8 +17,13 @@ repo available 0 testtags <inline>
 #>=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 <inline>
+#>install B-1-1.noarch@available
+#>install B2-1-1.noarch@available
+#>install C-1-1.noarch@available
index 6e562a3..0056b8a 100644 (file)
@@ -1,6 +1,7 @@
 repo system 0 testtags <inline>
 #>=Pkg: A 1 1 noarch
 #>=Pkg: A2 1 1 noarch
+#>=Pkg: D 1 1 noarch
 repo available 0 testtags <inline>
 #>=Pkg: A 1 1 noarch
 #>=Pkg: A 2 1 noarch
@@ -16,7 +17,13 @@ repo available 0 testtags <inline>
 #>=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 <inline>
+#>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 (file)
index 0000000..5c992d6
--- /dev/null
@@ -0,0 +1,27 @@
+# test that locked packages trump best rules
+
+repo system 0 testtags <inline>
+#>=Pkg: b 1 1 i686
+repo available 0 testtags <inline>
+#>=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 <inline>
+#>install a-2-1.i686@available
+
+nextjob
+job update name b [forcebest]
+job lock name b = 3-1
+result transaction,problems <inline>
+#>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 <inline>
diff --git a/test/testcases/lock/dup.t b/test/testcases/lock/dup.t
new file mode 100644 (file)
index 0000000..3c400da
--- /dev/null
@@ -0,0 +1,25 @@
+# test that locked packages trump dup rules
+
+repo system 0 testtags <inline>
+#>=Pkg: a 1 1 i686
+repo available 0 testtags <inline>
+#>=Pkg: a 2 1 i686
+
+system i686 * system
+
+job distupgrade all packages
+job lock name a
+result transaction,problems <inline>
+
+# 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 <inline>
+#>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
index f626da0..6e0a5e5 100644 (file)
@@ -7,3 +7,6 @@ repo test 0 testtags <inline>
 system x86_64 rpm system
 poolflags implicitobsoleteusescolors
 job install provides A(x32)
+result transaction,problems <inline>
+#>install A-1-1.i586@test
+#>install A-1-1.x86_64@test
index 128bcc3..a70a339 100644 (file)
@@ -9,3 +9,6 @@ repo test 0 testtags <inline>
 system x86_64 rpm system
 poolflags implicitobsoleteusescolors
 job update all packages
+result transaction,problems <inline>
+#>upgrade A-1-1.i586@system A-2-1.i586@test
+#>upgrade A-1-1.x86_64@system A-2-1.x86_64@test
index 6de4544..46a1139 100644 (file)
@@ -12,9 +12,16 @@ repo test 0 testtags <inline>
 #>=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 <inline>
+#>install A-1-1.noarch@test
+#>install B-1-1.x86_64@test
 
 nextjob
 job install name A2
+result transaction,problems <inline>
+#>install A2-1-1.noarch@test
+
+
index 802dc50..f19030e 100644 (file)
@@ -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 (file)
index 0000000..2b8f3c4
--- /dev/null
@@ -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 <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#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 <stdin> and writes a .solv file to <stdout>\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, "<stdin>");
+  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);
+}
index ad27541..4fac3f0 100644 (file)
@@ -15,6 +15,7 @@
 #include <stdlib.h>
 #include <unistd.h>
 #include <string.h>
+#include <getopt.h>
 
 #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 (file)
index 850b02c..0000000
+++ /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 <sys/types.h>
-#include <limits.h>
-#include <fcntl.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-
-#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 <stdin> and writes a .solv file to <stdout>\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, "<stdin>");
-
-  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);
-}
index bdd521e..1307657 100644 (file)
@@ -9,6 +9,7 @@
 #include <stdlib.h>
 #include <unistd.h>
 #include <string.h>
+#include <getopt.h>
 
 static int with_attr;
 static int dump_json;
index 6c090d8..4eb66a0 100644 (file)
@@ -34,6 +34,9 @@
 #include "solver.h"
 #include "solv_xfopen.h"
 
+#ifdef _WIN32
+#include "strfncs.h"
+#endif
 
 void
 usage(char** argv)
index 8746ac6..a10c686 100644 (file)
@@ -16,6 +16,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <assert.h>
+#include <getopt.h>
 
 #include "pool.h"
 #include "repo_solv.h"
index 3331fae..18dfcfe 100644 (file)
@@ -1,6 +1,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
+#include <getopt.h>
 
 #include "pool.h"
 #include "repo.h"
diff --git a/win32/CMakeLists.txt b/win32/CMakeLists.txt
new file mode 100644 (file)
index 0000000..9a87af7
--- /dev/null
@@ -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 (file)
index 0000000..c9ad4f4
--- /dev/null
@@ -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 <vl@iki.fi>
+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 (file)
index 0000000..ea88b08
--- /dev/null
@@ -0,0 +1,41 @@
+#include <stdio.h>
+#include <io.h>
+#include <fcntl.h>
+#include <windows.h>
+
+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 (file)
index 0000000..9e90852
--- /dev/null
@@ -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 <string.h>
+#include <fnmatch.h>
+#include <stdlib.h>
+#include <wchar.h>
+#include <wctype.h>
+
+#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<m) if (pat[k] == '^' || pat[k] == '!') k++;
+               if (k<m) if (pat[k] == ']') k++;
+               for (; k<m && pat[k] && pat[k]!=']'; k++) {
+                       if (k+1<m && pat[k+1] && pat[k]=='[' && (pat[k+1]==':' || pat[k+1]=='.' || pat[k+1]=='=')) {
+                               int z = pat[k+1];
+                               k+=2;
+                               if (k<m && pat[k]) k++;
+                               while (k<m && pat[k] && (pat[k-1]!=z || pat[k]!=']')) k++;
+                               if (k==m || !pat[k]) break;
+                       }
+               }
+               if (k==m || !pat[k]) {
+                       *step = 1;
+                       return '[';
+               }
+               *step = k+1;
+               return BRACKET;
+       }
+       if (pat[0] == '*')
+               return STAR;
+       if (pat[0] == '?')
+               return QUESTION;
+escaped:
+       if (pat[0] >= 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; p<endpat; p+=pinc) {
+               switch (pat_next(p, endpat-p, &pinc, flags)) {
+               case UNMATCHABLE:
+                       return FNM_NOMATCH;
+               case STAR:
+                       tailcnt=0;
+                       ptail = p+1;
+                       break;
+               default:
+                       tailcnt++;
+                       break;
+               }
+       }
+
+       /* Past this point we need not check for UNMATCHABLE in pat,
+        * because all of pat has already been parsed once. */
+
+       /* Compute real str length if it was initially unknown/-1 */
+       n = strnlen(str, n);
+       endstr = str + n;
+       if (n < tailcnt) return FNM_NOMATCH;
+
+       /* Find the final tailcnt chars of str, accounting for UTF-8.
+        * On illegal sequences we may get it wrong, but in that case
+        * we necessarily have a matching failure anyway. */
+       for (s=endstr; s>str && 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<endpat) {
+               p = pat;
+               s = str;
+               for (;;) {
+                       c = pat_next(p, endpat-p, &pinc, flags);
+                       p += pinc;
+                       /* Encountering * completes/commits a component */
+                       if (c == STAR) {
+                               pat = p;
+                               str = s;
+                               break;
+                       }
+                       k = str_next(s, endstr-s, &sinc);
+                       if (!k)
+                               return FNM_NOMATCH;
+                       kfold = flags & FNM_CASEFOLD ? casefold(k) : k;
+                       if (c == BRACKET) {
+                               if (!match_bracket(p-pinc, k, kfold))
+                                       break;
+                       } else if (c != QUESTION && k != c && kfold != c) {
+                               break;
+                       }
+                       s += sinc;
+               }
+               if (c == STAR) continue;
+               /* If we failed, advance str, by 1 char if it's a valid
+                * char, or past all invalid bytes otherwise. */
+               k = str_next(str, endstr-str, &sinc);
+               if (k > 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 (file)
index 0000000..f959321
--- /dev/null
@@ -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 (file)
index 0000000..1e7e451
--- /dev/null
@@ -0,0 +1,97 @@
+#include <unistd.h>
+#include <wchar.h>
+#include <string.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+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 (file)
index 0000000..35cbd35
--- /dev/null
@@ -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 (file)
index 0000000..7ef8327
--- /dev/null
@@ -0,0 +1,2953 @@
+/*
+  regcomp.c - TRE POSIX compatible regex compilation functions.
+
+  Copyright (c) 2001-2009 Ville Laurikari <vl@iki.fi>
+  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 <string.h>
+#include <stdlib.h>
+#include <regex.h>
+#include <limits.h>
+#include <stdint.h>
+#include <ctype.h>
+
+#include "tre.h"
+
+#include <assert.h>
+
+/***********************************************************************
+ 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, <type> 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; i<len && v<0x110000; i++) {
+                               c = hexval(s[i]);
+                               if (c < 0) break;
+                               v = 16*v + c;
+                       }
+                       s += i;
+                       if (len == 8) {
+                               if (*s != '}')
+                                       return REG_EBRACE;
+                               s++;
+                       }
+                       node = tre_ast_new_literal(ctx->mem, 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, &copy,
+                                         &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, &copy, &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 (file)
index 0000000..bc7ae31
--- /dev/null
@@ -0,0 +1,37 @@
+#include <string.h>
+#include <regex.h>
+#include <stdio.h>
+// #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 (file)
index 0000000..7996b58
--- /dev/null
@@ -0,0 +1,65 @@
+#ifndef _REGEX_H
+#define _REGEX_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// #include <features.h>
+
+// #define __NEED_regoff_t
+// #define __NEED_size_t
+#define CHARCLASS_NAME_MAX 14
+#define RE_DUP_MAX 255
+
+typedef size_t regoff_t;
+// #include <bits/alltypes.h>
+
+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 (file)
index 0000000..f22586e
--- /dev/null
@@ -0,0 +1,1028 @@
+/*
+  regexec.c - TRE POSIX compatible matching functions (and more).
+
+  Copyright (c) 2001-2009 Ville Laurikari <vl@iki.fi>
+  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 <stdlib.h>
+#include <string.h>
+#include <wchar.h>
+#include <wctype.h>
+#include <limits.h>
+#include <stdint.h>
+
+#include <regex.h>
+
+#include "tre.h"
+
+#include <assert.h>
+
+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:
+  <URL: http://compilers.iecc.com/comparch/article/93-03-102>
+
+    POSIX.2 REs require longest match, which is really exciting to
+    implement since the obsolete ("basic") variant also includes
+    \<digit>.  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 \<digit>, 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 \<digit> 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 (file)
index 0000000..2f7c011
--- /dev/null
@@ -0,0 +1,35 @@
+#include <string.h>
+#include <ctype.h>
+
+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 (file)
index 0000000..733bee7
--- /dev/null
@@ -0,0 +1,8 @@
+#include <string.h>
+
+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 (file)
index 0000000..0fbf7b2
--- /dev/null
@@ -0,0 +1,158 @@
+/*
+  tre-mem.c - TRE memory allocator
+
+  Copyright (c) 2001-2009 Ville Laurikari <vl@iki.fi>
+  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 <stdlib.h>
+#include <string.h>
+
+#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 (file)
index 0000000..c07c959
--- /dev/null
@@ -0,0 +1,230 @@
+/*
+  tre-internal.h - TRE internal definitions
+
+  Copyright (c) 2001-2009 Ville Laurikari <vl@iki.fi>
+  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 <regex.h>
+#include <wchar.h>
+#include <wctype.h>
+
+#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 (file)
index 0000000..41b121c
--- /dev/null
@@ -0,0 +1,4 @@
+#ifndef _UNISTD_H
+#define _UNISTD_H
+
+#endif
\ No newline at end of file