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