From 6a5878a3748d84d95e15b9a8b018e1bfc54fc894 Mon Sep 17 00:00:00 2001 From: "/C=EU/ST=EU/CN=Jozsef Kadlecsik/emailAddress=kadlec@blackhole.kfki.hu" Date: Sat, 15 Nov 2008 20:20:09 +0000 Subject: 2.4.5 - setlist type does not work properly together with swapping sets, bug reported by Thomas Jacob. - Include linux/capability.h explicitly in ip_set.c (Jan Engelhardt) --- ChangeLog | 4 + Makefile | 3 +- ipset.c | 8 +- ipset.h | 9 +- ipset_iphash.c | 2 +- ipset_ipmap.c | 2 +- ipset_ipporthash.c | 2 +- ipset_ipportiphash.c | 2 +- ipset_ipportnethash.c | 2 +- ipset_iptree.c | 2 +- ipset_iptreemap.c | 2 +- ipset_macipmap.c | 2 +- ipset_nethash.c | 2 +- ipset_portmap.c | 2 +- ipset_setlist.c | 2 +- kernel/ChangeLog | 5 + kernel/include/linux/netfilter_ipv4/ip_set.h | 7 +- .../include/linux/netfilter_ipv4/ip_set_setlist.h | 2 +- kernel/ip_set.c | 24 ++++- kernel/ip_set_setlist.c | 118 ++++++++++----------- kernel/ipt_SET.c | 4 +- kernel/ipt_set.c | 2 +- 22 files changed, 118 insertions(+), 90 deletions(-) diff --git a/ChangeLog b/ChangeLog index b1cbd1a..adb2eb9 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +2.4.5 + - Some compiler warning options are too aggressive and + therefore disabled. + 2.4.4 - Premature checking prevents to add valid elements to hash types, fixed (bug reported by JC Janos). diff --git a/Makefile b/Makefile index aa05b58..fbe20a2 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,7 @@ ifndef V V=0 endif -IPSET_VERSION:=2.4.4 +IPSET_VERSION:=2.4.5 PREFIX:=/usr/local LIBDIR:=$(PREFIX)/lib @@ -55,6 +55,7 @@ WARN_FLAGS:=-Wall \ -Wswitch-enum \ -Wundef \ -Wwrite-strings \ + -Wno-missing-field-initializers \ -Werror CFLAGS:=$(COPT_FLAGS) $(WARN_FLAGS) -Ikernel/include -I. # -g -DIPSET_DEBUG #-pg diff --git a/ipset.c b/ipset.c index 6a204b9..ba007e5 100644 --- a/ipset.c +++ b/ipset.c @@ -118,7 +118,7 @@ static struct option opts_long[] = { {"help", 2, 0, 'H'}, /* end */ - {0, 0, 0, 0}, + {NULL}, }; static char opts_short[] = @@ -731,13 +731,13 @@ string_to_number(const char *str, unsigned int min, unsigned int max, static int string_to_port(const char *str, ip_set_ip_t *port) { - struct servent *service = getservbyname(str, "tcp"); + struct servent *service; if ((service = getservbyname(str, "tcp")) != NULL) { *port = ntohs((uint16_t) service->s_port); return 0; } - return - 1; + return -1; } /* Fills the 'ip' with the parsed port in host byte order */ @@ -1952,7 +1952,7 @@ static int find_cmd(int option) int i; for (i = 1; i <= NUMBER_OF_CMD; i++) - if (cmdflags[i] == (char) option) + if (cmdflags[i] == option) return i; return CMD_NONE; diff --git a/ipset.h b/ipset.h index 35a2055..2596dfa 100644 --- a/ipset.h +++ b/ipset.h @@ -68,10 +68,11 @@ enum set_commands { CMD_ADT_GET, }; -/* Exit codes */ -#define OTHER_PROBLEM 1 -#define PARAMETER_PROBLEM 2 -#define VERSION_PROBLEM 3 +enum exittype { + OTHER_PROBLEM = 1, + PARAMETER_PROBLEM, + VERSION_PROBLEM +}; /* The view of an ipset in userspace */ struct set { diff --git a/ipset_iphash.c b/ipset_iphash.c index a11695b..edc22fb 100644 --- a/ipset_iphash.c +++ b/ipset_iphash.c @@ -127,7 +127,7 @@ static const struct option create_opts[] = { {.name = "probes", .has_arg = required_argument, .val = '2'}, {.name = "resize", .has_arg = required_argument, .val = '3'}, {.name = "netmask", .has_arg = required_argument, .val = '4'}, - {0, 0, 0, 0}, + {NULL}, }; /* Add, del, test parser */ diff --git a/ipset_ipmap.c b/ipset_ipmap.c index a0158bd..fed93d9 100644 --- a/ipset_ipmap.c +++ b/ipset_ipmap.c @@ -183,7 +183,7 @@ static const struct option create_opts[] = { {.name = "to", .has_arg = required_argument, .val = '2'}, {.name = "network", .has_arg = required_argument, .val = '3'}, {.name = "netmask", .has_arg = required_argument, .val = '4'}, - {0, 0, 0, 0}, + {NULL}, }; /* Add, del, test parser */ diff --git a/ipset_ipporthash.c b/ipset_ipporthash.c index 1e5f562..fa816c9 100644 --- a/ipset_ipporthash.c +++ b/ipset_ipporthash.c @@ -184,7 +184,7 @@ static const struct option create_opts[] = { {.name = "from", .has_arg = required_argument, .val = '4'}, {.name = "to", .has_arg = required_argument, .val = '5'}, {.name = "network", .has_arg = required_argument, .val = '6'}, - {0, 0, 0, 0}, + {NULL}, }; /* Add, del, test parser */ diff --git a/ipset_ipportiphash.c b/ipset_ipportiphash.c index 0942d30..f445d73 100644 --- a/ipset_ipportiphash.c +++ b/ipset_ipportiphash.c @@ -184,7 +184,7 @@ static const struct option create_opts[] = { {.name = "from", .has_arg = required_argument, .val = '4'}, {.name = "to", .has_arg = required_argument, .val = '5'}, {.name = "network", .has_arg = required_argument, .val = '6'}, - {0, 0, 0, 0}, + {NULL}, }; /* Add, del, test parser */ diff --git a/ipset_ipportnethash.c b/ipset_ipportnethash.c index eabe41e..9184007 100644 --- a/ipset_ipportnethash.c +++ b/ipset_ipportnethash.c @@ -184,7 +184,7 @@ static const struct option create_opts[] = { {.name = "from", .has_arg = required_argument, .val = '4'}, {.name = "to", .has_arg = required_argument, .val = '5'}, {.name = "network", .has_arg = required_argument, .val = '6'}, - {0, 0, 0, 0}, + {NULL}, }; /* Add, del, test parser */ diff --git a/ipset_iptree.c b/ipset_iptree.c index f8cd488..09f11db 100644 --- a/ipset_iptree.c +++ b/ipset_iptree.c @@ -70,7 +70,7 @@ create_final(void *data UNUSED, unsigned int flags UNUSED) /* Create commandline options */ static const struct option create_opts[] = { {.name = "timeout", .has_arg = required_argument, .val = '1'}, - {0, 0, 0, 0}, + {NULL}, }; /* Add, del, test parser */ diff --git a/ipset_iptreemap.c b/ipset_iptreemap.c index bcdf133..81bc8f3 100644 --- a/ipset_iptreemap.c +++ b/ipset_iptreemap.c @@ -59,7 +59,7 @@ create_final(void *data UNUSED, unsigned int flags UNUSED) static const struct option create_opts[] = { {.name = "gc", .has_arg = required_argument, .val = 'g'}, - {0, 0, 0, 0}, + {NULL}, }; static ip_set_ip_t diff --git a/ipset_macipmap.c b/ipset_macipmap.c index 839a6e0..186e68e 100644 --- a/ipset_macipmap.c +++ b/ipset_macipmap.c @@ -149,7 +149,7 @@ static const struct option create_opts[] = { {.name = "to", .has_arg = required_argument, .val = '2'}, {.name = "network", .has_arg = required_argument, .val = '3'}, {.name = "matchunset", .has_arg = no_argument, .val = '4'}, - {0, 0, 0, 0}, + {NULL}, }; static void diff --git a/ipset_nethash.c b/ipset_nethash.c index 7c1f609..9c9d6ac 100644 --- a/ipset_nethash.c +++ b/ipset_nethash.c @@ -106,7 +106,7 @@ static const struct option create_opts[] = { {.name = "hashsize", .has_arg = required_argument, .val = '1'}, {.name = "probes", .has_arg = required_argument, .val = '2'}, {.name = "resize", .has_arg = required_argument, .val = '3'}, - {0, 0, 0, 0}, + {NULL}, }; /* Add, del, test parser */ diff --git a/ipset_portmap.c b/ipset_portmap.c index b9f4ef8..b86dbb2 100644 --- a/ipset_portmap.c +++ b/ipset_portmap.c @@ -108,7 +108,7 @@ create_final(void *data, unsigned int flags) static const struct option create_opts[] = { {.name = "from", .has_arg = required_argument, .val = '1'}, {.name = "to", .has_arg = required_argument, .val = '2'}, - {0, 0, 0, 0}, + {NULL}, }; /* Add, del, test parser */ diff --git a/ipset_setlist.c b/ipset_setlist.c index 8e0bd94..064d67d 100644 --- a/ipset_setlist.c +++ b/ipset_setlist.c @@ -64,7 +64,7 @@ create_final(void *data UNUSED, unsigned int flags UNUSED) /* Create commandline options */ static const struct option create_opts[] = { {.name = "size", .has_arg = required_argument, .val = '1'}, - {0, 0, 0, 0}, + {NULL}, }; static void check_setname(const char *name) diff --git a/kernel/ChangeLog b/kernel/ChangeLog index 292e19f..178339b 100644 --- a/kernel/ChangeLog +++ b/kernel/ChangeLog @@ -1,3 +1,8 @@ +2.4.5 + - setlist type does not work properly together with swapping + sets, bug reported by Thomas Jacob. + - Include linux/capability.h explicitly in ip_set.c (Jan Engelhardt) + 2.4.4 - Premature checking prevents to add valid elements to hash types, fixed (bug reported by JC Janos). diff --git a/kernel/include/linux/netfilter_ipv4/ip_set.h b/kernel/include/linux/netfilter_ipv4/ip_set.h index c29a460..ec3e59f 100644 --- a/kernel/include/linux/netfilter_ipv4/ip_set.h +++ b/kernel/include/linux/netfilter_ipv4/ip_set.h @@ -487,11 +487,12 @@ struct ip_set_hash { /* register and unregister set references */ extern ip_set_id_t ip_set_get_byname(const char name[IP_SET_MAXNAMELEN]); -extern ip_set_id_t ip_set_get_byindex(ip_set_id_t id); -extern void ip_set_put(ip_set_id_t id); +extern ip_set_id_t ip_set_get_byindex(ip_set_id_t index); +extern void ip_set_put_byindex(ip_set_id_t index); +extern ip_set_id_t ip_set_id(ip_set_id_t index); extern ip_set_id_t __ip_set_get_byname(const char name[IP_SET_MAXNAMELEN], struct ip_set **set); -extern void __ip_set_put_byid(ip_set_id_t id); +extern void __ip_set_put_byindex(ip_set_id_t index); /* API for iptables set match, and SET target */ extern int ip_set_addip_kernel(ip_set_id_t id, diff --git a/kernel/include/linux/netfilter_ipv4/ip_set_setlist.h b/kernel/include/linux/netfilter_ipv4/ip_set_setlist.h index 55f0afb..ca044d8 100644 --- a/kernel/include/linux/netfilter_ipv4/ip_set_setlist.h +++ b/kernel/include/linux/netfilter_ipv4/ip_set_setlist.h @@ -10,7 +10,7 @@ struct ip_set_setlist { uint8_t size; - ip_set_id_t id[0]; + ip_set_id_t index[0]; }; struct ip_set_req_setlist_create { diff --git a/kernel/ip_set.c b/kernel/ip_set.c index c4fcc69..f60a63e 100644 --- a/kernel/ip_set.c +++ b/kernel/ip_set.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27) @@ -378,7 +379,7 @@ __ip_set_get_byname(const char *name, struct ip_set **set) return index; } -void __ip_set_put_byid(ip_set_id_t index) +void __ip_set_put_byindex(ip_set_id_t index) { if (ip_set_list[index]) __ip_set_put(index); @@ -433,12 +434,26 @@ ip_set_get_byindex(ip_set_id_t index) return index; } +/* + * Find the set id belonging to the index. + * We are protected by the mutex, so we do not need to use + * ip_set_lock. There is no need to reference the sets either. + */ +ip_set_id_t +ip_set_id(ip_set_id_t index) +{ + if (index >= ip_set_max || !ip_set_list[index]) + return IP_SET_INVALID_ID; + + return ip_set_list[index]->id; +} + /* * If the given set pointer points to a valid set, decrement * reference count by 1. The caller shall not assume the index * to be valid, after calling this function. */ -void ip_set_put(ip_set_id_t index) +void ip_set_put_byindex(ip_set_id_t index) { down(&ip_set_app_mutex); if (ip_set_list[index]) @@ -2037,9 +2052,10 @@ EXPORT_SYMBOL(ip_set_unregister_set_type); EXPORT_SYMBOL(ip_set_get_byname); EXPORT_SYMBOL(ip_set_get_byindex); -EXPORT_SYMBOL(ip_set_put); +EXPORT_SYMBOL(ip_set_put_byindex); +EXPORT_SYMBOL(ip_set_id); EXPORT_SYMBOL(__ip_set_get_byname); -EXPORT_SYMBOL(__ip_set_put_byid); +EXPORT_SYMBOL(__ip_set_put_byindex); EXPORT_SYMBOL(ip_set_addip_kernel); EXPORT_SYMBOL(ip_set_delip_kernel); diff --git a/kernel/ip_set_setlist.c b/kernel/ip_set_setlist.c index 50bc368..a7d074f 100644 --- a/kernel/ip_set_setlist.c +++ b/kernel/ip_set_setlist.c @@ -17,14 +17,14 @@ #include /* - * before ==> id, ref - * after ==> ref, id + * before ==> index, ref + * after ==> ref, index */ static inline bool -next_id_eq(const struct ip_set_setlist *map, int i, ip_set_id_t id) +next_index_eq(const struct ip_set_setlist *map, int i, ip_set_id_t index) { - return i < map->size && map->id[i] == id; + return i < map->size && map->index[i] == index; } static int @@ -33,15 +33,15 @@ setlist_utest(struct ip_set *set, const void *data, size_t size, { const struct ip_set_setlist *map = set->data; const struct ip_set_req_setlist *req = data; - ip_set_id_t id, ref = IP_SET_INVALID_ID; + ip_set_id_t index, ref = IP_SET_INVALID_ID; int i, res = 0; struct ip_set *s; if (req->before && req->ref[0] == '\0') return -EINVAL; - id = __ip_set_get_byname(req->name, &s); - if (id == IP_SET_INVALID_ID) + index = __ip_set_get_byname(req->name, &s); + if (index == IP_SET_INVALID_ID) return -EEXIST; if (req->ref[0] != '\0') { ref = __ip_set_get_byname(req->ref, &s); @@ -51,24 +51,24 @@ setlist_utest(struct ip_set *set, const void *data, size_t size, } } for (i = 0; i < map->size - && map->id[i] != IP_SET_INVALID_ID; i++) { - if (req->before && map->id[i] == id) { - res = next_id_eq(map, i + 1, ref); + && map->index[i] != IP_SET_INVALID_ID; i++) { + if (req->before && map->index[i] == index) { + res = next_index_eq(map, i + 1, ref); break; } else if (!req->before) { if ((ref == IP_SET_INVALID_ID - && map->id[i] == id) - || (map->id[i] == ref - && next_id_eq(map, i + 1, id))) { + && map->index[i] == index) + || (map->index[i] == ref + && next_index_eq(map, i + 1, index))) { res = 1; break; } } } if (ref != IP_SET_INVALID_ID) - __ip_set_put_byid(ref); + __ip_set_put_byindex(ref); finish: - __ip_set_put_byid(id); + __ip_set_put_byindex(index); return res; } @@ -83,27 +83,27 @@ setlist_ktest(struct ip_set *set, int i, res = 0; for (i = 0; i < map->size - && map->id[i] != IP_SET_INVALID_ID + && map->index[i] != IP_SET_INVALID_ID && res == 0; i++) - res = ip_set_testip_kernel(map->id[i], skb, flags); + res = ip_set_testip_kernel(map->index[i], skb, flags); return res; } static inline int -insert_setlist(struct ip_set_setlist *map, int i, ip_set_id_t id) +insert_setlist(struct ip_set_setlist *map, int i, ip_set_id_t index) { ip_set_id_t tmp; int j; - printk("i: %u, last %u\n", i, map->id[map->size - 1]); - if (i >= map->size || map->id[map->size - 1] != IP_SET_INVALID_ID) + DP("i: %u, last %u\n", i, map->index[map->size - 1]); + if (i >= map->size || map->index[map->size - 1] != IP_SET_INVALID_ID) return -ERANGE; for (j = i; j < map->size - && id != IP_SET_INVALID_ID; j++) { - tmp = map->id[j]; - map->id[j] = id; - id = tmp; + && index != IP_SET_INVALID_ID; j++) { + tmp = map->index[j]; + map->index[j] = index; + index = tmp; } return 0; } @@ -114,15 +114,15 @@ setlist_uadd(struct ip_set *set, const void *data, size_t size, { struct ip_set_setlist *map = set->data; const struct ip_set_req_setlist *req = data; - ip_set_id_t id, ref = IP_SET_INVALID_ID; + ip_set_id_t index, ref = IP_SET_INVALID_ID; int i, res = -ERANGE; struct ip_set *s; if (req->before && req->ref[0] == '\0') return -EINVAL; - id = __ip_set_get_byname(req->name, &s); - if (id == IP_SET_INVALID_ID) + index = __ip_set_get_byname(req->name, &s); + if (index == IP_SET_INVALID_ID) return -EEXIST; /* "Loop detection" */ if (strcmp(s->type->typename, "setlist") == 0) @@ -136,22 +136,22 @@ setlist_uadd(struct ip_set *set, const void *data, size_t size, } } for (i = 0; i < map->size; i++) { - if (map->id[i] != ref) + if (map->index[i] != ref) continue; if (req->before) - res = insert_setlist(map, i, id); + res = insert_setlist(map, i, index); else res = insert_setlist(map, ref == IP_SET_INVALID_ID ? i : i + 1, - id); + index); break; } if (ref != IP_SET_INVALID_ID) - __ip_set_put_byid(ref); - /* In case of success, we keep the reference to the id */ + __ip_set_put_byindex(ref); + /* In case of success, we keep the reference to the set */ finish: if (res != 0) - __ip_set_put_byid(id); + __ip_set_put_byindex(index); return res; } @@ -166,9 +166,9 @@ setlist_kadd(struct ip_set *set, int i, res = -EINVAL; for (i = 0; i < map->size - && map->id[i] != IP_SET_INVALID_ID + && map->index[i] != IP_SET_INVALID_ID && res != 0; i++) - res = ip_set_addip_kernel(map->id[i], skb, flags); + res = ip_set_addip_kernel(map->index[i], skb, flags); return res; } @@ -178,8 +178,8 @@ unshift_setlist(struct ip_set_setlist *map, int i) int j; for (j = i; j < map->size - 1; j++) - map->id[j] = map->id[j+1]; - map->id[map->size-1] = IP_SET_INVALID_ID; + map->index[j] = map->index[j+1]; + map->index[map->size-1] = IP_SET_INVALID_ID; return 0; } @@ -189,15 +189,15 @@ setlist_udel(struct ip_set *set, const void *data, size_t size, { struct ip_set_setlist *map = set->data; const struct ip_set_req_setlist *req = data; - ip_set_id_t id, ref = IP_SET_INVALID_ID; + ip_set_id_t index, ref = IP_SET_INVALID_ID; int i, res = -EEXIST; struct ip_set *s; if (req->before && req->ref[0] == '\0') return -EINVAL; - id = __ip_set_get_byname(req->name, &s); - if (id == IP_SET_INVALID_ID) + index = __ip_set_get_byname(req->name, &s); + if (index == IP_SET_INVALID_ID) return -EEXIST; if (req->ref[0] != '\0') { ref = __ip_set_get_byname(req->ref, &s); @@ -205,31 +205,31 @@ setlist_udel(struct ip_set *set, const void *data, size_t size, goto finish; } for (i = 0; i < map->size - && map->id[i] != IP_SET_INVALID_ID; i++) { + && map->index[i] != IP_SET_INVALID_ID; i++) { if (req->before) { - if (map->id[i] == id - && next_id_eq(map, i + 1, ref)) { + if (map->index[i] == index + && next_index_eq(map, i + 1, ref)) { res = unshift_setlist(map, i); break; } } else if (ref == IP_SET_INVALID_ID) { - if (map->id[i] == id) { + if (map->index[i] == index) { res = unshift_setlist(map, i); break; } - } else if (map->id[i] == ref - && next_id_eq(map, i + 1, id)) { + } else if (map->index[i] == ref + && next_index_eq(map, i + 1, index)) { res = unshift_setlist(map, i + 1); break; } } if (ref != IP_SET_INVALID_ID) - __ip_set_put_byid(ref); + __ip_set_put_byindex(ref); finish: - __ip_set_put_byid(id); - /* In case of success, release the reference to the id */ + __ip_set_put_byindex(index); + /* In case of success, release the reference to the set */ if (res == 0) - __ip_set_put_byid(id); + __ip_set_put_byindex(index); return res; } @@ -244,9 +244,9 @@ setlist_kdel(struct ip_set *set, int i, res = -EINVAL; for (i = 0; i < map->size - && map->id[i] != IP_SET_INVALID_ID + && map->index[i] != IP_SET_INVALID_ID && res != 0; i++) - res = ip_set_delip_kernel(map->id[i], skb, flags); + res = ip_set_delip_kernel(map->index[i], skb, flags); return res; } @@ -263,7 +263,7 @@ setlist_create(struct ip_set *set, const void *data, size_t size) return -ENOMEM; map->size = req->size; for (i = 0; i < map->size; i++) - map->id[i] = IP_SET_INVALID_ID; + map->index[i] = IP_SET_INVALID_ID; set->data = map; return 0; @@ -276,8 +276,8 @@ setlist_destroy(struct ip_set *set) int i; for (i = 0; i < map->size - && map->id[i] != IP_SET_INVALID_ID; i++) - __ip_set_put_byid(map->id[i]); + && map->index[i] != IP_SET_INVALID_ID; i++) + __ip_set_put_byindex(map->index[i]); kfree(map); set->data = NULL; @@ -290,9 +290,9 @@ setlist_flush(struct ip_set *set) int i; for (i = 0; i < map->size - && map->id[i] != IP_SET_INVALID_ID; i++) { - __ip_set_put_byid(map->id[i]); - map->id[i] = IP_SET_INVALID_ID; + && map->index[i] != IP_SET_INVALID_ID; i++) { + __ip_set_put_byindex(map->index[i]); + map->index[i] = IP_SET_INVALID_ID; } } @@ -320,7 +320,7 @@ setlist_list_members(const struct ip_set *set, void *data) int i; for (i = 0; i < map->size; i++) - *((ip_set_id_t *)data + i) = map->id[i]; + *((ip_set_id_t *)data + i) = ip_set_id(map->index[i]); } IP_SET_TYPE(setlist, IPSET_TYPE_SETNAME | IPSET_DATA_SINGLE) diff --git a/kernel/ipt_SET.c b/kernel/ipt_SET.c index 960e557..a6cad1d 100644 --- a/kernel/ipt_SET.c +++ b/kernel/ipt_SET.c @@ -179,9 +179,9 @@ static void destroy(const struct xt_target *target, } #endif if (info->add_set.index != IP_SET_INVALID_ID) - ip_set_put(info->add_set.index); + ip_set_put_byindex(info->add_set.index); if (info->del_set.index != IP_SET_INVALID_ID) - ip_set_put(info->del_set.index); + ip_set_put_byindex(info->del_set.index); } #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,17) diff --git a/kernel/ipt_set.c b/kernel/ipt_set.c index b08b3bb..114f973 100644 --- a/kernel/ipt_set.c +++ b/kernel/ipt_set.c @@ -175,7 +175,7 @@ static void destroy(const struct xt_match *match, return; } #endif - ip_set_put(info->match_set.index); + ip_set_put_byindex(info->match_set.index); } #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,17) -- cgit v1.2.3