xref: /titanic_44/usr/src/cmd/boot/bootadm/bootadm_upgrade.c (revision b9238976491622ad75a67ab0c12edf99e36212b9)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 #include <stdio.h>
29 #include <errno.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <unistd.h>
33 #include <sys/types.h>
34 #include <sys/stat.h>
35 #include <limits.h>
36 #include <fcntl.h>
37 #include <strings.h>
38 
39 #include <sys/mman.h>
40 #include <sys/elf.h>
41 #include <sys/multiboot.h>
42 
43 #include "message.h"
44 #include "bootadm.h"
45 
46 direct_or_multi_t bam_direct = BAM_DIRECT_NOT_SET;
47 hv_t bam_is_hv = BAM_HV_UNKNOWN;
48 
49 error_t
50 dboot_or_multiboot(const char *root)
51 {
52 	char fname[PATH_MAX];
53 	char *image;
54 	uchar_t *ident;
55 	int fd, m;
56 	multiboot_header_t *mbh;
57 	struct stat sb;
58 
59 	(void) snprintf(fname, PATH_MAX, "%s/%s", root,
60 	    "platform/i86pc/kernel/unix");
61 	fd = open(fname, O_RDONLY);
62 	if (fd < 0) {
63 		bam_error(OPEN_FAIL, fname, strerror(errno));
64 		return (BAM_ERROR);
65 	}
66 
67 	/*
68 	 * mmap the first 8K
69 	 */
70 	image = mmap(NULL, 8192, PROT_READ, MAP_SHARED, fd, 0);
71 	if (image == MAP_FAILED) {
72 		bam_error(MMAP_FAIL, fname, strerror(errno));
73 		return (BAM_ERROR);
74 	}
75 
76 	ident = (uchar_t *)image;
77 	if (ident[EI_MAG0] != ELFMAG0 || ident[EI_MAG1] != ELFMAG1 ||
78 	    ident[EI_MAG2] != ELFMAG2 || ident[EI_MAG3] != ELFMAG3) {
79 		bam_error(NOT_ELF_FILE, fname);
80 		return (BAM_ERROR);
81 	}
82 	if (ident[EI_CLASS] != ELFCLASS32) {
83 		bam_error(WRONG_ELF_CLASS, fname, ident[EI_CLASS]);
84 		return (BAM_ERROR);
85 	}
86 
87 	/*
88 	 * The GRUB multiboot header must be 32-bit aligned and completely
89 	 * contained in the 1st 8K of the file.  If the unix binary has
90 	 * a multiboot header, then it is a 'dboot' kernel.  Otherwise,
91 	 * this kernel must be booted via multiboot -- we call this a
92 	 * 'multiboot' kernel.
93 	 */
94 	bam_direct = BAM_DIRECT_MULTIBOOT;
95 	for (m = 0; m < 8192 - sizeof (multiboot_header_t); m += 4) {
96 		mbh = (void *)(image + m);
97 		if (mbh->magic == MB_HEADER_MAGIC) {
98 			bam_direct = BAM_DIRECT_DBOOT;
99 			break;
100 		}
101 	}
102 	(void) munmap(image, 8192);
103 	(void) close(fd);
104 
105 	if (bam_direct == BAM_DIRECT_DBOOT) {
106 		(void) snprintf(fname, PATH_MAX, "%s/%s", root, XEN_32);
107 		if (stat(fname, &sb) == 0) {
108 			bam_is_hv = BAM_HV_PRESENT;
109 		} else {
110 			bam_is_hv = BAM_HV_NO;
111 		}
112 	}
113 
114 	return (BAM_SUCCESS);
115 }
116 
117 #define	INST_RELEASE	"var/sadm/system/admin/INST_RELEASE"
118 
119 /*
120  * Return true if root has been bfu'ed.  bfu will blow away
121  * var/sadm/system/admin/INST_RELEASE, so if it's still there, we can
122  * assume the system has not been bfu'ed.
123  */
124 static int
125 is_bfu_system(const char *root)
126 {
127 	static int is_bfu = -1;
128 	char path[PATH_MAX];
129 	struct stat sb;
130 
131 	if (is_bfu != -1)
132 		return (is_bfu);
133 
134 	(void) snprintf(path, sizeof (path), "%s/%s", root, INST_RELEASE);
135 	if (stat(path, &sb) != 0) {
136 		is_bfu = 1;
137 	} else {
138 		is_bfu = 0;
139 	}
140 	return (is_bfu);
141 }
142 
143 #define	MENU_URL(root)	(is_bfu_system(root) ?		\
144 	"http://www.sun.com/msg/SUNOS-8000-CF" :	\
145 	"http://www.sun.com/msg/SUNOS-8000-AK")
146 
147 /*
148  * Simply allocate a new line and copy in cmd + sep + arg
149  */
150 void
151 update_line(line_t *linep)
152 {
153 	size_t size;
154 
155 	free(linep->line);
156 	size = strlen(linep->cmd) + strlen(linep->sep) + strlen(linep->arg) + 1;
157 	linep->line = s_calloc(1, size);
158 	(void) snprintf(linep->line, size, "%s%s%s", linep->cmd, linep->sep,
159 	    linep->arg);
160 }
161 
162 /*
163  * The parse_kernel_line function examines a menu.lst kernel line.  For
164  * multiboot, this is:
165  *
166  * kernel <multiboot path> <flags1> <kernel path> <flags2>
167  *
168  * <multiboot path> is either /platform/i86pc/multiboot or /boot/multiboot
169  *
170  * <kernel path> may be missing, or may be any full or relative path to unix.
171  *	We check for it by looking for a word ending in "/unix".  If it ends
172  *	in "kernel/unix", we upgrade it to a 32-bit entry.  If it ends in
173  *	"kernel/amd64/unix", we upgrade it to the default entry.  Otherwise,
174  *	it's a custom kernel, and we skip it.
175  *
176  * <flags*> are anything that doesn't fit either of the above - these will be
177  *	copied over.
178  *
179  * For direct boot, the defaults are
180  *
181  * kernel$ <kernel path> <flags>
182  *
183  * <kernel path> is one of:
184  *	/platform/i86pc/kernel/$ISADIR/unix
185  *	/platform/i86pc/kernel/unix
186  *	/platform/i86pc/kernel/amd64/unix
187  *	/boot/platform/i86pc/kernel/unix
188  *
189  * If <kernel path> is any of the last three, the command may also be "kernel".
190  *
191  * <flags> is anything that isn't <kernel path>.
192  *
193  * This function is only called if it applies to our target boot environment.
194  * If we can't make any sense of the kernel line, an error is printed and
195  * BAM_ERROR is returned.
196  *
197  * The desired install type is given in the global variable bam_direct.
198  * If the kernel line is of a different install type, we change it to the
199  * preferred type.  If the kernel line is already of the correct install
200  * type, we do nothing.  Either way, BAM_SUCCESS is returned.
201  *
202  * For safety, we do one more check: if the kernel path starts with /boot,
203  * we verify that the new kernel exists before changing it.  This is mainly
204  * done for bfu, as it may cause the failsafe archives to be a different
205  * boot architecture from the newly bfu'ed system.
206  */
207 static error_t
208 parse_kernel_line(line_t *linep, const char *root, uint8_t *flags)
209 {
210 	char path[PATH_MAX];
211 	int len, left, total_len;
212 	struct stat sb;
213 	char *new_ptr, *new_arg, *old_ptr;
214 	menu_cmd_t which;
215 
216 	/* Used when changing a multiboot line to dboot */
217 	char *unix_ptr, *flags1_ptr, *flags2_ptr;
218 
219 	/*
220 	 * Note that BAM_ENTRY_DBOOT refers to the entry we're looking at, not
221 	 * necessarily the system type.
222 	 */
223 	if (strncmp(linep->arg, DIRECT_BOOT_32,
224 	    sizeof (DIRECT_BOOT_32) - 1) == 0) {
225 		*flags |= BAM_ENTRY_DBOOT | BAM_ENTRY_32BIT;
226 	} else if ((strncmp(linep->arg, DIRECT_BOOT_KERNEL,
227 	    sizeof (DIRECT_BOOT_KERNEL) - 1) == 0) ||
228 	    (strncmp(linep->arg, DIRECT_BOOT_64,
229 	    sizeof (DIRECT_BOOT_64) - 1) == 0) ||
230 	    (strncmp(linep->arg, DIRECT_BOOT_FAILSAFE_KERNEL,
231 	    sizeof (DIRECT_BOOT_FAILSAFE_KERNEL) - 1) == 0)) {
232 		*flags |= BAM_ENTRY_DBOOT;
233 	} else if ((strncmp(linep->arg, MULTI_BOOT,
234 	    sizeof (MULTI_BOOT) - 1) == 0) ||
235 	    (strncmp(linep->arg, MULTI_BOOT_FAILSAFE,
236 	    sizeof (MULTI_BOOT_FAILSAFE) - 1) == 0)) {
237 		*flags &= ~BAM_ENTRY_DBOOT;
238 	} else {
239 		bam_error(NO_KERNEL_MATCH, linep->lineNum, MENU_URL(root));
240 		return (BAM_ERROR);
241 	}
242 
243 	if (((*flags & BAM_ENTRY_DBOOT) && (bam_direct == BAM_DIRECT_DBOOT)) ||
244 	    (((*flags & BAM_ENTRY_DBOOT) == 0) &&
245 	    (bam_direct == BAM_DIRECT_MULTIBOOT))) {
246 
247 		/* No action needed */
248 		return (BAM_SUCCESS);
249 	}
250 
251 	if (*flags & BAM_ENTRY_MINIROOT) {
252 		/*
253 		 * We're changing boot architectures - make sure
254 		 * the multiboot failsafe still exists.
255 		 */
256 		(void) snprintf(path, PATH_MAX, "%s%s", root,
257 		    (*flags & BAM_ENTRY_DBOOT) ? MULTI_BOOT_FAILSAFE :
258 		    DIRECT_BOOT_FAILSAFE_KERNEL);
259 		if (stat(path, &sb) != 0) {
260 			if (bam_verbose) {
261 				bam_error(FAILSAFE_MISSING, linep->lineNum);
262 			}
263 			return (BAM_SUCCESS);
264 		}
265 	}
266 
267 	/*
268 	 * Make sure we have the correct cmd - either kernel or kernel$
269 	 * The failsafe entry should always be KERNEL_CMD.
270 	 */
271 	which = ((bam_direct == BAM_DIRECT_MULTIBOOT) ||
272 	    (*flags & BAM_ENTRY_MINIROOT)) ? KERNEL_CMD : KERNEL_DOLLAR_CMD;
273 	free(linep->cmd);
274 	len = strlen(menu_cmds[which]) + 1;
275 	linep->cmd = s_calloc(1, len);
276 	(void) strncpy(linep->cmd, menu_cmds[which], len);
277 
278 	/*
279 	 * Since all arguments are copied, the new arg string should be close
280 	 * in size to the old one.  Just add 32 to cover the difference in
281 	 * the boot path.
282 	 */
283 	total_len = strlen(linep->arg) + 32;
284 	new_arg = s_calloc(1, total_len);
285 	old_ptr = strchr(linep->arg, ' ');
286 	if (old_ptr != NULL)
287 		old_ptr++;
288 
289 	/*
290 	 * Transitioning from dboot to multiboot is pretty simple.  We
291 	 * copy in multiboot and any args.
292 	 */
293 	if (bam_direct == BAM_DIRECT_MULTIBOOT) {
294 		if (old_ptr == NULL) {
295 			(void) snprintf(new_arg, total_len, "%s",
296 			    (*flags & BAM_ENTRY_MINIROOT) ?
297 			    MULTI_BOOT_FAILSAFE : MULTI_BOOT);
298 		} else {
299 			(void) snprintf(new_arg, total_len, "%s %s",
300 			    (*flags & BAM_ENTRY_MINIROOT) ?
301 			    MULTI_BOOT_FAILSAFE : MULTI_BOOT, old_ptr);
302 		}
303 		goto done;
304 	}
305 
306 	/*
307 	 * Transitioning from multiboot to directboot is a bit more
308 	 * complicated, since we may have two sets of arguments to
309 	 * copy and a unix path to parse.
310 	 *
311 	 * First, figure out if there's a unix path.
312 	 */
313 	if ((old_ptr != NULL) &&
314 	    ((unix_ptr = strstr(old_ptr, "/unix")) != NULL)) {
315 		/* See if there's anything past unix */
316 		flags2_ptr = unix_ptr + sizeof ("/unix");
317 		if (*flags2_ptr == '\0') {
318 			flags2_ptr = NULL;
319 		}
320 
321 		while ((unix_ptr > old_ptr) && (*unix_ptr != ' '))
322 			unix_ptr--;
323 
324 		if (unix_ptr == old_ptr) {
325 			flags1_ptr = NULL;
326 		} else {
327 			flags1_ptr = old_ptr;
328 		}
329 
330 		if (strstr(unix_ptr, "kernel/unix") != NULL) {
331 			*flags |= BAM_ENTRY_32BIT;
332 		} else if ((strstr(unix_ptr, "kernel/amd64/unix") == NULL) &&
333 		    (!bam_force)) {
334 			/*
335 			 * If the above strstr returns NULL, but bam_force is
336 			 * set, we'll be upgrading an Install kernel.  The
337 			 * result probably won't be what was intended, but we'll
338 			 * try it anyways.
339 			 */
340 			return (BAM_SKIP);
341 		}
342 	} else if (old_ptr != NULL) {
343 		flags1_ptr = old_ptr;
344 		unix_ptr = flags1_ptr + strlen(old_ptr);
345 		flags2_ptr = NULL;
346 	} else {
347 		unix_ptr = flags1_ptr = flags2_ptr = NULL;
348 	}
349 
350 	if (*flags & BAM_ENTRY_MINIROOT) {
351 		(void) snprintf(new_arg, total_len, "%s",
352 		    DIRECT_BOOT_FAILSAFE_KERNEL);
353 	} else if (*flags & BAM_ENTRY_32BIT) {
354 		(void) snprintf(new_arg, total_len, "%s", DIRECT_BOOT_32);
355 	} else {
356 		(void) snprintf(new_arg, total_len, "%s", DIRECT_BOOT_KERNEL);
357 	}
358 
359 	/*
360 	 * We now want to copy flags1_ptr through unix_ptr, and
361 	 * flags2_ptr through the end of the string
362 	 */
363 	if (flags1_ptr != NULL) {
364 		len = strlcat(new_arg, " ", total_len);
365 		left = total_len - len;
366 		new_ptr = new_arg + len;
367 
368 		if ((unix_ptr - flags1_ptr) < left)
369 			left = (unix_ptr - flags1_ptr) + 1;
370 		(void) strlcpy(new_ptr, flags1_ptr, left);
371 	}
372 	if (flags2_ptr != NULL) {
373 		(void) strlcat(new_arg, " ", total_len);
374 		(void) strlcat(new_arg, flags2_ptr, total_len);
375 	}
376 
377 done:
378 	free(linep->arg);
379 	linep->arg = new_arg;
380 	update_line(linep);
381 	return (BAM_SUCCESS);
382 }
383 
384 /*
385  * Similar to above, except this time we're looking at a module line,
386  * which is quite a bit simpler.
387  *
388  * Under multiboot, the archive line is:
389  *
390  * module /platform/i86pc/boot_archive
391  *
392  * Under directboot, the archive line is:
393  *
394  * module$ /platform/i86pc/$ISADIR/boot_archive
395  *
396  * which may be specified exactly as either of:
397  *
398  * module /platform/i86pc/boot_archive
399  * module /platform/i86pc/amd64/boot_archive
400  *
401  * For either dboot or multiboot, the failsafe is:
402  *
403  * module /boot/x86.miniroot-safe
404  */
405 static error_t
406 parse_module_line(line_t *linep, const char *root, uint8_t flags)
407 {
408 	int len;
409 	menu_cmd_t which;
410 	char *new;
411 
412 	/*
413 	 * If necessary, BAM_ENTRY_MINIROOT was already set in flags
414 	 * in upgrade_menu().  We re-check BAM_ENTRY_DBOOT here in here
415 	 * in case the kernel and module lines differ.
416 	 */
417 	if ((strcmp(linep->arg, DIRECT_BOOT_ARCHIVE) == 0) ||
418 	    (strcmp(linep->arg, DIRECT_BOOT_ARCHIVE_64) == 0)) {
419 		flags |= BAM_ENTRY_DBOOT;
420 	} else if ((strcmp(linep->arg, MULTI_BOOT_ARCHIVE) == 0) ||
421 	    (strcmp(linep->arg, MINIROOT) == 0)) {
422 		flags &= ~BAM_ENTRY_DBOOT;
423 	} else {
424 		bam_error(NO_MODULE_MATCH, linep->lineNum, MENU_URL(root));
425 		return (BAM_ERROR);
426 	}
427 
428 	if (((flags & BAM_ENTRY_DBOOT) && (bam_direct == BAM_DIRECT_DBOOT)) ||
429 	    (((flags & BAM_ENTRY_DBOOT) == 0) &&
430 	    (bam_direct == BAM_DIRECT_MULTIBOOT)) ||
431 	    ((flags & BAM_ENTRY_MINIROOT) &&
432 	    (strcmp(linep->cmd, menu_cmds[MODULE_CMD]) == 0))) {
433 
434 		/* No action needed */
435 		return (BAM_SUCCESS);
436 	}
437 
438 	/*
439 	 * Make sure we have the correct cmd - either module or module$
440 	 * The failsafe entry should always be MODULE_CMD.
441 	 */
442 	which = ((bam_direct == BAM_DIRECT_MULTIBOOT) ||
443 	    (flags & BAM_ENTRY_MINIROOT)) ? MODULE_CMD : MODULE_DOLLAR_CMD;
444 	free(linep->cmd);
445 	len = strlen(menu_cmds[which]) + 1;
446 	linep->cmd = s_calloc(1, len);
447 	(void) strncpy(linep->cmd, menu_cmds[which], len);
448 
449 	if (flags & BAM_ENTRY_MINIROOT) {
450 		new = MINIROOT;
451 	} else if ((bam_direct == BAM_DIRECT_DBOOT) &&
452 	    ((flags & BAM_ENTRY_32BIT) == 0)) {
453 		new = DIRECT_BOOT_ARCHIVE;
454 	} else {
455 		new = MULTI_BOOT_ARCHIVE;
456 	}
457 
458 	free(linep->arg);
459 	len = strlen(new) + 1;
460 	linep->arg = s_calloc(1, len);
461 	(void) strncpy(linep->arg, new, len);
462 	update_line(linep);
463 
464 	return (BAM_SUCCESS);
465 }
466 
467 /*ARGSUSED*/
468 error_t
469 upgrade_menu(menu_t *mp, char *root, char *opt)
470 {
471 	entry_t	*cur_entry;
472 	line_t	*cur_line;
473 	int	i, skipit, num_entries, found_hv;
474 	int	*hand_entries = NULL;
475 	boolean_t found_kernel = B_FALSE;
476 	error_t	rv;
477 	char	*rootdev, *grubdisk = NULL;
478 
479 	skipit = num_entries = found_hv = 0;
480 
481 	rootdev = get_special(root);
482 	if (rootdev) {
483 		grubdisk = os_to_grubdisk(rootdev, strlen(root) == 1);
484 		free(rootdev);
485 		rootdev = NULL;
486 	}
487 
488 	/* Loop through all OS entries in the menu.lst file */
489 	for (cur_entry = mp->entries; cur_entry != NULL;
490 	    cur_entry = cur_entry->next, skipit = 0) {
491 
492 		if ((cur_entry->flags & BAM_ENTRY_CHAINLOADER) ||
493 		    ((cur_entry->flags & BAM_ENTRY_MINIROOT) && !bam_force))
494 			continue;
495 
496 		/*
497 		 * We only change entries added by bootadm and live upgrade,
498 		 * and warn on the rest, unless the -f flag was passed.
499 		 */
500 		if ((!(cur_entry->flags & (BAM_ENTRY_BOOTADM|BAM_ENTRY_LU))) &&
501 		    !bam_force) {
502 			if (num_entries == 0) {
503 				hand_entries = s_calloc(1, sizeof (int));
504 			} else {
505 				hand_entries = s_realloc(hand_entries,
506 				    (num_entries + 1) * sizeof (int));
507 			}
508 			hand_entries[num_entries++] = cur_entry->entryNum;
509 			continue;
510 		}
511 
512 		if (cur_entry->flags & BAM_ENTRY_HV) {
513 			found_hv = 1;
514 			continue;
515 		}
516 
517 		/*
518 		 * We make two loops through the lines.  First, we check if
519 		 * there is a root entry, and if so, whether we should be
520 		 * checking this entry.
521 		 */
522 		if ((grubdisk != NULL) && (cur_entry->flags & BAM_ENTRY_ROOT)) {
523 			for (cur_line = cur_entry->start; cur_line != NULL;
524 			    cur_line = cur_line->next) {
525 				if ((cur_line->cmd == NULL) ||
526 				    (cur_line->arg == NULL))
527 					continue;
528 
529 				if (strcmp(cur_line->cmd,
530 				    menu_cmds[ROOT_CMD]) == 0) {
531 					if (strcmp(cur_line->arg,
532 					    grubdisk) != 0) {
533 						/* A different slice */
534 						skipit = 1;
535 					}
536 					break;
537 				}
538 				if (cur_line == cur_entry->end)
539 					break;
540 			}
541 		}
542 		if (skipit)
543 			continue;
544 
545 		for (cur_line = cur_entry->start; cur_line != NULL;
546 		    cur_line = cur_line->next) {
547 
548 			/*
549 			 * We only compare for the length of KERNEL_CMD,
550 			 * so that KERNEL_DOLLAR_CMD will also match.
551 			 */
552 			if (strncmp(cur_line->cmd, menu_cmds[KERNEL_CMD],
553 			    strlen(menu_cmds[KERNEL_CMD])) == 0) {
554 				rv = parse_kernel_line(cur_line, root,
555 				    &(cur_entry->flags));
556 				if (rv == BAM_SKIP) {
557 					break;
558 				} else if (rv != BAM_SUCCESS) {
559 					return (rv);
560 				}
561 				found_kernel = B_TRUE;
562 			} else if (strncmp(cur_line->cmd,
563 			    menu_cmds[MODULE_CMD],
564 			    strlen(menu_cmds[MODULE_CMD])) == 0) {
565 				rv = parse_module_line(cur_line, root,
566 				    cur_entry->flags);
567 				if (rv != BAM_SUCCESS) {
568 					return (rv);
569 				}
570 			}
571 			if (cur_line == cur_entry->end)
572 				break;
573 		}
574 	}
575 
576 	/*
577 	 * If we're upgrading to a virtualized kernel and there are no
578 	 * hv entries in menu.lst, we need to add one.
579 	 */
580 	if ((bam_is_hv == BAM_HV_PRESENT) && (found_hv == 0)) {
581 		(void) add_boot_entry(mp, NEW_HV_ENTRY, grubdisk,
582 		    XEN_MENU, KERNEL_MODULE_LINE, DIRECT_BOOT_ARCHIVE);
583 	}
584 
585 	/*
586 	 * We only want to output one error, to avoid confusing a user.  We
587 	 * rank "No kernels changed" as a higher priority than "will not
588 	 * update hand-added entries", since the former implies the latter.
589 	 */
590 	if (found_kernel == B_FALSE) {
591 		bam_error(NO_KERNELS_FOUND, MENU_URL(root));
592 		return (BAM_ERROR);
593 	} else if (num_entries > 0) {
594 		bam_error(HAND_ADDED_ENTRY, MENU_URL(root));
595 		bam_print_stderr("Entry Number%s: ", (num_entries > 1) ?
596 		    "s" : "");
597 		for (i = 0; i < num_entries; i++) {
598 			bam_print_stderr("%d ", hand_entries[i]);
599 		}
600 		bam_print_stderr("\n");
601 	}
602 	return (BAM_WRITE);
603 }
604