1/*	$NetBSD: cleanup_region.c,v 1.3 2020/03/18 19:05:15 christos Exp $	*/
2
3/*++
4/* NAME
5/*	cleanup_region 3
6/* SUMMARY
7/*	queue file region manager
8/* SYNOPSIS
9/*	#include "cleanup.h"
10/*
11/*	void	cleanup_region_init(state)
12/*	CLEANUP_STATE *state;
13/*
14/*	CLEANUP_REGION *cleanup_region_open(state, space_needed)
15/*	CLEANUP_STATE *state;
16/*	ssize_t	space_needed;
17/*
18/*	int	cleanup_region_close(state, rp)
19/*	CLEANUP_STATE *state;
20/*	CLEANUP_REGION *rp;
21/*
22/*	CLEANUP_REGION *cleanup_region_return(state, rp)
23/*	CLEANUP_STATE *state;
24/*	CLEANUP_REGION *rp;
25/*
26/*	void	cleanup_region_done(state)
27/*	CLEANUP_STATE *state;
28/* DESCRIPTION
29/*	This module maintains queue file regions. Regions are created
30/*	on-the-fly and can be reused multiple times. Each region
31/*	structure consists of a file offset, a length (0 for an
32/*	open-ended region at the end of the file), a write offset
33/*	(maintained by the caller), and list linkage. Region
34/*	boundaries are not enforced by this module. It is up to the
35/*	caller to ensure that they stay within bounds.
36/*
37/*	cleanup_region_init() performs mandatory initialization and
38/*	overlays an initial region structure over an already existing
39/*	queue file. This function must not be called before the
40/*	queue file is complete.
41/*
42/*	cleanup_region_open() opens an existing region or creates
43/*	a new region that can accommodate at least the specified
44/*	amount of space. A new region is an open-ended region at
45/*	the end of the file; it must be closed (see next) before
46/*	unrelated data can be appended to the same file.
47/*
48/*	cleanup_region_close() indicates that a region will not be
49/*	updated further. With an open-ended region, the region's
50/*	end is frozen just before the caller-maintained write offset.
51/*	With a close-ended region, unused space (beginning at the
52/*	caller-maintained write offset) may be returned to the free
53/*	pool.
54/*
55/*	cleanup_region_return() returns a list of regions to the
56/*	free pool, and returns a null pointer. To avoid fragmentation,
57/*	adjacent free regions may be coalesced together.
58/*
59/*	cleanup_region_done() destroys all in-memory information
60/*	that was allocated for administering queue file regions.
61/*
62/*	Arguments:
63/* .IP state
64/*	Queue file and message processing state. This state is
65/*	updated as records are processed and as errors happen.
66/* .IP space_needed
67/*	The minimum region size needed.
68/* LICENSE
69/* .ad
70/* .fi
71/*	The Secure Mailer license must be distributed with this software.
72/* AUTHOR(S)
73/*	Wietse Venema
74/*	IBM T.J. Watson Research
75/*	P.O. Box 704
76/*	Yorktown Heights, NY 10598, USA
77/*--*/
78
79/* System library. */
80
81#include <sys_defs.h>
82#include <sys/stat.h>
83
84/* Utility library. */
85
86#include <msg.h>
87#include <mymalloc.h>
88#include <warn_stat.h>
89
90/* Application-specific. */
91
92#include <cleanup.h>
93
94/* cleanup_region_alloc - create queue file region */
95
96static CLEANUP_REGION *cleanup_region_alloc(off_t start, off_t len)
97{
98    CLEANUP_REGION *rp;
99
100    rp = (CLEANUP_REGION *) mymalloc(sizeof(*rp));
101    rp->write_offs = rp->start = start;
102    rp->len = len;
103    rp->next = 0;
104
105    return (rp);
106}
107
108/* cleanup_region_free - destroy region list */
109
110static CLEANUP_REGION *cleanup_region_free(CLEANUP_REGION *regions)
111{
112    CLEANUP_REGION *rp;
113    CLEANUP_REGION *next;
114
115    for (rp = regions; rp != 0; rp = next) {
116	next = rp->next;
117	myfree((void *) rp);
118    }
119    return (0);
120}
121
122/* cleanup_region_init - create initial region overlay */
123
124void    cleanup_region_init(CLEANUP_STATE *state)
125{
126    const char *myname = "cleanup_region_init";
127
128    /*
129     * Sanity check.
130     */
131    if (state->free_regions != 0 || state->body_regions != 0)
132	msg_panic("%s: repeated call", myname);
133
134    /*
135     * Craft the first regions on the fly, from circumstantial evidence.
136     */
137    state->body_regions =
138	cleanup_region_alloc(state->append_hdr_pt_target,
139			  state->xtra_offset - state->append_hdr_pt_target);
140    if (msg_verbose)
141	msg_info("%s: body start %ld len %ld",
142		 myname, (long) state->body_regions->start, (long) state->body_regions->len);
143}
144
145/* cleanup_region_open - open existing region or create new region */
146
147CLEANUP_REGION *cleanup_region_open(CLEANUP_STATE *state, ssize_t len)
148{
149    const char *myname = "cleanup_region_open";
150    CLEANUP_REGION **rpp;
151    CLEANUP_REGION *rp;
152    struct stat st;
153
154    /*
155     * Find the first region that is large enough, or create a new region.
156     */
157    for (rpp = &state->free_regions; /* see below */ ; rpp = &(rp->next)) {
158
159	/*
160	 * Create an open-ended region at the end of the queue file. We
161	 * freeze the region size after we stop writing to it. XXX Assume
162	 * that fstat() returns a file size that is never less than the file
163	 * append offset. It is not a problem if fstat() returns a larger
164	 * result; we would just waste some space.
165	 */
166	if ((rp = *rpp) == 0) {
167	    if (fstat(vstream_fileno(state->dst), &st) < 0)
168		msg_fatal("%s: fstat file %s: %m", myname, cleanup_path);
169	    rp = cleanup_region_alloc(st.st_size, 0);
170	    break;
171	}
172
173	/*
174	 * Reuse an existing region.
175	 */
176	if (rp->len >= len) {
177	    (*rpp) = rp->next;
178	    rp->next = 0;
179	    rp->write_offs = rp->start;
180	    break;
181	}
182
183	/*
184	 * Skip a too small region.
185	 */
186	if (msg_verbose)
187	    msg_info("%s: skip start %ld len %ld < %ld",
188		     myname, (long) rp->start, (long) rp->len, (long) len);
189    }
190    if (msg_verbose)
191	msg_info("%s: done start %ld len %ld",
192		 myname, (long) rp->start, (long) rp->len);
193    return (rp);
194}
195
196/* cleanup_region_close - freeze queue file region size */
197
198void    cleanup_region_close(CLEANUP_STATE *unused_state, CLEANUP_REGION *rp)
199{
200    const char *myname = "cleanup_region_close";
201
202    /*
203     * If this region is still open ended, freeze the size. If this region is
204     * closed, some future version of this routine may shrink the size and
205     * return the unused portion to the free pool.
206     */
207    if (rp->len == 0)
208	rp->len = rp->write_offs - rp->start;
209    if (msg_verbose)
210	msg_info("%s: freeze start %ld len %ld",
211		 myname, (long) rp->start, (long) rp->len);
212}
213
214/* cleanup_region_return - return region list to free pool */
215
216CLEANUP_REGION *cleanup_region_return(CLEANUP_STATE *state, CLEANUP_REGION *rp)
217{
218    CLEANUP_REGION **rpp;
219
220    for (rpp = &state->free_regions; (*rpp) != 0; rpp = &(*rpp)->next)
221	 /* void */ ;
222    *rpp = rp;
223    return (0);
224}
225
226/* cleanup_region_done - destroy region metadata */
227
228void    cleanup_region_done(CLEANUP_STATE *state)
229{
230    if (state->free_regions != 0)
231	state->free_regions = cleanup_region_free(state->free_regions);
232    if (state->body_regions != 0)
233	state->body_regions = cleanup_region_free(state->body_regions);
234}
235