From d53596f78de7c116e4edba941fbd20f9731a976d Mon Sep 17 00:00:00 2001 From: Richard Russon Date: Fri, 28 Jun 2019 00:49:35 +0100 Subject: [PATCH] devel: graphviz dump Walk though the Account and Mailbox objects and draw a Graphviz diagram to represent them. The `.gv` file is given a timestamp filename. --- Makefile.autosetup | 2 +- auto.def | 8 +- graphviz.c | 811 +++++++++++++++++++++++++++++++++++++++++++++ index.c | 3 + mutt_signal.c | 4 + protos.h | 4 + version.c | 3 + 7 files changed, 833 insertions(+), 2 deletions(-) create mode 100644 graphviz.c diff --git a/Makefile.autosetup b/Makefile.autosetup index 87f9f3b9f..fb97a9dd3 100644 --- a/Makefile.autosetup +++ b/Makefile.autosetup @@ -71,7 +71,7 @@ NEOMUTTOBJS= addrbook.o alias.o bcache.o browser.o color.o commands.o \ muttlib.o mx.o myvar.o pager.o pattern.o postpone.o progress.o query.o \ recvattach.o recvcmd.o resize.o rfc3676.o score.o send.o sendlib.o \ sidebar.o smtp.o sort.o state.o status.o system.o terminal.o version.o \ - tracker.o + tracker.o graphviz.o @if HAVE_LIBUNWIND NEOMUTTOBJS+= backtrace.o @endif diff --git a/auto.def b/auto.def index 1b92e27db..ffbec1ffe 100644 --- a/auto.def +++ b/auto.def @@ -102,6 +102,8 @@ options { # Testing testing=0 => "Enable Unit Testing" coverage=0 => "Enable Coverage Testing" +# Development + devel-graphviz => "Enable Graphviz dump (DEVEL)" # Configure with pkg-config pkgconf=0 => "Use pkg-config during configure" # Enable all options @@ -116,7 +118,7 @@ options { if {1} { # Keep sorted, please. foreach opt { - autocrypt backtrace bdb coverage doc everything fmemopen full-doc gdbm + autocrypt backtrace bdb coverage devel-graphviz doc everything fmemopen full-doc gdbm gnutls gpgme gss homespool idn idn2 inotify kyotocabinet lmdb locales-fix lua mixmaster nls notmuch pgp pkgconf qdbm sasl smime sqlite ssl testing tokyocabinet @@ -384,6 +386,10 @@ switch [opt-val with-lock fcntl] { # Locales fix if {[get-define want-locales-fix]} {define LOCALES_HACK} +############################################################################### +# (DEVEL) Graphviz dump +if {[get-define want-devel-graphviz]} {define USE_DEVEL_GRAPHVIZ} + ############################################################################### # Documentation if {[get-define want-doc]} { diff --git a/graphviz.c b/graphviz.c new file mode 100644 index 000000000..fe658a6e5 --- /dev/null +++ b/graphviz.c @@ -0,0 +1,811 @@ +#include "config.h" +#include +#include +#include +#include +#include "imap/imap_private.h" +#include "maildir/maildir_private.h" +#include "notmuch/notmuch_private.h" +#include "pop/pop_private.h" +#include "email/lib.h" +#include "conn/conn.h" +#include "compress.h" +#include "context.h" +#include "core/lib.h" +#include "globals.h" +#include "mbox/mbox.h" +#include "nntp/nntp.h" +#include "notmuch/mutt_notmuch.h" + +// #define GV_HIDE_CONTEXT +#define GV_HIDE_CONTEXT_CONTENTS +// #define GV_HIDE_MBOX +// #define GV_HIDE_NEOMUTT +// #define GV_HIDE_CONFIG + +void dot_type_bool(FILE *fp, const char *name, bool val) +{ + static const char *values[] = { "false", "true" }; + fprintf(fp, "\t\t\n"); + fprintf(fp, "\t\t\t%s\n", name); + fprintf(fp, "\t\t\t=\n"); + fprintf(fp, "\t\t\t%s\n", values[val]); + fprintf(fp, "\t\t\n"); +} + +void dot_type_date(char *buf, size_t buflen, time_t timestamp) +{ + mutt_date_localtime_format(buf, buflen, "%Y-%m-%d %H:%M:%S", timestamp); +} + +void dot_type_file(FILE *fp, const char *name, FILE *struct_fp) +{ + fprintf(fp, "\t\t\n"); + fprintf(fp, "\t\t\t%s\n", name); + fprintf(fp, "\t\t\t=\n"); + if (struct_fp) + { + fprintf(fp, "\t\t\t%p (%d)\n", + (void *) struct_fp, fileno(struct_fp)); + } + else + { + fprintf(fp, "\t\t\tNULL\n"); + } + fprintf(fp, "\t\t\n"); +} + +void dot_type_number(FILE *fp, const char *name, int num) +{ + fprintf(fp, "\t\t\n"); + fprintf(fp, "\t\t\t%s\n", name); + fprintf(fp, "\t\t\t=\n"); + fprintf(fp, "\t\t\t%d\n", num); + fprintf(fp, "\t\t\n"); +} + +void dot_type_string_escape(char *buf, size_t buflen) +{ + for (; buf[0]; buf++) + { + if (buf[0] == '<') + mutt_str_inline_replace(buf, buflen, 1, "<"); + else if (buf[0] == '>') + mutt_str_inline_replace(buf, buflen, 1, ">"); + else if (buf[0] == '&') + mutt_str_inline_replace(buf, buflen, 1, "&"); + } +} + +void dot_type_string(FILE *fp, const char *name, const char *str) +{ + char buf[1024] = "[NULL]"; + + if (str) + { + mutt_str_strfcpy(buf, str, sizeof(buf)); + dot_type_string_escape(buf, sizeof(buf)); + } + + bool quoted = ((buf[0] != '[') && (buf[0] != '*')); + + fprintf(fp, "\t\t\n"); + fprintf(fp, "\t\t\t%s\n", name); + fprintf(fp, "\t\t\t=\n"); + if (quoted) + fprintf(fp, "\t\t\t\"%s\"\n", buf); + else + fprintf(fp, "\t\t\t%s\n", buf); + fprintf(fp, "\t\t\n"); +} + +void dot_type_umask(char *buf, size_t buflen, int umask) +{ + snprintf(buf, buflen, "0%03o", umask); +} + +void dot_ptr_name(char *buf, size_t buflen, void *ptr) +{ + snprintf(buf, buflen, "obj_%p", ptr); +} + +void dot_ptr(FILE *fp, const char *name, void *ptr, const char *colour) +{ + fprintf(fp, "\t\t\n"); + fprintf(fp, "\t\t\t%s\n", name); + fprintf(fp, "\t\t\t=\n"); + if (colour && ptr) + { + fprintf(fp, "\t\t\t%p\n", + colour, ptr); + } + else + { + fprintf(fp, "\t\t\t%p\n", ptr); + } + fprintf(fp, "\t\t\n"); +} + +void dot_add_link(struct ListHead *links, void *src, void *dst, const char *label, bool back) +{ + if (!src || !dst) + return; + + char obj1[16] = { 0 }; + char obj2[16] = { 0 }; + char text[256] = { 0 }; + char lstr[128] = { 0 }; + + dot_ptr_name(obj1, sizeof(obj1), src); + dot_ptr_name(obj2, sizeof(obj2), dst); + + if (label) + snprintf(lstr, sizeof(lstr), "edgetooltip=\"%s\"", label); + + snprintf(text, sizeof(text), "%s -> %s [ %s %s ]", obj1, obj2, + back ? "dir=back" : "", lstr); + mutt_list_insert_tail(links, mutt_str_strdup(text)); +} + +void dot_graph_header(FILE *fp) +{ + fprintf(fp, "digraph neomutt\n"); + fprintf(fp, "{\n\n"); + + fprintf(fp, "\tgraph [\n"); + fprintf(fp, "\t\trankdir=\"TB\"\n"); + fprintf(fp, "\t\tnodesep=\"0.5\"\n"); + fprintf(fp, "\t\tranksep=\"0.5\"\n"); + fprintf(fp, "\t];\n"); + fprintf(fp, "\n"); + fprintf(fp, "\tnode [\n"); + fprintf(fp, "\t\tshape=\"plain\"\n"); + fprintf(fp, "\t];\n"); + fprintf(fp, "\n"); + fprintf(fp, "\tedge [\n"); + fprintf(fp, "\t\tpenwidth=\"4.5\"\n"); + fprintf(fp, "\t\tarrowsize=\"1.0\"\n"); + fprintf(fp, "\t\tcolor=\"#c0c0c0\"\n"); + fprintf(fp, "\t];\n"); + fprintf(fp, "\n"); +} + +void dot_graph_footer(FILE *fp, struct ListHead *links) +{ + fprintf(fp, "\n"); + struct ListNode *np = NULL; + STAILQ_FOREACH(np, links, entries) + { + fprintf(fp, "\t%s;\n", np->data); + } + fprintf(fp, "\n}\n"); +} + +void dot_object_header(FILE *fp, void *ptr, const char *name, const char *colour) +{ + char obj[16] = { 0 }; + dot_ptr_name(obj, sizeof(obj), ptr); + + if (!colour) + colour = "#ffff80"; + + fprintf(fp, "\t%s [\n", obj); + fprintf(fp, "\t\tlabel=<\n"); + fprintf(fp, "\t\t\n"); + fprintf(fp, "\t\t\t\n", + colour, name, ptr); + fprintf(fp, "\t\t\n"); +} + +void dot_object_footer(FILE *fp) +{ + fprintf(fp, "\t\t
%s (%p)
>\n"); + fprintf(fp, "\t];\n"); + fprintf(fp, "\n"); +} + +void dot_node(FILE *fp, void *ptr, const char *name, const char *colour) +{ + char obj[16] = { 0 }; + dot_ptr_name(obj, sizeof(obj), ptr); + + fprintf(fp, "\t%s [\n", obj); + fprintf(fp, "\t\tlabel=<\n"); + fprintf(fp, "\t\t\n"); + fprintf(fp, "\t\t\t\n", + colour, name); + fprintf(fp, "\t\t\n"); + dot_object_footer(fp); +} + +void dot_node_link(FILE *fp, void *ptr, const char *name, void *link, const char *colour) +{ + char obj[16] = { 0 }; + dot_ptr_name(obj, sizeof(obj), ptr); + + fprintf(fp, "\t%s [\n", obj); + fprintf(fp, "\t\tlabel=<
%s
\n"); + fprintf(fp, "\t\t\n"); + fprintf(fp, "\t\t\t\n", + colour, name); + fprintf(fp, "\t\t\n"); + + fprintf(fp, "\t\t\n"); + fprintf(fp, "\t\t\t\n", colour, link); + fprintf(fp, "\t\t\n"); + + dot_object_footer(fp); +} + +void dot_path_fs(char *buf, size_t buflen, const char *path) +{ + const char *slash = strrchr(path, '/'); + if (slash) + slash++; + else + slash = path; + + mutt_str_strfcpy(buf, slash, buflen); +} + +void dot_path_imap(char *buf, size_t buflen, const char *path) +{ + char tmp[1024] = { 0 }; + mutt_str_strfcpy(tmp, path, sizeof(tmp)); + + struct Url *u = url_parse(tmp); + + if (u->path && (u->path[0] != '\0')) + mutt_str_strfcpy(buf, u->path, buflen); + else + snprintf(buf, buflen, "%s:%s", u->host, u->user); + + url_free(&u); +} + +void dot_config(FILE *fp, const char *name, int type, struct ConfigSubset *sub, + struct ListHead *links) +{ + if (!sub) + return; + + struct Buffer value = mutt_buffer_make(256); + dot_object_header(fp, (void *) name, "Config", "#ffff80"); + dot_type_string(fp, "scope", sub->name); + + char scope[256]; + snprintf(scope, sizeof(scope), "%s:", sub->name); + + struct HashElem **list = get_elem_list(sub->cs); + for (size_t i = 0; list[i]; i++) + { + struct HashElem *item = list[i]; + if ((item->type & type) == 0) + continue; + + const char *iname = item->key.strkey; + size_t slen = strlen(scope); + if (mutt_str_startswith(iname, scope, CASE_MATCH) != 0) + { + if (strchr(iname + slen, ':')) + continue; + mutt_buffer_reset(&value); + cs_subset_string_get(sub, item, &value); + dot_type_string(fp, iname + slen, value.data); + } + } + + dot_object_footer(fp); + mutt_buffer_dealloc(&value); +} + +void dot_comp(FILE *fp, struct CompressInfo *ci, struct ListHead *links) +{ + dot_object_header(fp, ci, "CompressInfo", "#c0c060"); + dot_type_string(fp, "append", ci->cmd_append); + dot_type_string(fp, "close", ci->cmd_close); + dot_type_string(fp, "open", ci->cmd_open); + dot_object_footer(fp); +} + +void dot_mailbox_type(FILE *fp, const char *name, enum MailboxType type) +{ + const char *typestr = NULL; + + switch (type) + { + case MUTT_MBOX: + typestr = "MBOX"; + break; + case MUTT_MMDF: + typestr = "MMDF"; + break; + case MUTT_MH: + typestr = "MH"; + break; + case MUTT_MAILDIR: + typestr = "MAILDIR"; + break; + case MUTT_NNTP: + typestr = "NNTP"; + break; + case MUTT_IMAP: + typestr = "IMAP"; + break; + case MUTT_NOTMUCH: + typestr = "NOTMUCH"; + break; + case MUTT_POP: + typestr = "POP"; + break; + case MUTT_COMPRESSED: + typestr = "COMPRESSED"; + break; + default: + typestr = "UNKNOWN"; + } + + fprintf(fp, "\t\t\n"); + fprintf(fp, "\t\t\t\n", name); + fprintf(fp, "\t\t\t\n"); + fprintf(fp, "\t\t\t\n", typestr); + fprintf(fp, "\t\t\n"); +} + +void dot_mailbox_imap(FILE *fp, struct ImapMboxData *mdata, struct ListHead *links) +{ + dot_object_header(fp, mdata, "ImapMboxData", "#60c060"); + dot_type_string(fp, "name", mdata->name); + dot_type_string(fp, "munge_name", mdata->munge_name); + dot_type_string(fp, "real_name", mdata->real_name); + dot_object_footer(fp); +} + +void dot_mailbox_maildir(FILE *fp, struct MaildirMboxData *mdata, struct ListHead *links) +{ + char buf[64] = { 0 }; + + dot_object_header(fp, mdata, "MaildirMboxData", "#60c060"); + + dot_type_date(buf, sizeof(buf), mdata->mtime_cur.tv_sec); + dot_type_string(fp, "mtime_cur", buf); + + dot_type_umask(buf, sizeof(buf), mdata->mh_umask); + dot_type_string(fp, "mh_umask", buf); + dot_object_footer(fp); +} + +void dot_mailbox_mbox(FILE *fp, struct MboxAccountData *mdata, struct ListHead *links) +{ + char buf[64] = { 0 }; + + dot_object_header(fp, mdata, "MboxAccountData", "#60c060"); + dot_ptr(fp, "fp", mdata->fp, NULL); + + dot_type_date(buf, sizeof(buf), mdata->atime.tv_sec); + dot_type_string(fp, "atime", buf); + + dot_object_footer(fp); +} + +void dot_mailbox_nntp(FILE *fp, struct NntpMboxData *mdata, struct ListHead *links) +{ + dot_object_header(fp, mdata, "NntpMboxData", "#60c060"); + dot_type_string(fp, "group", mdata->group); + dot_type_string(fp, "desc", mdata->desc); + + dot_type_number(fp, "first_message", mdata->first_message); + dot_type_number(fp, "last_message", mdata->last_message); + dot_type_number(fp, "last_loaded", mdata->last_loaded); + dot_type_number(fp, "last_cached", mdata->last_cached); + dot_type_number(fp, "unread", mdata->unread); + + dot_type_bool(fp, "subscribed", mdata->subscribed); + dot_type_bool(fp, "has_new_mail", mdata->has_new_mail); + dot_type_bool(fp, "allowed", mdata->allowed); + dot_type_bool(fp, "deleted", mdata->deleted); + + dot_object_footer(fp); +} + +void dot_mailbox_notmuch(FILE *fp, struct NmMboxData *mdata, struct ListHead *links) +{ + dot_object_header(fp, mdata, "NmMboxData", "#60c060"); + dot_type_number(fp, "db_limit", mdata->db_limit); + dot_object_footer(fp); +} + +void dot_mailbox_pop(FILE *fp, struct PopAccountData *mdata, struct ListHead *links) +{ + dot_object_header(fp, mdata, "PopAccountData", "#60c060"); + dot_ptr(fp, "conn", mdata->conn, "#ff8080"); + dot_object_footer(fp); +} + +void dot_mailbox(FILE *fp, struct Mailbox *m, struct ListHead *links) +{ + char buf[64] = { 0 }; + + dot_object_header(fp, m, "Mailbox", "#80ff80"); + dot_mailbox_type(fp, "type", m->magic); + if (m->name) + dot_type_string(fp, "name", m->name); + + if ((m->magic == MUTT_IMAP) || (m->magic == MUTT_POP)) + { + dot_path_imap(buf, sizeof(buf), mutt_b2s(&m->pathbuf)); + dot_type_string(fp, "pathbuf", buf); + dot_path_imap(buf, sizeof(buf), m->realpath); + dot_type_string(fp, "realpath", buf); + } + else + { + dot_path_fs(buf, sizeof(buf), mutt_b2s(&m->pathbuf)); + dot_type_string(fp, "pathbuf", buf); + dot_path_fs(buf, sizeof(buf), m->realpath); + dot_type_string(fp, "realpath", buf); + } + + // dot_ptr(fp, "mdata", m->mdata, "#e0e060"); + dot_ptr(fp, "account", m->account, "#80ffff"); + + dot_object_footer(fp); + + // dot_add_link(links, m, m->mdata, false); + + if (m->mdata) + { + if (m->magic == MUTT_MAILDIR) + dot_mailbox_maildir(fp, m->mdata, links); + else if (m->magic == MUTT_IMAP) + dot_mailbox_imap(fp, m->mdata, links); + else if (m->magic == MUTT_POP) + dot_mailbox_pop(fp, m->mdata, links); + else if (m->magic == MUTT_MBOX) + dot_mailbox_mbox(fp, m->mdata, links); + else if (m->magic == MUTT_NNTP) + dot_mailbox_nntp(fp, m->mdata, links); + else if (m->magic == MUTT_NOTMUCH) + dot_mailbox_notmuch(fp, m->mdata, links); + + dot_add_link(links, m, m->mdata, "Mailbox->mdata", false); + } + + if (m->compress_info) + { + dot_comp(fp, m->compress_info, links); + dot_add_link(links, m, m->compress_info, "Mailbox->compress_info", false); + } + +#ifndef GV_HIDE_CONFIG + if (m->name) + { + dot_config(fp, m->name, DT_INHERIT_MBOX, m->sub, links); + dot_add_link(links, m, m->name, "Mailbox Config", false); + } +#endif +} + +void dot_mailbox_node(FILE *fp, struct MailboxNode *mn, struct ListHead *links) +{ + dot_node(fp, mn, "MN", "#80ff80"); + + dot_mailbox(fp, mn->mailbox, links); + + dot_add_link(links, mn, mn->mailbox, "MailboxNode->mailbox", false); + + struct Buffer buf; + mutt_buffer_init(&buf); + + char name[256] = { 0 }; + mutt_buffer_addstr(&buf, "{ rank=same "); + + dot_ptr_name(name, sizeof(name), mn); + mutt_buffer_add_printf(&buf, "%s ", name); + + dot_ptr_name(name, sizeof(name), mn->mailbox); + mutt_buffer_add_printf(&buf, "%s ", name); + + if (mn->mailbox->mdata) + { + dot_ptr_name(name, sizeof(name), mn->mailbox->mdata); + mutt_buffer_add_printf(&buf, "%s ", name); + } + +#ifndef GV_HIDE_CONFIG + if (mn->mailbox->name) + { + dot_ptr_name(name, sizeof(name), mn->mailbox->name); + mutt_buffer_add_printf(&buf, "%s ", name); + } +#endif + + mutt_buffer_addstr(&buf, "}"); + + mutt_list_insert_tail(links, buf.data); + buf.data = NULL; +} + +void dot_mailbox_list(FILE *fp, struct MailboxList *ml, struct ListHead *links, bool abbr) +{ + struct MailboxNode *prev = NULL; + struct MailboxNode *np = NULL; + STAILQ_FOREACH(np, ml, entries) + { + if (abbr) + dot_node_link(fp, np, "MN", np->mailbox, "#80ff80"); + else + dot_mailbox_node(fp, np, links); + if (prev) + dot_add_link(links, prev, np, "MailboxNode->next", false); + prev = np; + } +} + +void dot_connection(FILE *fp, struct Connection *c, struct ListHead *links) +{ + dot_object_header(fp, c, "Connection", "#ff8080"); + dot_type_string(fp, "user", c->account.user); + dot_type_string(fp, "host", c->account.host); + dot_type_number(fp, "port", c->account.port); + // dot_ptr(fp, "data", c->data, "#60c0c0"); + dot_type_number(fp, "fd", c->fd); + dot_object_footer(fp); +} + +void dot_account_imap(FILE *fp, struct ImapAccountData *adata, struct ListHead *links) +{ + dot_object_header(fp, adata, "ImapAccountData", "#60c0c0"); + // dot_type_string(fp, "mbox_name", adata->mbox_name); + // dot_type_string(fp, "login", adata->conn_account.login); + dot_type_string(fp, "user", adata->conn_account.user); + dot_type_string(fp, "pass", adata->conn_account.pass[0] ? "***" : ""); + dot_type_number(fp, "port", adata->conn_account.port); + // dot_ptr(fp, "conn", adata->conn, "#ff8080"); + dot_ptr(fp, "mailbox", adata->mailbox, "#80ff80"); + dot_object_footer(fp); + + if (adata->conn) + { + dot_connection(fp, adata->conn, links); + dot_add_link(links, adata, adata->conn, "ImapAccountData->conn", false); + } +} + +void dot_account_mbox(FILE *fp, struct MboxAccountData *adata, struct ListHead *links) +{ + char buf[64] = { 0 }; + + dot_object_header(fp, adata, "MboxAccountData", "#60c0c0"); + dot_ptr(fp, "fp", adata->fp, NULL); + + dot_type_date(buf, sizeof(buf), adata->atime.tv_sec); + dot_type_string(fp, "atime", buf); + dot_type_bool(fp, "locked", adata->locked); + dot_type_bool(fp, "append", adata->append); + + dot_object_footer(fp); +} + +void dot_account_nntp(FILE *fp, struct NntpAccountData *adata, struct ListHead *links) +{ + dot_object_header(fp, adata, "NntpAccountData", "#60c0c0"); + dot_type_number(fp, "groups_num", adata->groups_num); + + dot_type_bool(fp, "hasCAPABILITIES", adata->hasCAPABILITIES); + dot_type_bool(fp, "hasSTARTTLS", adata->hasSTARTTLS); + dot_type_bool(fp, "hasDATE", adata->hasDATE); + dot_type_bool(fp, "hasLIST_NEWSGROUPS", adata->hasLIST_NEWSGROUPS); + dot_type_bool(fp, "hasXGTITLE", adata->hasXGTITLE); + dot_type_bool(fp, "hasLISTGROUP", adata->hasLISTGROUP); + dot_type_bool(fp, "hasLISTGROUPrange", adata->hasLISTGROUPrange); + dot_type_bool(fp, "hasOVER", adata->hasOVER); + dot_type_bool(fp, "hasXOVER", adata->hasXOVER); + dot_type_bool(fp, "cacheable", adata->cacheable); + dot_type_bool(fp, "newsrc_modified", adata->newsrc_modified); + + dot_type_string(fp, "authenticators", adata->authenticators); + dot_type_string(fp, "overview_fmt", adata->overview_fmt); + dot_type_string(fp, "newsrc_file", adata->newsrc_file); + dot_type_file(fp, "newsrc_fp", adata->fp_newsrc); + + dot_type_number(fp, "groups_num", adata->groups_num); + dot_type_number(fp, "groups_max", adata->groups_max); + + char buf[128]; + dot_type_date(buf, sizeof(buf), adata->mtime); + dot_type_string(fp, "mtime", buf); + dot_type_date(buf, sizeof(buf), adata->newgroups_time); + dot_type_string(fp, "newgroups_time", buf); + dot_type_date(buf, sizeof(buf), adata->check_time); + dot_type_string(fp, "check_time", buf); + + dot_object_footer(fp); + + if (adata->conn) + { + dot_connection(fp, adata->conn, links); + dot_add_link(links, adata, adata->conn, "NntpAccountData->conn", false); + } +} + +void dot_account_notmuch(FILE *fp, struct NmAccountData *adata, struct ListHead *links) +{ + dot_object_header(fp, adata, "NmAccountData", "#60c0c0"); + dot_ptr(fp, "db", adata->db, NULL); + dot_object_footer(fp); +} + +void dot_account_pop(FILE *fp, struct PopAccountData *adata, struct ListHead *links) +{ + char buf[64] = { 0 }; + + dot_object_header(fp, adata, "PopAccountData", "#60c0c0"); + + dot_type_date(buf, sizeof(buf), adata->check_time); + dot_type_string(fp, "check_time", buf); + + dot_type_string(fp, "login", adata->conn_account.login); + dot_type_string(fp, "user", adata->conn_account.user); + dot_type_string(fp, "pass", adata->conn_account.pass[0] ? "***" : ""); + dot_type_number(fp, "port", adata->conn_account.port); + // dot_ptr(fp, "conn", adata->conn, "#ff8080"); + dot_object_footer(fp); + + if (adata->conn) + { + dot_connection(fp, adata->conn, links); + dot_add_link(links, adata, adata->conn, "PopAccountData->conn", false); + } +} + +void dot_account(FILE *fp, struct Account *a, struct ListHead *links) +{ + dot_object_header(fp, a, "Account", "#80ffff"); + dot_mailbox_type(fp, "magic", a->magic); + dot_type_string(fp, "name", a->name); + // dot_ptr(fp, "adata", a->adata, "#60c0c0"); + dot_object_footer(fp); + + if (a->adata) + { + if (a->magic == MUTT_IMAP) + dot_account_imap(fp, a->adata, links); + else if (a->magic == MUTT_POP) + dot_account_pop(fp, a->adata, links); + else if (a->magic == MUTT_MBOX) + dot_account_mbox(fp, a->adata, links); + else if (a->magic == MUTT_NNTP) + dot_account_nntp(fp, a->adata, links); + else if (a->magic == MUTT_NOTMUCH) + dot_account_notmuch(fp, a->adata, links); + + dot_add_link(links, a, a->adata, "Account->adata", false); + } + +#ifndef GV_HIDE_CONFIG + if (a->name) + { + dot_config(fp, a->name, DT_INHERIT_ACC, a->sub, links); + dot_add_link(links, a, a->name, "Config", false); + + char name[256] = { 0 }; + struct Buffer buf; + mutt_buffer_init(&buf); + + mutt_buffer_addstr(&buf, "{ rank=same "); + + dot_ptr_name(name, sizeof(name), a); + mutt_buffer_add_printf(&buf, "%s ", name); + + dot_ptr_name(name, sizeof(name), a->name); + mutt_buffer_add_printf(&buf, "%s ", name); + + mutt_buffer_addstr(&buf, "}"); + mutt_list_insert_tail(links, buf.data); + buf.data = NULL; + } +#endif + + struct MailboxNode *first = STAILQ_FIRST(&a->mailboxes); + dot_add_link(links, a, first, "Account->mailboxes", false); + dot_mailbox_list(fp, &a->mailboxes, links, false); +} + +void dot_account_list(FILE *fp, struct AccountList *al, struct ListHead *links) +{ + struct Account *prev = NULL; + struct Account *np = NULL; + TAILQ_FOREACH(np, al, entries) + { +#ifdef GV_HIDE_MBOX + if (np->magic == MUTT_MBOX) + continue; +#endif + dot_account(fp, np, links); + if (prev) + dot_add_link(links, prev, np, "Account->next", false); + + prev = np; + } +} + +void dot_context(FILE *fp, struct Context *ctx, struct ListHead *links) +{ + dot_object_header(fp, ctx, "Context", "#ff80ff"); + dot_ptr(fp, "mailbox", ctx->mailbox, "#80ff80"); +#ifdef GV_HIDE_CONTEXT_CONTENTS + dot_type_number(fp, "vsize", ctx->vsize); + dot_type_string(fp, "pattern", ctx->pattern); + dot_type_bool(fp, "collapsed", ctx->collapsed); +#endif + dot_object_footer(fp); +} + +void dump_graphviz(const char *title) +{ + char name[256] = { 0 }; + struct ListHead links = STAILQ_HEAD_INITIALIZER(links); + + time_t now = time(NULL); + if (title) + { + char date[128]; + mutt_date_localtime_format(date, sizeof(date), "%R", now); + snprintf(name, sizeof(name), "%s-%s.gv", date, title); + } + else + { + mutt_date_localtime_format(name, sizeof(name), "%R.gv", now); + } + + umask(022); + FILE *fp = fopen(name, "w"); + if (!fp) + return; + + dot_graph_header(fp); + +#ifndef GV_HIDE_NEOMUTT + dot_node(fp, NeoMutt, "NeoMutt", "#ffa500"); + dot_add_link(&links, NeoMutt, TAILQ_FIRST(&NeoMutt->accounts), "NeoMutt->accounts", false); +#endif + + dot_account_list(fp, &NeoMutt->accounts, &links); + +#ifndef GV_HIDE_CONTEXT + if (Context) + dot_context(fp, Context, &links); + + /* Globals */ + fprintf(fp, "\t{ rank=same "); + if (Context) + { + dot_ptr_name(name, sizeof(name), Context); + fprintf(fp, "%s ", name); + } + dot_ptr_name(name, sizeof(name), NeoMutt); + fprintf(fp, "%s ", name); + fprintf(fp, "}\n"); +#endif + + fprintf(fp, "\t{ rank=same "); + struct Account *np = NULL; + TAILQ_FOREACH(np, &NeoMutt->accounts, entries) + { +#ifdef GV_HIDE_MBOX + if (np->magic == MUTT_MBOX) + continue; +#endif + dot_ptr_name(name, sizeof(name), np); + fprintf(fp, "%s ", name); + } + fprintf(fp, "}\n"); + + dot_graph_footer(fp, &links); + fclose(fp); + mutt_list_free(&links); +} diff --git a/index.c b/index.c index e517c70ff..a8fc87034 100644 --- a/index.c +++ b/index.c @@ -1646,6 +1646,9 @@ int mutt_index_menu(void) } log_queue_save(fp); +#ifdef USE_DEVEL_GRAPHVIZ + dump_graphviz("index"); +#endif mutt_file_fclose(&fp); mutt_do_pager("messages", tempfile, MUTT_PAGER_LOGS, NULL); diff --git a/mutt_signal.c b/mutt_signal.c index c1b27b736..cf25fbe73 100644 --- a/mutt_signal.c +++ b/mutt_signal.c @@ -34,6 +34,7 @@ #include "globals.h" #include "mutt_attach.h" #include "mutt_curses.h" +#include "protos.h" #ifdef HAVE_LIBUNWIND #include "mutt.h" #endif @@ -103,6 +104,9 @@ static void curses_segv_handler(int sig) #ifdef HAVE_LIBUNWIND show_backtrace(); #endif +#ifdef USE_DEVEL_GRAPHVIZ + dump_graphviz("segfault"); +#endif struct sigaction act; sigemptyset(&act.sa_mask); diff --git a/protos.h b/protos.h index 9ad5b65bb..e718d66e2 100644 --- a/protos.h +++ b/protos.h @@ -89,4 +89,8 @@ int wcscasecmp(const wchar_t *a, const wchar_t *b); int mutt_reply_observer(struct NotifyCallback *nc); +#ifdef USE_DEVEL_GRAPHVIZ +void dump_graphviz(const char *title); +#endif + #endif /* MUTT_PROTOS_H */ diff --git a/version.c b/version.c index 62964c16f..6f181f959 100644 --- a/version.c +++ b/version.c @@ -162,6 +162,9 @@ static struct CompileOptions comp_opts[] = { #else { "curs_set", 0 }, #endif +#ifdef USE_DEVEL_GRAPHVIZ + { "graphviz", 2 }, +#endif #ifdef USE_FCNTL { "fcntl", 1 }, #else -- 2.40.0
%s
%p
%s=%s