FAQ Search Today's Posts Mark Forums Read
» Video Reviews

» Linux Archive

Linux-archive is a website aiming to archive linux email lists and to make them easily accessible for linux users/developers.


» Sponsor

» Partners

» Sponsor

Go Back   Linux Archive > Redhat > Device-mapper Development

 
 
LinkBack Thread Tools
 
Old 07-27-2012, 08:55 PM
Benjamin Marzinski
 
Default multipath: check if a device belongs to multipath

This patch adds a new multipath option "-c", which checks if a device
belongs to multipath. This can be done during the add uevent for the
device, before the multipath device has even been created. This allows
udev to be able to handle multipath path devices differently. To do
this multipath now keeps track of the wwids of all previously created
multipath devices in /etc/multipath/wwids. The file creating and
editting code from alias.[ch] has been split out into file.[ch]. When a
device is checked to see if it's a multipath path, it's wwid is
compared against the ones in the wwids file. Also, since the
uid_attribute may not have been added to the udev database entry for
the device if this is called in a udev rule, when using the "-c" option,
get_uid will now also check the process' envirionment variables for the
uid_attribute.

Signed-off-by: Benjamin Marzinski <bmarzins@redhat.com>
---
libmultipath/Makefile | 2
libmultipath/alias.c | 153 ---------------------------------------
libmultipath/alias.h | 1
libmultipath/configure.c | 3
libmultipath/defaults.h | 1
libmultipath/discovery.c | 2
libmultipath/file.c | 180 +++++++++++++++++++++++++++++++++++++++++++++++
libmultipath/file.h | 11 ++
libmultipath/wwids.c | 139 ++++++++++++++++++++++++++++++++++++
libmultipath/wwids.h | 18 ++++
multipath/main.c | 37 ++++++++-
11 files changed, 389 insertions(+), 158 deletions(-)

Index: multipath-tools-120518/libmultipath/alias.c
================================================== =================
--- multipath-tools-120518.orig/libmultipath/alias.c
+++ multipath-tools-120518/libmultipath/alias.c
@@ -3,19 +3,16 @@
* Copyright (c) 2005 Benjamin Marzinski, Redhat
*/
#include <stdlib.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <limits.h>
#include <stdio.h>
-#include <signal.h>

#include "debug.h"
#include "uxsock.h"
#include "alias.h"
+#include "file.h"


/*
@@ -36,150 +33,6 @@
* See the file COPYING included with this distribution for more details.
*/

