First cut of the jscan utility. This will become the core utility for
[dragonfly.git] / sbin / jscan / jstream.c
1 /*
2  * Copyright (c) 2004,2005 The DragonFly Project.  All rights reserved.
3  * 
4  * This code is derived from software contributed to The DragonFly Project
5  * by Matthew Dillon <dillon@backplane.com>
6  * 
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in
15  *    the documentation and/or other materials provided with the
16  *    distribution.
17  * 3. Neither the name of The DragonFly Project nor the names of its
18  *    contributors may be used to endorse or promote products derived
19  *    from this software without specific, prior written permission.
20  * 
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
25  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26  * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
27  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
31  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  * 
34  * $DragonFly: src/sbin/jscan/jstream.c,v 1.1 2005/03/07 02:38:28 dillon Exp $
35  */
36
37 #include "jscan.h"
38
39 static struct jhash     *JHashAry[JHASH_SIZE];
40
41 static struct jstream *jaddrecord(struct jfile *jf, struct jstream *js);
42
43 /*
44  * Locate the next (or previous) complete virtual stream transaction given a
45  * file descriptor and direction.  Keep track of partial stream records as
46  * a side effect.
47  *
48  * Note that a transaction might represent a huge I/O operation, resulting
49  * in an overall node structure that spans gigabytes, but individual
50  * subrecord leaf nodes are limited in size and we depend on this to simplify
51  * the handling of leaf records. 
52  *
53  * A transaction may cover several raw records.  The jstream collection for
54  * a transaction is only returned when the entire transaction has been
55  * successfully scanned.  Due to the interleaving of transactions the ordering
56  * of returned JS's may be different (not exactly reversed) when scanning a
57  * journal backwards verses forwards.  Since parallel operations are 
58  * theoretically non-conflicting, this should not present a problem.
59  */
60 struct jstream *
61 jscan_stream(struct jfile *jf)
62 {
63     struct journal_rawrecbeg head;
64     struct journal_rawrecend tail;
65     int recsize;
66     int search;
67     int error;
68     struct jstream *js;
69
70     /*
71      * Get the current offset and make sure it is 16-byte aligned.  If it
72      * isn't, align it and enter search mode.
73      */
74     if (jf->jf_pos & 15) {
75         jf_warn(jf, "realigning bad offset and entering search mode");
76         jalign(jf);
77         search = 1;
78     } else {
79         search = 0;
80     }
81
82     error = 0;
83     js = NULL;
84
85     if (jf->jf_direction == JF_FORWARDS) {
86         /*
87          * Scan the journal forwards.  Note that the file pointer might not
88          * be seekable.
89          */
90         while ((error = jread(jf, &head, sizeof(head))) == 0) {
91             if (head.begmagic != JREC_BEGMAGIC) {
92                 if (search == 0)
93                     jf_warn(jf, "bad beginmagic, searching for new record");
94                 search = 1;
95                 jalign(jf);
96                 continue;
97             }
98             recsize = (head.recsize + 15) & ~15;
99             if (recsize <= 0) {
100                 jf_warn(jf, "bad recordsize: %d\n", recsize);
101                 search = 1;
102                 jalign(jf);
103                 continue;
104             }
105             jset(jf);
106             js = malloc(offsetof(struct jstream, js_data[recsize]));
107             bzero(js, sizeof(struct jstream));
108             bcopy(&head, js->js_data, sizeof(head));
109             error = jread(jf, js->js_data + sizeof(head), recsize - sizeof(head));
110             if (error) {
111                 jf_warn(jf, "Incomplete stream record\n");
112                 jreturn(jf);
113                 free(js);
114                 js = NULL;
115                 break;
116             }
117             js->js_size = recsize;
118             bcopy(js->js_data + recsize - sizeof(tail), &tail, sizeof(tail));
119             if (tail.endmagic != JREC_ENDMAGIC) {
120                 jf_warn(jf, "bad endmagic, searching for new record");
121                 search = 1;
122                 jreturn(jf);
123                 free(js);
124                 js = NULL;
125                 continue;
126             }
127             jflush(jf);
128             if ((js = jaddrecord(jf, js)) != NULL)
129                 break;
130         }
131     } else {
132         /*
133          * Scan the journal backwards.  Note that jread()'s reverse-seek and
134          * read.  The data read will be forward ordered, however.
135          */
136         while ((error = jread(jf, &tail, sizeof(tail))) == 0) {
137             if (tail.endmagic != JREC_ENDMAGIC) {
138                 if (search == 0)
139                     jf_warn(jf, "bad endmagic, searching for new record");
140                 search = 1;
141                 jalign(jf);
142                 continue;
143             }
144             recsize = (tail.recsize + 15) & ~15;
145             if (recsize <= 0) {
146                 jf_warn(jf, "bad recordsize: %d\n", recsize);
147                 search = 1;
148                 jalign(jf);
149                 continue;
150             }
151             jset(jf);
152             js = malloc(offsetof(struct jstream, js_data[recsize]));
153             bzero(js, sizeof(struct jstream));
154             bcopy(&tail, js->js_data + recsize - sizeof(tail), sizeof(tail));
155             error = jread(jf, js->js_data, recsize - sizeof(tail));
156
157             if (error) {
158                 jf_warn(jf, "Incomplete stream record\n");
159                 jreturn(jf);
160                 free(js);
161                 js = NULL;
162                 break;
163             }
164             js->js_size = recsize;
165             bcopy(js->js_data + recsize - sizeof(tail), &tail, sizeof(tail));
166             bcopy(js->js_data, &head, sizeof(head));
167             if (head.begmagic != JREC_BEGMAGIC) {
168                 jf_warn(jf, "bad begmagic, searching for new record");
169                 search = 1;
170                 jreturn(jf);
171                 free(js);
172                 continue;
173             }
174             jflush(jf);
175             if ((js = jaddrecord(jf, js)) != NULL)
176                 break;
177         }
178     }
179     jf->jf_error = error;
180     return(js);
181 }
182
183 /*
184  * Integrate a jstream record.  Deal with the transaction begin and end flags
185  * to create a forward-referenced collection of jstream records.  If we are
186  * able to complete a transaction, the first js associated with that
187  * transaction is returned.
188  *
189  * XXX we need to store the data for very large multi-record transactions
190  * separately since it might not fit into memory.
191  */
192 static struct jstream *
193 jaddrecord(struct jfile *jf, struct jstream *js)
194 {
195     struct journal_rawrecbeg *head = (void *)js->js_data;
196     struct jhash *jh;
197     struct jhash **jhp;
198     struct jstream *jscan;
199     off_t off;
200
201     /*
202      * Check for a completely self-contained transaction, just return the
203      * js if possible.
204      */
205     if ((head->streamid & (JREC_STREAMCTL_BEGIN|JREC_STREAMCTL_END)) ==
206         (JREC_STREAMCTL_BEGIN|JREC_STREAMCTL_END)
207     ) {
208         return (js);
209     }
210
211     /*
212      * Check for an open transaction in the hash table, create a new one
213      * if necessary.
214      */
215     jhp = &JHashAry[head->streamid & JHASH_MASK];
216     while ((jh = *jhp) != NULL) {
217         if (((jh->jh_transid ^ head->streamid) & JREC_STREAMID_MASK) == 0)
218             break;
219         jhp = &jh->jh_hash;
220     }
221     if (jh == NULL) {
222         jh = malloc(sizeof(*jh));
223         bzero(jh, sizeof(*jh));
224         *jhp = jh;
225         jh->jh_first = js;
226         jh->jh_last = js;
227         jh->jh_transid = head->streamid;
228         return (NULL);
229     }
230
231     /*
232      * Emplace the stream segment
233      */
234     jh->jh_transid |= head->streamid & JREC_STREAMCTL_MASK;
235     if (jf->jf_direction == JF_FORWARDS) {
236         jh->jh_last->js_next = js;
237         jh->jh_last = js;
238     } else {
239         js->js_next = jh->jh_first;
240         jh->jh_first = js;
241     }
242
243     /*
244      * If the transaction is complete, remove the hash entry and return the
245      * js representing the beginning of the transaction.
246      */
247     if ((jh->jh_transid & (JREC_STREAMCTL_BEGIN|JREC_STREAMCTL_END)) ==
248         (JREC_STREAMCTL_BEGIN|JREC_STREAMCTL_END)
249     ) {
250         *jhp = jh->jh_hash;
251         js = jh->jh_first;
252         free(jh);
253         off = 0;
254         for (jscan = js; jscan; jscan = jscan->js_next) {
255             jscan->js_off = off;
256             off += jscan->js_size;
257         }
258     } else {
259         js = NULL;
260     }
261     return (js);
262 }
263
264 void
265 jscan_dispose(struct jstream *js)
266 {
267     struct jstream *jnext;
268
269     if (js->js_alloc_buf) {
270         free(js->js_alloc_buf);
271         js->js_alloc_buf = NULL;
272         js->js_alloc_size = 0;
273     }
274
275     while (js) {
276         jnext = js->js_next;
277         free(js);
278         js = jnext;
279     }
280 }
281
282 /*
283  * Read the specified block of data out of a linked set of jstream
284  * structures.  Returns 0 on success or an error code on error.
285  */
286 int
287 jsread(struct jstream *js, off_t off, void *buf, int bytes)
288 {
289     const void *ptr;
290     int n;
291
292     while (bytes) {
293         n = jsreadany(js, off, &ptr);
294         if (n == 0)
295             return (ENOENT);
296         if (n > bytes)
297             n = bytes;
298         bcopy(ptr, buf, n);
299         buf = (char *)buf + n;
300         off += n;
301         bytes -= n;
302     }
303     return(0);
304 }
305
306 /*
307  * Read the specified block of data out of a linked set of jstream
308  * structures.  Attempt to return a pointer into the data set but
309  * allocate and copy if that is not possible.  Returns 0 on success
310  * or an error code on error.
311  */
312 int
313 jsreadp(struct jstream *js, off_t off, const void **bufp,
314         int bytes)
315 {
316     char *ptr;
317     int error;
318     int n;
319     int o;
320
321     n = jsreadany(js, off, bufp);
322     if (n < bytes) {
323         if (js->js_alloc_size < bytes) {
324             if (js->js_alloc_buf)
325                 free(js->js_alloc_buf);
326             js->js_alloc_buf = malloc(bytes);
327             js->js_alloc_size = bytes;
328         }
329         error = jsread(js, off, js->js_alloc_buf, bytes);
330         if (error) {
331             *bufp = NULL;
332         } else {
333             *bufp = js->js_alloc_buf;
334         }
335     }
336     return(error);
337 }
338
339 /*
340  * Return the largest contiguous buffer starting at the specified offset,
341  * or 0.
342  */
343 int
344 jsreadany(struct jstream *js, off_t off, const void **bufp)
345 {
346     struct jstream *scan;
347     int n;
348
349     if ((scan = js->js_cache) == NULL || scan->js_cache_off > off)
350         scan = js;
351     while (scan && scan->js_off <= off) {
352         js->js_cache = scan;
353         js->js_cache_off = scan->js_off;
354         if (scan->js_off + scan->js_size > off) {
355             n = (int)(off - scan->js_off);
356             *bufp = scan->js_data + n;
357             return(scan->js_size - n);
358         }
359         scan = scan->js_next;
360     }
361     return(0);
362 }
363