From 773bfaf91ebe1ef80f37d473714a11f962e753fb Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Wed, 6 Jul 2011 17:08:24 -0400 Subject: Implement stream isolation This is the meat of proposal 171: we change circuit_is_acceptable() to require that the connection is compatible with every connection that has been linked to the circuit; we update circuit_is_better to prefer attaching streams to circuits in the way that decreases the circuits' usefulness the least; and we update link_apconn_to_circ() to do the appropriate bookkeeping. --- src/common/util.c | 26 ++++++++++++++++++++++++++ src/common/util.h | 1 + 2 files changed, 27 insertions(+) (limited to 'src/common') diff --git a/src/common/util.c b/src/common/util.c index b95ee3a61..bf0bbe060 100644 --- a/src/common/util.c +++ b/src/common/util.c @@ -412,6 +412,32 @@ round_uint64_to_next_multiple_of(uint64_t number, uint64_t divisor) return number; } +/** Return the number of bits set in v. */ +int +n_bits_set_u8(uint8_t v) +{ + static const int nybble_table[] = { + 0, /* 0000 */ + 1, /* 0001 */ + 1, /* 0010 */ + 2, /* 0011 */ + 1, /* 0100 */ + 2, /* 0101 */ + 2, /* 0110 */ + 3, /* 0111 */ + 1, /* 1000 */ + 2, /* 1001 */ + 2, /* 1010 */ + 3, /* 1011 */ + 2, /* 1100 */ + 3, /* 1101 */ + 3, /* 1110 */ + 4, /* 1111 */ + }; + + return nybble_table[v & 15] + nybble_table[v>>4]; +} + /* ===== * String manipulation * ===== */ diff --git a/src/common/util.h b/src/common/util.h index 6496c42db..de06c3c5f 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -160,6 +160,7 @@ uint64_t round_to_power_of_2(uint64_t u64); unsigned round_to_next_multiple_of(unsigned number, unsigned divisor); uint32_t round_uint32_to_next_multiple_of(uint32_t number, uint32_t divisor); uint64_t round_uint64_to_next_multiple_of(uint64_t number, uint64_t divisor); +int n_bits_set_u8(uint8_t v); /* Compute the CEIL of a divided by b, for nonnegative a * and positive b. Works on integer types only. Not defined if a+b can -- cgit v1.2.3 From 94f85f216ae4b6196d2a3438bfaf328375ebaad6 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 19 Jul 2011 02:36:11 -0400 Subject: Turn streq_opt into a generic strcmp_opt. --- src/common/util.c | 17 +++++++++++++++++ src/common/util.h | 1 + src/or/config.c | 7 +------ 3 files changed, 19 insertions(+), 6 deletions(-) (limited to 'src/common') diff --git a/src/common/util.c b/src/common/util.c index bf0bbe060..15b6e7130 100644 --- a/src/common/util.c +++ b/src/common/util.c @@ -521,6 +521,23 @@ tor_strisnonupper(const char *s) return 1; } +/** As strcmp, except that either string may be NULL. The NULL string is + * considered to be before any non-NULL string. */ +int +strcmp_opt(const char *s1, const char *s2) +{ + if (!s1) { + if (!s2) + return 0; + else + return -1; + } else if (!s2) { + return 1; + } else { + return strcmp(s1, s2); + } +} + /** Compares the first strlen(s2) characters of s1 with s2. Returns as for * strcmp. */ diff --git a/src/common/util.h b/src/common/util.h index de06c3c5f..99355871f 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -175,6 +175,7 @@ void tor_strlower(char *s) ATTR_NONNULL((1)); void tor_strupper(char *s) ATTR_NONNULL((1)); int tor_strisprint(const char *s) ATTR_PURE ATTR_NONNULL((1)); int tor_strisnonupper(const char *s) ATTR_PURE ATTR_NONNULL((1)); +int strcmp_opt(const char *s1, const char *s2) ATTR_PURE; int strcmpstart(const char *s1, const char *s2) ATTR_PURE ATTR_NONNULL((1,2)); int strcmp_len(const char *s1, const char *s2, size_t len) ATTR_PURE ATTR_NONNULL((1,2)); diff --git a/src/or/config.c b/src/or/config.c index 86ccb9296..cc1561bca 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -3864,12 +3864,7 @@ options_validate(or_options_t *old_options, or_options_t *options, static int opt_streq(const char *s1, const char *s2) { - if (!s1 && !s2) - return 1; - else if (s1 && s2 && !strcmp(s1,s2)) - return 1; - else - return 0; + return 0 == strcmp_opt(s1, s2); } /** Check if any of the previous options have changed but aren't allowed to. */ -- cgit v1.2.3 From 718252b253049f84fefa14e212cf560838e6607c Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Wed, 20 Jul 2011 13:16:06 -0400 Subject: Check return value in fmt_addr Previously, if tor_addr_to_str() returned NULL, we would reuse the last value returned by fmt_addr(). (This could happen if we were erroneously asked to format an AF_UNSPEC address.) Now instead we return "???". --- changes/fmt_addr | 4 ++++ src/common/address.c | 6 ++++-- 2 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 changes/fmt_addr (limited to 'src/common') diff --git a/changes/fmt_addr b/changes/fmt_addr new file mode 100644 index 000000000..b88c9e1bf --- /dev/null +++ b/changes/fmt_addr @@ -0,0 +1,4 @@ + o Minor bugfixes: + - When unable to format an address as a string, report its value + as "???" rather than reusing the last formatted address. Bugfix + on 0.2.1.5-alpha. diff --git a/src/common/address.c b/src/common/address.c index 1c725393d..7fc730105 100644 --- a/src/common/address.c +++ b/src/common/address.c @@ -958,8 +958,10 @@ fmt_addr(const tor_addr_t *addr) { static char buf[TOR_ADDR_BUF_LEN]; if (!addr) return ""; - tor_addr_to_str(buf, addr, sizeof(buf), 0); - return buf; + if (tor_addr_to_str(buf, addr, sizeof(buf), 0)) + return buf; + else + return "???"; } /** Convert the string in src to a tor_addr_t addr. The string -- cgit v1.2.3 From e802199cb3a2ebb4c09eaf4949e279acd3b46fc8 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Mon, 1 Aug 2011 12:36:59 -0400 Subject: Initial patch to build Tor with msvc and nmake We'll still need to tweak it so that it looks for includes and libraries somewhere more sensible than "where we happened to find them on Erinn's system"; so that tests and tools get built too; so that it's a bit documented; and so that we actually try running the output. Work done with Erinn Clark. --- Makefile.am | 1 + Makefile.nmake | 5 +++++ changes/nmake | 3 +++ src/common/Makefile.am | 2 +- src/common/Makefile.nmake | 20 ++++++++++++++++++++ src/common/torgzip.c | 4 ---- src/common/util.c | 6 +++--- src/or/Makefile.am | 2 +- src/or/Makefile.nmake | 28 ++++++++++++++++++++++++++++ src/or/networkstatus.c | 7 ------- src/win32/orconfig.h | 10 ++++++++++ 11 files changed, 72 insertions(+), 16 deletions(-) create mode 100644 Makefile.nmake create mode 100644 changes/nmake create mode 100644 src/common/Makefile.nmake create mode 100644 src/or/Makefile.nmake (limited to 'src/common') diff --git a/Makefile.am b/Makefile.am index 0daca63e9..cd0d8833c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -15,6 +15,7 @@ EXTRA_DIST = \ ChangeLog \ INSTALL \ LICENSE \ + Makefile.nmake \ README \ ReleaseNotes \ tor.spec \ diff --git a/Makefile.nmake b/Makefile.nmake new file mode 100644 index 000000000..425f1ec26 --- /dev/null +++ b/Makefile.nmake @@ -0,0 +1,5 @@ +all: + cd src/common + $(MAKE) /F Makefile.nmake + cd ../../src/or + $(MAKE) /F Makefile.nmake diff --git a/changes/nmake b/changes/nmake new file mode 100644 index 000000000..47f4f8f96 --- /dev/null +++ b/changes/nmake @@ -0,0 +1,3 @@ + o Minor features (build compatibility): + - Limited, experimental support for building with nmake and MSVC. + diff --git a/src/common/Makefile.am b/src/common/Makefile.am index 48218491b..2244fe58d 100644 --- a/src/common/Makefile.am +++ b/src/common/Makefile.am @@ -1,7 +1,7 @@ noinst_LIBRARIES = libor.a libor-crypto.a libor-event.a -EXTRA_DIST = common_sha1.i sha256.c +EXTRA_DIST = common_sha1.i sha256.c Makefile.nmake #CFLAGS = -Wall -Wpointer-arith -O2 diff --git a/src/common/Makefile.nmake b/src/common/Makefile.nmake new file mode 100644 index 000000000..c8b598866 --- /dev/null +++ b/src/common/Makefile.nmake @@ -0,0 +1,20 @@ +all: libor.lib libor-crypto.lib libor-event.lib + +CFLAGS = /I ..\win32 /I ..\..\..\build-alpha\include + +LIBOR_OBJECTS = address.obj compat.obj container.obj di_ops.obj \ + log.obj memarea.obj mempool.obj procmon.obj util.obj \ + util_codedigest.obj + +LIBOR_CRYPTO_OBJECTS = aes.obj crypto.obj torgzip.obj tortls.obj + +LIBOR_EVENT_OBJECTS = compat_libevent.obj + +libor.lib: $(LIBOR_OBJECTS) + lib $(LIBOR_OBJECTS) /out:libor.lib + +libor-crypto.lib: $(LIBOR_CRYPTO_OBJECTS) + lib $(LIBOR_CRYPTO_OBJECTS) /out:libor-crypto.lib + +libor-event.lib: $(LIBOR_EVENT_OBJECTS) + lib $(LIBOR_EVENT_OBJECTS) /out:libor-event.lib diff --git a/src/common/torgzip.c b/src/common/torgzip.c index 2937c67de..ae7d7cfc0 100644 --- a/src/common/torgzip.c +++ b/src/common/torgzip.c @@ -43,11 +43,7 @@ #define off64_t int64_t #endif -#ifdef _MSC_VER -#include "..\..\contrib\zlib\zlib.h" -#else #include -#endif /** Set to 1 if zlib is a version that supports gzip; set to 0 if it doesn't; * set to -1 if we haven't checked yet. */ diff --git a/src/common/util.c b/src/common/util.c index 15b6e7130..601f2be3e 100644 --- a/src/common/util.c +++ b/src/common/util.c @@ -14,6 +14,9 @@ #define _GNU_SOURCE #include "orconfig.h" +#ifdef HAVE_FCNTL_H +#include +#endif #define UTIL_PRIVATE #include "util.h" #include "torlog.h" @@ -68,9 +71,6 @@ #ifdef HAVE_SYS_FCNTL_H #include #endif -#ifdef HAVE_FCNTL_H -#include -#endif #ifdef HAVE_TIME_H #include #endif diff --git a/src/or/Makefile.am b/src/or/Makefile.am index 344e63ff8..e2a1b6d64 100644 --- a/src/or/Makefile.am +++ b/src/or/Makefile.am @@ -7,7 +7,7 @@ else tor_platform_source= endif -EXTRA_DIST=ntmain.c or_sha1.i +EXTRA_DIST=ntmain.c or_sha1.i Makefile.nmake if USE_EXTERNAL_EVDNS evdns_source= diff --git a/src/or/Makefile.nmake b/src/or/Makefile.nmake new file mode 100644 index 000000000..919edbbf2 --- /dev/null +++ b/src/or/Makefile.nmake @@ -0,0 +1,28 @@ +all: tor.exe + +CFLAGS = /I ..\win32 /I ..\..\..\build-alpha\include /I ..\common + +LIBS = ..\..\..\build-alpha\lib\libevent.a \ + ..\..\..\build-alpha\lib\libcrypto.a \ + ..\..\..\build-alpha\lib\libssl.a \ + ..\..\..\build-alpha\lib\libz.a \ + ws2_32.lib advapi32.lib shell32.lib + +LIBTOR_OBJECTS = buffers.obj circuitbuild.obj circuitlist.obj circuituse.obj \ + command.obj config.obj connection.obj connection_edge.obj \ + connection_or.obj control.obj cpuworker.obj directory.obj \ + dirserv.obj dirvote.obj dns.obj dnsserv.obj geoip.obj \ + hibernate.obj main.obj microdesc.obj networkstatus.obj \ + nodelist.obj onion.obj policies.obj reasons.obj relay.obj \ + rendclient.obj rendcommon.obj rendmid.obj rendservice.obj \ + rephist.obj router.obj routerlist.obj routerparse.obj status.obj \ + config_codedigest.obj ntmain.obj + +libtor.lib: $(LIBTOR_OBJECTS) + lib $(LIBTOR_OBJECTS) /out:libtor.lib + +tor.exe: libtor.lib tor_main.obj + $(CC) $(CFLAGS) $(LIBS) libtor.lib ..\common\*.lib tor_main.obj + +clean: + del $(LIBTOR_OBJECTS) *.lib tor.exe diff --git a/src/or/networkstatus.c b/src/or/networkstatus.c index 2586ce6eb..868c2a2a7 100644 --- a/src/or/networkstatus.c +++ b/src/or/networkstatus.c @@ -2006,13 +2006,6 @@ routers_update_status_from_consensus_networkstatus(smartlist_t *routers, tor_memcmp(rs->identity_digest, router->cache_info.identity_digest, DIGEST_LEN), { -#if 0 - /* We have no routerstatus for this router. Clear flags and skip it. */ - if (!authdir) { - if (router->purpose == ROUTER_PURPOSE_GENERAL) - router_clear_status_flags(router); - } -#endif }) { /* We have a routerstatus for this router. */ const char *digest = router->cache_info.identity_digest; diff --git a/src/win32/orconfig.h b/src/win32/orconfig.h index ed6da8eae..cf493392e 100644 --- a/src/win32/orconfig.h +++ b/src/win32/orconfig.h @@ -122,6 +122,7 @@ /* Define to 1 if you have the header file. */ #undef HAVE_SYS_SOCKET_H + /* Define to 1 if you have the header file. */ #define HAVE_SYS_STAT_H @@ -234,3 +235,12 @@ /* Version number of package */ #define VERSION "0.2.3.2-alpha-dev" + + + +#define HAVE_STRUCT_SOCKADDR_IN6 +#define HAVE_STRUCT_IN6_ADDR +#define RSHIFT_DOES_SIGN_EXTEND +#define FLEXIBLE_ARRAY_MEMBER 0 +#define HAVE_EVENT2_EVENT_H +#define SHARE_DATADIR "" -- cgit v1.2.3 From 0a5338e03cdf14ef80584c6ff8adeb49200b8a76 Mon Sep 17 00:00:00 2001 From: Sebastian Hahn Date: Tue, 9 Aug 2011 10:59:03 +0200 Subject: Sockets are unsigned on windows this gets rid of a warning about signed/unsigned comparison --- src/common/compat.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/common') diff --git a/src/common/compat.h b/src/common/compat.h index 98642e2d9..8e271ba90 100644 --- a/src/common/compat.h +++ b/src/common/compat.h @@ -401,7 +401,7 @@ typedef int socklen_t; #ifdef MS_WINDOWS #define tor_socket_t intptr_t -#define SOCKET_OK(s) ((s) != INVALID_SOCKET) +#define SOCKET_OK(s) ((unsigned)(s) != INVALID_SOCKET) #else #define tor_socket_t int #define SOCKET_OK(s) ((s) >= 0) -- cgit v1.2.3 From bed79c47f4ec0ee72b19e2b81c54131d516d07ef Mon Sep 17 00:00:00 2001 From: Sebastian Hahn Date: Tue, 9 Aug 2011 11:00:25 +0200 Subject: Get rid of an unused parameter warning on win --- src/common/util.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/common') diff --git a/src/common/util.c b/src/common/util.c index 601f2be3e..542093fb5 100644 --- a/src/common/util.c +++ b/src/common/util.c @@ -1736,6 +1736,8 @@ check_private_dir(const char *dirname, cpd_check_t check, struct passwd *pw = NULL; uid_t running_uid; gid_t running_gid; +#else + (void)effective_user; #endif tor_assert(dirname); -- cgit v1.2.3 From e42a74e56351a41c0a68999ed5fce48ab03166d7 Mon Sep 17 00:00:00 2001 From: Robert Ransom Date: Wed, 3 Aug 2011 15:49:39 -0700 Subject: Add smartlist_[v]asprintf_add I should have added this before implementing #2411. --- src/common/util.c | 24 ++++++++++++++++++++++++ src/common/util.h | 5 +++++ 2 files changed, 29 insertions(+) (limited to 'src/common') diff --git a/src/common/util.c b/src/common/util.c index 601f2be3e..1e5e454c6 100644 --- a/src/common/util.c +++ b/src/common/util.c @@ -2679,6 +2679,30 @@ tor_sscanf(const char *buf, const char *pattern, ...) return r; } +/** Append the string produced by tor_asprintf(pattern, ...) + * to sl. */ +void +smartlist_asprintf_add(struct smartlist_t *sl, const char *pattern, ...) +{ + va_list ap; + va_start(ap, pattern); + smartlist_vasprintf_add(sl, pattern, ap); + va_end(ap); +} + +/** va_list-based backend of smartlist_asprintf_add. */ +void +smartlist_vasprintf_add(struct smartlist_t *sl, const char *pattern, + va_list args) +{ + char *str = NULL; + + tor_vasprintf(&str, pattern, args); + tor_assert(str != NULL); + + smartlist_add(sl, str); +} + /** Return a new list containing the filenames in the directory dirname. * Return NULL on error or if dirname is not a directory. */ diff --git a/src/common/util.h b/src/common/util.h index 99355871f..a1def6cc3 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -220,6 +220,11 @@ int tor_sscanf(const char *buf, const char *pattern, ...) #endif ; +void smartlist_asprintf_add(struct smartlist_t *sl, const char *pattern, ...) + CHECK_PRINTF(2, 3); +void smartlist_vasprintf_add(struct smartlist_t *sl, const char *pattern, + va_list args); + int hex_decode_digit(char c); void base16_encode(char *dest, size_t destlen, const char *src, size_t srclen); int base16_decode(char *dest, size_t destlen, const char *src, size_t srclen); -- cgit v1.2.3 From f137ae896ef2741079ac698e8cabf229b9f4cde8 Mon Sep 17 00:00:00 2001 From: Sebastian Hahn Date: Thu, 11 Aug 2011 20:37:51 +0200 Subject: Don't warn on http connection to my orport Also remove a few other related warnings that could occur during the ssl handshake. We do this because the relay operator can't do anything about them, and they aren't their fault. --- changes/bug3700 | 6 ++++++ src/common/tortls.c | 16 ++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 changes/bug3700 (limited to 'src/common') diff --git a/changes/bug3700 b/changes/bug3700 new file mode 100644 index 000000000..cef7296ad --- /dev/null +++ b/changes/bug3700 @@ -0,0 +1,6 @@ + o Minor bugfixes: + - Get rid of a harmless warning that could happen on relays running + with bufferevents. The warning was caused by someone doing an http + request to a relay's orport. Also don't warn for a few related + non-errors. Fixes bug 3700; bugfix on 0.2.3.1-alpha. + diff --git a/src/common/tortls.c b/src/common/tortls.c index 21f2c5072..455603030 100644 --- a/src/common/tortls.c +++ b/src/common/tortls.c @@ -273,6 +273,22 @@ tor_tls_log_one_error(tor_tls_t *tls, unsigned long err, addr = tls ? tls->address : NULL; + /* Some errors are known-benign, meaning they are the fault of the other + * side of the connection. The caller doesn't know this, so override the + * priority for those cases. */ + switch (ERR_GET_REASON(err)) { + case SSL_R_HTTP_REQUEST: + case SSL_R_HTTPS_PROXY_REQUEST: + case SSL_R_RECORD_LENGTH_MISMATCH: + case SSL_R_RECORD_TOO_LARGE: + case SSL_R_UNKNOWN_PROTOCOL: + case SSL_R_UNSUPPORTED_PROTOCOL: + severity = LOG_INFO; + break; + default: + break; + } + msg = (const char*)ERR_reason_error_string(err); lib = (const char*)ERR_lib_error_string(err); func = (const char*)ERR_func_error_string(err); -- cgit v1.2.3 From 6a06f45b04b8c9336ff150d299732c67bb5cb885 Mon Sep 17 00:00:00 2001 From: Sebastian Hahn Date: Tue, 16 Aug 2011 01:38:15 +0200 Subject: Actually pick a random port when "auto" is specified ddc65e2b3303559ab7b842a176ee6c2eda9e4027 had broken this --- src/common/torint.h | 9 +++++++++ src/or/connection.c | 4 +++- 2 files changed, 12 insertions(+), 1 deletion(-) (limited to 'src/common') diff --git a/src/common/torint.h b/src/common/torint.h index 0b5c29adc..af975471f 100644 --- a/src/common/torint.h +++ b/src/common/torint.h @@ -111,6 +111,15 @@ typedef signed int int32_t; typedef unsigned int uint32_t; #define HAVE_UINT32_T #endif +#ifndef UINT16_MAX +#define UINT16_MAX 0xffffu +#endif +#ifndef INT16_MAX +#define INT16_MAX 0x7fff +#endif +#ifndef INT16_MIN +#define INT16_MIN (-INT16_MAX-1) +#endif #ifndef UINT32_MAX #define UINT32_MAX 0xffffffffu #endif diff --git a/src/or/connection.c b/src/or/connection.c index 2087ceda4..452ddc189 100644 --- a/src/or/connection.c +++ b/src/or/connection.c @@ -1787,6 +1787,8 @@ retry_listener_ports(smartlist_t *old_conns, socklen_t listensocklen = 0; char *address=NULL; connection_t *conn; + int real_port = port->port == CFG_AUTO_PORT ? 0 : port->port; + tor_assert(real_port <= UINT16_MAX); if (port->is_unix_addr) { listensockaddr = (struct sockaddr *) @@ -1795,7 +1797,7 @@ retry_listener_ports(smartlist_t *old_conns, } else { listensockaddr = tor_malloc(sizeof(struct sockaddr_storage)); listensocklen = tor_addr_to_sockaddr(&port->addr, - port->port, + real_port, listensockaddr, sizeof(struct sockaddr_storage)); address = tor_dup_addr(&port->addr); -- cgit v1.2.3 From 52e36feda153e70cd08d624df73035b7e59a95ef Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Wed, 17 Aug 2011 14:44:16 -0400 Subject: Call evthread_use_windows_threads when running with IOCP on windows --- changes/le-win-threads | 3 +++ src/common/compat_libevent.c | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 changes/le-win-threads (limited to 'src/common') diff --git a/changes/le-win-threads b/changes/le-win-threads new file mode 100644 index 000000000..5be44a2b5 --- /dev/null +++ b/changes/le-win-threads @@ -0,0 +1,3 @@ + o Major bugfixes (IOCP): + - When using IOCP on windows, we need to enable Libevent windows threading + support. Bugfix on 0.2.3.1-alpha. diff --git a/src/common/compat_libevent.c b/src/common/compat_libevent.c index c338dd6c0..8752de749 100644 --- a/src/common/compat_libevent.c +++ b/src/common/compat_libevent.c @@ -19,6 +19,7 @@ #ifdef HAVE_EVENT2_EVENT_H #include +#include #else #include #endif @@ -183,8 +184,10 @@ tor_libevent_initialize(tor_libevent_cfg *torcfg) struct event_config *cfg = event_config_new(); #if defined(MS_WINDOWS) && defined(USE_BUFFEREVENTS) - if (! torcfg->disable_iocp) + if (! torcfg->disable_iocp) { + evthread_use_windows_threads(); event_config_set_flag(cfg, EVENT_BASE_FLAG_STARTUP_IOCP); + } #endif #if defined(LIBEVENT_VERSION_NUMBER) && LIBEVENT_VERSION_NUMBER >= V(2,0,7) -- cgit v1.2.3