-static int
-ensure_directories_exist(char *str, mode_t dir_mode)
-{
- char *pathname;
- char *end;
- int err;
-
- pathname = strdup(str);
- if (!pathname){
- condlog(0, "Cannot copy bindings file pathname : %s",
- strerror(errno));
- return -1;
- }
- end = pathname;
- /* skip leading slashes */
- while (end && *end && (*end == '/'))
- end++;
-
- while ((end = strchr(end, '/'))) {
- /* if there is another slash, make the dir. */
- *end = '';
- err = mkdir(pathname, dir_mode);
- if (err && errno != EEXIST) {
- condlog(0, "Cannot make directory [%s] : %s",
- pathname, strerror(errno));
- free(pathname);
- return -1;
- }
- if (!err)
- condlog(3, "Created dir [%s]", pathname);
- *end = '/';
- end++;
- }
- free(pathname);
- return 0;
-}
-
-static void
-sigalrm(int sig)
-{
- /* do nothing */
-}
-
-static int
-lock_bindings_file(int fd)
-{
- struct sigaction act, oldact;
- sigset_t set, oldset;
- struct flock lock;
- int err;
-
- memset(&lock, 0, sizeof(lock));
- lock.l_type = F_WRLCK;
- lock.l_whence = SEEK_SET;
-
- act.sa_handler = sigalrm;
- sigemptyset(&act.sa_mask);
- act.sa_flags = 0;
- sigemptyset(&set);
- sigaddset(&set, SIGALRM);
-
- sigaction(SIGALRM, &act, &oldact);
- sigprocmask(SIG_UNBLOCK, &set, &oldset);
-
- alarm(BINDINGS_FILE_TIMEOUT);
- err = fcntl(fd, F_SETLKW, &lock);
- alarm(0);
-
- if (err) {
- if (errno != EINTR)
- condlog(0, "Cannot lock bindings file : %s",
- strerror(errno));
- else
- condlog(0, "Bindings file is locked. Giving up.");
- }
-
- sigprocmask(SIG_SETMASK, &oldset, NULL);
- sigaction(SIGALRM, &oldact, NULL);
- return err;
-
-}
-
-
-static int
-open_bindings_file(char *file, int *can_write)
-{
- int fd;
- struct stat s;
-
- if (ensure_directories_exist(file, 0700))
- return -1;
- *can_write = 1;
- fd = open(file, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
- if (fd < 0) {
- if (errno == EROFS) {
- *can_write = 0;
- condlog(3, "Cannot open bindings file [%s] read/write. "
- " trying readonly", file);
- fd = open(file, O_RDONLY);
- if (fd < 0) {
- condlog(0, "Cannot open bindings file [%s] "
- "readonly : %s", file, strerror(errno));
- return -1;
- }
- }
- else {
- condlog(0, "Cannot open bindings file [%s] : %s", file,
- strerror(errno));
- return -1;
- }
- }
- if (*can_write && lock_bindings_file(fd) < 0)
- goto fail;
-
- memset(&s, 0, sizeof(s));
- if (fstat(fd, &s) < 0){
- condlog(0, "Cannot stat bindings file : %s", strerror(errno));
- goto fail;
- }
- if (s.st_size == 0) {
- if (*can_write == 0)
- goto fail;
- /* If bindings file is empty, write the header */
- size_t len = strlen(BINDINGS_FILE_HEADER);
- if (write_all(fd, BINDINGS_FILE_HEADER, len) != len) {
- condlog(0,
- "Cannot write header to bindings file : %s",
- strerror(errno));
- /* cleanup partially written header */
- if (ftruncate(fd, 0))
- condlog(0, "Cannot truncate the header : %s",
- strerror(errno));
- goto fail;
- }
- fsync(fd);
- condlog(3, "Initialized new bindings file [%s]", file);
- }
-
- return fd;
-
-fail:
- close(fd);
- return -1;
-}

static int
format_devname(char *name, int id, int len, char *prefix)
@@ -370,7 +223,7 @@ get_user_friendly_alias(char *wwid, char
return NULL;
}

- fd = open_bindings_file(file, &can_write);
+ fd = open_file(file, &can_write, BINDINGS_FILE_HEADER);
if (fd < 0)
return NULL;

@@ -414,7 +267,7 @@ get_user_friendly_wwid(char *alias, char
return NULL;
}

- fd = open_bindings_file(file, &unused);
+ fd = open_file(file, &unused, BINDINGS_FILE_HEADER);
if (fd < 0)
return NULL;

Index: multipath-tools-120518/libmultipath/alias.h
================================================== =================
--- multipath-tools-120518.orig/libmultipath/alias.h
+++ multipath-tools-120518/libmultipath/alias.h
@@ -1,4 +1,3 @@
-#define BINDINGS_FILE_TIMEOUT 30
#define BINDINGS_FILE_HEADER
"# Multipath bindings, Version : 1.0
"
"# NOTE: this file is automatically maintained by the multipath program.
"
Index: multipath-tools-120518/libmultipath/configure.c
================================================== =================
--- multipath-tools-120518.orig/libmultipath/configure.c
+++ multipath-tools-120518/libmultipath/configure.c
@@ -37,6 +37,7 @@
#include "prio.h"
#include "util.h"
#include "uxsock.h"
+#include "wwids.h"

extern int
setup_map (struct multipath * mpp, char * params, int params_size)
@@ -407,6 +408,8 @@ domap (struct multipath * mpp, char * pa
* DM_DEVICE_CREATE, DM_DEVICE_RENAME, or DM_DEVICE_RELOAD
* succeeded
*/
+ if (mpp->action == ACT_CREATE)
+ remember_wwid(mpp->wwid);
if (!conf->daemon) {
/* multipath client mode */
dm_switchgroup(mpp->alias, mpp->bestpg);
Index: multipath-tools-120518/libmultipath/defaults.h
================================================== =================
--- multipath-tools-120518.orig/libmultipath/defaults.h
+++ multipath-tools-120518/libmultipath/defaults.h
@@ -24,5 +24,6 @@
#define DEFAULT_SOCKET "/var/run/multipathd.sock"
#define DEFAULT_CONFIGFILE "/etc/multipath.conf"
#define DEFAULT_BINDINGS_FILE "/etc/multipath/bindings"
+#define DEFAULT_WWIDS_FILE "/etc/multipath/wwids"

char * set_default (char * str);
Index: multipath-tools-120518/libmultipath/file.c
================================================== =================
--- /dev/null
+++ multipath-tools-120518/libmultipath/file.c
@@ -0,0 +1,180 @@
+/*
+ * Copyright (c) 2005 Christophe Varoqui
+ * Copyright (c) 2005 Benjamin Marzinski, Redhat
+ */
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+#include <limits.h>
+#include <stdio.h>
+#include <signal.h>
+
+#include "file.h"
+#include "debug.h"
+#include "uxsock.h"
+
+
+/*
+ * significant parts of this file were taken from iscsi-bindings.c of the
+ * linux-iscsi project.
+ * Copyright (C) 2002 Cisco Systems, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that 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.
+ *
+ * See the file COPYING included with this distribution for more details.
+ */
+
+static int
+ensure_directories_exist(char *str, mode_t dir_mode)
+{
+ char *pathname;
+ char *end;
+ int err;
+
+ pathname = strdup(str);
+ if (!pathname){
+ condlog(0, "Cannot copy file pathname %s : %s",
+ str, strerror(errno));
+ return -1;
+ }
+ end = pathname;
+ /* skip leading slashes */
+ while (end && *end && (*end == '/'))
+ end++;
+
+ while ((end = strchr(end, '/'))) {
+ /* if there is another slash, make the dir. */
+ *end = '';
+ err = mkdir(pathname, dir_mode);
+ if (err && errno != EEXIST) {
+ condlog(0, "Cannot make directory [%s] : %s",
+ pathname, strerror(errno));
+ free(pathname);
+ return -1;
+ }
+ if (!err)
+ condlog(3, "Created dir [%s]", pathname);
+ *end = '/';
+ end++;
+ }
+ free(pathname);
+ return 0;
+}
+
+static void
+sigalrm(int sig)
+{
+ /* do nothing */
+}
+
+static int
+lock_file(int fd, char *file_name)
+{
+ struct sigaction act, oldact;
+ sigset_t set, oldset;
+ struct flock lock;
+ int err;
+
+ memset(&lock, 0, sizeof(lock));
+ lock.l_type = F_WRLCK;
+ lock.l_whence = SEEK_SET;
+
+ act.sa_handler = sigalrm;
+ sigemptyset(&act.sa_mask);
+ act.sa_flags = 0;
+ sigemptyset(&set);
+ sigaddset(&set, SIGALRM);
+
+ sigaction(SIGALRM, &act, &oldact);
+ sigprocmask(SIG_UNBLOCK, &set, &oldset);
+
+ alarm(FILE_TIMEOUT);
+ err = fcntl(fd, F_SETLKW, &lock);
+ alarm(0);
+
+ if (err) {
+ if (errno != EINTR)
+ condlog(0, "Cannot lock %s : %s", file_name,
+ strerror(errno));
+ else
+ condlog(0, "%s is locked. Giving up.", file_name);
+ }
+
+ sigprocmask(SIG_SETMASK, &oldset, NULL);
+ sigaction(SIGALRM, &oldact, NULL);
+ return err;
+}
+
+int
+open_file(char *file, int *can_write, char *header)
+{
+ int fd;
+ struct stat s;
+
+ if (ensure_directories_exist(file, 0700))
+ return -1;
+ *can_write = 1;
+ fd = open(file, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
+ if (fd < 0) {
+ if (errno == EROFS) {
+ *can_write = 0;
+ condlog(3, "Cannot open file [%s] read/write. "
+ " trying readonly", file);
+ fd = open(file, O_RDONLY);
+ if (fd < 0) {
+ condlog(0, "Cannot open file [%s] "
+ "readonly : %s", file, strerror(errno));
+ return -1;
+ }
+ }
+ else {
+ condlog(0, "Cannot open file [%s] : %s", file,
+ strerror(errno));
+ return -1;
+ }
+ }
+ if (*can_write && lock_file(fd, file) < 0)
+ goto fail;
+
+ memset(&s, 0, sizeof(s));
+ if (fstat(fd, &s) < 0){
+ condlog(0, "Cannot stat file %s : %s", file, strerror(errno));
+ goto fail;
+ }
+ if (s.st_size == 0) {
+ if (*can_write == 0)
+ goto fail;
+ /* If file is empty, write the header */
+ size_t len = strlen(header);
+ if (write_all(fd, header, len) != len) {
+ condlog(0,
+ "Cannot write header to file %s : %s", file,
+ strerror(errno));
+ /* cleanup partially written header */
+ if (ftruncate(fd, 0))
+ condlog(0, "Cannot truncate header : %s",
+ strerror(errno));
+ goto fail;
+ }
+ fsync(fd);
+ condlog(3, "Initialized new file [%s]", file);
+ }
+
+ return fd;
+
+fail:
+ close(fd);
+ return -1;
+}
Index: multipath-tools-120518/libmultipath/file.h
================================================== =================
--- /dev/null
+++ multipath-tools-120518/libmultipath/file.h
@@ -0,0 +1,11 @@
+/*
+ * Copyright (c) 2010 Benjamin Marzinski, Redhat
+ */
+
+#ifndef _FILE_H
+#define _FILE_H
+
+#define FILE_TIMEOUT 30
+int open_file(char *file, int *can_write, char *header);
+
+#endif /* _FILE_H */
Index: multipath-tools-120518/multipath/main.c
================================================== =================
--- multipath-tools-120518.orig/multipath/main.c
+++ multipath-tools-120518/multipath/main.c
@@ -53,6 +53,7 @@
#include <errno.h>
#include <sys/time.h>
#include <sys/resource.h>
+#include <wwids.h>
#include "dev_t.h"

int logsink;
@@ -82,7 +83,7 @@ usage (char * progname)
{
fprintf (stderr, VERSION_STRING);
fprintf (stderr, "Usage:
");
- fprintf (stderr, " %s [-d] [-r] [-v lvl] [-p pol] [-b fil] [-q] [dev]
", progname);
+ fprintf (stderr, " %s [-c] [-d] [-r] [-v lvl] [-p pol] [-b fil] [-q] [dev]
", progname);
fprintf (stderr, " %s -l|-ll|-f [-v lvl] [-b fil] [dev]
", progname);
fprintf (stderr, " %s -F [-v lvl]
", progname);
fprintf (stderr, " %s -t
", progname);
@@ -95,6 +96,7 @@ usage (char * progname)
" -ll show multipath topology (maximum info)
"
" -f flush a multipath device map
"
" -F flush all multipath device maps
"
+ " -c check if a device should be a path in a multipath device
"
" -q allow queue_if_no_path when multipathd is not running
"
" -d dry run, do not create or update devmaps
"
" -t dump internal hardware table
"
@@ -209,6 +211,7 @@ get_dm_mpvec (vector curmp, vector pathv

if (!conf->dry_run)
reinstate_paths(mpp);
+ remember_wwid(mpp->wwid);
}
return 0;
}
@@ -259,9 +262,13 @@ configure (void)
* if we have a blacklisted device parameter, exit early
*/
if (dev &&
- (filter_devnode(conf->blist_devnode, conf->elist_devnode, dev) > 0))
- goto out;
-
+ (filter_devnode(conf->blist_devnode,
+ conf->elist_devnode, dev) > 0)) {
+ if (conf->dry_run == 2)
+ printf("%s is not a valid multipath device path
",
+ conf->dev);
+ goto out;
+ }
/*
* scope limiting must be translated into a wwid
* failing the translation is fatal (by policy)
@@ -277,6 +284,15 @@ configure (void)
if (filter_wwid(conf->blist_wwid, conf->elist_wwid,
refwwid) > 0)
goto out;
+ if (conf->dry_run == 2) {
+ if (check_wwids_file(refwwid, 0) == 0){
+ printf("%s is a valid multipath device path
", conf->dev);
+ r = 0;
+ }
+ else
+ printf("%s is not a valid multipath device path
", conf->dev);
+ goto out;
+ }
}

/*
@@ -412,7 +428,7 @@ main (int argc, char *argv[])
if (load_config(DEFAULT_CONFIGFILE))
exit(1);

- while ((arg = getopt(argc, argv, ":dhl::FfM:v:b:Brtq")) != EOF ) {
+ while ((arg = getopt(argc, argv, ":dchl::FfM:v:b:Brtq")) != EOF ) {
switch(arg) {
case 1: printf("optarg : %s
",optarg);
break;
@@ -434,8 +450,12 @@ main (int argc, char *argv[])
case 'q':
conf->allow_queueing = 1;
break;
+ case 'c':
+ conf->dry_run = 2;
+ break;
case 'd':
- conf->dry_run = 1;
+ if (!conf->dry_run)
+ conf->dry_run = 1;
break;
case 'f':
conf->remove = FLUSH_ONE;
@@ -517,6 +537,11 @@ main (int argc, char *argv[])
}
dm_init();

+ if (conf->dry_run == 2 &&
+ (!conf->dev || conf->dev_type == DEV_DEVMAP)) {
+ condlog(0, "the -c option requires a path to check");
+ goto out;
+ }
if (conf->remove == FLUSH_ONE) {
if (conf->dev_type == DEV_DEVMAP)
r = dm_flush_map(conf->dev);
Index: multipath-tools-120518/libmultipath/Makefile
================================================== =================
--- multipath-tools-120518.orig/libmultipath/Makefile
+++ multipath-tools-120518/libmultipath/Makefile
@@ -15,7 +15,7 @@ OBJS = memory.o parser.o vector.o devmap
pgpolicies.o debug.o regex.o defaults.o uevent.o
switchgroup.o uxsock.o print.o alias.o log_pthread.o
log.o configure.o structs_vec.o sysfs.o prio.o checkers.o
- lock.o waiter.o
+ lock.o waiter.o file.o wwids.o

LIBDM_API_FLUSH = $(shell grep -Ecs '^[a-z]*[[:space:]]+dm_task_no_flush' /usr/include/libdevmapper.h)

Index: multipath-tools-120518/libmultipath/wwids.c
================================================== =================
--- /dev/null
+++ multipath-tools-120518/libmultipath/wwids.c
@@ -0,0 +1,139 @@
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+#include <limits.h>
+#include <stdio.h>
+
+#include "checkers.h"
+#include "vector.h"
+#include "structs.h"
+#include "debug.h"
+#include "uxsock.h"
+#include "file.h"
+#include "wwids.h"
+#include "defaults.h"
+
+/*
+ * Copyright (c) 2010 Benjamin Marzinski, Redhat
+ */
+
+static int
+lookup_wwid(FILE *f, char *wwid) {
+ int c;
+ char buf[LINE_MAX];
+ int count;
+
+ while ((c = fgetc(f)) != EOF){
+ if (c != '/') {
+ if (fgets(buf, LINE_MAX, f) == NULL)
+ return 0;
+ else
+ continue;
+ }
+ count = 0;
+ while ((c = fgetc(f)) != '/') {
+ if (c == EOF)
+ return 0;
+ if (count >= WWID_SIZE - 1)
+ goto next;
+ if (wwid[count] == '')
+ goto next;
+ if (c != wwid[count++])
+ goto next;
+ }
+ if (wwid[count] == '')
+ return 1;
+next:
+ if (fgets(buf, LINE_MAX, f) == NULL)
+ return 0;
+ }
+ return 0;
+}
+
+static int
+write_out_wwid(int fd, char *wwid) {
+ int ret;
+ off_t offset;
+ char buf[WWID_SIZE + 3];
+
+ ret = snprintf(buf, WWID_SIZE + 3, "/%s/
", wwid);
+ if (ret >= (WWID_SIZE + 3) || ret < 0){
+ condlog(0, "can't format wwid for writing (%d) : %s",
+ ret, strerror(errno));
+ return -1;
+ }
+ offset = lseek(fd, 0, SEEK_END);
+ if (offset < 0) {
+ condlog(0, "can't seek to the end of wwids file : %s",
+ strerror(errno));
+ return -1;
+ }
+ if (write_all(fd, buf, strlen(buf)) != strlen(buf)) {
+ condlog(0, "cannot write wwid to wwids file : %s",
+ strerror(errno));
+ if (ftruncate(fd, offset))
+ condlog(0, "cannot truncate failed wwid write : %s",
+ strerror(errno));
+ return -1;
+ }
+ return 1;
+}
+
+int
+check_wwids_file(char *wwid, int write_wwid)
+{
+ int fd, can_write, found, ret;
+ FILE *f;
+ fd = open_file(DEFAULT_WWIDS_FILE, &can_write, WWIDS_FILE_HEADER);
+ if (fd < 0)
+ return -1;
+
+ f = fdopen(fd, "r");
+ if (!f) {
+ condlog(0,"can't fdopen wwids file : %s", strerror(errno));
+ close(fd);
+ return -1;
+ }
+ found = lookup_wwid(f, wwid);
+ if (found) {
+ ret = 0;
+ goto out;
+ }
+ if (!write_wwid) {
+ ret = -1;
+ goto out;
+ }
+ if (!can_write) {
+ condlog(0, "wwids file is read-only. Can't write wwid");
+ ret = -1;
+ goto out;
+ }
+
+ if (fflush(f) != 0) {
+ condlog(0, "cannot fflush wwids file stream : %s",
+ strerror(errno));
+ ret = -1;
+ goto out;
+ }
+
+ ret = write_out_wwid(fd, wwid);
+out:
+ fclose(f);
+ return ret;
+}
+
+int
+remember_wwid(char *wwid)
+{
+ int ret = check_wwids_file(wwid, 1);
+ if (ret < 0){
+ condlog(3, "failed writing wwid %s to wwids file", wwid);
+ return -1;
+ }
+ if (ret == 1)
+ condlog(3, "wrote wwid %s to wwids file", wwid);
+ else
+ condlog(4, "wwid %s already in wwids file", wwid);
+ return 0;
+}
Index: multipath-tools-120518/libmultipath/wwids.h
================================================== =================
--- /dev/null
+++ multipath-tools-120518/libmultipath/wwids.h
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2010 Benjamin Marzinski, Redhat
+ */
+
+#ifndef _WWIDS_H
+#define _WWIDS_H
+
+#define WWIDS_FILE_HEADER
+"# Multipath wwids, Version : 1.0
"
+"# NOTE: This file is automatically maintained by multipath and multipathd.
"
+"# You should not need to edit this file in normal circumstances.
"
+"#
"
+"# Valid WWIDs:
"
+
+int remember_wwid(char *wwid);
+int check_wwids_file(char *wwid, int write_wwid);
+
+#endif /* _WWIDS_H */
Index: multipath-tools-120518/libmultipath/discovery.c
================================================== =================
--- multipath-tools-120518.orig/libmultipath/discovery.c
+++ multipath-tools-120518/libmultipath/discovery.c
@@ -810,6 +810,8 @@ get_uid (struct path * pp)

memset(pp->wwid, 0, WWID_SIZE);
value = udev_device_get_property_value(pp->udev, pp->uid_attribute);
+ if ((!value || strlen(value) == 0) && conf->dry_run == 2)
+ value = getenv(pp->uid_attribute);
if (value && strlen(value)) {
size_t len = WWID_SIZE;


--
dm-devel mailing list
dm-devel@redhat.com
https://www.redhat.com/mailman/listinfo/dm-devel
 

Thread Tools




All times are GMT. The time now is 07:24 AM.

VBulletin, Copyright ©2000 - 2014, Jelsoft Enterprises Ltd.
Content Relevant URLs by vBSEO ©2007, Crawlability, Inc.
Copyright 2007 - 2008, www.linux-archive.org