[PATCH] Implement kqueue(2) support via CONFIG_ELOOP_KQUEUE

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



NOTE: kqueue has to be closed and re-build after forking.
epoll *should* do the same, but it seems that wpa_supplicant doesn't
need it at least.

I have re-worked a little bit of the epoll code (moved into a similar
kqueue function) so it's trivial to requeue epoll if needed in the future.

Roy
commit 785bd6035f31b2918050df2a08aa07fb23dcb30b
Author: Roy Marples <roy@xxxxxxxxxxxx>
Date:   Wed Jan 20 17:10:08 2016 +0000

    Implement kqueue(2) support for eloop via CONFIG_ELOOP_QUEUE.

diff --git a/hostapd/Makefile b/hostapd/Makefile
index 45afedf..fd3105e 100644
--- a/hostapd/Makefile
+++ b/hostapd/Makefile
@@ -140,6 +140,10 @@ ifdef CONFIG_ELOOP_EPOLL
 CFLAGS += -DCONFIG_ELOOP_EPOLL
 endif
 
+ifdef CONFIG_ELOOP_KQUEUE
+CFLAGS += -DCONFIG_ELOOP_KQUEUE
+endif
+
 OBJS += ../src/utils/common.o
 OBJS_c += ../src/utils/common.o
 OBJS += ../src/utils/wpa_debug.o
diff --git a/hostapd/hostapd_cli.c b/hostapd/hostapd_cli.c
index 52dfc9f..7e6ac23 100644
--- a/hostapd/hostapd_cli.c
+++ b/hostapd/hostapd_cli.c
@@ -1444,7 +1444,7 @@ int main(int argc, char *argv[])
 		}
 	}
 
-	if (daemonize && os_daemonize(pid_file))
+	if (daemonize && os_daemonize(pid_file) && eloop_sock_requeue())
 		return -1;
 
 	if (interactive)
diff --git a/hostapd/main.c b/hostapd/main.c
index 4913cbd..1b9002c 100644
--- a/hostapd/main.c
+++ b/hostapd/main.c
@@ -408,9 +408,16 @@ static int hostapd_global_run(struct hapd_interfaces *ifaces, int daemonize,
 	}
 #endif /* EAP_SERVER_TNC */
 
-	if (daemonize && os_daemonize(pid_file)) {
-		wpa_printf(MSG_ERROR, "daemon: %s", strerror(errno));
-		return -1;
+	if (daemonize) {
+		if (os_daemonize(pid_file)) {
+			wpa_printf(MSG_ERROR, "daemon: %s", strerror(errno));
+			return -1;
+		}
+		if (eloop_sock_requeue()) {
+			wpa_printf(MSG_ERROR, "eloop_sock_requeue: %s",
+				   strerror(errno));
+			return -1;
+		}
 	}
 
 	eloop_run();
diff --git a/src/utils/eloop.c b/src/utils/eloop.c
index 8647229..656f001 100644
--- a/src/utils/eloop.c
+++ b/src/utils/eloop.c
@@ -18,7 +18,12 @@
 #error Do not define both of poll and epoll
 #endif
 
-#if !defined(CONFIG_ELOOP_POLL) && !defined(CONFIG_ELOOP_EPOLL)
+#if defined(CONFIG_ELOOP_POLL) && defined(CONFIG_ELOOP_KQUEUE)
+#error Do not define both of poll and kqueue
+#endif
+
+#if !defined(CONFIG_ELOOP_POLL) && !defined(CONFIG_ELOOP_EPOLL) && \
+    !defined(CONFIG_ELOOP_KQUEUE)
 #define CONFIG_ELOOP_SELECT
 #endif
 
@@ -30,6 +35,10 @@
 #include <sys/epoll.h>
 #endif /* CONFIG_ELOOP_EPOLL */
 
+#ifdef CONFIG_ELOOP_KQUEUE
+#include <sys/event.h>
+#endif /* CONFIG_ELOOP_KQUEUE */
+
 struct eloop_sock {
 	int sock;
 	void *eloop_data;
@@ -75,13 +84,20 @@ struct eloop_data {
 	struct pollfd *pollfds;
 	struct pollfd **pollfds_map;
 #endif /* CONFIG_ELOOP_POLL */
+#if defined(CONFIG_ELOOP_EPOLL) || defined(CONFIG_ELOOP_KQUEUE)
+	int max_fd;
+	struct eloop_sock *fd_table;
+#endif
 #ifdef CONFIG_ELOOP_EPOLL
 	int epollfd;
 	int epoll_max_event_num;
-	int epoll_max_fd;
-	struct eloop_sock *epoll_table;
 	struct epoll_event *epoll_events;
 #endif /* CONFIG_ELOOP_EPOLL */
+#ifdef CONFIG_ELOOP_KQUEUE
+	int kqueuefd;
+	int kqueue_nevents;
+	struct kevent *kqueue_events;
+#endif /* CONFIG_ELOOP_KQUEUE */
 	struct eloop_sock_table readers;
 	struct eloop_sock_table writers;
 	struct eloop_sock_table exceptions;
@@ -153,26 +169,95 @@ int eloop_init(void)
 			   __func__, strerror(errno));
 		return -1;
 	}
+#endif /* CONFIG_ELOOP_EPOLL */
+#ifdef CONFIG_ELOOP_KQUEUE
+	eloop.kqueuefd = kqueue();
+	if (eloop.kqueuefd < 0) {
+		wpa_printf(MSG_ERROR, "%s: kqueue failed. %s\n",
+			   __func__, strerror(errno));
+		return -1;
+	}
+#endif /* CONFIG_ELOOP_KQUEUE */
+#if defined(CONFIG_ELOOP_EPOLL) || defined(CONFIG_ELOOP_KQUEUE)
 	eloop.readers.type = EVENT_TYPE_READ;
 	eloop.writers.type = EVENT_TYPE_WRITE;
 	eloop.exceptions.type = EVENT_TYPE_EXCEPTION;
-#endif /* CONFIG_ELOOP_EPOLL */
+#endif
 #ifdef WPA_TRACE
 	signal(SIGSEGV, eloop_sigsegv_handler);
 #endif /* WPA_TRACE */
 	return 0;
 }
 
