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