lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <1396017924-7754-23-git-send-email-jovi.zhangwei@gmail.com>
Date:	Fri, 28 Mar 2014 10:45:17 -0400
From:	Jovi Zhangwei <jovi.zhangwei@...il.com>
To:	Ingo Molnar <mingo@...hat.com>,
	Steven Rostedt <rostedt@...dmis.org>
Cc:	linux-kernel@...r.kernel.org,
	Masami Hiramatsu <masami.hiramatsu.pt@...achi.com>,
	Greg Kroah-Hartman <gregkh@...uxfoundation.org>,
	Frederic Weisbecker <fweisbec@...il.com>,
	Andi Kleen <andi@...stfloor.org>,
	Jovi Zhangwei <jovi.zhangwei@...il.com>
Subject: [PATCH v2 22/29] ktap: add events parse code(tools/ktap/kp_parse_events.c)

Function 'kp_parse_events' parse event string passed by user,
and return events description structure, it covers:

1). tracepoint
        Search tracepoint name through '/sys/kernel/debug/tracing/events/',
        and get event id.

2). kprobe
        Search symbol name through '/proc/kallsyms', then write event to
        '/sys/kernel/debug/tracing/kprobe_events', and read events id.

3). uprobe
        Search symbol name through libelf, then write event to
        '/sys/kernel/debug/tracing/uprobe_events', then read events id.

4). SDT
        Same as uprobe.

All events id will assembly ktap_eventdesc_t structure, and finially
pass to 'kdebug.trace_by_id' function.

Signed-off-by: Jovi Zhangwei <jovi.zhangwei@...il.com>
---
 tools/ktap/kp_parse_events.c | 798 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 798 insertions(+)
 create mode 100644 tools/ktap/kp_parse_events.c

