Date: Mon, 9 Jun 2008 22:50:42 GMT From: Anders Nore <andenore@FreeBSD.org> To: Perforce Change Reviews <perforce@FreeBSD.org> Subject: PERFORCE change 143220 for review Message-ID: <200806092250.m59Mog8G052587@repoman.freebsd.org>
next in thread | raw e-mail | index | archive | help
http://perforce.freebsd.org/chv.cgi?CH=143220 Change 143220 by andenore@andenore_laptop on 2008/06/09 22:49:41 Added the indexing of 'which' functionality in converter, and pkg_info -W uses this index if cache is available, huge performance boost compared to the old method. Affected files ... .. //depot/projects/soc2008/andenore_pkginstall/src/usr.sbin/pkg_install/convert/converter.c#2 edit .. //depot/projects/soc2008/andenore_pkginstall/src/usr.sbin/pkg_install/info/main.c#3 edit .. //depot/projects/soc2008/andenore_pkginstall/src/usr.sbin/pkg_install/info/perform.c#3 edit .. //depot/projects/soc2008/andenore_pkginstall/src/usr.sbin/pkg_install/lib/database.c#2 edit .. //depot/projects/soc2008/andenore_pkginstall/src/usr.sbin/pkg_install/lib/global.c#2 edit .. //depot/projects/soc2008/andenore_pkginstall/src/usr.sbin/pkg_install/lib/lib.h#3 edit .. //depot/projects/soc2008/andenore_pkginstall/src/usr.sbin/pkg_install/lib/match.c#3 edit Differences ... ==== //depot/projects/soc2008/andenore_pkginstall/src/usr.sbin/pkg_install/convert/converter.c#2 (text+ko) ==== @@ -8,15 +8,7 @@ #include "lib.h" -#define MAX_PATH 1024 - -#define FILE_LOOKUP_LENGTH 11 -static const char *fileLookup[] = { CONTENTS_FNAME, COMMENT_FNAME, DESC_FNAME, INSTALL_FNAME, POST_INSTALL_FNAME, DEINSTALL_FNAME, POST_DEINSTALL_FNAME, REQUIRE_FNAME, REQUIRED_BY_FNAME, DISPLAY_FNAME, MTREE_FNAME }; - -Verbose = TRUE; -static DB *db = NULL; - -void addPortToDatabase(const DB *db, const char *dirname); +int addPortToDatabase(const DB *db, const char *dirname); static int fname_cmp(const FTSENT * const *a, const FTSENT * const *b); int main(int argc, char **argv) { @@ -24,7 +16,7 @@ FTSENT *f = NULL; const char *paths[2] = {LOG_DIR, NULL}; - db = opendb(PKG_DBCACHE_FILE); + openDatabase(PKG_DBCACHE_FILE); signal(SIGHUP, cleanup); signal(SIGINT, cleanup); @@ -35,7 +27,7 @@ while ((f = fts_read(ftsp)) != NULL) { if (f->fts_info == FTS_D && f->fts_level == 1) { fts_set(ftsp, f, FTS_SKIP); - addPortToDatabase(db, f->fts_name); + addPortToDatabase(database, f->fts_name); } } fts_close(ftsp); @@ -44,7 +36,7 @@ exit(1); } - closedb(db); + closedb(database); printf("Successful close\n"); return 0; @@ -55,20 +47,66 @@ * This function puts the information needed by a package e.g. /var/pkg/db/zip-2.32 * inside a database. The file is indexed by the portname+version and contains the * date added in the datapart. + * Return 0 on success + * TODO: Need other date? Date of caching? */ -void addPortToDatabase(const DB *db, const char *portname) { +int addPortToDatabase(const DB *db, const char *portname) { char *date = "000000"; - + char tmp[PATH_MAX]; DBT key, data; printf("portname: %s\n", portname); + char *portkey; + asprintf(&portkey, "%s", portname); - key.size = strlen(portname) + 1; - key.data = portname; + /* Index portname to date (used instead of scanning LOG_DIR */ + key.size = strlen(portkey) + 1; + key.data = portkey; - data.size = strlen(date) + 1; - data.data = date; + data.size = strlen(portname) + 1; + data.data = portname; dbput(db, &key, &data); + free(portkey); + + /* + * Add Which indexing i.e. index files installed by package and they point + * to the installed package (alot of redundant data) + */ + FILE *fp; + Package pkg; + PackingList itr; + char *cwd = NULL; + + snprintf(tmp, PATH_MAX, "%s/%s/%s", LOG_DIR, portname, CONTENTS_FNAME); + fp = fopen(tmp, "r"); + if (fp == NULL) { + warn("%s", tmp); + return 1; + } + + pkg.head = pkg.tail = NULL; + read_plist(&pkg, fp); + fclose(fp); + for (itr = pkg.head; itr != pkg.tail; itr = itr->next) { + if (itr->type == PLIST_CWD) { + cwd = itr->name; + } else if (itr->type == PLIST_FILE) { + /* Save abspath of file installed in key, and portname in data */ + char *entry = NULL; + asprintf(&entry, "%s/%s", cwd, itr->name); + printf("\tSaving entry %s -> %s\n", entry, portname); + + key.size = strlen(entry) + 1; + key.data = entry; + data.size = strlen(portname) + 1; + data.data = portname; + dbput(db, &key, &data); + free(entry); + } + } + free_plist(&pkg); + + return 0; } ==== //depot/projects/soc2008/andenore_pkginstall/src/usr.sbin/pkg_install/info/main.c#3 (text+ko) ==== @@ -220,8 +220,8 @@ } } - argc -= optind; - argv += optind; + argc -= optind; + argv += optind; if (Flags & SHOW_PTREV) { if (!Quiet) @@ -236,26 +236,27 @@ /* Get all the remaining package names, if any */ while (*argv) { - /* - * Don't try to apply heuristics if arguments are regexs or if - * the argument refers to an existing file. - */ - if (MatchType != MATCH_REGEX && MatchType != MATCH_EREGEX && !isfile(*argv) && !isURL(*argv)) - while ((pkgs_split = strrchr(*argv, (int)'/')) != NULL) { - *pkgs_split++ = '\0'; - /* - * If character after the '/' is alphanumeric or shell - * metachar, then we've found the package name. Otherwise - * we've come across a trailing '/' and need to continue our - * quest. - */ - if (isalnum(*pkgs_split) || ((MatchType == MATCH_GLOB) && \ - strpbrk(pkgs_split, "*?[]") != NULL)) { - *argv = pkgs_split; - break; + /* + * Don't try to apply heuristics if arguments are regexs or if + * the argument refers to an existing file. + */ + if (MatchType != MATCH_REGEX && MatchType != MATCH_EREGEX && !isfile(*argv) && !isURL(*argv)) { + while ((pkgs_split = strrchr(*argv, (int)'/')) != NULL) { + *pkgs_split++ = '\0'; + /* + * If character after the '/' is alphanumeric or shell + * metachar, then we've found the package name. Otherwise + * we've come across a trailing '/' and need to continue our + * quest. + */ + if (isalnum(*pkgs_split) || ((MatchType == MATCH_GLOB) && \ + strpbrk(pkgs_split, "*?[]") != NULL)) { + *argv = pkgs_split; + break; + } + } } - } - *pkgs++ = *argv++; + *pkgs++ = *argv++; } /* If no packages, yelp */ ==== //depot/projects/soc2008/andenore_pkginstall/src/usr.sbin/pkg_install/info/perform.c#3 (text+ko) ==== @@ -56,6 +56,7 @@ if (MatchType != MATCH_EXACT) { matched = matchinstalled(MatchType, pkgs, &errcode); + if (errcode != 0) return 1; /* Not reached */ @@ -77,12 +78,12 @@ default: break; } - } + } - for (i = 0; pkgs[i]; i++) + for (i = 0; pkgs[i]; i++) err_cnt += pkg_do(pkgs[i]); - return err_cnt; + return err_cnt; } static char *Home; @@ -112,7 +113,7 @@ if (*pkg != '/') { if (!getcwd(fname, FILENAME_MAX)) - upchuck("getcwd"); + upchuck("getcwd"); len = strlen(fname); snprintf(&fname[len], FILENAME_MAX - len, "/%s", pkg); } @@ -231,24 +232,24 @@ puts(InfoPrefix); } free_plist(&plist); - bail: - leave_playpen(); - if (isTMP) - unlink(fname); - return code; +bail: + leave_playpen(); + if (isTMP) + unlink(fname); + return code; } void cleanup(int sig) { - static int in_cleanup = 0; + static int in_cleanup = 0; - if (!in_cleanup) { - in_cleanup = 1; - leave_playpen(); - } - if (sig) - exit(1); + if (!in_cleanup) { + in_cleanup = 1; + leave_playpen(); + } + if (sig) + exit(1); } /* @@ -259,40 +260,40 @@ static char * abspath(const char *pathname) { - char *tmp, *tmp1, *resolved_path; - char *cwd = NULL; - int len; + char *tmp, *tmp1, *resolved_path; + char *cwd = NULL; + int len; - if (pathname[0] != '/') { - cwd = getcwd(NULL, MAXPATHLEN); - asprintf(&resolved_path, "%s/%s/", cwd, pathname); - } else - asprintf(&resolved_path, "%s/", pathname); + if (pathname[0] != '/') { + cwd = getcwd(NULL, MAXPATHLEN); + asprintf(&resolved_path, "%s/%s/", cwd, pathname); + } else + asprintf(&resolved_path, "%s/", pathname); - if (resolved_path == NULL) - errx(2, NULL); + if (resolved_path == NULL) + errx(2, NULL); - if (cwd != NULL) - free(cwd); + if (cwd != NULL) + free(cwd); - while ((tmp = strstr(resolved_path, "//")) != NULL) - strcpy(tmp, tmp + 1); + while ((tmp = strstr(resolved_path, "//")) != NULL) + strcpy(tmp, tmp + 1); - while ((tmp = strstr(resolved_path, "/./")) != NULL) - strcpy(tmp, tmp + 2); + while ((tmp = strstr(resolved_path, "/./")) != NULL) + strcpy(tmp, tmp + 2); - while ((tmp = strstr(resolved_path, "/../")) != NULL) { - *tmp = '\0'; - if ((tmp1 = strrchr(resolved_path, '/')) == NULL) - tmp1 = resolved_path; - strcpy(tmp1, tmp + 3); - } + while ((tmp = strstr(resolved_path, "/../")) != NULL) { + *tmp = '\0'; + if ((tmp1 = strrchr(resolved_path, '/')) == NULL) + tmp1 = resolved_path; + strcpy(tmp1, tmp + 3); + } - len = strlen(resolved_path); - if (len > 1 && resolved_path[len - 1] == '/') - resolved_path[len - 1] = '\0'; + len = strlen(resolved_path); + if (len > 1 && resolved_path[len - 1] == '/') + resolved_path[len - 1] = '\0'; - return resolved_path; + return resolved_path; } /* @@ -302,27 +303,27 @@ static int cmp_path(const char *target, const char *current, const char *cwd) { - char *resolved, *temp; - int rval; + char *resolved, *temp; + int rval; - asprintf(&temp, "%s/%s", cwd, current); - if (temp == NULL) - errx(2, NULL); + asprintf(&temp, "%s/%s", cwd, current); + if (temp == NULL) + errx(2, NULL); /* * Make sure there's no multiple /'s or other weird things in the PLIST, * since some plists seem to have them and it could screw up our strncmp. */ - resolved = abspath(temp); + resolved = abspath(temp); - if (strcmp(target, resolved) == 0) - rval = 1; - else - rval = 0; + if (strcmp(target, resolved) == 0) + rval = 1; + else + rval = 0; - free(temp); - free(resolved); - return rval; + free(temp); + free(resolved); + return rval; } /* @@ -333,98 +334,114 @@ static int find_pkg(struct which_head *which_list) { - char **installed; - int errcode, i; - struct which_entry *wp; + char **installed; + int errcode, i; + struct which_entry *wp; - TAILQ_FOREACH(wp, which_list, next) { - const char *msg = "file cannot be found"; - char *tmp; + TAILQ_FOREACH(wp, which_list, next) { + const char *msg = "file cannot be found"; + char *tmp; + + wp->skip = TRUE; + /* If it's not a file, we'll see if it's an executable. */ + if (isfile(wp->file) == FALSE) { + if (strchr(wp->file, '/') == NULL) { + tmp = vpipe("/usr/bin/which %s", wp->file); + if (tmp != NULL) { + strlcpy(wp->file, tmp, PATH_MAX); + wp->skip = FALSE; + free(tmp); + } else + msg = "file is not in PATH"; + } + } else { + tmp = abspath(wp->file); + if (isfile(tmp)) { + strlcpy(wp->file, tmp, PATH_MAX); + wp->skip = FALSE; + } + free(tmp); + } + if (wp->skip == TRUE) + warnx("%s: %s", wp->file, msg); + } - wp->skip = TRUE; - /* If it's not a file, we'll see if it's an executable. */ - if (isfile(wp->file) == FALSE) { - if (strchr(wp->file, '/') == NULL) { - tmp = vpipe("/usr/bin/which %s", wp->file); - if (tmp != NULL) { - strlcpy(wp->file, tmp, PATH_MAX); - wp->skip = FALSE; - free(tmp); - } else - msg = "file is not in PATH"; - } + if(CacheExists == TRUE) { + DEBUG("Which - USES CACHE\n"); + TAILQ_FOREACH(wp, which_list, next) { + if (wp->skip == TRUE) + continue; + DBT tmp; + if(dbKeyExists(wp->file, &tmp)) + strlcpy(wp->package, tmp.data, PATH_MAX); + else + printf("Doesn't exist\n"); + } } else { - tmp = abspath(wp->file); - if (isfile(tmp)) { - strlcpy(wp->file, tmp, PATH_MAX); - wp->skip = FALSE; - } - free(tmp); - } - if (wp->skip == TRUE) - warnx("%s: %s", wp->file, msg); - } + DEBUG("Which - NO CACHE\n"); - installed = matchinstalled(MATCH_ALL, NULL, &errcode); - if (installed == NULL) - return errcode; + installed = matchinstalled(MATCH_ALL, NULL, &errcode); + if (installed == NULL) + return errcode; - for (i = 0; installed[i] != NULL; i++) { - FILE *fp; - Package pkg; - PackingList itr; - char *cwd = NULL; - char tmp[PATH_MAX]; + for (i = 0; installed[i] != NULL; i++) { + FILE *fp; + Package pkg; + PackingList itr; + char *cwd = NULL; + char tmp[PATH_MAX]; + + snprintf(tmp, PATH_MAX, "%s/%s/%s", LOG_DIR, installed[i], + CONTENTS_FNAME); + fp = fopen(tmp, "r"); + if (fp == NULL) { + warn("%s", tmp); + return 1; + } + + pkg.head = pkg.tail = NULL; + read_plist(&pkg, fp); + fclose(fp); + for (itr = pkg.head; itr != pkg.tail; itr = itr->next) { + if (itr->type == PLIST_CWD) { + cwd = itr->name; + } else if (itr->type == PLIST_FILE) { + TAILQ_FOREACH(wp, which_list, next) { + if (wp->skip == TRUE) + continue; + if (!cmp_path(wp->file, itr->name, cwd)) + continue; + if (wp->package[0] != '\0') { + warnx("both %s and %s claim to have installed %s\n", + wp->package, installed[i], wp->file); + } else { + strlcpy(wp->package, installed[i], PATH_MAX); + } + } + } + } + free_plist(&pkg); + } - snprintf(tmp, PATH_MAX, "%s/%s/%s", LOG_DIR, installed[i], - CONTENTS_FNAME); - fp = fopen(tmp, "r"); - if (fp == NULL) { - warn("%s", tmp); - return 1; } - pkg.head = pkg.tail = NULL; - read_plist(&pkg, fp); - fclose(fp); - for (itr = pkg.head; itr != pkg.tail; itr = itr->next) { - if (itr->type == PLIST_CWD) { - cwd = itr->name; - } else if (itr->type == PLIST_FILE) { - TAILQ_FOREACH(wp, which_list, next) { - if (wp->skip == TRUE) - continue; - if (!cmp_path(wp->file, itr->name, cwd)) - continue; - if (wp->package[0] != '\0') { - warnx("both %s and %s claim to have installed %s\n", - wp->package, installed[i], wp->file); - } else { - strlcpy(wp->package, installed[i], PATH_MAX); - } + TAILQ_FOREACH(wp, which_list, next) { + if (wp->package[0] != '\0') { + if (Quiet) + puts(wp->package); + else + printf("%s was installed by package %s\n", \ + wp->file, wp->package); } - } } - free_plist(&pkg); - } - - TAILQ_FOREACH(wp, which_list, next) { - if (wp->package[0] != '\0') { - if (Quiet) - puts(wp->package); - else - printf("%s was installed by package %s\n", \ - wp->file, wp->package); + while (!TAILQ_EMPTY(which_list)) { + wp = TAILQ_FIRST(which_list); + TAILQ_REMOVE(which_list, wp, next); + free(wp); } - } - while (!TAILQ_EMPTY(which_list)) { - wp = TAILQ_FIRST(which_list); - TAILQ_REMOVE(which_list, wp, next); - free(wp); - } - free(which_list); - return 0; + free(which_list); + return 0; } /* ==== //depot/projects/soc2008/andenore_pkginstall/src/usr.sbin/pkg_install/lib/database.c#2 (text+ko) ==== @@ -1,7 +1,26 @@ #include "lib.h" +#include <assert.h> + +int compare_func(const DBT *, const DBT *); -DB *database = NULL; -Boolean CacheExists = FALSE; +/* + * Btree information structure + * I'm not sure if these values are optimal + */ +BTREEINFO btinfo = { + .flags = 0, + .cachesize = 0, + .maxkeypage = 0, + .minkeypage = 0, + .psize = 0, + .compare = compare_func, + .prefix = NULL, + .lorder = 0 +}; + +int compare_func(const DBT *key1, const DBT *key2) { + return strcmp(key1->data, key2->data); +} /* * Checks if database cache exists @@ -23,7 +42,7 @@ * Opens/creates database file given the filename with read/write permission for user/group */ DB *opendb(const char *filename) { - return dbopen(filename, O_CREAT | O_RDWR, 0666, DB_BTREE, NULL); + return dbopen(filename, O_CREAT | O_RDWR, 0666, DB_BTREE, &btinfo); } void openDatabase(const char *filename) { @@ -52,32 +71,55 @@ * aKey - the key we are searching after */ char *dbgetdata(const char *aKey) { - /*static DBT data; + assert(database != NULL); + + static DBT data; DBT key; key.size = strlen(aKey) + 1; key.data = aKey; - db->get(db, &key, &data, 0); - return data.data;*/ - return NULL; + database->get(database, &key, &data, 0); + return data.data; } /* * Checks if the given key exists */ -Boolean dbKeyExists(const char *aKey) { - DBT key, data; +Boolean dbKeyExists(const char *aKey, DBT *data) { + assert(database != NULL); + + DBT key; key.size = strlen(aKey) + 1; key.data = aKey; - if(database->get(database, &key, &data, 0) == 1) - return FALSE; + if(database->get(database, &key, data, 0) == 0) + return TRUE; + else + return FALSE; +} + +/* + * Checks if the given port exists + */ +Boolean dbPortExists(const char *portname) { + assert(database != NULL); + + DBT key, data; + +// char *tmp; +// asprintf(tmp, "%s", portname); + key.size = strlen(portname) + 1; + key.data = portname; + + if(database->get(database, &key, &data, 0) == 0) + return TRUE; else - return TRUE; + return FALSE; } + /* * Scans the database, returning a new key/data pair for each call * Returns 1 when finished ==== //depot/projects/soc2008/andenore_pkginstall/src/usr.sbin/pkg_install/lib/global.c#2 (text+ko) ==== @@ -30,3 +30,5 @@ Boolean Force = FALSE; int AutoAnswer = FALSE; int Verbose = 0; /* Allow multiple levels of verbose. */ +DB *database = NULL; +Boolean CacheExists = FALSE; /* TRUE if cache exists (bdb file) */ ==== //depot/projects/soc2008/andenore_pkginstall/src/usr.sbin/pkg_install/lib/lib.h#3 (text+ko) ==== @@ -252,7 +252,8 @@ int dbput(const DB *db, DBT *key, DBT *data); int dbget(const DB *db, DBT *key, DBT *data); char *dbgetdata(const char *aKey); -Boolean dbKeyExists(const char *aKey); +Boolean dbKeyExists(const char *aKey, DBT *data); +Boolean dbPortExists(const char *portname); int closedb(DB *db); int dbscan(const DB *db, DBT *key, DBT *data); ==== //depot/projects/soc2008/andenore_pkginstall/src/usr.sbin/pkg_install/lib/match.c#3 (text+ko) ==== @@ -99,16 +99,16 @@ len = 0; for (i = 0; i < len; i++) - lmatched[i] = FALSE; + lmatched[i] = FALSE; - /* TODO: Make use of cache to get directory names */ + /* Scans db file for installed packages */ if (CacheExists == TRUE) { - DEBUG("USING CACHE\n"); DBT key, data; while(dbscan(database, &key, &data) == 0) { matched = key.data; - DEBUG(matched); - storeappend(store, matched); + if(matched[0] != '/' && matched[0] != '.' && matched[0] != '(') { + storeappend(store, key.data); + } } return store->store; } else { @@ -172,37 +172,37 @@ /* do we have an appended condition? */ condition = strpbrk(pattern, "<>="); if (condition) { - const char *ch; - /* yes, isolate the pattern from the condition ... */ - if (condition > pattern && condition[-1] == '!') - condition--; - condchar = *condition; - *condition = '\0'; - /* ... and compare the name without version */ - ch = strrchr(fname, '-'); - if (ch && ch - fname < PATH_MAX) { - strlcpy(basefname, fname, ch - fname + 1); - fname = basefname; - } + const char *ch; + /* yes, isolate the pattern from the condition ... */ + if (condition > pattern && condition[-1] == '!') + condition--; + condchar = *condition; + *condition = '\0'; + /* ... and compare the name without version */ + ch = strrchr(fname, '-'); + if (ch && ch - fname < PATH_MAX) { + strlcpy(basefname, fname, ch - fname + 1); + fname = basefname; + } } switch (MatchType) { case MATCH_EREGEX: case MATCH_REGEX: - errcode = rex_match(pattern, fname, MatchType == MATCH_EREGEX ? 1 : 0); - break; + errcode = rex_match(pattern, fname, MatchType == MATCH_EREGEX ? 1 : 0); + break; case MATCH_NGLOB: case MATCH_GLOB: - errcode = (csh_match(pattern, fname, 0) == 0) ? 1 : 0; - break; + errcode = (csh_match(pattern, fname, 0) == 0) ? 1 : 0; + break; case MATCH_EXACT: - errcode = (strcmp(pattern, fname) == 0) ? 1 : 0; - break; + errcode = (strcmp(pattern, fname) == 0) ? 1 : 0; + break; case MATCH_ALL: - errcode = 1; - break; + errcode = 1; + break; default: - break; + break; } /* loop over all appended conditions */ @@ -261,12 +261,12 @@ char **installed, **allorigins = NULL, **matches = NULL; int i, j; - if (retval != NULL) - *retval = 0; + if (retval != NULL) + *retval = 0; - installed = matchinstalled(MATCH_ALL, NULL, retval); - if (installed == NULL) - return NULL; + installed = matchinstalled(MATCH_ALL, NULL, retval); + if (installed == NULL) + return NULL; /* Gather origins for all installed packages */ for (i = 0; installed[i] != NULL; i++) { @@ -315,19 +315,19 @@ } /* Resolve origins into package names, retaining the sequence */ - for (i = 0; origins[i] != NULL; i++) { - matches = realloc(matches, (i + 1) * sizeof(*matches)); - matches[i] = NULL; - - for (j = 0; installed[j] != NULL; j++) { - if (allorigins[j]) { - if (csh_match(origins[i], allorigins[j], FNM_PATHNAME) == 0) { - matches[i] = installed[j]; - break; + for (i = 0; origins[i] != NULL; i++) { + matches = realloc(matches, (i + 1) * sizeof(*matches)); + matches[i] = NULL; + + for (j = 0; installed[j] != NULL; j++) { + if (allorigins[j]) { + if (csh_match(origins[i], allorigins[j], FNM_PATHNAME) == 0) { + matches[i] = installed[j]; + break; + } + } } - } } - } if (allorigins) { for (i = 0; installed[i] != NULL; i++) @@ -377,7 +377,6 @@ * * Return 1 if the specified package is installed, * 0 if not, and -1 if an error occured. - * TODO: Use bdb cache lookup */ int isinstalledpkg(const char *name) @@ -393,7 +392,7 @@ /* If we have cache available use it to perform fast lookup */ if(CacheExists) { - return dbKeyExists(name); + return dbPortExists(name); } else DEBUG("Not using cache!\n");
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?200806092250.m59Mog8G052587>