"Fossies" - the Fresh Open Source Software Archive 
Member "leafnode-1.12.0/texpire.c" (28 Dec 2021, 18738 Bytes) of package /linux/misc/leafnode-1.12.0.tar.xz:
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style:
standard) with prefixed line numbers and
code folding option.
Alternatively you can here
view or
download the uninterpreted source code file.
For more information about "texpire.c" see the
Fossies "Dox" file reference documentation and the latest
Fossies "Diffs" side-by-side code changes report:
1.11.12_vs_1.12.0.
1 /*
2 texpire -- expire old articles
3
4 Written by Arnt Gulbrandsen <agulbra@troll.no> and copyright 1995
5 Troll Tech AS, Postboks 6133 Etterstad, 0602 Oslo, Norway, fax +47
6 22646949.
7 Modified by Cornelius Krasel <krasel@wpxx02.toxi.uni-wuerzburg.de>
8 and Randolf Skerka <Randolf.Skerka@gmx.de>.
9 Copyright of the modifications 1997.
10 Modified by Kent Robotti <robotti@erols.com>. Copyright of the
11 modifications 1998.
12 Modified by Markus Enzenberger <enz@cip.physik.uni-muenchen.de>.
13 Copyright of the modifications 1998.
14 Modified by Cornelius Krasel <krasel@wpxx02.toxi.uni-wuerzburg.de>.
15 Copyright of the modifications 1998, 1999.
16 Modified by Kazushi (Jam) Marukawa <jam@pobox.com>.
17 Copyright of the modifications 1998, 1999.
18 Modified by Matthias Andree <matthias.andree@gmx.de>.
19 Copyright of the modifications 2000 - 2010.
20
21 See file COPYING for restrictions on the use of this software.
22 */
23
24 #include "leafnode.h"
25 #include "ln_log.h"
26
27 #ifdef SOCKS
28 #include <socks.h>
29 #endif
30
31 #include <ctype.h>
32 #include <limits.h>
33 #include <sys/types.h>
34 #include <sys/stat.h>
35 #include <fcntl.h>
36 #include "system.h"
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <syslog.h>
41 #include <unistd.h>
42 #include <errno.h>
43 #include <signal.h>
44 #include "mysigact.h"
45 #include "mastring.h"
46
47 static time_t default_expire;
48
49 int verbose = 0;
50 int debug = 0;
51 static int repair = 0; /* run expensive checks */
52
53 static int use_atime = 1; /* look for atime on articles to expire */
54 static int quiet = 0; /* shut up */
55
56 static int eflag; /* set to 1 if "mids" file based expiry must not take place */
57
58 static const char *const MIDSFILE = "mids";
59
60 struct exp {
61 char *xover; /* full xover info */
62 int kill;
63 int exists;
64 };
65
66 static sigjmp_buf jmpbuffer;
67 static int blocksig;
68
69 static void
70 sig_int(int signo)
71 {
72 if (blocksig) return;
73 if (signo == SIGINT || signo == SIGTERM) {
74 siglongjmp(jmpbuffer, 1);
75 }
76 }
77
78 /* hook for traverseidtree */
79 /* writes "mids" file for reliable expiry without counting hard links
80 * to evade local hard link attack DoS */
81 static int
82 th(const char *mm) {
83 const char *f;
84 char *p, *t;
85 int fd;
86 ssize_t ml;
87 char *m;
88 struct stat st;
89 /*@only@*/ static char *b;
90 static size_t b_siz;
91
92 if (mm == NULL)
93 {
94 b_siz = 0;
95 free(b);
96 return 0;
97 }
98
99 m = critstrdup(mm, "th");
100 f = lookup(m);
101 p = critmalloc(strlen(f) + 6, "th");
102 strcpy(p, f);
103 t = strrchr(p, '/');
104 if (!t) {
105 ln_log(LNLOG_SERR, LNLOG_CTOP, "can't find / - internal error");
106 free(m);
107 free(p);
108 return 1;
109 }
110 strcpy(++t, MIDSFILE);
111
112 fd = open(p, O_WRONLY|O_APPEND|O_CREAT, 0600);
113 if (fd < 0) {
114 ln_log(LNLOG_SERR, LNLOG_CTOP, "cannot append to file %s: %m", p);
115 free(p);
116 free(m);
117 return 1;
118 }
119 if (fstat(fd, &st)) {
120 ln_log(LNLOG_SERR, LNLOG_CTOP, "cannot fstat fd #%d: %m", fd);
121 free(p);
122 free(m);
123 close(fd);
124 return 1;
125 }
126 /* this file is not portable across endianness, why bother, we're
127 * alone - the spool is locked */
128
129 ml = strlen(m);
130
131 /* resize buffer memory, generously */
132 if (b_siz < ml + 1 + sizeof(ml)) {
133 if (b) free(b);
134 b_siz = ml + 128 + sizeof(ml);
135 b = critmalloc(b_siz, "th");
136 }
137
138 /* make some effort to write the whole record (size + content)
139 * atomically, to avoid corruption when we're interrupted */
140 memcpy(b, &ml, sizeof(ml));
141 for(t = m; *t; t++)
142 if (*t == '/')
143 *t = '@';
144 strcpy(b + sizeof(ml), m);
145 if (write(fd, b, ml + sizeof(ml)) < (ssize_t)(ml + sizeof(ml))) {
146 /* short write -> rollback: truncate file to old size */
147 ftruncate(fd, st.st_size);
148 goto barf;
149 }
150 if (close(fd) < 0) goto barf;
151 free(m);
152 free(p);
153 return 0;
154 barf:
155 ln_log(LNLOG_SERR, LNLOG_CTOP, "write error on file %s: %m", p);
156 close(fd);
157 free(m);
158 free(p);
159 return 1;
160 }
161
162 static void
163 dogroup(/*@null@*/ struct newsgroup *g, const char *name, int expdays)
164 {
165 char *gdir = NULL;
166 size_t s_gdir;
167 char *p;
168 char *q;
169 DIR *d;
170 struct dirent *de;
171 struct stat st;
172 unsigned long first, last, art, dupli = 0;
173 struct exp *articles;
174 int n;
175 int fd;
176 char *overview; /* xover: read then free */
177
178 int deleted, kept;
179
180 deleted = kept = 0;
181 clearidtree();
182
183 /* eliminate empty groups */
184 if (!chdirgroup(name, FALSE)) {
185 if (g) { g->first = g->last + 1; }
186 return;
187 }
188 if (!agetcwd(&gdir, &s_gdir)) {
189 ln_log(LNLOG_SERR, LNLOG_CGROUP, "getcwd: %m");
190 return;
191 }
192
193 /* find low-water and high-water marks */
194
195 d = opendir(".");
196 if (!d) {
197 ln_log(LNLOG_SERR, LNLOG_CGROUP, "opendir in %s: %m", gdir);
198 free(gdir);
199 return;
200 }
201
202 first = ULONG_MAX;
203 last = 0;
204 while ((de = readdir(d)) != 0) {
205 if (!isdigit((unsigned char)de->d_name[0]) ||
206 stat(de->d_name, &st) || !S_ISREG(st.st_mode))
207 continue;
208 art = strtoul(de->d_name, &p, 10);
209 if (p && !*p) {
210 if (art < first)
211 first = art;
212 if (art > last)
213 last = art;
214 }
215 }
216 closedir(d);
217
218 /* update overview info */
219 getxover();
220 freexover();
221
222 if (last < first) {
223 if (verbose > 1) printf("%s: empty group\n", name);
224 if (g) g->first = g->last + 1;
225 free(gdir);
226 return;
227 }
228
229 if (verbose > 1)
230 printf("%s: low water mark %lu, high water mark %lu\n",
231 name, first, last);
232 if (debugmode)
233 syslog(LOG_DEBUG,
234 "%s: expire %lu, low water mark %lu, high water mark %lu",
235 name, (unsigned long)expdays, first, last);
236
237 /* allocate and clear article array */
238 articles = (struct exp *)critmalloc((last - first + 1) * sizeof(struct exp),
239 "Reading articles to expire");
240 for (art = 0; art <= last - first; art++) {
241 articles[art].xover = NULL;
242 articles[art].kill = 0;
243 articles[art].exists = 0;
244 }
245
246 /* read in overview info, to be purged and written back */
247 overview = NULL;
248
249 if (stat(".overview", &st) == 0) {
250 overview = critmalloc(st.st_size + 1, "Reading article overview info");
251 if ((fd = open(".overview", O_RDONLY)) < 0 ||
252 ((off_t) read(fd, overview, st.st_size) < st.st_size)) {
253 ln_log(LNLOG_SERR, LNLOG_CGROUP, "can't open/read %s/.overview: %m", gdir);
254 *overview = '\0';
255 if (fd > -1)
256 close(fd);
257 } else {
258 close(fd);
259 overview[st.st_size] = '\0'; /* 0-terminate string */
260 }
261
262 p = overview;
263 while (p && *p) {
264 while (p && isspace((unsigned char)*p))
265 p++;
266 art = strtoul(p, NULL, 10);
267 if (art >= first && art <= last && !articles[art - first].xover) {
268 articles[art - first].xover = p;
269 articles[art - first].kill = 1;
270 }
271 p = strchr(p, '\n');
272 if (p) {
273 *p = '\0';
274 if (p[-1] == '\r')
275 p[-1] = '\0';
276 p++;
277 }
278 }
279 }
280
281 /* check the syntax of the .overview info, and delete all illegal stuff */
282 for (art = first; art <= last; art++) {
283 const char *x;
284
285 if (articles[art - first].xover &&
286 !legalxoverline(articles[art - first].xover, &x)) {
287 articles[art - first].xover = NULL;
288 }
289 }
290
291 /* insert articles in tree, and clear 'kill' for new or read articles */
292 d = opendir(".");
293 if (!d) {
294 ln_log(LNLOG_SERR, LNLOG_CGROUP, "opendir in %s: %m", gdir);
295 free(gdir);
296 free(articles);
297 return;
298 }
299 while ((de = readdir(d)) != 0) {
300 art = strtoul(de->d_name, &p, 10);
301 if (p && !*p && art <= last && art >= first) {
302 articles[art - first].exists = 1;
303 /* mark all articles as to-be-deleted and rescue those
304 * which fulfill certain criteria */
305 articles[art - first].kill = 1;
306 /* save file if it is a regular non-empty file
307 * and has no expire time */
308 if (stat(de->d_name, &st) == 0 &&
309 (S_ISREG(st.st_mode)) &&
310 (st.st_size != 0) &&
311 (expdays < 0
312 || (st.st_mtime > expdays)
313 || (use_atime && (st.st_atime > expdays)))) {
314 articles[art - first].kill = 0;
315 p = articles[art - first].xover;
316 for (n = 0; n < 4; n++)
317 if (p && (p = strchr(p + 1, '\t')))
318 p++;
319 q = p ? strchr(p, '\t') : NULL;
320 if (p && q) {
321 *q = '\0';
322 if (findmsgid(p)) { /* another file with same msgid? */
323 /* kill this article and keep the first to have
324 * that message-id */
325 articles[art - first].kill = 1;
326 ln_log(LNLOG_SINFO, LNLOG_CARTICLE,
327 "%s: removing duplicate article %lu %s",
328 name, art, p);
329 dupli++;
330 } else {
331 int relink = 0;
332 const char *t = lookup(p);
333
334 insertmsgid(p);
335
336 if (repair == 0) {
337 /* fast path, relink only if link is
338 * obviously missing (may not work for
339 * cross-posted articles) */
340 if (st.st_nlink < 2) {
341 relink = 1;
342 }
343 } else {
344 /* slow path, texpire -r => repair/relink
345 * mode, will check if newsgroup file is
346 * same as message.id file linked */
347 struct stat st2;
348 if (stat(t, &st2)
349 || st2.st_dev != st.st_dev
350 || st2.st_ino != st.st_ino) {
351 relink = 1;
352 }
353 }
354
355 if (relink) { /* repair fs damage */
356 if (link(de->d_name, t)
357 /* if EEXIST, link reverse
358 * rename first because it is atomic and
359 * guarantees the file de->d_name is
360 * always present. This file is precious.
361 * If we used unlink and link, a lone
362 * message.id/000 file would be deleted
363 * by expiremsgid()!
364 */
365 && (errno != EEXIST
366 || rename(t, de->d_name)
367 || link(de->d_name, t)))
368 {
369 ln_log(LNLOG_SERR, LNLOG_CGROUP,
370 "%s: relink of %s <-> %s failed: %s (%s)",
371 name, p, de->d_name, strerror(errno), t);
372 } else {
373 ln_log(LNLOG_SINFO, LNLOG_CARTICLE,
374 "%s: relinked message %s <-> %s", name, p, de->d_name);
375 }
376 }
377 *q = '\t';
378 }
379 } else if (articles[art - first].xover) {
380 /* data structure inconsistency: delete and be rid of it */
381 articles[art - first].kill = 1;
382 } else {
383 /* possibly read the xover line into memory? */
384 }
385 }
386 }
387 }
388 closedir(d);
389
390 /* compute new low-water mark */
391
392 art = first;
393 while (art <= last && articles[art - first].kill)
394 art++;
395 if (g) g->first = art;
396
397 /* remove old postings */
398
399 for (art = first; art <= last; art++) {
400 char artname[40]; /* must hold a decimal long + NUL */ /* RATS: ignore */
401 if (articles[art - first].exists) {
402 if (articles[art - first].kill) {
403 snprintf(artname, sizeof(artname), "%lu", art);
404 if (0 == unlink(artname)) {
405 if (debugmode)
406 syslog(LOG_DEBUG, "deleted article %s/%lu", gdir, art);
407 deleted++;
408 } else if (errno != ENOENT && errno != EEXIST) {
409 /* if file was deleted alredy or it was not a file */
410 /* but a directory, skip error message */
411 kept++;
412 ln_log(LNLOG_SERR, LNLOG_CGROUP, "unlink %s/%lu: %m", gdir, art);
413 } else {
414 /* deleted by someone else */
415 }
416 } else {
417 kept++;
418 }
419 }
420 }
421 free((char *)articles);
422 if (overview)
423 free(overview);
424
425 if (g && last > g->last) /* try to correct insane newsgroup info */
426 g->last = last;
427
428 if (!quiet)
429 printf("%s: %d article%s deleted (%lu duplicate%s), %d kept\n",
430 name, deleted, PLURAL(deleted), dupli, PLURAL(dupli), kept);
431 syslog(LOG_INFO,
432 "%s: %d article%s deleted (%lu duplicate%s), %d kept",
433 name, deleted, PLURAL(deleted), dupli, PLURAL(dupli), kept);
434
435 if (!kept) {
436 if (unlink(".overview") < 0)
437 ln_log(LNLOG_SERR, LNLOG_CGROUP, "unlink %s/.overview: %m", gdir);
438 if (!chdir("..") && (isinteresting(name) == 0)) {
439 /* delete directory and empty parent directories */
440 while (rmdir(gdir) == 0) {
441 if (!agetcwd(&gdir, &s_gdir)) {
442 ln_log(LNLOG_SERR, LNLOG_CGROUP, "getcwd: %m");
443 break;
444 }
445 chdir("..");
446 }
447 }
448 }
449 if (gdir)
450 free(gdir); /* previous loop may have freed *gdir */
451
452 /* write MIDSFILE */
453 if (!eflag)
454 eflag |= traverseidtree(th);
455
456 clearidtree();
457 }
458
459 static void
460 expiregroup(void)
461 {
462 struct newsgroup *g;
463 struct stringlist *t, *l = get_grouplist();
464 int expdays;
465
466 if (!l) {
467 ln_log(LNLOG_SERR, LNLOG_CTOP, "cannot obtain group list\n");
468 return;
469 }
470
471 for(t = l; t; t = t -> next) {
472 char *x = t->string;
473
474 g = findgroup(x);
475 if ((expdays = lookup_expiredays(x)) >= 0) {
476 if (expdays == 0 || !(expdays = lookup_expire(x)))
477 expdays = default_expire;
478 } else {
479 expdays = -1;
480 if (verbose) {
481 printf("%s: never expires\n", x);
482 }
483 syslog(LOG_INFO, "%s: never expires", x);
484 }
485 dogroup(g, x, expdays);
486 }
487 freelist(l);
488 }
489
490 static void
491 fixupgroup(/*@null@*/ struct newsgroup *g)
492 {
493 for (/*nil*/ ; g && g->name; g++) {
494 if (!chdirgroup(g->name, FALSE))
495 g->first = g->last + 1;
496 }
497 }
498
499 static int
500 readmids(void)
501 {
502 int fd;
503 ssize_t l;
504 ssize_t r;
505 char *buf;
506 ssize_t bufsiz = 128;
507 int rc = 0;
508
509 fd = open(MIDSFILE, O_RDONLY);
510 if (fd < 0) {
511 if (errno != ENOENT) {
512 ln_log(LNLOG_SERR, LNLOG_CTOP, "cannot open \"%s\" file: %m",
513 MIDSFILE);
514 return 1;
515 }
516 return 0;
517 }
518
519 /* delete file early so we don't barf again and again if the file is
520 * corrupt */
521 log_unlink(MIDSFILE, 0);
522
523 buf = critmalloc(bufsiz, "readmids");
524
525 while((r = read(fd, &l, sizeof(l))) == (ssize_t)sizeof(l)) {
526 /* length obtained */
527 if (l+1 > bufsiz) {
528 free(buf);
529 bufsiz = l + 1;
530 buf = critmalloc(bufsiz, "readmids");
531 }
532 if ((r = read(fd, buf, l)) < l) {
533 /* short read */
534 rc = -1;
535 break;
536 }
537 buf[l] = '\0';
538 /* sanity check */
539 if (strlen(buf) != (size_t)l) {
540 rc = -1;
541 break;
542 }
543 insertmsgid(buf);
544 }
545 free(buf);
546 (void)close(fd);
547 if (rc)
548 ln_log(LNLOG_SERR, LNLOG_CTOP, "corrupt \"%s\" file", MIDSFILE);
549 if (r < 0) {
550 ln_log(LNLOG_SERR, LNLOG_CTOP, "cannot read \"%s\" file: %m", MIDSFILE);
551 rc = -1;
552 }
553 return rc;
554 }
555
556 /* returns 0 for success */
557 static int
558 cleanmids(void)
559 {
560 int n, rc = 0;
561 mastr *s = mastr_new(256);
562
563 for (n = 0; n < 1000; n++) {
564 char buf[4];
565 snprintf(buf, sizeof(buf), "%03d", n); /* safe */
566 mastr_clear(s);
567 mastr_vcat(s, spooldir, "/message.id/", buf, "/", MIDSFILE, NULL);
568 if (log_unlink(mastr_str(s), 1))
569 rc = 1;
570 }
571 mastr_delete(s);
572 return rc;
573 }
574
575 static void
576 expiremsgid(void)
577 {
578 int n, s_len;
579 DIR *d;
580 struct dirent *de;
581 struct stat st;
582 int deleted, kept;
583 const char *t;
584 int nomids = eflag;
585
586 deleted = kept = 0;
587
588 if (verbose)
589 puts("Expiring message.id...");
590
591 for (n = 0; n < 1000; n++) {
592 char s[SIZE_s+1];
593
594 s_len = xsnprintf(s, SIZE_s, "%s/message.id/%03d/", spooldir, n);
595 if (chdir(s)) {
596 if (errno == ENOENT)
597 mkdir(s, 0755); /* file system damage? */
598 if (chdir(s)) {
599 ln_log(LNLOG_SERR, LNLOG_CGROUP, "chdir %s: %m", s);
600 continue;
601 }
602 }
603
604 if (nomids == 0)
605 nomids |= readmids();
606 else
607 unlink(MIDSFILE); /* ignore errors */
608
609 d = opendir(".");
610 if (!d)
611 continue;
612 while ((de = readdir(d)) != 0) {
613 if (stat(de->d_name, &st) == 0 && S_ISREG(st.st_mode)) {
614 int ul = 0;
615 const char *reason = "";
616 if (st.st_nlink < 2) ul = 1, reason = "link count below 2";
617 if (!nomids && !findmsgid(de->d_name)) ul = 1, reason = "not seen in group scan";
618 if (ul) {
619 if (debugmode)
620 ln_log(LNLOG_SDEBUG, LNLOG_CARTICLE, "unlinking %03d/%s, %s",
621 n, de->d_name, reason);
622 if (0 == log_unlink(de->d_name, 1)
623 && de->d_name[0] == '<' /* only count MID files */)
624 deleted++;
625 } else {
626 kept++;
627 /* check hash */
628 t = lookup(de->d_name);
629 if (strncmp(t, s, s_len)) {
630 /* in wrong directory, move to the right one
631 * note however that if the right file is
632 * already present, we'll leave it in place,
633 * because it may have been relinked from a
634 * group directory and we don't want to break
635 * links again
636 */
637 if (link(de->d_name, t) && errno != EEXIST)
638 ln_log(LNLOG_SERR, LNLOG_CARTICLE,
639 "rehash: cannot move %s%s to %s: %m",
640 s, de->d_name, t);
641 else {
642 char buf[4];
643 memcpy(buf, t + s_len - 4, 3);
644 buf[3] = '\0';
645
646 ln_log(LNLOG_SINFO, LNLOG_CARTICLE,
647 "rehashed %s from %03d to %s", de->d_name,
648 n, buf);
649 }
650 log_unlink(de->d_name, 0);
651 }
652 }
653 }
654 }
655 closedir(d);
656 clearidtree();
657 }
658
659 if (verbose)
660 puts("Done.");
661
662 if (!quiet)
663 printf("message.id/: %d article%s deleted, %d kept\n", deleted, PLURAL(deleted), kept);
664 syslog(LOG_INFO, "message.id/: %d article%s deleted, %d kept", deleted, PLURAL(deleted), kept);
665 }
666
667
668 int
669 main(int argc, char **argv)
670 {
671 int option;
672 int rc = 1;
673
674 myopenlog("texpire");
675 if (!initvars(argv[0]))
676 exit(1);
677
678 while ((option = getopt(argc, argv, "vfqhr")) != -1) {
679 switch(option) {
680 case 'v':
681 verbose++;
682 quiet = 0;
683 break;
684 case 'f':
685 use_atime = 0;
686 break;
687 case 'r':
688 repair = 1;
689 break;
690 case 'q':
691 quiet = 1;
692 verbose = 0;
693 break;
694 case 'h':
695 rc = 0;
696 /*FALLTHROUGH*/
697 default:
698 if (rc)
699 fprintf(stderr, "texpire: unknown option -%c.\n", optopt);
700 fprintf(stderr, "Usage: texpire {[-v[v[v[v]]]]|-q} [-f]\n"
701 " -q: be quiet (cancels -v)\n"
702 " -v: more verbose (cancels -q, may be repeated)\n"
703 " -f: force expire irrespective of access time\n");
704 exit(rc);
705 }
706 }
707
708 expire = 0;
709 expire_base = NULL;
710
711 if (!readconfig(0)) {
712 fprintf(stderr, "Reading configuration failed, exiting "
713 "(see syslog for more information).\n");
714 exit(2);
715 }
716 freeservers();
717
718 if (verbose || debugmode) {
719 printf("texpire %s: verbosity level %d, debugmode %d, %s\n", version,
720 verbose, debugmode,
721 use_atime ? "check mtime and atime" : "check mtime only");
722 }
723 syslog(LOG_INFO, "texpire %s: use_atime is %d, verbosity level %d, "
724 "debugmode %d", version, use_atime, verbose, debugmode);
725
726 if (try_lock(timeout_lock)) {
727 ln_log(LNLOG_SERR, LNLOG_CTOP, "Cannot obtain lock file, aborting.\n");
728 exit(1);
729 }
730
731 if (cleanmids()) {
732 ln_log(LNLOG_SERR, LNLOG_CTOP, "Cannot weed out MIDS files, aborting.\n");
733 unlink(lockfile);
734 exit(1);
735 }
736
737 readactive();
738 if (!active) {
739 ln_log(LNLOG_SWARNING, LNLOG_CTOP, "Reading active file failed. Trying to build my own.");
740 fakeactive();
741 }
742
743 if (expire == 0) {
744 fprintf(stderr, "%s: no expire time\n", argv[0]);
745 unlink(lockfile);
746 exit(2);
747 }
748
749 default_expire = expire;
750
751 if (sigsetjmp(jmpbuffer, 1) == 0) {
752 /* if we can't catch either signal, don't care,
753 * it's just more work next time */
754 (void)mysigact(SIGINT, 0, sig_int, 0);
755 (void)mysigact(SIGTERM, 0, sig_int, 0);
756 expiregroup();
757 fixupgroup(active);
758 expiremsgid();
759 } else {
760 blocksig = 1;
761 ln_log(LNLOG_SNOTICE, LNLOG_CTOP,
762 "caught interrupt/termination signal, aborting gracefully.");
763 }
764 if (writeactive())
765 ln_log(LNLOG_SERR, LNLOG_CTOP, "error writing groupinfo.");
766 freeactive(active);
767 unlink(lockfile);
768 freeservers();
769 freexover();
770 freeconfig();
771 th(NULL);
772 return 0;
773 }