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

[Libvir] PATCH: Pull generic I/O apis out of QEMU driver



The QEMU driver has a number of handy APIs which I want to use from the
storage driver:

 - Read entire file in one go
 - Check filename suffix
 - Compare filename against name + suffix
 - Check link destination
 - Construct a filename from dir, name + suffix
 - Recursively make a directory

This patch pulls all these methods out of the QEMU driver, gives them a
sensible name and adds them to the util.h file. No functional change, this
is a straight refactoring.

 qemu_conf.c   |  256 ++++------------------------------------------------------
 qemu_conf.h   |    1 
 qemu_driver.c |    6 -
 util.c        |  223 ++++++++++++++++++++++++++++++++++++++++++++++++++
 util.h        |   29 ++++++
 5 files changed, 275 insertions(+), 240 deletions(-)


diff -r 41c9eb891349 src/qemu_conf.c
--- a/src/qemu_conf.c	Sun Dec 02 13:30:44 2007 -0500
+++ b/src/qemu_conf.c	Sun Dec 02 13:40:44 2007 -0500
@@ -48,6 +48,7 @@
 #include "uuid.h"
 #include "buf.h"
 #include "conf.h"
+#include "util.h"
 
 #define qemudLog(level, msg...) fprintf(stderr, msg)
 
@@ -227,55 +228,6 @@ void qemudFreeVM(struct qemud_vm *vm) {
     free(vm);
 }
 
-/* Build up a fully qualfiied path for a config file to be
- * associated with a persistent guest or network */
-static int
-qemudMakeConfigPath(const char *configDir,
-                    const char *name,
-                    const char *ext,
-                    char *buf,
-                    unsigned int buflen) {
-    if ((strlen(configDir) + 1 + strlen(name) + (ext ? strlen(ext) : 0) + 1) > buflen)
-        return -1;
-
-    strcpy(buf, configDir);
-    strcat(buf, "/");
-    strcat(buf, name);
-    if (ext)
-        strcat(buf, ext);
-    return 0;
-}
-
-int
-qemudEnsureDir(const char *path)
-{
-    struct stat st;
-    char parent[PATH_MAX];
-    char *p;
-    int err;
-
-    if (stat(path, &st) >= 0)
-        return 0;
-
-    strncpy(parent, path, PATH_MAX);
-    parent[PATH_MAX - 1] = '\0';
-
-    if (!(p = strrchr(parent, '/')))
-        return EINVAL;
-
-    if (p == parent)
-        return EPERM;
-
-    *p = '\0';
-
-    if ((err = qemudEnsureDir(parent)))
-        return err;
-
-    if (mkdir(path, 0777) < 0 && errno != EEXIST)
-        return errno;
-
-    return 0;
-}
 
 /* The list of possible machine types for various architectures,
    as supported by QEMU - taken from 'qemu -M ?' for each arch */
