chiark / gitweb /
wip split into multiple files and make compile
[inn-innduct.git] / cli.c
1 /*========== command and control (CLI) connections ==========*/
2
3 static int cli_master;
4
5 typedef struct CliConn CliConn;
6 struct CliConn {
7   void (*destroy)(CliConn*);
8   int fd;
9   oop_read *rd;
10   FILE *out;
11   union {
12     struct sockaddr sa;
13     struct sockaddr_un un;
14   } sa;
15   socklen_t salen;
16 };
17
18 static const oop_rd_style cli_rd_style= {
19   OOP_RD_DELIM_STRIP, '\n',
20   OOP_RD_NUL_FORBID,
21   OOP_RD_SHORTREC_FORBID
22 };
23
24 static void cli_destroy(CliConn *cc) {
25   cc->destroy(cc);
26 }
27
28 static void cli_checkouterr(CliConn *cc /* may destroy*/) {
29   if (ferror(cc->out) | fflush(cc->out)) {
30     info("CTRL%d write error %s", cc->fd, strerror(errno));
31     cli_destroy(cc);
32   }
33 }
34
35 static void cli_prompt(CliConn *cc /* may destroy*/) {
36   fprintf(cc->out, "%s| ", sitename);
37   cli_checkouterr(cc);
38 }
39
40 struct CliCommand {
41   const char *cmd;
42   void (*f)(CliConn *cc, const CliCommand *ccmd,
43             const char *arg, size_t argsz);
44   void *xdata;
45   int xval;
46 };
47
48 static const CliCommand cli_commands[];
49
50 #define CCMD(wh)                                                \
51   static void ccmd_##wh(CliConn *cc, const CliCommand *c,       \
52                         const char *arg, size_t argsz)
53
54 CCMD(help) {
55   fputs("commands:\n", cc->out);
56   const CliCommand *ccmd;
57   for (ccmd=cli_commands; ccmd->cmd; ccmd++)
58     fprintf(cc->out, " %s\n", ccmd->cmd);
59   fputs("NB: permissible arguments are not shown above."
60         "  Not all commands listed are safe.  See innduct(8).\n", cc->out);
61 }
62
63 CCMD(flush) {
64   int ok= trigger_flush_ok("manual request");
65   if (!ok) fprintf(cc->out,"already flushing (state is %s)\n", sms_names[sms]);
66 }
67
68 CCMD(stop) {
69   preterminate();
70   notice("terminating (CTRL%d)",cc->fd);
71   raise_default(SIGTERM);
72   abort();
73 }
74
75 CCMD(dump);
76
77 /* messing with our head: */
78 CCMD(period) { period(); }
79 CCMD(setintarg) { *(int*)c->xdata= atoi(arg); }
80 CCMD(setint) { *(int*)c->xdata= c->xval; }
81 CCMD(setint_period) { *(int*)c->xdata= c->xval; period(); }
82
83 static const CliCommand cli_commands[]= {
84   { "h",             ccmd_help      },
85   { "flush",         ccmd_flush     },
86   { "stop",          ccmd_stop      },
87   { "dump q",        ccmd_dump, 0,0 },
88   { "dump a",        ccmd_dump, 0,1 },
89
90   { "p",             ccmd_period    },
91
92 #define POKES(cmd,func)                                                 \
93   { cmd "flush",     func,           &until_flush,             1 },     \
94   { cmd "conn",      func,           &until_connect,           0 },     \
95   { cmd "blscan",    func,           &until_backlog_nextscan,  0 },
96 POKES("next ", ccmd_setint)
97 POKES("prod ", ccmd_setint_period)
98
99   { "pretend flush", ccmd_setintarg, &simulate_flush             },
100   { "wedge blscan",  ccmd_setint,    &until_backlog_nextscan, -1 },
101   { 0 }
102 };
103
104 static void *cli_rd_ok(oop_source *lp, oop_read *oread, oop_rd_event ev,
105                        const char *errmsg, int errnoval,
106                        const char *data, size_t recszu, void *cc_v) {
107   CliConn *cc= cc_v;
108
109   if (!data) {
110     info("CTRL%d closed", cc->fd);
111     cc->destroy(cc);
112     return OOP_CONTINUE;
113   }
114
115   if (recszu == 0) goto prompt;
116   assert(recszu <= INT_MAX);
117   int recsz= recszu;
118
119   const CliCommand *ccmd;
120   for (ccmd=cli_commands; ccmd->cmd; ccmd++) {
121     int l= strlen(ccmd->cmd);
122     if (recsz < l) continue;
123     if (recsz > l && data[l] != ' ') continue;
124     if (memcmp(data, ccmd->cmd, l)) continue;
125
126     int argl= (int)recsz - (l+1); 
127     ccmd->f(cc, ccmd, argl>=0 ? data+l+1 : 0, argl);
128     goto prompt;
129   }
130
131   fputs("unknown command; h for help\n", cc->out);
132
133  prompt:
134   cli_prompt(cc);
135   return OOP_CONTINUE;
136 }
137
138 static void *cli_rd_err(oop_source *lp, oop_read *oread, oop_rd_event ev,
139                         const char *errmsg, int errnoval,
140                         const char *data, size_t recsz, void *cc_v) {
141   CliConn *cc= cc_v;
142   
143   info("CTRL%d read error %s", cc->fd, errmsg);
144   cc->destroy(cc);
145   return OOP_CONTINUE;
146 }
147
148 static int cli_conn_startup(CliConn *cc /* may destroy*/,
149                                 const char *how) {
150   cc->rd= oop_rd_new_fd(loop, cc->fd, 0,0);
151   if (!cc->rd) { warn("oop_rd_new_fd cli failed"); return -1; }
152
153   int er= oop_rd_read(cc->rd, &cli_rd_style, MAX_CLI_COMMAND,
154                       cli_rd_ok, cc,
155                       cli_rd_err, cc);
156   if (er) { errno= er; syswarn("oop_rd_read cli failed"); return -1; }
157
158   info("CTRL%d %s ready", cc->fd, how);
159   cli_prompt(cc);
160   return 0;
161 }
162
163 static void cli_stdio_destroy(CliConn *cc) {
164   if (cc->rd) {
165     oop_rd_cancel(cc->rd);
166     errno= oop_rd_delete_tidy(cc->rd);
167     if (errno) syswarn("oop_rd_delete tidy failed (no-nonblock stdin?)");
168   }
169   free(cc);
170 }
171
172 static void cli_stdio(void) {
173   NEW_DECL(CliConn *,cc);
174   cc->destroy= cli_stdio_destroy;
175
176   cc->fd= 0;
177   cc->out= stdout;
178   int r= cli_conn_startup(cc,"stdio");
179   if (r) cc->destroy(cc);
180 }
181
182 static void cli_accepted_destroy(CliConn *cc) {
183   if (cc->rd) {
184     oop_rd_cancel(cc->rd);
185     oop_rd_delete_kill(cc->rd);
186   }
187   if (cc->out) { fclose(cc->out); cc->fd=0; }
188   close_perhaps(&cc->fd);
189   free(cc);
190 }
191
192 static void *cli_master_readable(oop_source *lp, int master,
193                                  oop_event ev, void *u) {
194   NEW_DECL(CliConn *,cc);
195   cc->destroy= cli_accepted_destroy;
196
197   cc->salen= sizeof(cc->sa);
198   cc->fd= accept(master, &cc->sa.sa, &cc->salen);
199   if (cc->fd<0) { syswarn("error accepting cli connection"); goto x; }
200
201   cc->out= fdopen(cc->fd, "w");
202   if (!cc->out) { syswarn("error fdopening accepted cli connection"); goto x; }
203
204   int r= cli_conn_startup(cc, "accepted");
205   if (r) goto x;
206
207   return OOP_CONTINUE;
208
209  x:
210   cc->destroy(cc);
211   return OOP_CONTINUE;
212 }
213
214 #define NOCLI(...) do{                                          \
215     syswarn("no cli listener, because failed to " __VA_ARGS__); \
216     goto nocli;                                                 \
217   }while(0)
218
219 static void cli_init(void) {
220   union {
221     struct sockaddr sa;
222     struct sockaddr_un un;
223   } sa;
224
225   memset(&sa,0,sizeof(sa));
226   int maxlen= sizeof(sa.un.sun_path);
227
228   if (!path_cli) {
229     info("control command line disabled");
230     return;
231   }
232
233   int pathlen= strlen(path_cli);
234   if (pathlen > maxlen) {
235     warn("no cli listener, because cli socket path %s too long (%d>%d)",
236          path_cli, pathlen, maxlen);
237     return;
238   }
239
240   if (path_cli_dir) {
241     int r= mkdir(path_cli_dir, 0700);
242     if (r && errno!=EEXIST)
243       NOCLI("create cli socket directory %s", path_cli_dir);
244   }
245
246   int r= unlink(path_cli);
247   if (r && errno!=ENOENT)
248     NOCLI("remove old cli socket %s", path_cli);
249
250   cli_master= socket(PF_UNIX, SOCK_STREAM, 0);
251   if (cli_master<0) NOCLI("create new cli master socket");
252
253   int sl= pathlen + offsetof(struct sockaddr_un, sun_path);
254   sa.un.sun_family= AF_UNIX;
255   memcpy(sa.un.sun_path, path_cli, pathlen);
256
257   r= bind(cli_master, &sa.sa, sl);
258   if (r) NOCLI("bind to cli socket path %s", sa.un.sun_path);
259
260   r= listen(cli_master, 5);
261   if (r) NOCLI("listen to cli master socket");
262
263   xsetnonblock(cli_master, 1);
264
265   loop->on_fd(loop, cli_master, OOP_READ, cli_master_readable, 0);
266   info("cli ready, listening on %s", path_cli);
267
268   return;
269
270  nocli:
271   xclose_perhaps(&cli_master, "cli master",0);
272   return;
273 }
274
275 /*========== dumping state ==========*/
276
277 static void dump_article_list(FILE *f, const CliCommand *c,
278                               const ArticleList *al) {
279   fprintf(f, " count=%d\n", al->count);
280   if (!c->xval) return;
281   
282   int i; Article *art;
283   for (i=0, art=LIST_HEAD(*al); art; i++, art=LIST_NEXT(art)) {
284     fprintf(f," #%05d %-11s", i, artstate_names[art->state]);
285     DUMPV("%p", art->,ipf);
286     DUMPV("%d", art->,missing);
287     DUMPV("%lu", (unsigned long)art->,offset);
288     DUMPV("%d", art->,blanklen);
289     DUMPV("%d", art->,midlen);
290     fprintf(f, " %s %s\n", TokenToText(art->token), art->messageid);
291   }
292 }
293   
294 static void dump_input_file(FILE *f, const CliCommand *c,
295                             InputFile *ipf, const char *wh) {
296   char *dipf= dbg_report_ipf(ipf);
297   fprintf(f,"input %s %s", wh, dipf);
298   free(dipf);
299   
300   if (ipf) {
301     DUMPV("%d", ipf->,readcount_ok);
302     DUMPV("%d", ipf->,readcount_blank);
303     DUMPV("%d", ipf->,readcount_err);
304     DUMPV("%d", ipf->,count_nooffer_missing);
305   }
306   fprintf(f,"\n");
307   if (ipf) {
308     ArtState state; const char *const *statename; 
309     for (state=0, statename=artstate_names; *statename; state++,statename++) {
310 #define RC_DUMP_FMT(x) " " #x "=%d"
311 #define RC_DUMP_VAL(x) ,ipf->counts[state][RC_##x]
312       fprintf(f,"input %s counts %-11s"
313               RESULT_COUNTS(RC_DUMP_FMT,RC_DUMP_FMT) "\n",
314               wh, *statename
315               RESULT_COUNTS(RC_DUMP_VAL,RC_DUMP_VAL));
316     }
317     fprintf(f,"input %s queue", wh);
318     dump_article_list(f,c,&ipf->queue);
319   }
320 }
321
322 CCMD(dump) {
323   int i;
324   fprintf(cc->out, "dumping state to %s\n", path_dump);
325   FILE *f= fopen(path_dump, "w");
326   if (!f) { fprintf(cc->out, "failed: open: %s\n", strerror(errno)); return; }
327
328   fprintf(f,"general");
329   DUMPV("%s", sms_names,[sms]);
330   DUMPV("%d", ,until_flush);
331   DUMPV("%ld", (long),self_pid);
332   DUMPV("%p", , defer);
333   DUMPV("%d", , until_connect);
334   DUMPV("%d", , until_backlog_nextscan);
335   DUMPV("%d", , simulate_flush);
336   fprintf(f,"\nnocheck");
337   DUMPV("%#.10f", , accept_proportion);
338   DUMPV("%d", , nocheck);
339   DUMPV("%d", , nocheck_reported);
340   fprintf(f,"\n");
341
342   fprintf(f,"special");
343   DUMPV("%ld", (long),connecting_child);
344   DUMPV("%d", , connecting_fdpass_sock);
345   DUMPV("%d", , cli_master);
346   fprintf(f,"\n");
347
348   fprintf(f,"lowvol");
349   DUMPV("%d", , lowvol_circptr);
350   DUMPV("%d", , lowvol_total);
351   fprintf(f,":");
352   for (i=0; i<lowvol_periods; i++) {
353     fprintf(f," ");
354     if (i==lowvol_circptr) fprintf(f,"*");
355     fprintf(f,"%d",lowvol_perperiod[i]);
356   }
357   fprintf(f,"\n");
358
359   fprintf(f,"filemon ");
360   filemon_method_dump_info(f);
361
362   dump_input_file(f,c, main_input_file,     "main"    );
363   dump_input_file(f,c, flushing_input_file, "flushing");
364   dump_input_file(f,c, backlog_input_file,  "backlog" );
365
366   fprintf(f,"conns count=%d\n", conns.count);
367
368   Conn *conn;
369   FOR_CONN(conn) {
370
371     fprintf(f,"C%d",conn->fd);
372     DUMPV("%p",conn->,rd);             DUMPV("%d",conn->,max_queue);
373     DUMPV("%d",conn->,stream);         DUMPV("\"%s\"",conn->,quitting);
374     DUMPV("%d",conn->,since_activity);
375     fprintf(f,"\n");
376
377     fprintf(f,"C%d waiting", conn->fd); dump_article_list(f,c,&conn->waiting);
378     fprintf(f,"C%d priority",conn->fd); dump_article_list(f,c,&conn->priority);
379     fprintf(f,"C%d sent",    conn->fd); dump_article_list(f,c,&conn->sent);
380
381     fprintf(f,"C%d xmit xmitu=%d\n", conn->fd, conn->xmitu);
382     for (i=0; i<conn->xmitu; i++) {
383       const struct iovec *iv= &conn->xmit[i];
384       const XmitDetails *xd= &conn->xmitd[i];
385       char *dinfo;
386       switch (xd->kind) {
387       case xk_Const:    dinfo= xasprintf("Const");                 break;
388       case xk_Artdata:  dinfo= xasprintf("A%p", xd->info.sm_art);  break;
389       default:
390         abort();
391       }
392       fprintf(f," #%03d %-11s l=%d %s\n", i, dinfo, iv->iov_len,
393               sanitise(iv->iov_base, iv->iov_len));
394       free(dinfo);
395     }
396   }
397
398   fprintf(f,"paths");
399   DUMPV("%s", , feedfile);
400   DUMPV("%s", , path_cli);
401   DUMPV("%s", , path_lock);
402   DUMPV("%s", , path_flushing);
403   DUMPV("%s", , path_defer);
404   DUMPV("%s", , path_dump);
405   DUMPV("%s", , globpat_backlog);
406   fprintf(f,"\n");
407
408   if (!!ferror(f) + !!fclose(f)) {
409     fprintf(cc->out, "failed: write: %s\n", strerror(errno));
410     return;
411   }
412 }