+#ifdef CONFIG_ELOOP_EPOLL
+static int eloop_sock_queue(int sock, eloop_event_type type)
+{
+	struct epoll_event ev;
+
+	os_memset(&ev, 0, sizeof(ev));
+	switch (type) {
+	case EVENT_TYPE_READ:
+		ev.events = EPOLLIN;
+		break;
+	case EVENT_TYPE_WRITE:
+		ev.events = EPOLLOUT;
+		break;
+	/*
+	 * Exceptions are always checked when using epoll, but I suppose it's
+	 * possible that someone registered a socket *only* for exception
+	 * handling.
+	 */
+	case EVENT_TYPE_EXCEPTION:
+		ev.events = EPOLLERR | EPOLLHUP;
+		break;
+	}
+	ev.data.fd = sock;
+	if (epoll_ctl(eloop.epollfd, EPOLL_CTL_ADD, sock, &ev) < 0) {
+		wpa_printf(MSG_ERROR, "%s: epoll_ctl(ADD) for fd=%d "
+			   "failed. %s\n", __func__, sock, strerror(errno));
+		return -1;
+	}
+	return 0;
+}
+#endif /* CONFIG_ELOOP_EPOLL */
+
+#ifdef CONFIG_ELOOP_KQUEUE
+static int eloop_sock_queue(int sock, eloop_event_type type)
+{
+	int filter;
+	struct kevent ke;
+
+	switch (type) {
+	case EVENT_TYPE_READ:
+		filter = EVFILT_READ;
+		break;
+	case EVENT_TYPE_WRITE:
+		filter = EVFILT_WRITE;
+		break;
+	default:
+		filter = 0;
+	}
+	EV_SET(&ke, sock, filter, EV_ADD, 0, 0, NULL);
+	if (kevent(eloop.kqueuefd, &ke, 1, NULL, 0, NULL) == -1) {
+		wpa_printf(MSG_ERROR, "%s: kevent(ADD) for fd=%d "
+			   "failed. %s\n", __func__, sock, strerror(errno));
+		return -1;
+	}
+	return 0;
+}
+#endif /* CONFIG_ELOOP_KQUEUE */
 
 static int eloop_sock_table_add_sock(struct eloop_sock_table *table,
                                      int sock, eloop_sock_handler handler,
                                      void *eloop_data, void *user_data)
 {
 #ifdef CONFIG_ELOOP_EPOLL
+	struct epoll_event *temp_events;
+#endif /* CONFIG_ELOOP_EPOLL */
+#if defined(CONFIG_ELOOP_EPOLL) || defined(CONFIG_ELOOP_KQUEUE)
 	struct eloop_sock *temp_table;
-	struct epoll_event ev, *temp_events;
 	int next;
-#endif /* CONFIG_ELOOP_EPOLL */
+#endif
 	struct eloop_sock *tmp;
 	int new_max_sock;
 
@@ -208,18 +293,20 @@ static int eloop_sock_table_add_sock(struct eloop_sock_table *table,
 		eloop.pollfds = n;
 	}
 #endif /* CONFIG_ELOOP_POLL */
-#ifdef CONFIG_ELOOP_EPOLL
-	if (new_max_sock >= eloop.epoll_max_fd) {
-		next = eloop.epoll_max_fd == 0 ? 16 : eloop.epoll_max_fd * 2;
-		temp_table = os_realloc_array(eloop.epoll_table, next,
+#if defined(CONFIG_ELOOP_EPOLL) || defined(CONFIG_ELOOP_KQUEUE)
+	if (new_max_sock >= eloop.max_fd) {
+		next = eloop.max_fd == 0 ? 16 : eloop.max_fd * 2;
+		temp_table = os_realloc_array(eloop.fd_table, next,
 					      sizeof(struct eloop_sock));
 		if (temp_table == NULL)
 			return -1;
 
-		eloop.epoll_max_fd = next;
-		eloop.epoll_table = temp_table;
+		eloop.max_fd = next;
+		eloop.fd_table = temp_table;
 	}
+#endif
 
+#ifdef CONFIG_ELOOP_EPOLL
 	if (eloop.count + 1 > eloop.epoll_max_event_num) {
 		next = eloop.epoll_max_event_num == 0 ? 8 :
 			eloop.epoll_max_event_num * 2;
@@ -235,6 +322,21 @@ static int eloop_sock_table_add_sock(struct eloop_sock_table *table,
 		eloop.epoll_events = temp_events;
 	}
 #endif /* CONFIG_ELOOP_EPOLL */
+#ifdef CONFIG_ELOOP_KQUEUE
+	if (eloop.count + 1 > eloop.kqueue_nevents) {
+		next = eloop.kqueue_nevents == 0 ? 8 : eloop.kqueue_nevents * 2;
+		os_free(eloop.kqueue_events);
+		eloop.kqueue_events = os_malloc(next *
+					        sizeof(eloop.kqueue_events));
+		if (eloop.kqueue_events == NULL) {
+			wpa_printf(MSG_ERROR, "%s: malloc for kqueue failed. "
+				   "%s\n", __func__, strerror(errno));
+			return -1;
+		}
+
+		eloop.kqueue_nevents = next;
+	}
+#endif /* CONFIG_ELOOP_KQUEUE */
 
 	eloop_trace_sock_remove_ref(table);
 	tmp = os_realloc_array(table->table, table->count + 1,
@@ -256,33 +358,12 @@ static int eloop_sock_table_add_sock(struct eloop_sock_table *table,
 	table->changed = 1;
 	eloop_trace_sock_add_ref(table);
 
-#ifdef CONFIG_ELOOP_EPOLL
-	os_memset(&ev, 0, sizeof(ev));
-	switch (table->type) {
-	case EVENT_TYPE_READ:
-		ev.events = EPOLLIN;
-		break;
-	case EVENT_TYPE_WRITE:
-		ev.events = EPOLLOUT;
-		break;
-	/*
-	 * Exceptions are always checked when using epoll, but I suppose it's
-	 * possible that someone registered a socket *only* for exception
-	 * handling.
-	 */
-	case EVENT_TYPE_EXCEPTION:
-		ev.events = EPOLLERR | EPOLLHUP;
-		break;
-	}
-	ev.data.fd = sock;
-	if (epoll_ctl(eloop.epollfd, EPOLL_CTL_ADD, sock, &ev) < 0) {
-		wpa_printf(MSG_ERROR, "%s: epoll_ctl(ADD) for fd=%d "
-			   "failed. %s\n", __func__, sock, strerror(errno));
+#if defined(CONFIG_ELOOP_EPOLL) || defined(CONFIG_ELOOP_KQUEUE)
+	if (eloop_sock_queue(sock, table->type) == -1)
 		return -1;
-	}
-	os_memcpy(&eloop.epoll_table[sock], &table->table[table->count - 1],
+	os_memcpy(&eloop.fd_table[sock], &table->table[table->count - 1],
 		  sizeof(struct eloop_sock));
-#endif /* CONFIG_ELOOP_EPOLL */
+#endif
 	return 0;
 }
 
@@ -290,6 +371,9 @@ static int eloop_sock_table_add_sock(struct eloop_sock_table *table,
 static void eloop_sock_table_remove_sock(struct eloop_sock_table *table,
                                          int sock)
 {
+#ifdef CONFIG_ELOOP_KQUEUE
+	struct kevent ke;
+#endif
 	int i;
 
 	if (table == NULL || table->table == NULL || table->count == 0)
@@ -317,8 +401,17 @@ static void eloop_sock_table_remove_sock(struct eloop_sock_table *table,
 			   "failed. %s\n", __func__, sock, strerror(errno));
 		return;
 	}
-	os_memset(&eloop.epoll_table[sock], 0, sizeof(struct eloop_sock));
+	os_memset(&eloop.fd_table[sock], 0, sizeof(struct eloop_sock));
 #endif /* CONFIG_ELOOP_EPOLL */
+#ifdef CONFIG_ELOOP_KQUEUE
+	EV_SET(&ke, sock, 0, EV_DELETE, 0, 0, NULL);
+	if (kevent(eloop.kqueuefd, &ke, 1, NULL, 0, NULL) == -1) {
+		wpa_printf(MSG_ERROR, "%s: kevent(DEL) for fd=%d "
+			   "failed. %s\n", __func__, sock, strerror(errno));
+		return;
+	}
+	os_memset(&eloop.fd_table[sock], 0, sizeof(struct eloop_sock));
+#endif /* CONFIG_ELOOP_KQUEUE */
 }
 
 
@@ -511,7 +604,7 @@ static void eloop_sock_table_dispatch(struct epoll_event *events, int nfds)
 	int i;
 
 	for (i = 0; i < nfds; i++) {
-		table = &eloop.epoll_table[events[i].data.fd];
+		table = &eloop.fd_table[events[i].data.fd];
 		if (table->handler == NULL)
 			continue;
 		table->handler(table->sock, table->eloop_data,
@@ -525,6 +618,67 @@ static void eloop_sock_table_dispatch(struct epoll_event *events, int nfds)
 #endif /* CONFIG_ELOOP_EPOLL */
 
 
+#ifdef CONFIG_ELOOP_KQUEUE
+static void eloop_sock_table_dispatch(struct kevent *events, int nfds)
+{
+	struct eloop_sock *table;
+	int i;
+
+	for (i = 0; i < nfds; i++) {
+		table = &eloop.fd_table[events[i].ident];
+		if (table->handler == NULL)
+			continue;
+		table->handler(table->sock, table->eloop_data,
+			       table->user_data);
+		if (eloop.readers.changed ||
+		    eloop.writers.changed ||
+		    eloop.exceptions.changed)
+			break;
+	}
+}
+
+static int eloop_sock_table_requeue(struct eloop_sock_table *table)
+{
+	int i, r;
+	struct kevent ke;
+
+	r = 0;
+	for (i = 0; i < table->count && table->table; i++) {
+		if (eloop_sock_queue(table->table[i].sock, table->type) == -1)
+			r = -1;
+	}
+	return r;
+}
+
+int eloop_sock_requeue(void)
+{
+	int r = 0;
+
+	close(eloop.kqueuefd);
+	eloop.kqueuefd = kqueue();
+	if (eloop.kqueuefd < 0) {
+		wpa_printf(MSG_ERROR, "%s: kqueue failed. %s\n",
+			   __func__, strerror(errno));
+		return -1;
+	}
+
+	if (eloop_sock_table_requeue(&eloop.readers) == -1)
+		r = -1;
+	if (eloop_sock_table_requeue(&eloop.writers) == -1)
+		r = -1;
+	if (eloop_sock_table_requeue(&eloop.exceptions) == -1)
+		r = -1;
+
+	return r;
+}
+#else /* CONFIG_ELOOP_KQUEUE */
+int eloop_sock_requeue(void)
+{
+
+	return 0;
+}
+#endif /* !CONFIG_ELOOP_KQUEUE */
+
 static void eloop_sock_table_destroy(struct eloop_sock_table *table)
 {
 	if (table) {
@@ -905,6 +1059,9 @@ void eloop_run(void)
 #ifdef CONFIG_ELOOP_EPOLL
 	int timeout_ms = -1;
 #endif /* CONFIG_ELOOP_EPOLL */
+#ifdef CONFIG_ELOOP_KQUEUE
+	struct timespec ts;
+#endif /* CONFIG_ELOOP_KQUEUE */
 	int res;
 	struct os_reltime tv, now;
 
@@ -949,6 +1106,10 @@ void eloop_run(void)
 			_tv.tv_sec = tv.sec;
 			_tv.tv_usec = tv.usec;
 #endif /* CONFIG_ELOOP_SELECT */
+#ifdef CONFIG_ELOOP_KQUEUE
+			ts.tv_sec = tv.sec;
+			ts.tv_nsec = tv.usec * 1000L;
+#endif /* CONFIG_ELOOP_KQUEUE */
 		}
 
 #ifdef CONFIG_ELOOP_POLL
@@ -974,6 +1135,15 @@ void eloop_run(void)
 					 eloop.count, timeout_ms);
 		}
 #endif /* CONFIG_ELOOP_EPOLL */
+#ifdef CONFIG_ELOOP_KQUEUE
+		if (eloop.count == 0) {
+			res = 0;
+		} else {
+			res = kevent(eloop.kqueuefd, NULL, 0,
+				     eloop.kqueue_events, eloop.kqueue_nevents,
+				     timeout ? &ts : NULL);
+		}
+#endif /* CONFIG_ELOOP_KQUEUE */
 		if (res < 0 && errno != EINTR && errno != 0) {
 			wpa_printf(MSG_ERROR, "eloop: %s: %s",
 #ifdef CONFIG_ELOOP_POLL
@@ -985,6 +1155,10 @@ void eloop_run(void)
 #ifdef CONFIG_ELOOP_EPOLL
 				   "epoll"
 #endif /* CONFIG_ELOOP_EPOLL */
+#ifdef CONFIG_ELOOP_KQUEUE
+				   "kqueue"
+#endif /* CONFIG_ELOOP_EKQUEUE */
+
 				   , strerror(errno));
 			goto out;
 		}
@@ -1040,6 +1214,9 @@ void eloop_run(void)
 #ifdef CONFIG_ELOOP_EPOLL
 		eloop_sock_table_dispatch(eloop.epoll_events, res);
 #endif /* CONFIG_ELOOP_EPOLL */
+#ifdef CONFIG_ELOOP_KQUEUE
+		eloop_sock_table_dispatch(eloop.kqueue_events, res);
+#endif /* CONFIG_ELOOP_KQUEUE */
 	}
 
 	eloop.terminate = 0;
@@ -1092,11 +1269,17 @@ void eloop_destroy(void)
 	os_free(eloop.pollfds);
 	os_free(eloop.pollfds_map);
 #endif /* CONFIG_ELOOP_POLL */
+#if defined(CONFIG_ELOOP_EPOLL) || defined(CONFIG_ELOOP_KQUEUE)
+	os_free(eloop.fd_table);
+#endif
 #ifdef CONFIG_ELOOP_EPOLL
-	os_free(eloop.epoll_table);
 	os_free(eloop.epoll_events);
 	close(eloop.epollfd);
 #endif /* CONFIG_ELOOP_EPOLL */
+#ifdef CONFIG_ELOOP_KQUEUE
+	os_free(eloop.kqueue_events);
+	close(eloop.kqueuefd);
+#endif /* CONFIG_ELOOP_KQUEUE */
 }
 
 
@@ -1135,6 +1318,17 @@ void eloop_wait_for_read_sock(int sock)
 	FD_SET(sock, &rfds);
 	select(sock + 1, &rfds, NULL, NULL, NULL);
 #endif /* defined(CONFIG_ELOOP_SELECT) || defined(CONFIG_ELOOP_EPOLL) */
+#ifdef CONFIG_ELOOP_KQUEUE
+	int kfd;
+	struct kevent ke1, ke2;
+
+	kfd = kqueue();
+	if (kfd == -1)
+		return;
+	EV_SET(&ke1, sock, EVFILT_READ, EV_ADD | EV_ONESHOT, 0, 0, NULL);
+	kevent(kfd, &ke1, 1, &ke2, 1, NULL);
+	close(kfd);
+#endif /* CONFIG_ELOOP_KQUEUE */
 }
 
 #ifdef CONFIG_ELOOP_SELECT
diff --git a/src/utils/eloop.h b/src/utils/eloop.h
index 07b8c0d..97af16f 100644
--- a/src/utils/eloop.h
+++ b/src/utils/eloop.h
@@ -313,6 +313,14 @@ int eloop_register_signal_reconfig(eloop_signal_handler handler,
 				   void *user_data);
 
 /**
+ * eloop_sock_requeue - Requeue sockets
+ *
+ * Requeue sockets after forking because some implementations require this,
+ * such as epoll and kqueue.
+ */
+int eloop_sock_requeue(void);
+
+/**
  * eloop_run - Start the event loop
  *
  * Start the event loop and continue running as long as there are any
diff --git a/wpa_supplicant/Makefile b/wpa_supplicant/Makefile
index 8fa35e5..234a0bf 100644
--- a/wpa_supplicant/Makefile
+++ b/wpa_supplicant/Makefile
@@ -170,6 +170,10 @@ ifdef CONFIG_ELOOP_EPOLL
 CFLAGS += -DCONFIG_ELOOP_EPOLL
 endif
 
+ifdef CONFIG_ELOOP_KQUEUE
+CFLAGS += -DCONFIG_ELOOP_KQUEUE
+endif
+
 ifdef CONFIG_EAPOL_TEST
 CFLAGS += -Werror -DEAPOL_TEST
 endif
diff --git a/wpa_supplicant/wpa_cli.c b/wpa_supplicant/wpa_cli.c
index 1aea0a6..265f72c 100644
--- a/wpa_supplicant/wpa_cli.c
+++ b/wpa_supplicant/wpa_cli.c
@@ -4441,7 +4441,7 @@ int main(int argc, char *argv[])
 			}
 		}
 
-		if (daemonize && os_daemonize(pid_file))
+		if (daemonize && os_daemonize(pid_file) && eloop_sock_requeue())
 			return -1;
 
 		if (action_file)
diff --git a/wpa_supplicant/wpa_priv.c b/wpa_supplicant/wpa_priv.c
index fa5a6de..3f91cc1 100644
--- a/wpa_supplicant/wpa_priv.c
+++ b/wpa_supplicant/wpa_priv.c
@@ -1128,7 +1128,7 @@ int main(int argc, char *argv[])
 		interfaces = iface;
 	}
 
-	if (daemonize && os_daemonize(pid_file))
+	if (daemonize && os_daemonize(pid_file) && eloop_sock_requeue())
 		goto out;
 
 	eloop_register_signal_terminate(wpa_priv_terminate, NULL);
diff --git a/wpa_supplicant/wpa_supplicant.c b/wpa_supplicant/wpa_supplicant.c
index 03b1353..b71c459 100644
--- a/wpa_supplicant/wpa_supplicant.c
+++ b/wpa_supplicant/wpa_supplicant.c
@@ -5221,7 +5221,8 @@ int wpa_supplicant_run(struct wpa_global *global)
 	struct wpa_supplicant *wpa_s;
 
 	if (global->params.daemonize &&
-	    wpa_supplicant_daemon(global->params.pid_file))
+	    (wpa_supplicant_daemon(global->params.pid_file) ||
+	    eloop_sock_requeue()))
 		return -1;
 
 	if (global->params.wait_for_monitor) {
_______________________________________________
Hostap mailing list
Hostap@xxxxxxxxxxxxxxxxxxx
http://lists.infradead.org/mailman/listinfo/hostap

[Index of Archives]     [Linux Wireless]     [Linux Kernel]     [ATH6KL]     [Linux Bluetooth]     [Linux Netdev]     [Kernel Newbies]     [IDE]     [Security]     [Git]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux ATA RAID]     [Samba]     [Device Mapper]

  Powered by Linux