1 /*
2 * Copyright (c) 2007 William Pitcock
3 * Rights to this code are as documented in doc/LICENSE.
4 *
5 * Creates a .dot file for use with neato which displays
6 * user->channel relationships.
7 *
8 * == How to generate the graphs and How It Works ==
9 * Graphtastical creates a .dot file for graphviz's neato
10 * filter to use. The DOT language describes a graph's
11 * structure to graphviz in an opaque way.
12 *
13 * Because Graphviz nodes use unique identifiers for
14 * interconnection, the channels.dot file contains also
15 * information about social networks.
16 *
17 * Eventually Graphtastical will dump other graph datafiles
18 * too.
19 *
20 * To make a file from the data dumped by Graphtastical,
21 * the following commands will do:
22 *
23 * $ cat channels.dot | neato -Tgif -o map-channels.gif
24 * $ cat channels.dot | neato -Tsvg -o map-channels.svg
25 *
26 * Some maps (for larger networks) are going to be large,
27 * so you may want to provide links to both the GIF and
28 * SVG files as some people may only be able to make use of
29 * one or the other. Why that is, I'm not sure, and I'm not
30 * covering it here.
31 *
32 * == Privacy concerns ==
33 * If you are running Graphtastical on a network that has
34 * privacy concerns; you probably shouldn't.
35 */
36
37 #include "atheme-compat.h"
38
39 DECLARE_MODULE_V1
40 (
41 "contrib/graphtastical", true, _modinit, NULL,
42 PACKAGE_STRING,
43 VENDOR_STRING
44 );
45
46 static mowgli_eventloop_timer_t *channels_timer = NULL;
47 static mowgli_eventloop_timer_t *uchannels_timer = NULL;
48
49 /* write channels.dot */
write_channels_dot_file(void * arg)50 static void write_channels_dot_file(void *arg)
51 {
52 mychan_t *mc;
53 chanacs_t *ca;
54 mowgli_node_t *tn;
55 FILE *f;
56 int errno1, was_errored = 0;
57 mowgli_patricia_iteration_state_t state;
58 int root = 1;
59 mychan_t *pmc;
60
61 errno = 0;
62
63 /* write to a temporary file first */
64 if (!(f = fopen(DATADIR "/channels.dot.new", "w")))
65 {
66 errno1 = errno;
67 slog(LG_ERROR, "graphtastical: cannot create channels.dot.new: %s", strerror(errno1));
68 return;
69 }
70
71 fprintf(f, "graph channels {\n");
72 fprintf(f, "edge [color=blue len=7.5 fontname=\"Verdana\" fontsize=8]\n");
73 fprintf(f, "node [fontname=\"Verdana\" fontsize=8]\n");
74
75 slog(LG_DEBUG, "graphtastical: dumping mychans");
76
77 MOWGLI_PATRICIA_FOREACH(mc, &state, mclist)
78 {
79 fprintf(f, "\"%s\"", mc->name);
80
81 if (!root)
82 fprintf(f, "-- \"%s\"", pmc->name);
83
84 pmc = mc;
85
86 fprintf(f, "[fontname=\"Verdana\" fontsize=8]\n");
87
88 MOWGLI_ITER_FOREACH(tn, mc->chanacs.head)
89 {
90 ca = (chanacs_t *)tn->data;
91
92 if (ca->level & CA_AKICK)
93 continue;
94
95 fprintf(f, "\"%s\" -- \"%s\" [fontname=\"Verdana\" fontsize=8]\n", ca->entity ? ca->entity->name : ca->host, mc->name);
96 }
97 }
98
99 fprintf(f, "}\n");
100
101 was_errored = ferror(f);
102 was_errored |= fclose(f);
103 if (was_errored)
104 {
105 errno1 = errno;
106 slog(LG_ERROR, "graphtastical: cannot write to channels.dot.new: %s", strerror(errno1));
107 return;
108 }
109
110 /* now, replace the old database with the new one, using an atomic rename */
111 if ((srename(DATADIR "/channels.dot.new", DATADIR "/channels.dot")) < 0)
112 {
113 errno1 = errno;
114 slog(LG_ERROR, "graphtastical: cannot rename channels.dot.new to channels.dot: %s", strerror(errno1));
115 return;
116 }
117 }
118
119 /* write uchannels.dot */
write_uchannels_dot_file(void * arg)120 static void write_uchannels_dot_file(void *arg)
121 {
122 channel_t *c;
123 chanuser_t *cu;
124 mowgli_node_t *tn;
125 FILE *f;
126 int errno1, was_errored = 0;
127 mowgli_patricia_iteration_state_t state;
128
129 errno = 0;
130
131 /* write to a temporary file first */
132 if (!(f = fopen(DATADIR "/uchannels.dot.new", "w")))
133 {
134 errno1 = errno;
135 slog(LG_ERROR, "graphtastical: cannot create channels.dot.new: %s", strerror(errno1));
136 return;
137 }
138
139 fprintf(f, "graph uchannels {\n");
140 fprintf(f, "edge [color=blue len=7.5 fontname=\"Verdana\" fontsize=8]\n");
141 fprintf(f, "node [fontname=\"Verdana\" fontsize=8]\n");
142
143 slog(LG_DEBUG, "graphtastical: dumping chans");
144
145 MOWGLI_PATRICIA_FOREACH(c, &state, chanlist)
146 {
147 fprintf(f, "\"%s\"", c->name);
148
149 fprintf(f, "[fontname=\"Verdana\" fontsize=8]\n");
150
151 MOWGLI_ITER_FOREACH(tn, c->members.head)
152 {
153 cu = (chanuser_t *)tn->data;
154
155 fprintf(f, "\"%s\" -- \"%s\" [fontname=\"Verdana\" fontsize=8]\n", cu->user->nick, c->name);
156 }
157 }
158
159 fprintf(f, "}\n");
160
161 was_errored = ferror(f);
162 was_errored |= fclose(f);
163 if (was_errored)
164 {
165 errno1 = errno;
166 slog(LG_ERROR, "graphtastical: cannot write to uchannels.dot.new: %s", strerror(errno1));
167 return;
168 }
169
170 /* now, replace the old database with the new one, using an atomic rename */
171 if ((srename(DATADIR "/uchannels.dot.new", DATADIR "/uchannels.dot")) < 0)
172 {
173 errno1 = errno;
174 slog(LG_ERROR, "graphtastical: cannot rename uchannels.dot.new to uchannels.dot: %s", strerror(errno1));
175 return;
176 }
177 }
178
_modinit(module_t * m)179 void _modinit(module_t *m)
180 {
181 write_channels_dot_file(NULL);
182 write_uchannels_dot_file(NULL);
183
184 channels_timer = mowgli_timer_add(base_eventloop, "write_channels_dot_file", write_channels_dot_file, NULL, 60);
185 uchannels_timer = mowgli_timer_add(base_eventloop, "write_uchannels_dot_file", write_uchannels_dot_file, NULL, 60);
186 }
187
_moddeinit(module_unload_intent_t intent)188 void _moddeinit(module_unload_intent_t intent)
189 {
190 mowgli_timer_destroy(base_eventloop, channels_timer);
191 mowgli_timer_destroy(base_eventloop, uchannels_timer);
192 }
193
194 /* vim:cinoptions=>s,e0,n0,f0,{0,}0,^0,=s,ps,t0,c3,+s,(2s,us,)20,*30,gs,hs
195 * vim:ts=8
196 * vim:sw=8
197 * vim:noexpandtab
198 */
199