@@ -2075,22 +2027,22 @@ qemudSaveVMDef(virConnectPtr conn,
     if (vm->configFile[0] == '\0') {
         int err;
 
-        if ((err = qemudEnsureDir(driver->configDir))) {
+        if ((err = virFileMakePath(driver->configDir))) {
             qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
                              "cannot create config directory %s: %s",
                              driver->configDir, strerror(err));
             return -1;
         }
 
-        if (qemudMakeConfigPath(driver->configDir, def->name, ".xml",
-                                vm->configFile, PATH_MAX) < 0) {
+        if (virFileBuildPath(driver->configDir, def->name, ".xml",
+                             vm->configFile, PATH_MAX) < 0) {
             qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
                              "cannot construct config file path");
             return -1;
         }
 
-        if (qemudMakeConfigPath(driver->autostartDir, def->name, ".xml",
-                                vm->autostartLink, PATH_MAX) < 0) {
+        if (virFileBuildPath(driver->autostartDir, def->name, ".xml",
+                             vm->autostartLink, PATH_MAX) < 0) {
             qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
                              "cannot construct autostart link path");
             vm->configFile[0] = '\0';
@@ -2114,7 +2066,7 @@ static int qemudSaveNetworkConfig(virCon
         return -1;
     }
 
-    if ((err = qemudEnsureDir(driver->networkConfigDir))) {
+    if ((err = virFileMakePath(driver->networkConfigDir))) {
         qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
                          "cannot create config directory %s: %s",
                          driver->networkConfigDir, strerror(err));
@@ -2521,22 +2473,22 @@ qemudSaveNetworkDef(virConnectPtr conn,
     if (network->configFile[0] == '\0') {
         int err;
 
-        if ((err = qemudEnsureDir(driver->networkConfigDir))) {
+        if ((err = virFileMakePath(driver->networkConfigDir))) {
             qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
                              "cannot create config directory %s: %s",
                              driver->networkConfigDir, strerror(err));
             return -1;
         }
 
-        if (qemudMakeConfigPath(driver->networkConfigDir, def->name, ".xml",
-                                network->configFile, PATH_MAX) < 0) {
+        if (virFileBuildPath(driver->networkConfigDir, def->name, ".xml",
+                             network->configFile, PATH_MAX) < 0) {
             qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
                              "cannot construct config file path");
             return -1;
         }
 
-        if (qemudMakeConfigPath(driver->networkAutostartDir, def->name, ".xml",
-                                network->autostartLink, PATH_MAX) < 0) {
+        if (virFileBuildPath(driver->networkAutostartDir, def->name, ".xml",
+                             network->autostartLink, PATH_MAX) < 0) {
             qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
                              "cannot construct autostart link path");
             network->configFile[0] = '\0';
@@ -2547,174 +2499,6 @@ qemudSaveNetworkDef(virConnectPtr conn,
     return qemudSaveNetworkConfig(conn, driver, network, def);
 }
 
-static int
-qemudReadFile(const char *path,
-              char *buf,
-              int maxlen) {
-    FILE *fh;
-    struct stat st;
-    int ret = 0;
-
-    if (!(fh = fopen(path, "r"))) {
-        qemudLog(QEMUD_WARN, "Failed to open file '%s': %s",
-                 path, strerror(errno));
-        goto error;
-    }
-
-    if (fstat(fileno(fh), &st) < 0) {
-        qemudLog(QEMUD_WARN, "Failed to stat file '%s': %s",
-                 path, strerror(errno));
-        goto error;
-    }
-
-    if (S_ISDIR(st.st_mode)) {
-        qemudDebug("Ignoring directory '%s' - clearly not a config file", path);
-        goto error;
-    }
-
-    if (st.st_size >= maxlen) {
-        qemudLog(QEMUD_WARN, "File '%s' is too large", path);
-        goto error;
-    }
-
-    if ((ret = fread(buf, st.st_size, 1, fh)) != 1) {
-        qemudLog(QEMUD_WARN, "Failed to read config file '%s': %s",
-                 path, strerror(errno));
-        goto error;
-    }
-
-    buf[st.st_size] = '\0';
-
-    ret = 1;
-
- error:
-    if (fh)
-        fclose(fh);
-
-    return ret;
-}
-
-static int
-compareFileToNameSuffix(const char *file,
-                        const char *name,
-                        const char *suffix) {
-    int filelen = strlen(file);
-    int namelen = strlen(name);
-    int suffixlen = strlen(suffix);
-
-    if (filelen == (namelen + suffixlen) &&
-        !strncmp(file, name, namelen) &&
-        !strncmp(file + namelen, suffix, suffixlen))
-        return 1;
-    else
-        return 0;
-}
-
-static int
-hasSuffix(const char *str,
-          const char *suffix)
-{
-    int len = strlen(str);
-    int suffixlen = strlen(suffix);
-
-    if (len < suffixlen)
-        return 0;
-
-    return strcmp(str + len - suffixlen, suffix) == 0;
-}
-
-static int
-checkLinkPointsTo(const char *checkLink,
-                  const char *checkDest)
-{
-    char dest[PATH_MAX];
-    char real[PATH_MAX];
-    char checkReal[PATH_MAX];
-    int n;
-    int passed = 0;
-
-    /* read the link destination */
-    if ((n = readlink(checkLink, dest, PATH_MAX)) < 0) {
-        switch (errno) {
-        case ENOENT:
-        case ENOTDIR:
-            break;
-
-        case EINVAL:
-            qemudLog(QEMUD_WARN, "Autostart file '%s' is not a symlink",
-                     checkLink);
-            break;
-
-        default:
-            qemudLog(QEMUD_WARN, "Failed to read autostart symlink '%s': %s",
-                     checkLink, strerror(errno));
-            break;
-        }
-
-        goto failed;
-    } else if (n >= PATH_MAX) {
-        qemudLog(QEMUD_WARN, "Symlink '%s' contents too long to fit in buffer",
-                 checkLink);
-        goto failed;
-    }
-
-    dest[n] = '\0';
-
-    /* make absolute */
-    if (dest[0] != '/') {
-        char dir[PATH_MAX];
-        char tmp[PATH_MAX];
-        char *p;
-
-        strncpy(dir, checkLink, PATH_MAX);
-        dir[PATH_MAX-1] = '\0';
-
-        if (!(p = strrchr(dir, '/'))) {
-            qemudLog(QEMUD_WARN, "Symlink path '%s' is not absolute", checkLink);
-            goto failed;
-        }
-
-        if (p == dir) /* handle unlikely root dir case */
-            p++;
-
-        *p = '\0';
-
-        if (qemudMakeConfigPath(dir, dest, NULL, tmp, PATH_MAX) < 0) {
-            qemudLog(QEMUD_WARN, "Path '%s/%s' is too long", dir, dest);
-            goto failed;
-        }
-
-        strncpy(dest, tmp, PATH_MAX);
-        dest[PATH_MAX-1] = '\0';
-    }
-
-    /* canonicalize both paths */
-    if (!realpath(dest, real)) {
-        qemudLog(QEMUD_WARN, "Failed to expand path '%s' :%s",
-                 dest, strerror(errno));
-        strncpy(real, dest, PATH_MAX);
-        real[PATH_MAX-1] = '\0';
-    }
-
-    if (!realpath(checkDest, checkReal)) {
-        qemudLog(QEMUD_WARN, "Failed to expand path '%s' :%s",
-                 checkDest, strerror(errno));
-        strncpy(checkReal, checkDest, PATH_MAX);
-        checkReal[PATH_MAX-1] = '\0';
-    }
-
-    /* compare */
-    if (strcmp(checkReal, real) != 0) {
-        qemudLog(QEMUD_WARN, "Autostart link '%s' is not a symlink to '%s', ignoring",
-                 checkLink, checkReal);
-        goto failed;
-    }
-
-    passed = 1;
-
- failed:
-    return passed;
-}
 
 static struct qemud_vm *
 qemudLoadConfig(struct qemud_driver *driver,
@@ -2732,7 +2516,7 @@ qemudLoadConfig(struct qemud_driver *dri
         return NULL;
     }
 
-    if (!compareFileToNameSuffix(file, def->name, ".xml")) {
+    if (!virFileMatchesNameSuffix(file, def->name, ".xml")) {
         qemudLog(QEMUD_WARN, "QEMU guest config filename '%s' does not match guest name '%s'",
                  path, def->name);
         qemudFreeVMDef(def);
@@ -2751,7 +2535,7 @@ qemudLoadConfig(struct qemud_driver *dri
     strncpy(vm->autostartLink, autostartLink, PATH_MAX);
     vm->autostartLink[PATH_MAX-1] = '\0';
 
-    vm->autostart = checkLinkPointsTo(vm->autostartLink, vm->configFile);
+    vm->autostart = virFileLinkPointsTo(vm->autostartLink, vm->configFile);
 
     return vm;
 }
@@ -2772,7 +2556,7 @@ qemudLoadNetworkConfig(struct qemud_driv
         return NULL;
     }
 
-    if (!compareFileToNameSuffix(file, def->name, ".xml")) {
+    if (!virFileMatchesNameSuffix(file, def->name, ".xml")) {
         qemudLog(QEMUD_WARN, "Network config filename '%s' does not match network name '%s'",
                  path, def->name);
         qemudFreeNetworkDef(def);
@@ -2791,7 +2575,7 @@ qemudLoadNetworkConfig(struct qemud_driv
     strncpy(network->autostartLink, autostartLink, PATH_MAX);
     network->autostartLink[PATH_MAX-1] = '\0';
 
-    network->autostart = checkLinkPointsTo(network->autostartLink, network->configFile);
+    network->autostart = virFileLinkPointsTo(network->autostartLink, network->configFile);
 
     return network;
 }
@@ -2820,22 +2604,22 @@ int qemudScanConfigDir(struct qemud_driv
         if (entry->d_name[0] == '.')
             continue;
 
-        if (!hasSuffix(entry->d_name, ".xml"))
+        if (!virFileHasSuffix(entry->d_name, ".xml"))
             continue;
 
-        if (qemudMakeConfigPath(configDir, entry->d_name, NULL, path, PATH_MAX) < 0) {
+        if (virFileBuildPath(configDir, entry->d_name, NULL, path, PATH_MAX) < 0) {
             qemudLog(QEMUD_WARN, "Config filename '%s/%s' is too long",
                      configDir, entry->d_name);
             continue;
         }
 
-        if (qemudMakeConfigPath(autostartDir, entry->d_name, NULL, autostartLink, PATH_MAX) < 0) {
+        if (virFileBuildPath(autostartDir, entry->d_name, NULL, autostartLink, PATH_MAX) < 0) {
             qemudLog(QEMUD_WARN, "Autostart link path '%s/%s' is too long",
                      autostartDir, entry->d_name);
             continue;
         }
 
-        if (!qemudReadFile(path, xml, QEMUD_MAX_XML_LEN))
+        if (virFileReadAll(path, xml, QEMUD_MAX_XML_LEN) < 0)
             continue;
 
         if (isGuest)
diff -r 41c9eb891349 src/qemu_conf.h
--- a/src/qemu_conf.h	Sun Dec 02 13:30:44 2007 -0500
+++ b/src/qemu_conf.h	Sun Dec 02 13:40:44 2007 -0500
@@ -362,7 +362,6 @@ int         qemudDeleteConfig           
                                          struct qemud_driver *driver,
                                          const char *configFile,
                                          const char *name);
-int         qemudEnsureDir              (const char *path);
 
 void        qemudFreeVMDef              (struct qemud_vm_def *vm);
 void        qemudFreeVM                 (struct qemud_vm *vm);
diff -r 41c9eb891349 src/qemu_driver.c
--- a/src/qemu_driver.c	Sun Dec 02 13:30:44 2007 -0500
+++ b/src/qemu_driver.c	Sun Dec 02 13:40:44 2007 -0500
@@ -632,7 +632,7 @@ static int qemudStartVMDaemon(virConnect
     strcat(logfile, vm->def->name);
     strcat(logfile, ".log");
 
-    if (qemudEnsureDir(driver->logDir) < 0) {
+    if (virFileMakePath(driver->logDir) < 0) {
         qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
                          "cannot create log directory %s: %s",
                          driver->logDir, strerror(errno));
@@ -2458,7 +2458,7 @@ static int qemudDomainSetAutostart(virDo
     if (autostart) {
         int err;
 
-        if ((err = qemudEnsureDir(driver->autostartDir))) {
+        if ((err = virFileMakePath(driver->autostartDir))) {
             qemudReportError(dom->conn, dom, NULL, VIR_ERR_INTERNAL_ERROR,
                              "cannot create autostart directory %s: %s",
                              driver->autostartDir, strerror(err));
@@ -2806,7 +2806,7 @@ static int qemudNetworkSetAutostart(virN
     if (autostart) {
         int err;
 
-        if ((err = qemudEnsureDir(driver->networkAutostartDir))) {
+        if ((err = virFileMakePath(driver->networkAutostartDir))) {
             qemudReportError(net->conn, NULL, net, VIR_ERR_INTERNAL_ERROR,
                              "cannot create autostart directory %s: %s",
                              driver->networkAutostartDir, strerror(err));
diff -r 41c9eb891349 src/util.c
--- a/src/util.c	Sun Dec 02 13:30:44 2007 -0500
+++ b/src/util.c	Sun Dec 02 13:40:44 2007 -0500
@@ -30,12 +30,16 @@
 #include <fcntl.h>
 #include <paths.h>
 #include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
 #include <libvirt/virterror.h>
 #include "event.h"
 #include "buf.h"
 #include "util.h"
 
 #define MAX_ERROR_LEN   1024
+
+#define virLog(msg...) fprintf(stderr, msg)
 
 static void 
 ReportError(virConnectPtr conn,
@@ -214,6 +218,7 @@ ssize_t safewrite(int fd, const void *bu
 	size_t nwritten = 0;
 	while (count > 0) {
 		int r = write(fd, buf, count);
+
 		if (r < 0 && errno == EINTR)
 			continue;
 		if (r < 0)
@@ -226,3 +231,221 @@ ssize_t safewrite(int fd, const void *bu
 	}
 	return nwritten;
 }
+
+
+int virFileReadAll(const char *path,
+                   char *buf,
+                   unsigned int buflen)
+{
+    FILE *fh;
+    struct stat st;
+    int ret = -1;
+
+    if (!(fh = fopen(path, "r"))) {
+        virLog("Failed to open file '%s': %s",
+               path, strerror(errno));
+        goto error;
+    }
+
+    if (fstat(fileno(fh), &st) < 0) {
+        virLog("Failed to stat file '%s': %s",
+               path, strerror(errno));
+        goto error;
+    }
+
+    if (S_ISDIR(st.st_mode)) {
+        virLog("Ignoring directory '%s'", path);
+        goto error;
+    }
+
+    if (st.st_size >= (buflen-1)) {
+        virLog("File '%s' is too large", path);
+        goto error;
+    }
+
+    if ((ret = fread(buf, st.st_size, 1, fh)) != 1) {
+        virLog("Failed to read config file '%s': %s",
+               path, strerror(errno));
+        goto error;
+    }
+
+    buf[st.st_size] = '\0';
+
+    ret = 0;
+
+ error:
+    if (fh)
+        fclose(fh);
+
+    return ret;
+}
+
+int virFileMatchesNameSuffix(const char *file,
+                             const char *name,
+                             const char *suffix)
+{
+    int filelen = strlen(file);
+    int namelen = strlen(name);
+    int suffixlen = strlen(suffix);
+
+    if (filelen == (namelen + suffixlen) &&
+        !strncmp(file, name, namelen) &&
+        !strncmp(file + namelen, suffix, suffixlen))
+        return 1;
+    else
+        return 0;
+}
+
+int virFileHasSuffix(const char *str,
+                     const char *suffix)
+{
+    int len = strlen(str);
+    int suffixlen = strlen(suffix);
+
+    if (len < suffixlen)
+        return 0;
+
+    return strcmp(str + len - suffixlen, suffix) == 0;
+}
+
+
+int virFileLinkPointsTo(const char *checkLink,
+                        const char *checkDest)
+{
+    char dest[PATH_MAX];
+    char real[PATH_MAX];
+    char checkReal[PATH_MAX];
+    int n;
+
+    /* read the link destination */
+    if ((n = readlink(checkLink, dest, PATH_MAX)) < 0) {
+        switch (errno) {
+        case ENOENT:
+        case ENOTDIR:
+            return 0;
+
+        case EINVAL:
+            virLog("File '%s' is not a symlink",
+                   checkLink);
+            return 0;
+
+        }
+        virLog("Failed to read symlink '%s': %s",
+               checkLink, strerror(errno));
+        return 0;
+    } else if (n >= PATH_MAX) {
+        virLog("Symlink '%s' contents too long to fit in buffer",
+               checkLink);
+        return 0;
+    }
+
+    dest[n] = '\0';
+
+    /* make absolute */
+    if (dest[0] != '/') {
+        char dir[PATH_MAX];
+        char tmp[PATH_MAX];
+        char *p;
+
+        strncpy(dir, checkLink, PATH_MAX);
+        dir[PATH_MAX-1] = '\0';
+
+        if (!(p = strrchr(dir, '/'))) {
+            virLog("Symlink path '%s' is not absolute", checkLink);
+            return 0;
+        }
+
+        if (p == dir) /* handle unlikely root dir case */
+            p++;
+
+        *p = '\0';
+
+        if (virFileBuildPath(dir, dest, NULL, tmp, PATH_MAX) < 0) {
+            virLog("Path '%s/%s' is too long", dir, dest);
+            return 0;
+        }
+
+        strncpy(dest, tmp, PATH_MAX);
+        dest[PATH_MAX-1] = '\0';
+    }
+
+    /* canonicalize both paths */
+    if (!realpath(dest, real)) {
+        virLog("Failed to expand path '%s' :%s", dest, strerror(errno));
+        strncpy(real, dest, PATH_MAX);
+        real[PATH_MAX-1] = '\0';
+    }
+
+    if (!realpath(checkDest, checkReal)) {
+        virLog("Failed to expand path '%s' :%s", checkDest, strerror(errno));
+        strncpy(checkReal, checkDest, PATH_MAX);
+        checkReal[PATH_MAX-1] = '\0';
+    }
+
+    /* compare */
+    if (strcmp(checkReal, real) != 0) {
+        virLog("Link '%s' does not point to '%s', ignoring",
+               checkLink, checkReal);
+        return 0;
+    }
+
+    return 1;
+}
+
+int virFileMakePath(const char *path)
+{
+    struct stat st;
+    char parent[PATH_MAX];
+    char *p;
+    int err;
+
+    if (stat(path, &st) >= 0)
+        return 0;
+
+    strncpy(parent, path, PATH_MAX);
+    parent[PATH_MAX - 1] = '\0';
+
+    if (!(p = strrchr(parent, '/')))
+        return EINVAL;
+
+    if (p == parent)
+        return EPERM;
+
+    *p = '\0';
+
+    if ((err = virFileMakePath(parent)))
+        return err;
+
+    if (mkdir(path, 0777) < 0 && errno != EEXIST)
+        return errno;
+
+    return 0;
+}
+
+/* Build up a fully qualfiied path for a config file to be
+ * associated with a persistent guest or network */
+int virFileBuildPath(const char *dir,
+                     const char *name,
+                     const char *ext,
+                     char *buf,
+                     unsigned int buflen)
+{
+    if ((strlen(dir) + 1 + strlen(name) + (ext ? strlen(ext) : 0) + 1) >= (buflen-1))
+        return -1;
+
+    strcpy(buf, dir);
+    strcat(buf, "/");
+    strcat(buf, name);
+    if (ext)
+        strcat(buf, ext);
+    return 0;
+}
+
+/*
+ * Local variables:
+ *  indent-tabs-mode: nil
+ *  c-indent-level: 4
+ *  c-basic-offset: 4
+ *  tab-width: 4
+ * End:
+ */
diff -r 41c9eb891349 src/util.h
--- a/src/util.h	Sun Dec 02 13:30:44 2007 -0500
+++ b/src/util.h	Sun Dec 02 13:40:44 2007 -0500
@@ -21,8 +21,37 @@
  * File created Jul 18, 2007 - Shuveb Hussain <shuveb binarykarma com>
  */
 
+#ifndef __VIR_UTIL_H__
+#define __VIR_UTIL_H__
+
+#include "internal.h"
+
 int virExec(virConnectPtr conn, char **argv, int *retpid, int infd, int *outfd, int *errfd);
 int virExecNonBlock(virConnectPtr conn, char **argv, int *retpid, int infd, int *outfd, int *errfd);
 
 int saferead(int fd, void *buf, size_t count);
 ssize_t safewrite(int fd, const void *buf, size_t count);
+
+int virFileReadAll(const char *path,
+                   char *buf,
+                   unsigned int buflen);
+
+int virFileMatchesNameSuffix(const char *file,
+                             const char *name,
+                             const char *suffix);
+
+int virFileHasSuffix(const char *str,
+                     const char *suffix);
+
+int virFileLinkPointsTo(const char *checkLink,
+                        const char *checkDest);
+int virFileMakePath(const char *path);
+
+int virFileBuildPath(const char *dir,
+                     const char *name,
+                     const char *ext,
+                     char *buf,
+                     unsigned int buflen);
+
+
+#endif /* __VIR_UTIL_H__ */


Dan.
-- 
|=- Red Hat, Engineering, Emerging Technologies, Boston.  +1 978 392 2496 -=|
|=-           Perl modules: http://search.cpan.org/~danberr/              -=|
|=-               Projects: http://freshmeat.net/~danielpb/               -=|
|=-  GnuPG: 7D3B9505   F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505  -=| 


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