diff --git a/tools/ktap/kp_parse_events.c b/tools/ktap/kp_parse_events.c
new file mode 100644
index 0000000..50fea2f
--- /dev/null
+++ b/tools/ktap/kp_parse_events.c
@@ -0,0 +1,798 @@
+/*
+ * parse_events.c - ktap events parser
+ *
+ * This file is part of ktap by Jovi Zhangwei.
+ *
+ * Copyright (C) 2012-2014 Jovi Zhangwei <jovi.zhangwei@...il.com>.
+ *
+ * ktap is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * ktap is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <ctype.h>
+
+#include "../../include/uapi/ktap/ktap_types.h"
+#include "../../include/uapi/ktap/ktap_bc.h"
+#include "kp_symbol.h"
+#include "kp_util.h"
+
+#define TRACING_EVENTS_DIR "/sys/kernel/debug/tracing/events"
+
+static u8 *idmap;
+static int idmap_size = 1024; /* set init size */
+static int id_nr;
+
+static int idmap_init(void)
+{
+	idmap = malloc(idmap_size);
+	if (!idmap)
+		return -1;
+
+	memset(idmap, 0, idmap_size);
+	return 0;
+}
+
+static void idmap_free(void)
+{
+	id_nr = 0;
+	free(idmap);
+}
+
+static inline int idmap_is_set(int id)
+{
+	return idmap[id / 8] & (1 << (id % 8));
+}
+
+static void idmap_set(int id)
+{
+	if (id >= idmap_size * 8) {
+		int newsize = id + 100; /* allocate extra 800 id */
+		idmap = realloc(idmap, newsize);
+		memset(idmap + idmap_size, 0, newsize - idmap_size);
+		idmap_size = newsize;
+	}
+
+	if (!idmap_is_set(id))
+		id_nr++;
+
+	idmap[id / 8] = idmap[id / 8] | (1 << (id % 8));
+}
+
+static void idmap_clear(int id)
+{
+	if (!idmap_is_set(id))
+		return;
+
+	id_nr--;
+	idmap[id / 8] = idmap[id / 8] & ~ (1 << (id % 8));
+}
+
+static int idmap_get_max_id(void)
+{
+	return idmap_size * 8;
+}
+
+static int *get_id_array()
+{
+	int *id_array;
+	int i, j = 0;
+
+	id_array = malloc(sizeof(int) * id_nr);
+	if (!id_array)
+		return NULL;
+
+	for (i = 0; i < idmap_get_max_id(); i++) {
+		if (idmap_is_set(i))
+			id_array[j++] = i;
+	}
+
+	return id_array;
+}
+
+static int add_event(char *evtid_path)
+{
+	char id_buf[24];
+	int id, fd;
+
+	fd = open(evtid_path, O_RDONLY);
+	if (fd < 0) {
+		/*
+		 * some tracepoint doesn't have id file, like ftrace,
+		 * return success in here, and don't print error.
+		 */
+		verbose_printf("warning: cannot open file %s\n", evtid_path);
+		return 0;
+	}
+
+	if (read(fd, id_buf, sizeof(id_buf)) < 0) {
+		fprintf(stderr, "read file error %s\n", evtid_path);
+		close(fd);
+		return -1;
+	}
+
+	id = atoll(id_buf);
+
+	idmap_set(id);
+
+	close(fd);
+	return 0;
+}
+
+static int add_tracepoint(const char *sys_name, const char *evt_name)
+{
+	char evtid_path[PATH_MAX] = {0};
+
+	snprintf(evtid_path, PATH_MAX, "%s/%s/%s/id", TRACING_EVENTS_DIR,
+					sys_name, evt_name);
+	return add_event(evtid_path);
+}
+
+static int parse_events_add_tracepoint(char *sys, char *event)
+{
+	process_available_tracepoints(sys, event, add_tracepoint);
+	return 0;
+}
+
+enum {
+	KPROBE_EVENT,
+	UPROBE_EVENT,
+};
+
+struct probe_list {
+	struct probe_list *next;
+	int type;
+	char event[64];
+};
+
+static struct probe_list *probe_list_head; /* for cleanup resources */
+
+/*
+ * Some symbol format cannot write to uprobe_events in debugfs, like:
+ * symbol "check_one_fd.part.0" in glibc.
+ * For those symbols, we change the format to:
+ * "check_one_fd.part.0" -> "check_one_fd_part_0"
+ */
+static char *format_symbol_name(const char *old_symbol)
+{
+	char *new_name = strdup(old_symbol);
+	char *name = new_name;
+	int changed = 0;
+
+        if (!isalpha(*name) && *name != '_') {
+		*name = '_';
+		changed = 1;
+	}
+
+        while (*++name != '\0') {
+                if (!isalpha(*name) && !isdigit(*name) && *name != '_') {
+			*name = '_';
+			changed = 1;
+			continue;
+		}
+        }
+
+	if (changed)
+		fprintf(stderr,
+			"Warning: symbol \"%s\" transformed to event \"%s\"\n",
+			old_symbol, new_name);
+
+	/* this is a good name */
+        return new_name;
+}
+
+
+#define KPROBE_EVENTS_PATH "/sys/kernel/debug/tracing/kprobe_events"
+
+/**
+ * @return 0 on success, otherwise -1
+ */
+static int
+write_kprobe_event(int fd, int ret_probe, const char *symbol,
+		   unsigned long start, char *fetch_args)
+{
+	char probe_event[128] = {0};
+	char event[64] = {0};
+	struct probe_list *pl;
+	char event_id_path[128] = {0};
+	char *symbol_name;
+	int id_fd, ret;
+
+	/* In case some symbols cannot write to uprobe_events debugfs file */
+	symbol_name = format_symbol_name(symbol);
+
+	if (!fetch_args)
+		fetch_args = " ";
+
+	if (ret_probe) {
+		snprintf(event, 64, "ktap_kprobes_%d/ret_%s",
+			 getpid(), symbol_name);
+		/* Return probe point must be a symbol */
+		snprintf(probe_event, 128, "r:%s %s %s",
+			 event, symbol, fetch_args);
+	} else {
+		snprintf(event, 64, "ktap_kprobes_%d/%s",
+			 getpid(), symbol_name);
+		snprintf(probe_event, 128, "p:%s 0x%lx %s",
+			 event, start, fetch_args);
+	}
+
+	sprintf(event_id_path, "/sys/kernel/debug/tracing/events/%s/id", event);
+	/* if event id already exist, then don't write to kprobes_event again */
+	id_fd = open(event_id_path, O_RDONLY);
+	if (id_fd > 0) {
+		close(id_fd);
+
+		/* remember add event id to ids_array */
+		ret = add_event(event_id_path);
+		if (ret)
+			goto error;
+
+		goto out;
+	}
+
+	verbose_printf("write kprobe event %s\n", probe_event);
+
+	if (write(fd, probe_event, strlen(probe_event)) <= 0) {
+		fprintf(stderr, "Cannot write %s to %s\n", probe_event,
+				KPROBE_EVENTS_PATH);
+		goto error;
+	}
+
+	/* add to cleanup list */
+	pl = malloc(sizeof(struct probe_list));
+	if (!pl)
+		goto error;
+
+	pl->type = KPROBE_EVENT;
+	pl->next = probe_list_head;
+	memcpy(pl->event, event, 64);
+	probe_list_head = pl;
+
+	ret = add_event(event_id_path);
+	if (ret < 0)
+		goto error;
+
+ out:
+	free(symbol_name);
+	return 0;
+
+ error:
+	free(symbol_name);
+	return -1;
+}
+
+static unsigned long kprobes_text_start;
+static unsigned long kprobes_text_end;
+
+static void init_kprobe_prohibited_area(void)
+{
+	static int once = 0;
+
+	if (once > 0)
+		return;
+
+	once = 1;
+	kprobes_text_start     = find_kernel_symbol("__kprobes_text_start");
+	kprobes_text_end       = find_kernel_symbol("__kprobes_text_end");
+}
+
+static int check_kprobe_addr_prohibited(unsigned long addr)
+{
+	if (addr >= kprobes_text_start && addr <= kprobes_text_end)
+		return -1;
+
+	return 0;
+}
+
+struct probe_cb_base {
+	int fd;
+	int ret_probe;
+	const char *event;
+	char *binary;
+	char *symbol;
+	char *fetch_args;
+};
+
+static int kprobe_symbol_actor(void *arg, const char *name, char type,
+			       unsigned long start)
+{
+	struct probe_cb_base *base = (struct probe_cb_base *)arg;
+
+	/* only can probe text function */
+	if (type != 't' && type != 'T')
+		return -1;
+
+	if (!strglobmatch(name, base->symbol))
+		return -1;
+
+	if (check_kprobe_addr_prohibited(start))
+		return -1;
+
+	/* ignore reture code of write debugfs */
+	write_kprobe_event(base->fd, base->ret_probe, name, start,
+			   base->fetch_args);
+
+	return 0; /* success */
+}
+
+static int parse_events_add_kprobe(char *event)
+{
+	char *symbol, *end;
+	struct probe_cb_base base;
+	int fd, ret;
+
+	fd = open(KPROBE_EVENTS_PATH, O_WRONLY);
+	if (fd < 0) {
+		fprintf(stderr, "Cannot open %s\n", KPROBE_EVENTS_PATH);
+		return -1;
+	}
+
+	end = strpbrk(event, "% ");
+	if (end)
+		symbol = strndup(event, end - event);
+	else
+		symbol = strdup(event);
+
+	base.fd = fd;
+	base.ret_probe = !!strstr(event, "%return");
+	base.symbol = symbol;
+	base.fetch_args = strchr(event, ' ');
+
+	init_kprobe_prohibited_area();
+
+	ret = kallsyms_parse(&base, kprobe_symbol_actor);
+	if (ret <= 0) {
+		fprintf(stderr, "cannot parse symbol \"%s\"\n", symbol);
+		ret = -1;
+	} else {
+		ret = 0;
+	}
+
+	free(symbol);
+	close(fd);
+
+	return ret;
+}
+
+#define UPROBE_EVENTS_PATH "/sys/kernel/debug/tracing/uprobe_events"
+
+/**
+ * @return 0 on success, otherwise -1
+ */
+static int
+write_uprobe_event(int fd, int ret_probe, const char *binary,
+		   const char *symbol, unsigned long addr,
+		   char *fetch_args)
+{
+	char probe_event[128] = {0};
+	char event[64] = {0};
+	struct probe_list *pl;
+	char event_id_path[128] = {0};
+	char *symbol_name;
+	int id_fd, ret;
+
+	/* In case some symbols cannot write to uprobe_events debugfs file */
+	symbol_name = format_symbol_name(symbol);
+
+	if (!fetch_args)
+		fetch_args = " ";
+
+	if (ret_probe) {
+		snprintf(event, 64, "ktap_uprobes_%d/ret_%s",
+			 getpid(), symbol_name);
+		snprintf(probe_event, 128, "r:%s %s:0x%lx %s",
+			 event, binary, addr, fetch_args);
+	} else {
+		snprintf(event, 64, "ktap_uprobes_%d/%s",
+			 getpid(), symbol_name);
+		snprintf(probe_event, 128, "p:%s %s:0x%lx %s",
+			 event, binary, addr, fetch_args);
+	}
+
+	sprintf(event_id_path, "/sys/kernel/debug/tracing/events/%s/id", event);
+	/* if event id already exist, then don't write to uprobes_event again */
+	id_fd = open(event_id_path, O_RDONLY);
+	if (id_fd > 0) {
+		close(id_fd);
+
+		/* remember add event id to ids_array */
+		ret = add_event(event_id_path);
+		if (ret)
+			goto error;
+
+		goto out;
+	}
+
+	verbose_printf("write uprobe event %s\n", probe_event);
+
+	if (write(fd, probe_event, strlen(probe_event)) <= 0) {
+		fprintf(stderr, "Cannot write %s to %s\n", probe_event,
+				UPROBE_EVENTS_PATH);
+		goto error;
+	}
+
+	/* add to cleanup list */
+	pl = malloc(sizeof(struct probe_list));
+	if (!pl)
+		goto error;
+
+	pl->type = UPROBE_EVENT;
+	pl->next = probe_list_head;
+	memcpy(pl->event, event, 64);
+	probe_list_head = pl;
+
+	ret = add_event(event_id_path);
+	if (ret < 0)
+		goto error;
+
+ out:
+	free(symbol_name);
+	return 0;
+
+ error:
+	free(symbol_name);
+	return -1;
+}
+
+/**
+ * TODO: avoid copy-paste stuff
+ *
+ * @return 1 on success, otherwise 0
+ */
+#ifdef NO_LIBELF
+static int parse_events_resolve_symbol(int fd, char *event, int type)
+{
+	char *colon, *binary, *fetch_args;
+	unsigned long symbol_address;
+
+	colon = strchr(event, ':');
+	if (!colon)
+		return -1;
+
+	symbol_address = strtol(colon + 1 /* skip ":" */, NULL, 0);
+
+	fetch_args = strchr(event, ' ');
+
+	/**
+	 * We already have address, no need in resolving.
+	 */
+	if (symbol_address) {
+		int ret;
+
+		binary = strndup(event, colon - event);
+		ret = write_uprobe_event(fd, !!strstr(event, "%return"), binary,
+					 "NULL", symbol_address, fetch_args);
+		free(binary);
+		return ret;
+	}
+
+	fprintf(stderr, "error: cannot resolve event \"%s\" without libelf, "
+			"please recompile ktap with NO_LIBELF disabled\n",
+			event);
+	exit(EXIT_FAILURE);
+	return -1;
+}
+
+#else
+static int uprobe_symbol_actor(const char *name, vaddr_t addr, void *arg)
+{
+	struct probe_cb_base *base = (struct probe_cb_base *)arg;
+	int ret;
+
+	if (!strglobmatch(name, base->symbol))
+		return 0;
+
+	verbose_printf("uprobe: binary: \"%s\" symbol \"%s\" "
+			"resolved to 0x%lx\n",
+			base->binary, base->symbol, (unsigned long)addr);
+
+	ret = write_uprobe_event(base->fd, base->ret_probe, base->binary,
+				 name, addr, base->fetch_args);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int parse_events_resolve_symbol(int fd, char *event, int type)
+{
+	char *colon, *end;
+	vaddr_t symbol_address;
+	int ret;
+	struct probe_cb_base base = {
+		.fd = fd,
+		.event = event
+	};
+
+	colon = strchr(event, ':');
+	if (!colon)
+		return 0;
+
+	base.ret_probe = !!strstr(event, "%return");
+	symbol_address = strtol(colon + 1 /* skip ":" */, NULL, 0);
+	base.binary = strndup(event, colon - event);
+
+	base.fetch_args = strchr(event, ' ');
+
+	/*
+	 * We already have address, no need in resolving.
+	 */
+	if (symbol_address) {
+		int ret;
+		ret = write_uprobe_event(fd, base.ret_probe, base.binary,
+					 "NULL", symbol_address,
+					 base.fetch_args);
+		free(base.binary);
+		return ret;
+	}
+
+	end = strpbrk(event, "% ");
+	if (end)
+		base.symbol = strndup(colon + 1, end - 1 - colon);
+	else
+		base.symbol = strdup(colon + 1);
+
+	ret = parse_dso_symbols(base.binary, type, uprobe_symbol_actor,
+				(void *)&base);
+	if (!ret) {
+		fprintf(stderr, "error: cannot find symbol %s in binary %s\n",
+			base.symbol, base.binary);
+		ret = -1;
+	} else if(ret > 0) {
+		/* no error found when parse symbols */
+		ret = 0;
+	}
+
+	free(base.binary);
+	free(base.symbol);
+
+	return ret;
+}
+#endif
+
+static int parse_events_add_uprobe(char *old_event, int type)
+{
+	int ret;
+	int fd;
+
+	fd = open(UPROBE_EVENTS_PATH, O_WRONLY);
+	if (fd < 0) {
+		fprintf(stderr, "Cannot open %s\n", UPROBE_EVENTS_PATH);
+		return -1;
+	}
+
+	ret = parse_events_resolve_symbol(fd, old_event, type);
+
+	close(fd);
+	return ret;
+}
+
+static int parse_events_add_probe(char *old_event)
+{
+	char *separator;
+
+	separator = strchr(old_event, ':');
+	if (!separator || (separator == old_event))
+		return parse_events_add_kprobe(old_event);
+	else
+		return parse_events_add_uprobe(old_event, FIND_SYMBOL);
+}
+
+static int parse_events_add_sdt(char *old_event)
+{
+	return parse_events_add_uprobe(old_event, FIND_STAPSDT_NOTE);
+}
+
+static void strim(char *s)
+{
+	size_t size;
+	char *end;
+
+	size = strlen(s);
+	if (!size)
+		return;
+
+	end = s + size -1;
+	while (end >= s && isspace(*end))
+		end--;
+
+	*(end + 1) = '\0';
+}
+
+static int get_sys_event_filter_str(char *start,
+				    char **sys, char **event, char **filter)
+{
+	char *separator, *separator2, *ptr, *end;
+
+	while (*start == ' ')
+		start++;
+
+	/* find sys */
+	separator = strchr(start, ':');
+	if (!separator || (separator == start)) {
+		return -1;
+	}
+
+	ptr = malloc(separator - start + 1);
+	if (!ptr)
+		return -1;
+
+	strncpy(ptr, start, separator - start);
+	ptr[separator - start] = '\0';
+
+	strim(ptr);
+	*sys = ptr;
+
+	if (!strcmp(*sys, "probe") && (*(separator + 1) == '/')) {
+		/* it's uprobe event */
+		separator2 = strchr(separator + 1, ':');
+		if (!separator2)
+			return -1;
+	} else
+		separator2 = separator;
+
+	/* find filter */
+	end = start + strlen(start);
+	while (*--end == ' ') {
+	}
+
+	if (*end == '/') {
+		char *filter_start;
+
+		filter_start = strchr(separator2, '/');
+		if (filter_start == end)
+			return -1;
+
+		ptr = malloc(end - filter_start);
+		if (!ptr)
+			return -1;
+
+		memcpy(ptr, filter_start + 1, end - filter_start - 1);
+		ptr[end - filter_start - 1] = '\0';
+
+		*filter = ptr;
+
+		end = filter_start;
+	} else {
+		*filter = NULL;
+		end++;
+	}
+
+	/* find event */
+	ptr = malloc(end - separator);
+	if (!ptr)
+		return -1;
+
+	memcpy(ptr, separator + 1, end - separator - 1);
+	ptr[end - separator - 1] = '\0';
+
+	strim(ptr);
+	*event = ptr;
+
+	return 0;
+}
+
+static char *get_next_eventdef(char *str)
+{
+	char *separator;
+
+	separator = strchr(str, ',');
+	if (!separator)
+		return str + strlen(str);
+
+	*separator = '\0';
+	return separator + 1;
+}
+
+ktap_eventdesc_t *kp_parse_events(const char *eventdef)
+{
+	char *str = strdup(eventdef);
+	char *sys, *event, *filter, *next;
+	ktap_eventdesc_t *evdef_info;
+	int ret;
+
+	idmap_init();
+
+ parse_next_eventdef:
+	next = get_next_eventdef(str);
+
+	if (get_sys_event_filter_str(str, &sys, &event, &filter))
+		goto error;
+
+	verbose_printf("parse_eventdef: sys[%s], event[%s], filter[%s]\n",
+		       sys, event, filter);
+
+	if (!strcmp(sys, "probe"))
+		ret = parse_events_add_probe(event);
+	else if (!strcmp(sys, "sdt"))
+		ret = parse_events_add_sdt(event);
+	else
+		ret = parse_events_add_tracepoint(sys, event);
+
+	if (ret)
+		goto error;
+
+	/* don't trace ftrace:function when all tracepoints enabled */
+	if (!strcmp(sys, "*"))
+		idmap_clear(1);
+
+
+	if (filter && *next != '\0') {
+		fprintf(stderr, "Error: eventdef only can append one filter\n");
+		goto error;
+	}
+
+	str = next;
+	if (*next != '\0')
+		goto parse_next_eventdef;
+
+	evdef_info = malloc(sizeof(*evdef_info));
+	if (!evdef_info)
+		goto error;
+
+	evdef_info->nr = id_nr;
+	evdef_info->id_arr = get_id_array();
+	evdef_info->filter = filter;
+
+	idmap_free();
+	return evdef_info;
+ error:
+	idmap_free();
+	cleanup_event_resources();
+	return NULL;
+}
+
+void cleanup_event_resources(void)
+{
+	struct probe_list *pl;
+	const char *path;
+	char probe_event[128] = {0};
+	int fd, ret;
+
+	for (pl = probe_list_head; pl; pl = pl->next) {
+		if (pl->type == KPROBE_EVENT)
+			path = KPROBE_EVENTS_PATH;
+		else if (pl->type == UPROBE_EVENT)
+			path = UPROBE_EVENTS_PATH;
+		else {
+			fprintf(stderr, "Cannot cleanup event type %d\n",
+					pl->type);
+			continue;
+		}
+
+		snprintf(probe_event, 128, "-:%s", pl->event);
+
+		fd = open(path, O_WRONLY);
+		if (fd < 0) {
+			fprintf(stderr, "Cannot open %s\n", UPROBE_EVENTS_PATH);
+			continue;
+		}
+
+		ret = write(fd, probe_event, strlen(probe_event));
+		if (ret <= 0) {
+			fprintf(stderr, "Cannot write %s to %s\n", probe_event,
+					path);
+			close(fd);
+			continue;
+		}
+
+		close(fd);
+	}
+}
+
-- 
1.8.1.4

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