fix possible crash on user deletion
[srvx.git] / patches / helpserv-pgsql.diff
1 Index: src/Makefile.am
2 ===================================================================
3 RCS file: /cvsroot/srvx/services/src/Makefile.am,v
4 retrieving revision 1.59
5 diff -u -r1.59 Makefile.am
6 --- src/Makefile.am     9 Sep 2003 01:56:55 -0000       1.59
7 +++ src/Makefile.am     5 Nov 2003 14:15:46 -0000
8 @@ -9,7 +9,7 @@
9         ./expnhelp < $(srcdir)/nickserv.help.m4 > $@
10  
11  EXTRA_srvx_SOURCES = proto-bahamut.c proto-common.c proto-p10.c mod-snoop.c mod-memoserv.c
12 -srvx_LDADD = @MODULE_OBJS@
13 +srvx_LDADD = @MODULE_OBJS@ -lpq
14  srvx_DEPENDENCIES = @MODULE_OBJS@
15  srvx_SOURCES = \
16         chanserv.c chanserv.h \
17 Index: src/helpserv.c
18 ===================================================================
19 RCS file: /cvsroot/srvx/services/src/helpserv.c,v
20 retrieving revision 1.84
21 diff -u -r1.84 helpserv.c
22 --- src/helpserv.c      5 Nov 2003 13:52:23 -0000       1.84
23 +++ src/helpserv.c      5 Nov 2003 14:15:48 -0000
24 @@ -46,6 +46,8 @@
25  #include "opserv.h"
26  #include "saxdb.h"
27  #include "timeq.h"
28 +#include "ioset.h"
29 +#include <postgresql/libpq-fe.h>
30  
31  #define HELPSERV_CONF_NAME "services/helpserv"
32  #define HELPSERV_HELPFILE_NAME "helpserv.help"
33 @@ -94,6 +96,7 @@
34  #define KEY_REQ_ON_JOIN "req_on_join"
35  #define KEY_AUTO_VOICE "auto_voice"
36  #define KEY_AUTO_DEVOICE "auto_devoice"
37 +#define KEY_LOG_SQL "log_sql"
38  
39  /* General */
40  #define HSMSG_WRITE_SUCCESS      "HelpServ db write completed (in "FMT_TIME_T".%03lu seconds)."
41 @@ -401,6 +404,7 @@
42      const char *description;
43      unsigned long db_backup_frequency;
44      const char *reqlogfile;
45 +    PGconn *sql_log;
46  } helpserv_conf;
47  
48  static int helpserv_enabled;
49 @@ -443,6 +447,7 @@
50      unsigned int req_on_join : 1;
51      unsigned int auto_voice : 1;
52      unsigned int auto_devoice : 1;
53 +    unsigned int log_sql : 1;
54  
55      unsigned int helpchan_empty : 1;
56  
57 @@ -608,36 +613,153 @@
58      return dict_find(hs->users, hi->handle, NULL);
59  }
60  
61 -static void helpserv_log_request(struct helpserv_request *req, const char *reason) {
62 -    char key[27+NICKLEN];
63 -    char userhost[USERLEN+HOSTLEN+2];
64 +static struct string_list sql_queue;
65 +static struct io_fd *sql_fd;
66  
67 -    if (!reqlog_ctx || !req)
68 +static void pgsql_send_next_query() {
69 +    int res;
70 +
71 +    res = PQsendQuery(helpserv_conf.sql_log, sql_queue.list[0]);
72 +    if (!res) {
73 +        log_module(MAIN_LOG, LOG_ERROR, "Error sending query \"%s\": %s", sql_queue.list[0], PQerrorMessage(helpserv_conf.sql_log));
74          return;
75 -    if (!reason)
76 -        reason = "";
77 +    }
78 +    res = PQflush(helpserv_conf.sql_log);
79 +    if (res == EOF)
80 +        log_module(MAIN_LOG, LOG_ERROR, "Error flushing PgSql output: %s", PQerrorMessage(helpserv_conf.sql_log));
81 +}
82 +
83 +static void pgsql_readable(UNUSED_ARG(struct io_fd *fd)) {
84 +    PGconn *conn;
85 +    PGresult *pgres;
86 +    unsigned int ii;
87 +    int res;
88 +    ExecStatusType st;
89 +
90 +    conn = helpserv_conf.sql_log;
91 +    res = PQconsumeInput(conn);
92 +    if (!res)
93 +        log_module(MAIN_LOG, LOG_ERROR, "Error consuming PgSql input: %s", PQerrorMessage(conn));
94 +    if (PQisBusy(conn))
95 +        return;
96 +    while ((pgres = PQgetResult(conn))) {
97 +        st = PQresultStatus(pgres);
98 +        if (st != PGRES_COMMAND_OK)
99 +            log_module(MAIN_LOG, LOG_ERROR, "PgSql error in \"%s\": %s", sql_queue.list[0], PQresultErrorMessage(pgres));
100 +        PQclear(pgres);
101 +    }
102 +    if (sql_queue.used == 1)
103 +        sql_queue.list[1] = NULL;
104 +    free(sql_queue.list[0]);
105 +    sql_queue.used--;
106 +    for (ii = 0; ii < sql_queue.used; ++ii)
107 +        sql_queue.list[ii] = sql_queue.list[ii+1];
108 +    if (sql_queue.used)
109 +        pgsql_send_next_query();
110 +}
111  
112 -    sprintf(key, "%s-" FMT_TIME_T "-%lu", req->hs->helpserv->nick, req->opened, req->id);
113 -    saxdb_start_record(reqlog_ctx, key, 1);
114 -    if (req->helper) {
115 -        saxdb_write_string(reqlog_ctx, KEY_REQUEST_HELPER, req->helper->handle->handle);
116 -        saxdb_write_int(reqlog_ctx, KEY_REQUEST_ASSIGNED, req->assigned);
117 +static void
118 +string_buffer_append_quoted(struct string_buffer *dest, const char *src) {
119 +    if (src) {
120 +        size_t len = strlen(src);
121 +        string_buffer_append(dest, '\'');
122 +        if (dest->size < (dest->used + len * 2)) {
123 +            if (dest->size < len * 2)
124 +                dest->size = len * 4;
125 +            else
126 +                dest->size = dest->size * 2;
127 +            dest->list = realloc(dest->list, dest->size);
128 +        }
129 +        dest->used += PQescapeString(dest->list + dest->used, src, len);
130 +        string_buffer_append_string(dest, "', ");
131 +    } else {
132 +        string_buffer_append_string(dest, "NULL, ");
133      }
134 -    if (req->handle) {
135 -        saxdb_write_string(reqlog_ctx, KEY_REQUEST_HANDLE, req->handle->handle);
136 +}
137 +
138 +static void
139 +string_buffer_append_time(struct string_buffer *dest, time_t when) {
140 +    struct tm broken_out;
141 +    if (!when) {
142 +        string_buffer_append_string(dest, "NULL, ");
143 +        return;
144      }
145 -    if (req->user) {
146 -        saxdb_write_string(reqlog_ctx, KEY_REQUEST_NICK, req->user->nick);
147 -        sprintf(userhost, "%s@%s", req->user->ident, req->user->hostname);
148 -        saxdb_write_string(reqlog_ctx, KEY_REQUEST_USERHOST, userhost);
149 +    if (dest->size < dest->used + 20) {
150 +        if (dest->size < 20) {
151 +            dest->size = 40;
152 +        } else {
153 +            dest->size = dest->size * 2;
154 +        }
155 +        dest->list = realloc(dest->list, dest->size);
156      }
157 -    saxdb_write_int(reqlog_ctx, KEY_REQUEST_OPENED, req->opened);
158 -    saxdb_write_int(reqlog_ctx, KEY_REQUEST_CLOSED, now);
159 -    saxdb_write_string(reqlog_ctx, KEY_REQUEST_CLOSEREASON, reason);
160 -    saxdb_write_string_list(reqlog_ctx, KEY_REQUEST_TEXT, req->text);
161 -    saxdb_end_record(reqlog_ctx);
162 +    dest->used += strftime(dest->list + dest->used, dest->size - dest->used, "'%Y-%m-%d %H:%M:%S', ", localtime_r(&when, &broken_out));
163 +}
164  
165 -    fflush(reqlog_f);
166 +static void
167 +pgsql_insert(char *query) {
168 +    string_list_append(&sql_queue, query);
169 +    if (sql_queue.used == 1)
170 +        pgsql_send_next_query();
171 +}
172 +
173 +static void helpserv_log_request(struct helpserv_request *req, const char *reason) {
174 +    char userhost[USERLEN+HOSTLEN+2];
175 +
176 +    if (req->user)
177 +        sprintf(userhost, "%s@%s", req->user->ident, req->user->hostname);
178 +    else
179 +        userhost[0] = 0;
180 +    if (reqlog_ctx) {
181 +        char key[27+NICKLEN];
182 +        sprintf(key, "%s-" FMT_TIME_T "-%lu", req->hs->helpserv->nick, req->opened, req->id);
183 +        saxdb_start_record(reqlog_ctx, key, 1);
184 +        if (req->helper) {
185 +            saxdb_write_string(reqlog_ctx, KEY_REQUEST_HELPER, req->helper->handle->handle);
186 +            saxdb_write_int(reqlog_ctx, KEY_REQUEST_ASSIGNED, req->assigned);
187 +        }
188 +        if (req->handle)
189 +            saxdb_write_string(reqlog_ctx, KEY_REQUEST_HANDLE, req->handle->handle);
190 +        if (req->user) {
191 +            saxdb_write_string(reqlog_ctx, KEY_REQUEST_NICK, req->user->nick);
192 +            saxdb_write_string(reqlog_ctx, KEY_REQUEST_USERHOST, userhost);
193 +        }
194 +        saxdb_write_int(reqlog_ctx, KEY_REQUEST_OPENED, req->opened);
195 +        saxdb_write_int(reqlog_ctx, KEY_REQUEST_CLOSED, now);
196 +        saxdb_write_string(reqlog_ctx, KEY_REQUEST_CLOSEREASON, reason);
197 +        saxdb_write_string_list(reqlog_ctx, KEY_REQUEST_TEXT, req->text);
198 +        saxdb_end_record(reqlog_ctx);
199 +        fflush(reqlog_f);
200 +    }
201 +    if (helpserv_conf.sql_log && req->hs->log_sql) {
202 +        struct string_buffer query, sb;
203 +        unsigned int ii;
204 +
205 +        sb.used = query.used = 0;
206 +        sb.size = query.size = 512;
207 +        query.list = malloc(query.size);
208 +        sb.list = malloc(sb.size);
209 +        string_buffer_append_string(&query, "INSERT INTO srvx_helpserv_reqs (c_bot, t_opened, t_assigned, t_closed, i_id, c_helper, c_user_account, c_user_nick, c_user_host, c_close_reason, c_text) VALUES (");
210 +        string_buffer_append_quoted(&query, req->hs->helpserv->nick);
211 +        string_buffer_append_time(&query, req->opened);
212 +        string_buffer_append_time(&query, req->assigned);
213 +        string_buffer_append_time(&query, now);
214 +        string_buffer_append_printf(&query, "%lu, ", req->id);
215 +        string_buffer_append_quoted(&query, req->helper ? req->helper->handle->handle : NULL);
216 +        string_buffer_append_quoted(&query, req->handle ? req->handle->handle : NULL);
217 +        string_buffer_append_quoted(&query, req->user ? req->user->nick : NULL);
218 +        string_buffer_append_quoted(&query, req->user ? userhost : NULL);
219 +        string_buffer_append_quoted(&query, reason);
220 +        for (ii = 0; ii < req->text->used; ++ii) {
221 +            string_buffer_append_string(&sb, req->text->list[ii]);
222 +            string_buffer_append(&sb, '\n');
223 +        }
224 +        string_buffer_append(&sb, 0);
225 +        string_buffer_append_quoted(&query, sb.list);
226 +        query.used -= 2; /* chop off ", " from append_quoted */
227 +        string_buffer_append_string(&query, ");");
228 +        pgsql_insert(query.list);
229 +        free(sb.list);
230 +    }
231  }
232  
233  /* Searches for a request by number, nick, or account (num|nick|*account).
234 @@ -2931,6 +3053,28 @@
235      OPTION_BINARY(hs->auto_devoice, "AutoDeVoice");
236  }
237  
238 +static HELPSERV_OPTION(opt_log_sql) {
239 +    int changed = 0;
240 +    if (argc > 0) {
241 +        if (!from_opserv) {
242 +            helpserv_notice(user, HSMSG_SET_NEED_OPER);
243 +            return 0;
244 +        }
245 +        if (enabled_string(argv[0])) {
246 +            hs->log_sql = 1;
247 +            changed = 1;
248 +        } else if (disabled_string(argv[0])) {
249 +            hs->log_sql = 0;
250 +            changed = 1;
251 +        } else {
252 +            helpserv_notice(user, MSG_INVALID_BINARY, argv[0]);
253 +            return 0;
254 +        }
255 +    }
256 +    helpserv_notice(user, HSMSG_STRING_VALUE, "LogSql", hs->log_sql ? "Enabled" : "Disabled");
257 +    return changed;
258 +}
259 +
260  static HELPSERV_FUNC(cmd_set) {
261      helpserv_option_func_t *opt;
262  
263 @@ -2944,7 +3088,7 @@
264              opt_empty_interval, opt_stale_delay, opt_request_persistence,
265              opt_helper_persistence, opt_notification, opt_id_wrap,
266              opt_req_maxlen, opt_privmsg_only, opt_req_on_join, opt_auto_voice,
267 -            opt_auto_devoice
268 +            opt_auto_devoice, opt_log_sql
269          };
270  
271          helpserv_notice(user, HSMSG_QUEUE_OPTIONS);
272 @@ -3267,6 +3411,7 @@
273      saxdb_write_int(ctx, KEY_REQ_ON_JOIN, hs->req_on_join);
274      saxdb_write_int(ctx, KEY_AUTO_VOICE, hs->auto_voice);
275      saxdb_write_int(ctx, KEY_AUTO_DEVOICE, hs->auto_devoice);
276 +    saxdb_write_int(ctx, KEY_LOG_SQL, hs->log_sql);
277  
278      /* End bot record */
279      saxdb_end_record(ctx);
280 @@ -3376,6 +3521,8 @@
281      hs->auto_voice = str ? enabled_string(str) : 0;
282      str = database_get_data(GET_RECORD_OBJECT(br), KEY_AUTO_DEVOICE, RECDB_QSTRING);
283      hs->auto_devoice = str ? enabled_string(str) : 0;
284 +    str = database_get_data(GET_RECORD_OBJECT(br), KEY_LOG_SQL, RECDB_QSTRING);
285 +    hs->log_sql = str ? enabled_string(str) : 0;
286  
287      dict_foreach(users, user_read_helper, hs);
288  
289 @@ -3422,6 +3569,31 @@
290          helpserv_conf.reqlogfile = NULL;
291      }
292  
293 +    if (helpserv_conf.sql_log) {
294 +        PQfinish(helpserv_conf.sql_log);
295 +        helpserv_conf.sql_log = NULL;
296 +    }
297 +    str = database_get_data(conf_node, "sql_log", RECDB_QSTRING);
298 +    if (str) {
299 +        PGconn *conn = PQconnectdb(str);
300 +        if (!conn) {
301 +            log_module(HS_LOG, LOG_ERROR, "Unable to allocate pgsql connection");
302 +        } else if (PQstatus(conn) == CONNECTION_BAD) {
303 +            log_module(HS_LOG, LOG_ERROR, "Pgsql connection failed: %s", PQerrorMessage(conn));
304 +            PQfinish(conn);
305 +        } else if (PQsetnonblocking(conn, 1) == -1) {
306 +            log_module(HS_LOG, LOG_ERROR, "Unable to make pgsql non-blocking");
307 +            PQfinish(conn);
308 +        } else {
309 +            helpserv_conf.sql_log = conn;
310 +            sql_fd = ioset_add(PQsocket(conn));
311 +            sql_fd->connected = 1;
312 +            sql_fd->wants_reads = 1;
313 +            sql_fd->readable_cb = pgsql_readable;
314 +            while (PQflush(conn)) ;
315 +        }
316 +    }
317 +
318      if (reqlog_ctx) {
319          saxdb_close_context(reqlog_ctx);
320          reqlog_ctx = NULL;
321 @@ -4159,16 +4331,20 @@
322      return mktime(timeinfo);
323  }
324  
325 -/* If data != NULL, then don't add to the timeq */
326  static void helpserv_run_stats(time_t when) {
327      struct tm when_s;
328 +    struct string_buffer query;
329      struct helpserv_bot *hs;
330      struct helpserv_user *hs_user;
331      int i;
332      dict_iterator_t it, it2;
333 +    char timestamp[64];
334  
335      last_stats_update = when;
336      localtime_r(&when, &when_s);
337 +    strftime(timestamp, sizeof(timestamp), "'%Y-%m-%d %H:%M:%S', ", &when_s);
338 +    query.size = 512;
339 +
340      for (it=dict_first(helpserv_bots_dict); it; it=iter_next(it)) {
341          hs = iter_data(it);
342  
343 @@ -4195,6 +4371,18 @@
344                  hs_user->reassigned_to[i] = hs_user->reassigned_to[i-1];
345              }
346  
347 +            /* Log to SQL */
348 +            if (helpserv_conf.sql_log && hs->log_sql) {
349 +                query.list = malloc(query.size);
350 +                query.used = 0;
351 +                string_buffer_append_string(&query, "INSERT INTO srvx_helpserv_stats (c_bot, t_weekstart, c_helper, i_time, i_picked_up, i_closed, i_reassigned_from, i_reassigned_to) VALUES(");
352 +                string_buffer_append_quoted(&query, hs->helpserv->nick);
353 +                string_buffer_append_quoted(&query, timestamp);
354 +                string_buffer_append_quoted(&query, hs_user->handle->handle);
355 +                string_buffer_append_printf(&query, "%d, %d, %d, %d, %d);", hs_user->time_per_week[0], hs_user->picked_up[0], hs_user->closed[0], hs_user->reassigned_from[0], hs_user->reassigned_to[0]);
356 +                pgsql_insert(query.list);
357 +            }
358 +
359              /* Reset it for this week */
360              hs_user->time_per_week[0] = hs_user->picked_up[0] = hs_user->closed[0] = hs_user->reassigned_from[0] = hs_user->reassigned_to[0] = 0;
361          }
362 @@ -4228,6 +4416,10 @@
363          saxdb_close_context(reqlog_ctx);
364      if (reqlog_f)
365          fclose(reqlog_f);
366 +    if (helpserv_conf.sql_log) {
367 +        PQfinish(helpserv_conf.sql_log);
368 +        helpserv_conf.sql_log = NULL;
369 +    }
370  }
371  
372  void init_helpserv() {
373 @@ -4293,6 +4485,7 @@
374      helpserv_define_option("REQONJOIN", opt_req_on_join);
375      helpserv_define_option("AUTOVOICE", opt_auto_voice);
376      helpserv_define_option("AUTODEVOICE", opt_auto_devoice);
377 +    helpserv_define_option("LOGSQL", opt_log_sql);
378  
379      helpserv_bots_dict = dict_new();
380      dict_set_free_data(helpserv_bots_dict, helpserv_free_bot);