1 /* $Id: icd.c 9659 2014-08-30 08:08:11Z iulius $
2 **
3 ** Routines to read and write the active file.
4 */
5
6 #include "config.h"
7 #include "clibrary.h"
8 #include "portable/mmap.h"
9 #include <sys/uio.h>
10
11 #include "inn/fdflag.h"
12 #include "inn/innconf.h"
13 #include "inn/mmap.h"
14 #include "innd.h"
15 #include "inn/ov.h"
16
17 /* If we fork and exec under Cygwin, children hold onto the mmap */
18 /* of active, and Windows won't let us resize or replace it. */
19 #ifdef __CYGWIN__
20 # undef HAVE_MMAP
21 #endif
22
23 static char *ICDactpath = NULL;
24 static char *ICDactpointer;
25 static int ICDactfd;
26 static int ICDactsize;
27
28
29 /*
30 ** Set and unset (or copy) IOVEC elements. We make copies to
31 ** avoid problems with mmap.
32 */
33 #ifdef HAVE_MMAP
34 static void
ICDiovset(struct iovec * iovp,char * base,int len)35 ICDiovset(struct iovec *iovp, char *base, int len)
36 {
37 iovp->iov_len = len;
38 iovp->iov_base = xmalloc(iovp->iov_len);
39 memcpy(iovp->iov_base, base, iovp->iov_len);
40 }
41 #define ICDiovrelease(iovp) free((iovp)->iov_base)
42
43 #else /* !HAVE_MMAP */
44
45 #define ICDiovset(iovp, base, len) \
46 (iovp)->iov_base = base, (iovp)->iov_len = len
47 #define ICDiovrelease(iovp) /* NULL */
48
49 #endif /* HAVE_MMAP */
50
51
52 /*
53 ** Close the active file, releasing its resources.
54 */
55 static void
ICDcloseactive(void)56 ICDcloseactive(void)
57 {
58 if (ICDactpointer) {
59 #ifdef HAVE_MMAP
60 if (munmap(ICDactpointer, ICDactsize) < 0)
61 syslog(L_ERROR, "%s cant munmap %s %m", LogName, ICDactpath);
62 #else
63 free(ICDactpointer);
64 #endif
65 ICDactpointer = NULL;
66 if (close(ICDactfd) < 0) {
67 syslog(L_FATAL, "%s cant close %s %m", LogName, ICDactpath);
68 exit(1);
69 }
70 }
71 }
72
73
74 /*
75 ** Set up the hash and in-core tables.
76 */
77 void
ICDsetup(bool StartSites)78 ICDsetup(bool StartSites)
79 {
80 if (ICDneedsetup == true) {
81 ICDneedsetup = false;
82 }
83 else {
84 ICDcloseactive();
85 NGparsefile();
86 }
87 if (NGfind("control") == NULL || NGfind("junk") == NULL) {
88 syslog(L_FATAL, "%s internal no control and/or junk group", LogName);
89 exit(1);
90 }
91 if (NGfind("control.cancel") == NULL) {
92 syslog(L_FATAL, "%s internal no control.cancel group", LogName);
93 exit(1);
94 }
95 if (innconf->mergetogroups && NGfind("to") == NULL) {
96 syslog(L_FATAL, "%s internal no to group", LogName);
97 exit(1);
98 }
99 SITEparsefile(StartSites);
100 }
101
102
103 /*
104 ** Write out all in-core data.
105 */
106 void
ICDwrite(void)107 ICDwrite(void)
108 {
109 HISsync(History);
110 SMflushcacheddata(SM_ALL);
111
112 if (ICDactivedirty != 0) {
113 ICDwriteactive();
114 ICDactivedirty = 0;
115 }
116
117 /* Flush log and error log. */
118 if (fflush(Log) == EOF)
119 syslog(L_ERROR, "%s cant fflush log %m", LogName);
120 if (fflush(Errlog) == EOF)
121 syslog(L_ERROR, "%s cant fflush errlog %m", LogName);
122 }
123
124
125 /*
126 ** Close things down.
127 */
128 void
ICDclose(void)129 ICDclose(void)
130 {
131 ICDwrite();
132 ICDcloseactive();
133 }
134
135
136 /*
137 ** Scan the active file, and renumber the min/max counts.
138 */
139 bool
ICDrenumberactive(void)140 ICDrenumberactive(void)
141 {
142 int i;
143 NEWSGROUP *ngp;
144
145 for (i = nGroups, ngp = Groups; --i >= 0; ngp++)
146 if (!NGrenumber(ngp))
147 return false;
148 if (i < 0)
149 ICDwrite();
150 return true;
151 }
152
153
154 /*
155 ** Use writev() to replace the active file.
156 */
157 static bool
ICDwritevactive(struct iovec * vp,int vpcount)158 ICDwritevactive(struct iovec *vp, int vpcount)
159 {
160 static char *BACKUP = NULL;
161 static char *NEWACT = NULL;
162 static char WHEN[] = "backup active";
163 int fd;
164 int oerrno;
165 #ifdef __CYGWIN__
166 size_t newactsize, padactsize, wrote;
167 struct iovec *newvp;
168 char *filler;
169 int i;
170 #endif
171
172 if (BACKUP == NULL)
173 BACKUP = concatpath(innconf->pathdb, INN_PATH_OLDACTIVE);
174 if (NEWACT == NULL)
175 NEWACT = concatpath(innconf->pathdb, INN_PATH_NEWACTIVE);
176 /* Write the current file to a backup. */
177 if (unlink(BACKUP) < 0 && errno != ENOENT) {
178 oerrno = errno;
179 syslog(L_ERROR, "%s cant unlink %s %m", LogName, BACKUP);
180 IOError(WHEN, oerrno);
181 }
182 if ((fd = open(BACKUP, O_WRONLY | O_TRUNC | O_CREAT, 0664)) < 0) {
183 oerrno = errno;
184 syslog(L_ERROR, "%s cant open %s %m", LogName, BACKUP);
185 IOError(WHEN, oerrno);
186 }
187 else if (xwrite(fd, ICDactpointer, ICDactsize) < 0) {
188 oerrno = errno;
189 syslog(L_ERROR, "%s cant write %s %m", LogName, BACKUP);
190 IOError(WHEN, oerrno);
191 close(fd);
192 }
193 else if (close(fd) < 0) {
194 oerrno = errno;
195 syslog(L_ERROR, "%s cant close %s %m", LogName, BACKUP);
196 IOError(WHEN, oerrno);
197 }
198
199 #ifdef __CYGWIN__
200 /* If we are shrinking active, junk will be at the end between the */
201 /* writev and ftruncate. Clobber it with values that overview and */
202 /* nnrpd can ignore. */
203 for (newactsize = 0, i = 0; i < vpcount; i++)
204 newactsize += vp[i].iov_len;
205 if (newactsize < ICDactsize) {
206 padactsize = ICDactsize - newactsize;
207 newvp = xmalloc((vpcount + 1) * sizeof(struct iovec));
208 for (i = 0; i < vpcount; i++)
209 newvp[i] = vp[i];
210 filler = xcalloc(padactsize, 1);
211 *filler = '.';
212 filler[padactsize - 1] = '\n';
213 newvp[vpcount].iov_base = filler;
214 newvp[vpcount].iov_len = padactsize;
215 vpcount++;
216 }
217 else {
218 padactsize = 0;
219 newvp = vp;
220 }
221 oerrno = 0;
222 if (lseek(ICDactfd, 0, SEEK_SET) == -1) {
223 oerrno = errno;
224 syslog(L_ERROR, "%s cant rewind %s %m", LogName, ICDactpath);
225 IOError(WHEN, oerrno);
226 goto bailout;
227 }
228 if (xwritev(ICDactfd, newvp, vpcount) < 0) {
229 oerrno = errno;
230 syslog(L_ERROR, "%s cant write %s %m", LogName, ICDactpath);
231 IOError(WHEN, oerrno);
232 goto bailout;
233 }
234 if (newactsize < ICDactsize && ftruncate(ICDactfd, newactsize) != 0) {
235 oerrno = errno;
236 syslog(L_ERROR, "%s cant truncate %s", LogName, ICDactpath);
237 }
238
239 bailout:
240 if (padactsize != 0) {
241 free(filler);
242 free(newvp);
243 }
244 if (oerrno != 0)
245 return false;
246
247 #else /* !__CYGWIN__, do it the Unix way. */
248
249 /* Open the active file. */
250 fd = open(NEWACT, O_WRONLY | O_TRUNC | O_CREAT, ARTFILE_MODE);
251 if (fd < 0) {
252 oerrno = errno;
253 syslog(L_ERROR, "%s cant open %s %m", LogName, NEWACT);
254 IOError(WHEN, oerrno);
255 return false;
256 }
257
258 /* Write it. */
259 if (xwritev(fd, vp, vpcount) < 0) {
260 oerrno = errno;
261 syslog(L_ERROR, "%s cant write %s %m", LogName, NEWACT);
262 IOError(WHEN, oerrno);
263 close(fd);
264 return false;
265 }
266
267 /* Close it. */
268 close(fd);
269
270 /* Rename it to be the canonical active file */
271 if (rename(NEWACT, ICDactpath) < 0) {
272 oerrno = errno;
273 syslog(L_ERROR, "%s cant rename %s to %s %m",
274 LogName, NEWACT, ICDactpath);
275 IOError(WHEN, oerrno);
276 return false;
277 }
278
279 #endif /* __CYGWIN__ */
280
281 /* Invalidate in-core pointers. */
282 ICDcloseactive();
283
284 /* Restore in-core pointers. */
285 if (Mode != OMrunning) {
286 ICDneedsetup = true;
287 /* Force the active file into memory. */
288 NGparsefile();
289 }
290 else
291 ICDsetup(true);
292 return true;
293 }
294
295
296 /*
297 ** Change the flag on a newsgroup. Fairly easy.
298 */
299 bool
ICDchangegroup(NEWSGROUP * ngp,char * Rest)300 ICDchangegroup(NEWSGROUP *ngp, char *Rest)
301 {
302 static char NEWLINE[] = "\n";
303 int i;
304 struct iovec iov[3];
305 bool ret;
306 char *Name;
307 long Last;
308
309 /* Set up the scatter/gather vectors. */
310 ICDiovset(&iov[0], ICDactpointer, ngp->Rest - ICDactpointer);
311 ICDiovset(&iov[1], Rest, strlen(Rest));
312 Name = xstrdup(ngp->Name);
313 Last = ngp->Last;
314 if (++ngp < &Groups[nGroups]) {
315 /* Not the last group, keep the \n from the next line. */
316 i = ngp->Start;
317 ICDiovset(&iov[2], &ICDactpointer[i - 1], ICDactsize - i + 1);
318 }
319 else {
320 /* Last group -- append a newline. */
321 ICDiovset(&iov[2], NEWLINE, strlen(NEWLINE));
322 }
323 ret = ICDwritevactive(iov, 3);
324 ICDiovrelease(&iov[0]);
325 ICDiovrelease(&iov[1]);
326 ICDiovrelease(&iov[2]);
327
328 if (ret) {
329 if (innconf->enableoverview && !OVgroupadd(Name, 0, Last, Rest)) {
330 free(Name);
331 return false;
332 }
333 }
334 free(Name);
335 return ret;
336 }
337
338
339 /*
340 ** Add a newsgroup. Append a line to the end of the active file and reload.
341 */
342 bool
ICDnewgroup(char * Name,char * Rest)343 ICDnewgroup(char *Name, char *Rest)
344 {
345 char buff[SMBUF];
346 struct iovec iov[2];
347 bool ret;
348
349 /* Set up the scatter/gather vectors. */
350 if (strlen(Name) + strlen(Rest) > SMBUF - 24) {
351 syslog(L_ERROR, "%s too_long %s", LogName, MaxLength(Name, Name));
352 return false;
353 }
354 snprintf(buff, sizeof(buff), "%s 0000000000 0000000001 %s\n", Name, Rest);
355 ICDiovset(&iov[0], ICDactpointer, ICDactsize);
356 ICDiovset(&iov[1], buff, strlen(buff));
357
358 ret = ICDwritevactive(iov, 2);
359 ICDiovrelease(&iov[0]);
360 ICDiovrelease(&iov[1]);
361 if (ret) {
362 if (innconf->enableoverview && !OVgroupadd(Name, 1, 0, Rest))
363 return false;
364 }
365 return ret;
366 }
367
368
369 /*
370 ** Remove a newsgroup. Splice the line out of the active file and reload.
371 */
372 bool
ICDrmgroup(NEWSGROUP * ngp)373 ICDrmgroup(NEWSGROUP *ngp)
374 {
375 struct iovec iov[2];
376 int i;
377 bool ret;
378 char *Name;
379
380 /* Don't let anyone remove newsgroups that INN requires exist. */
381 if (strcmp(ngp->Name, "junk") == 0 || strcmp(ngp->Name, "control") == 0
382 || strcmp(ngp->Name, "control.cancel") == 0)
383 return false;
384 if (innconf->mergetogroups && strcmp(ngp->Name, "to") == 0)
385 return false;
386
387 Name = xstrdup(ngp->Name);
388 /* If this is the first group in the file, write everything after. */
389 if (ngp == &Groups[0]) {
390 i = ngp[1].Start;
391 ICDiovset(&iov[0], &ICDactpointer[i], ICDactsize - i);
392 ret = ICDwritevactive(iov, 1);
393 ICDiovrelease(&iov[0]);
394 if (ret) {
395 if (innconf->enableoverview && !OVgroupdel(Name)) {
396 free(Name);
397 return false;
398 }
399 }
400 free(Name);
401 return ret;
402 }
403
404 /* Write everything up to this group. */
405 ICDiovset(&iov[0], ICDactpointer, ngp->Start);
406
407 /* If this is the last group, that's all we have to write. */
408 if (ngp == &Groups[nGroups - 1]) {
409 ret = ICDwritevactive(iov, 1);
410 ICDiovrelease(&iov[0]);
411 if (ret) {
412 if (innconf->enableoverview && !OVgroupdel(Name)) {
413 free(Name);
414 return false;
415 }
416 }
417 free(Name);
418 return ret;
419 }
420
421 /* Write everything after this group. */
422 i = ngp[1].Start;
423 ICDiovset(&iov[1], &ICDactpointer[i], ICDactsize - i);
424 ret = ICDwritevactive(iov, 2);
425 ICDiovrelease(&iov[0]);
426 ICDiovrelease(&iov[1]);
427 if (ret) {
428 if (innconf->enableoverview && !OVgroupdel(Name)) {
429 free(Name);
430 return false;
431 }
432 }
433 free(Name);
434 return ret;
435 }
436
437
438
439 /*
440 ** Open the active file and "map" it into memory.
441 */
442 char *
ICDreadactive(char ** endp)443 ICDreadactive(char **endp)
444 {
445 struct stat Sb;
446
447 if (ICDactpointer) {
448 *endp = ICDactpointer + ICDactsize;
449 return ICDactpointer;
450 }
451 if (ICDactpath == NULL)
452 ICDactpath = concatpath(innconf->pathdb, INN_PATH_ACTIVE);
453 if ((ICDactfd = open(ICDactpath, O_RDWR)) < 0) {
454 syslog(L_FATAL, "%s cant open %s %m", LogName, ICDactpath);
455 exit(1);
456 }
457 fdflag_close_exec(ICDactfd, true);
458
459 #ifdef HAVE_MMAP
460
461 if (fstat(ICDactfd, &Sb) < 0) {
462 syslog(L_FATAL, "%s cant fstat %d %s %m",
463 LogName, ICDactfd, ICDactpath);
464 exit(1);
465 }
466 ICDactsize = Sb.st_size;
467 ICDactpointer = mmap(NULL, ICDactsize, PROT_READ|PROT_WRITE,
468 MAP_SHARED, ICDactfd, 0);
469 if (ICDactpointer == (char *)-1) {
470 syslog(L_FATAL, "%s cant mmap %d %s %m",
471 LogName, ICDactfd, ICDactpath);
472 exit(1);
473 }
474
475 #else /* !HAVE_MMAP */
476
477 if ((ICDactpointer = ReadInDescriptor(ICDactfd, &Sb)) == NULL) {
478 syslog(L_FATAL, "%s cant read %s %m", LogName, ICDactpath);
479 exit(1);
480 }
481 ICDactsize = Sb.st_size;
482
483 #endif /* HAVE_MMAP */
484
485 *endp = ICDactpointer + ICDactsize;
486 return ICDactpointer;
487 }
488
489
490 /*
491 ** Write the active file out.
492 */
493 void
ICDwriteactive(void)494 ICDwriteactive(void)
495 {
496 #ifdef HAVE_MMAP
497 if (inn_msync_page(ICDactpointer, ICDactsize, MS_ASYNC) < 0) {
498 syslog(L_FATAL, "%s msync failed %s 0x%lx %d %m", LogName, ICDactpath, (unsigned long) ICDactpointer, ICDactsize);
499 exit(1);
500 }
501 #else /* !HAVE_MMAP */
502 if (lseek(ICDactfd, 0, SEEK_SET) == -1) {
503 syslog(L_FATAL, "%s cant rewind %s %m", LogName, ICDactpath);
504 exit(1);
505 }
506 if (xwrite(ICDactfd, ICDactpointer, ICDactsize) < 0) {
507 syslog(L_FATAL, "%s cant write %s %m", LogName, ICDactpath);
508 exit(1);
509 }
510 #endif /* HAVE_MMAP */
511 }
512