xref: /dragonfly/sbin/hammer/cmd_show.c (revision 36a3d1d6)
1 /*
2  * Copyright (c) 2008 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/hammer/cmd_show.c,v 1.18 2008/10/09 04:20:59 dillon Exp $
35  */
36 
37 #include "hammer.h"
38 
39 #define FLAG_TOOFARLEFT		0x0001
40 #define FLAG_TOOFARRIGHT	0x0002
41 #define FLAG_BADTYPE		0x0004
42 #define FLAG_BADCHILDPARENT	0x0008
43 #define FLAG_BADMIRRORTID	0x0010
44 
45 typedef struct btree_search {
46 	u_int32_t	lo;
47 	int64_t		obj_id;
48 } *btree_search_t;
49 
50 static void print_btree_node(hammer_off_t node_offset, btree_search_t search,
51 			int depth, int spike, hammer_tid_t mirror_tid,
52 			hammer_base_elm_t left_bound,
53 			hammer_base_elm_t right_bound);
54 static const char *check_data_crc(hammer_btree_elm_t elm);
55 static void print_record(hammer_btree_elm_t elm);
56 static void print_btree_elm(hammer_btree_elm_t elm, int i, u_int8_t type,
57 			int flags, const char *label, const char *ext);
58 static int print_elm_flags(hammer_node_ondisk_t node, hammer_off_t node_offset,
59 			hammer_btree_elm_t elm, u_int8_t btype,
60 			hammer_base_elm_t left_bound,
61 			hammer_base_elm_t right_bound);
62 static void print_bigblock_fill(hammer_off_t offset);
63 
64 void
65 hammer_cmd_show(hammer_off_t node_offset, u_int32_t lo, int64_t obj_id,
66 		int depth,
67 		hammer_base_elm_t left_bound, hammer_base_elm_t right_bound)
68 {
69 	struct volume_info *volume;
70 	struct btree_search search;
71 	btree_search_t searchp;
72 	int zone;
73 
74 	AssertOnFailure = 0;
75 
76 	if (node_offset == (hammer_off_t)-1) {
77 		volume = get_volume(RootVolNo);
78 		node_offset = volume->ondisk->vol0_btree_root;
79 		if (QuietOpt < 3) {
80 			printf("Volume header\trecords=%jd next_tid=%016jx\n",
81 			       (intmax_t)volume->ondisk->vol0_stat_records,
82 			       (uintmax_t)volume->ondisk->vol0_next_tid);
83 			printf("\t\tbufoffset=%016jx\n",
84 			       (uintmax_t)volume->ondisk->vol_buf_beg);
85 			for (zone = 0; zone < HAMMER_MAX_ZONES; ++zone) {
86 				printf("\t\tzone %d\tnext_offset=%016jx\n",
87 					zone,
88 					(uintmax_t)volume->ondisk->vol0_blockmap[zone].next_offset
89 				);
90 			}
91 		}
92 		rel_volume(volume);
93 	}
94 
95 	if (lo == 0 && obj_id == (int64_t)HAMMER_MIN_OBJID) {
96 		searchp = NULL;
97 		printf("show %016jx depth %d\n", (uintmax_t)node_offset, depth);
98 	} else {
99 		search.lo = lo;
100 		search.obj_id = obj_id;
101 		searchp = &search;
102 		printf("show %016jx lo %08x obj_id %016jx depth %d\n",
103 			(uintmax_t)node_offset, lo, (uintmax_t)obj_id, depth);
104 	}
105 	print_btree_node(node_offset, searchp, depth, 0, HAMMER_MAX_TID,
106 			 left_bound, right_bound);
107 	print_btree_node(node_offset, searchp, depth, 1, HAMMER_MAX_TID,
108 			 left_bound, right_bound);
109 
110 	AssertOnFailure = 1;
111 }
112 
113 static void
114 print_btree_node(hammer_off_t node_offset, btree_search_t search,
115 		int depth, int spike, hammer_tid_t mirror_tid,
116 		hammer_base_elm_t left_bound, hammer_base_elm_t right_bound)
117 {
118 	struct buffer_info *buffer = NULL;
119 	hammer_node_ondisk_t node;
120 	hammer_btree_elm_t elm;
121 	int i;
122 	int flags;
123 	int maxcount;
124 	char badc;
125 	char badm;
126 	const char *ext;
127 
128 	node = get_node(node_offset, &buffer);
129 
130 	if (node == NULL) {
131 		printf("BI   NODE %016jx (IO ERROR)\n",
132 		       (uintmax_t)node_offset);
133 		return;
134 	}
135 
136 	if (crc32(&node->crc + 1, HAMMER_BTREE_CRCSIZE) == node->crc)
137 		badc = ' ';
138 	else
139 		badc = 'B';
140 
141 	if (node->mirror_tid <= mirror_tid) {
142 		badm = ' ';
143 	} else {
144 		badm = 'M';
145 		badc = 'B';
146 	}
147 
148 	if (spike == 0) {
149 		printf("%c%c   NODE %016jx cnt=%02d p=%016jx "
150 		       "type=%c depth=%d",
151 		       badc,
152 		       badm,
153 		       (uintmax_t)node_offset, node->count,
154 		       (uintmax_t)node->parent,
155 		       (node->type ? node->type : '?'), depth);
156 		printf(" mirror %016jx", (uintmax_t)node->mirror_tid);
157 		if (QuietOpt < 3) {
158 			printf(" fill=");
159 			print_bigblock_fill(node_offset);
160 		}
161 		printf(" {\n");
162 
163 		maxcount = (node->type == HAMMER_BTREE_TYPE_INTERNAL) ?
164 			   HAMMER_BTREE_INT_ELMS : HAMMER_BTREE_LEAF_ELMS;
165 
166 		for (i = 0; i < node->count && i < maxcount; ++i) {
167 			elm = &node->elms[i];
168 
169 			if (node->type != HAMMER_BTREE_TYPE_INTERNAL) {
170 				ext = NULL;
171 				if (search &&
172 				    elm->base.localization == search->lo &&
173 				     elm->base.obj_id == search->obj_id) {
174 					ext = " *";
175 				}
176 			} else if (search) {
177 				ext = " *";
178 				if (elm->base.localization > search->lo ||
179 				    (elm->base.localization == search->lo &&
180 				     elm->base.obj_id > search->obj_id)) {
181 					ext = NULL;
182 				}
183 				if (elm[1].base.localization < search->lo ||
184 				    (elm[1].base.localization == search->lo &&
185 				     elm[1].base.obj_id < search->obj_id)) {
186 					ext = NULL;
187 				}
188 			} else {
189 				ext = NULL;
190 			}
191 
192 			flags = print_elm_flags(node, node_offset,
193 						elm, elm->base.btype,
194 						left_bound, right_bound);
195 			print_btree_elm(elm, i, node->type, flags, "ELM", ext);
196 		}
197 		if (node->type == HAMMER_BTREE_TYPE_INTERNAL) {
198 			elm = &node->elms[i];
199 
200 			flags = print_elm_flags(node, node_offset,
201 						elm, 'I',
202 						left_bound, right_bound);
203 			print_btree_elm(elm, i, node->type, flags, "RBN", NULL);
204 		}
205 		printf("    }\n");
206 	}
207 
208 	for (i = 0; i < node->count; ++i) {
209 		elm = &node->elms[i];
210 
211 		switch(node->type) {
212 		case HAMMER_BTREE_TYPE_INTERNAL:
213 			if (search) {
214 				if (elm->base.localization > search->lo ||
215 				    (elm->base.localization == search->lo &&
216 				     elm->base.obj_id > search->obj_id)) {
217 					break;
218 				}
219 				if (elm[1].base.localization < search->lo ||
220 				    (elm[1].base.localization == search->lo &&
221 				     elm[1].base.obj_id < search->obj_id)) {
222 					break;
223 				}
224 			}
225 			if (elm->internal.subtree_offset) {
226 				print_btree_node(elm->internal.subtree_offset,
227 						 search, depth + 1, spike,
228 						 elm->internal.mirror_tid,
229 						 &elm[0].base, &elm[1].base);
230 				/*
231 				 * Cause show to iterate after seeking to
232 				 * the lo:objid
233 				 */
234 				search = NULL;
235 			}
236 			break;
237 		default:
238 			break;
239 		}
240 	}
241 	rel_buffer(buffer);
242 }
243 
244 static
245 void
246 print_btree_elm(hammer_btree_elm_t elm, int i, u_int8_t type,
247 		int flags, const char *label, const char *ext)
248 {
249 	char flagstr[8] = { 0, '-', '-', '-', '-', '-', '-', 0 };
250 
251 	flagstr[0] = flags ? 'B' : 'G';
252 	if (flags & FLAG_TOOFARLEFT)
253 		flagstr[2] = 'L';
254 	if (flags & FLAG_TOOFARRIGHT)
255 		flagstr[3] = 'R';
256 	if (flags & FLAG_BADTYPE)
257 		flagstr[4] = 'T';
258 	if (flags & FLAG_BADCHILDPARENT)
259 		flagstr[5] = 'C';
260 	if (flags & FLAG_BADMIRRORTID)
261 		flagstr[6] = 'M';
262 
263 	printf("%s\t%s %2d %c ",
264 	       flagstr, label, i,
265 	       (elm->base.btype ? elm->base.btype : '?'));
266 	printf("obj=%016jx key=%016jx lo=%08x rt=%02x ot=%02x\n",
267 	       (uintmax_t)elm->base.obj_id,
268 	       (uintmax_t)elm->base.key,
269 	       elm->base.localization,
270 	       elm->base.rec_type,
271 	       elm->base.obj_type);
272 	printf("\t       %c tids %016jx:%016jx ",
273 		(elm->base.delete_tid ? 'd' : ' '),
274 	       (uintmax_t)elm->base.create_tid,
275 	       (uintmax_t)elm->base.delete_tid);
276 
277 	switch(type) {
278 	case HAMMER_BTREE_TYPE_INTERNAL:
279 		printf("suboff=%016jx",
280 		       (uintmax_t)elm->internal.subtree_offset);
281 		if (QuietOpt < 3) {
282 			printf(" mirror %016jx",
283 			       (uintmax_t)elm->internal.mirror_tid);
284 		}
285 		if (ext)
286 			printf(" %s", ext);
287 		break;
288 	case HAMMER_BTREE_TYPE_LEAF:
289 		if (ext)
290 			printf(" %s", ext);
291 		switch(elm->base.btype) {
292 		case HAMMER_BTREE_TYPE_RECORD:
293 			if (QuietOpt < 3)
294 				printf("\n%s\t         ", check_data_crc(elm));
295 			else
296 				printf("\n\t         ");
297 			printf("dataoff=%016jx/%d",
298 			       (uintmax_t)elm->leaf.data_offset,
299 			       elm->leaf.data_len);
300 			if (QuietOpt < 3) {
301 				printf(" crc=%04x", elm->leaf.data_crc);
302 				printf("\n\t         fills=");
303 				print_bigblock_fill(elm->leaf.data_offset);
304 			}
305 			if (QuietOpt < 2)
306 				print_record(elm);
307 			break;
308 		}
309 		break;
310 	default:
311 		break;
312 	}
313 	printf("\n");
314 }
315 
316 static
317 int
318 print_elm_flags(hammer_node_ondisk_t node, hammer_off_t node_offset,
319 		hammer_btree_elm_t elm, u_int8_t btype,
320 		hammer_base_elm_t left_bound, hammer_base_elm_t right_bound)
321 {
322 	int flags = 0;
323 
324 	switch(node->type) {
325 	case HAMMER_BTREE_TYPE_INTERNAL:
326 		if (elm->internal.subtree_offset) {
327 			struct buffer_info *buffer = NULL;
328 			hammer_node_ondisk_t subnode;
329 
330 			subnode = get_node(elm->internal.subtree_offset,
331 					   &buffer);
332 			if (subnode == NULL)
333 				flags |= FLAG_BADCHILDPARENT;
334 			else if (subnode->parent != node_offset)
335 				flags |= FLAG_BADCHILDPARENT;
336 			rel_buffer(buffer);
337 		}
338 		if (elm->internal.mirror_tid > node->mirror_tid)
339 			flags |= FLAG_BADMIRRORTID;
340 
341 		switch(btype) {
342 		case HAMMER_BTREE_TYPE_INTERNAL:
343 			if (left_bound == NULL || right_bound == NULL)
344 				break;
345 			if (hammer_btree_cmp(&elm->base, left_bound) < 0)
346 				flags |= FLAG_TOOFARLEFT;
347 			if (hammer_btree_cmp(&elm->base, right_bound) > 0)
348 				flags |= FLAG_TOOFARRIGHT;
349 			break;
350 		case HAMMER_BTREE_TYPE_LEAF:
351 			if (left_bound == NULL || right_bound == NULL)
352 				break;
353 			if (hammer_btree_cmp(&elm->base, left_bound) < 0)
354 				flags |= FLAG_TOOFARLEFT;
355 			if (hammer_btree_cmp(&elm->base, right_bound) >= 0)
356 				flags |= FLAG_TOOFARRIGHT;
357 			break;
358 		default:
359 			flags |= FLAG_BADTYPE;
360 			break;
361 		}
362 		break;
363 	case HAMMER_BTREE_TYPE_LEAF:
364 		if (elm->base.create_tid &&
365 		    elm->base.create_tid > node->mirror_tid) {
366 			flags |= FLAG_BADMIRRORTID;
367 		}
368 		if (elm->base.delete_tid &&
369 		    elm->base.delete_tid > node->mirror_tid) {
370 			flags |= FLAG_BADMIRRORTID;
371 		}
372 		switch(btype) {
373 		case HAMMER_BTREE_TYPE_RECORD:
374 			if (left_bound == NULL || right_bound == NULL)
375 				break;
376 			if (hammer_btree_cmp(&elm->base, left_bound) < 0)
377 				flags |= FLAG_TOOFARLEFT;
378 			if (hammer_btree_cmp(&elm->base, right_bound) >= 0)
379 				flags |= FLAG_TOOFARRIGHT;
380 			break;
381 		default:
382 			flags |= FLAG_BADTYPE;
383 			break;
384 		}
385 		break;
386 	default:
387 		flags |= FLAG_BADTYPE;
388 		break;
389 	}
390 	return(flags);
391 }
392 
393 static
394 void
395 print_bigblock_fill(hammer_off_t offset)
396 {
397 	struct hammer_blockmap_layer1 layer1;
398 	struct hammer_blockmap_layer2 layer2;
399 	int fill;
400 	int error;
401 
402 	blockmap_lookup(offset, &layer1, &layer2, &error);
403 	if (error) {
404 		printf("z%d:%lld=BADZ",
405 			HAMMER_ZONE_DECODE(offset),
406 			(offset & ~HAMMER_OFF_ZONE_MASK) /
407 			    HAMMER_LARGEBLOCK_SIZE
408 		);
409 	} else {
410 		fill = layer2.bytes_free * 100 / HAMMER_LARGEBLOCK_SIZE;
411 		fill = 100 - fill;
412 
413 		printf("z%d:%lld=%d%%",
414 			HAMMER_ZONE_DECODE(offset),
415 			(offset & ~HAMMER_OFF_ZONE_MASK) /
416 			    HAMMER_LARGEBLOCK_SIZE,
417 			fill
418 		);
419 	}
420 }
421 
422 /*
423  * Check the generic crc on a data element.  Inodes record types are
424  * special in that some of their fields are not CRCed.
425  *
426  * Also check that the zone is valid.
427  */
428 static
429 const char *
430 check_data_crc(hammer_btree_elm_t elm)
431 {
432 	struct buffer_info *data_buffer;
433 	hammer_off_t data_offset;
434 	int32_t data_len;
435 	int32_t len;
436 	u_int32_t crc;
437 	int error;
438 	char *ptr;
439 
440 	data_offset = elm->leaf.data_offset;
441 	data_len = elm->leaf.data_len;
442 	data_buffer = NULL;
443 	if (data_offset == 0 || data_len == 0)
444 		return("Z");
445 
446 	crc = 0;
447 	error = 0;
448 	while (data_len) {
449 		blockmap_lookup(data_offset, NULL, NULL, &error);
450 		if (error)
451 			break;
452 
453 		ptr = get_buffer_data(data_offset, &data_buffer, 0);
454 		len = HAMMER_BUFSIZE - ((int)data_offset & HAMMER_BUFMASK);
455 		if (len > data_len)
456 			len = (int)data_len;
457 		if (elm->leaf.base.rec_type == HAMMER_RECTYPE_INODE &&
458 		    data_len == sizeof(struct hammer_inode_data)) {
459 			crc = crc32_ext(ptr, HAMMER_INODE_CRCSIZE, crc);
460 		} else {
461 			crc = crc32_ext(ptr, len, crc);
462 		}
463 		data_len -= len;
464 		data_offset += len;
465 	}
466 	if (data_buffer)
467 		rel_buffer(data_buffer);
468 	if (error)
469 		return("BO");		/* bad offset */
470 	if (crc == elm->leaf.data_crc)
471 		return("");
472 	return("BX");			/* bad crc */
473 }
474 
475 static
476 void
477 print_record(hammer_btree_elm_t elm)
478 {
479 	struct buffer_info *data_buffer;
480 	hammer_off_t data_offset;
481 	int32_t data_len;
482 	hammer_data_ondisk_t data;
483 
484 	data_offset = elm->leaf.data_offset;
485 	data_len = elm->leaf.data_len;
486 	data_buffer = NULL;
487 
488 	if (data_offset)
489 		data = get_buffer_data(data_offset, &data_buffer, 0);
490 	else
491 		data = NULL;
492 
493 	switch(elm->leaf.base.rec_type) {
494 	case HAMMER_RECTYPE_INODE:
495 		printf("\n%17s", "");
496 		printf("size=%jd nlinks=%jd",
497 		       (intmax_t)data->inode.size,
498 		       (intmax_t)data->inode.nlinks);
499 		if (QuietOpt < 1) {
500 			printf(" mode=%05o uflags=%08x\n",
501 				data->inode.mode,
502 				data->inode.uflags);
503 			printf("%17s", "");
504 			printf("ctime=%016jx pobjid=%016jx obj_type=%d\n",
505 				(uintmax_t)data->inode.ctime,
506 				(uintmax_t)data->inode.parent_obj_id,
507 				data->inode.obj_type);
508 			printf("%17s", "");
509 			printf("mtime=%016jx", (uintmax_t)data->inode.mtime);
510 			printf(" caps=%02x", data->inode.cap_flags);
511 		}
512 		break;
513 	case HAMMER_RECTYPE_DIRENTRY:
514 		printf("\n%17s", "");
515 		data_len -= HAMMER_ENTRY_NAME_OFF;
516 		printf("dir-entry ino=%016jx lo=%08x name=\"%*.*s\"",
517 		       (uintmax_t)data->entry.obj_id,
518 		       data->entry.localization,
519 		       data_len, data_len, data->entry.name);
520 		break;
521 	case HAMMER_RECTYPE_FIX:
522 		switch(elm->leaf.base.key) {
523 		case HAMMER_FIXKEY_SYMLINK:
524 			data_len -= HAMMER_SYMLINK_NAME_OFF;
525 			printf("\n%17s", "");
526 			printf("symlink=\"%*.*s\"", data_len, data_len,
527 				data->symlink.name);
528 			break;
529 		default:
530 			break;
531 		}
532 		break;
533 	default:
534 		break;
535 	}
536 	if (data_buffer)
537 		rel_buffer(data_buffer);
538 }
539 
540 /*
541  * Dump the UNDO FIFO
542  */
543 void
544 hammer_cmd_show_undo(void)
545 {
546 	struct volume_info *volume;
547 	hammer_blockmap_t rootmap;
548 	hammer_off_t scan_offset;
549 	hammer_fifo_any_t head;
550 	struct buffer_info *data_buffer = NULL;
551 
552 	volume = get_volume(RootVolNo);
553 	rootmap = &volume->ondisk->vol0_blockmap[HAMMER_ZONE_UNDO_INDEX];
554 	printf("Volume header UNDO %016jx-%016jx/%016jx\n",
555 		(intmax_t)rootmap->first_offset,
556 		(intmax_t)rootmap->next_offset,
557 		(intmax_t)rootmap->alloc_offset);
558 	printf("Undo map is %jdMB\n",
559 		(intmax_t)((rootmap->alloc_offset & HAMMER_OFF_LONG_MASK) /
560 			   (1024 * 1024)));
561 	scan_offset = HAMMER_ZONE_ENCODE(HAMMER_ZONE_UNDO_INDEX, 0);
562 	while (scan_offset < rootmap->alloc_offset) {
563 		head = get_buffer_data(scan_offset, &data_buffer, 0);
564 		printf("%016jx ", scan_offset);
565 
566 		switch(head->head.hdr_type) {
567 		case HAMMER_HEAD_TYPE_PAD:
568 			printf("PAD(%04x)\n", head->head.hdr_size);
569 			break;
570 		case HAMMER_HEAD_TYPE_DUMMY:
571 			printf("DUMMY(%04x) seq=%08x\n",
572 				head->head.hdr_size, head->head.hdr_seq);
573 			break;
574 		case HAMMER_HEAD_TYPE_UNDO:
575 			printf("UNDO(%04x) seq=%08x "
576 			       "dataoff=%016jx bytes=%d\n",
577 				head->head.hdr_size, head->head.hdr_seq,
578 				(intmax_t)head->undo.undo_offset,
579 				head->undo.undo_data_bytes);
580 			break;
581 		case HAMMER_HEAD_TYPE_REDO:
582 			printf("REDO(%04x) seq=%08x flags=%08x "
583 			       "objid=%016jx logoff=%016jx bytes=%d\n",
584 				head->head.hdr_size, head->head.hdr_seq,
585 				head->redo.redo_flags,
586 				(intmax_t)head->redo.redo_objid,
587 				(intmax_t)head->redo.redo_offset,
588 				head->redo.redo_data_bytes);
589 			break;
590 		default:
591 			printf("UNKNOWN(%04x,%04x) seq=%08x\n",
592 				head->head.hdr_type,
593 				head->head.hdr_size,
594 				head->head.hdr_seq);
595 			break;
596 		}
597 		if ((head->head.hdr_size & HAMMER_HEAD_ALIGN_MASK) ||
598 		    head->head.hdr_size == 0 ||
599 		    head->head.hdr_size > HAMMER_UNDO_ALIGN -
600 				    ((u_int)scan_offset & HAMMER_UNDO_MASK)) {
601 			printf("Illegal size field, skipping to "
602 			       "next boundary\n");
603 			scan_offset = (scan_offset + HAMMER_UNDO_MASK) &
604 					~HAMMER_UNDO_MASK64;
605 		} else {
606 			scan_offset += head->head.hdr_size;
607 		}
608 	}
609 	if (data_buffer)
610 		rel_buffer(data_buffer);
611 }
612