geom_part.c revision 222630
1/*-
2 * Copyright (c) 2007, 2008 Marcel Moolenaar
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 */
26
27#include <sys/cdefs.h>
28__FBSDID("$FreeBSD: head/sbin/geom/class/part/geom_part.c 222630 2011-06-02 21:59:21Z ae $");
29
30#include <sys/stat.h>
31#include <sys/vtoc.h>
32
33#include <assert.h>
34#include <ctype.h>
35#include <err.h>
36#include <errno.h>
37#include <fcntl.h>
38#include <libgeom.h>
39#include <libutil.h>
40#include <paths.h>
41#include <signal.h>
42#include <stdint.h>
43#include <stdio.h>
44#include <stdlib.h>
45#include <limits.h>
46#include <inttypes.h>
47#include <string.h>
48#include <strings.h>
49#include <unistd.h>
50
51#include "core/geom.h"
52#include "misc/subr.h"
53
54#ifdef STATIC_GEOM_CLASSES
55#define	PUBSYM(x)	gpart_##x
56#else
57#define	PUBSYM(x)	x
58#endif
59
60uint32_t PUBSYM(lib_version) = G_LIB_VERSION;
61uint32_t PUBSYM(version) = 0;
62
63static char sstart[32];
64static char ssize[32];
65volatile sig_atomic_t undo_restore;
66
67#define	GPART_AUTOFILL	"*"
68#define	GPART_FLAGS	"C"
69
70#define	GPART_PARAM_BOOTCODE	"bootcode"
71#define	GPART_PARAM_INDEX	"index"
72#define	GPART_PARAM_PARTCODE	"partcode"
73
74static struct gclass *find_class(struct gmesh *, const char *);
75static struct ggeom * find_geom(struct gclass *, const char *);
76static const char *find_geomcfg(struct ggeom *, const char *);
77static const char *find_provcfg(struct gprovider *, const char *);
78static struct gprovider *find_provider(struct ggeom *, off_t);
79static const char *fmtsize(int64_t);
80static int gpart_autofill(struct gctl_req *);
81static int gpart_autofill_resize(struct gctl_req *);
82static void gpart_bootcode(struct gctl_req *, unsigned int);
83static void *gpart_bootfile_read(const char *, ssize_t *);
84static void gpart_issue(struct gctl_req *, unsigned int);
85static void gpart_show(struct gctl_req *, unsigned int);
86static void gpart_show_geom(struct ggeom *, const char *, int);
87static int gpart_show_hasopt(struct gctl_req *, const char *, const char *);
88static void gpart_write_partcode(struct ggeom *, int, void *, ssize_t);
89static void gpart_write_partcode_vtoc8(struct ggeom *, int, void *);
90static void gpart_print_error(const char *);
91static void gpart_backup(struct gctl_req *, unsigned int);
92static void gpart_restore(struct gctl_req *, unsigned int);
93
94struct g_command PUBSYM(class_commands)[] = {
95	{ "add", 0, gpart_issue, {
96		{ 'a', "alignment", GPART_AUTOFILL, G_TYPE_STRING },
97		{ 'b', "start", GPART_AUTOFILL, G_TYPE_STRING },
98		{ 's', "size", GPART_AUTOFILL, G_TYPE_STRING },
99		{ 't', "type", NULL, G_TYPE_STRING },
100		{ 'i', GPART_PARAM_INDEX, G_VAL_OPTIONAL, G_TYPE_NUMBER },
101		{ 'l', "label", G_VAL_OPTIONAL, G_TYPE_STRING },
102		{ 'f', "flags", GPART_FLAGS, G_TYPE_STRING },
103		G_OPT_SENTINEL },
104	    "-t type [-a alignment] [-b start] [-s size] [-i index] "
105		"[-l label] [-f flags] geom"
106	},
107	{ "backup", 0, gpart_backup, G_NULL_OPTS,
108	    "geom"
109	},
110	{ "bootcode", 0, gpart_bootcode, {
111		{ 'b', GPART_PARAM_BOOTCODE, G_VAL_OPTIONAL, G_TYPE_STRING },
112		{ 'p', GPART_PARAM_PARTCODE, G_VAL_OPTIONAL, G_TYPE_STRING },
113		{ 'i', GPART_PARAM_INDEX, G_VAL_OPTIONAL, G_TYPE_NUMBER },
114		{ 'f', "flags", GPART_FLAGS, G_TYPE_STRING },
115		G_OPT_SENTINEL },
116	    "[-b bootcode] [-p partcode -i index] [-f flags] geom"
117	},
118	{ "commit", 0, gpart_issue, G_NULL_OPTS,
119	    "geom"
120	},
121	{ "create", 0, gpart_issue, {
122		{ 's', "scheme", NULL, G_TYPE_STRING },
123		{ 'n', "entries", G_VAL_OPTIONAL, G_TYPE_NUMBER },
124		{ 'f', "flags", GPART_FLAGS, G_TYPE_STRING },
125		G_OPT_SENTINEL },
126	    "-s scheme [-n entries] [-f flags] provider"
127	},
128	{ "delete", 0, gpart_issue, {
129		{ 'i', GPART_PARAM_INDEX, NULL, G_TYPE_NUMBER },
130		{ 'f', "flags", GPART_FLAGS, G_TYPE_STRING },
131		G_OPT_SENTINEL },
132	    "-i index [-f flags] geom"
133	},
134	{ "destroy", 0, gpart_issue, {
135		{ 'F', "force", NULL, G_TYPE_BOOL },
136		{ 'f', "flags", GPART_FLAGS, G_TYPE_STRING },
137		G_OPT_SENTINEL },
138	    "[-F] [-f flags] geom"
139	},
140	{ "modify", 0, gpart_issue, {
141		{ 'i', GPART_PARAM_INDEX, NULL, G_TYPE_NUMBER },
142		{ 'l', "label", G_VAL_OPTIONAL, G_TYPE_STRING },
143		{ 't', "type", G_VAL_OPTIONAL, G_TYPE_STRING },
144		{ 'f', "flags", GPART_FLAGS, G_TYPE_STRING },
145		G_OPT_SENTINEL },
146	    "-i index [-l label] [-t type] [-f flags] geom"
147	},
148	{ "set", 0, gpart_issue, {
149		{ 'a', "attrib", NULL, G_TYPE_STRING },
150		{ 'i', GPART_PARAM_INDEX, NULL, G_TYPE_NUMBER },
151		{ 'f', "flags", GPART_FLAGS, G_TYPE_STRING },
152		G_OPT_SENTINEL },
153	    "-a attrib -i index [-f flags] geom"
154	},
155	{ "show", 0, gpart_show, {
156		{ 'l', "show_label", NULL, G_TYPE_BOOL },
157		{ 'r', "show_rawtype", NULL, G_TYPE_BOOL },
158		{ 'p', "show_providers", NULL, G_TYPE_BOOL },
159		G_OPT_SENTINEL },
160	    "[-l | -r] [-p] [geom ...]"
161	},
162	{ "undo", 0, gpart_issue, G_NULL_OPTS,
163	    "geom"
164	},
165	{ "unset", 0, gpart_issue, {
166		{ 'a', "attrib", NULL, G_TYPE_STRING },
167		{ 'i', GPART_PARAM_INDEX, NULL, G_TYPE_NUMBER },
168		{ 'f', "flags", GPART_FLAGS, G_TYPE_STRING },
169		G_OPT_SENTINEL },
170	    "-a attrib -i index [-f flags] geom"
171	},
172	{ "resize", 0, gpart_issue, {
173		{ 'a', "alignment", GPART_AUTOFILL, G_TYPE_STRING },
174		{ 's', "size", GPART_AUTOFILL, G_TYPE_STRING },
175		{ 'i', GPART_PARAM_INDEX, NULL, G_TYPE_NUMBER },
176		{ 'f', "flags", GPART_FLAGS, G_TYPE_STRING },
177		G_OPT_SENTINEL },
178	    "-i index [-a alignment] [-s size] [-f flags] geom"
179	},
180	{ "restore", 0, gpart_restore, {
181		{ 'F', "force", NULL, G_TYPE_BOOL },
182		{ 'l', "restore_labels", NULL, G_TYPE_BOOL },
183		{ 'f', "flags", GPART_FLAGS, G_TYPE_STRING },
184		G_OPT_SENTINEL },
185	    "[-lF] [-f flags] provider [...]"
186	},
187	{ "recover", 0, gpart_issue, {
188		{ 'f', "flags", GPART_FLAGS, G_TYPE_STRING },
189		G_OPT_SENTINEL },
190	    "[-f flags] geom"
191	},
192	G_CMD_SENTINEL
193};
194
195static struct gclass *
196find_class(struct gmesh *mesh, const char *name)
197{
198	struct gclass *classp;
199
200	LIST_FOREACH(classp, &mesh->lg_class, lg_class) {
201		if (strcmp(classp->lg_name, name) == 0)
202			return (classp);
203	}
204	return (NULL);
205}
206
207static struct ggeom *
208find_geom(struct gclass *classp, const char *name)
209{
210	struct ggeom *gp;
211
212	if (strncmp(name, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
213		name += sizeof(_PATH_DEV) - 1;
214	LIST_FOREACH(gp, &classp->lg_geom, lg_geom) {
215		if (strcmp(gp->lg_name, name) == 0)
216			return (gp);
217	}
218	return (NULL);
219}
220
221static const char *
222find_geomcfg(struct ggeom *gp, const char *cfg)
223{
224	struct gconfig *gc;
225
226	LIST_FOREACH(gc, &gp->lg_config, lg_config) {
227		if (!strcmp(gc->lg_name, cfg))
228			return (gc->lg_val);
229	}
230	return (NULL);
231}
232
233static const char *
234find_provcfg(struct gprovider *pp, const char *cfg)
235{
236	struct gconfig *gc;
237
238	LIST_FOREACH(gc, &pp->lg_config, lg_config) {
239		if (!strcmp(gc->lg_name, cfg))
240			return (gc->lg_val);
241	}
242	return (NULL);
243}
244
245static struct gprovider *
246find_provider(struct ggeom *gp, off_t minsector)
247{
248	struct gprovider *pp, *bestpp;
249	const char *s;
250	off_t sector, bestsector;
251
252	bestpp = NULL;
253	bestsector = 0;
254	LIST_FOREACH(pp, &gp->lg_provider, lg_provider) {
255		s = find_provcfg(pp, "start");
256		sector = (off_t)strtoimax(s, NULL, 0);
257		if (sector < minsector)
258			continue;
259		if (bestpp != NULL && sector >= bestsector)
260			continue;
261
262		bestpp = pp;
263		bestsector = sector;
264	}
265	return (bestpp);
266}
267
268static const char *
269fmtsize(int64_t rawsz)
270{
271	static char buf[5];
272
273	humanize_number(buf, sizeof(buf), rawsz, "", HN_AUTOSCALE,
274	    HN_B | HN_NOSPACE | HN_DECIMAL);
275	return (buf);
276}
277
278static const char *
279fmtattrib(struct gprovider *pp)
280{
281	static char buf[128];
282	struct gconfig *gc;
283	u_int idx;
284
285	buf[0] = '\0';
286	idx = 0;
287	LIST_FOREACH(gc, &pp->lg_config, lg_config) {
288		if (strcmp(gc->lg_name, "attrib") != 0)
289			continue;
290		idx += snprintf(buf + idx, sizeof(buf) - idx, "%s%s",
291		    (idx == 0) ? " [" : ",", gc->lg_val);
292	}
293	if (idx > 0)
294		snprintf(buf + idx, sizeof(buf) - idx, "] ");
295	return (buf);
296}
297
298#define	ALIGNDOWN(d, a)	((d) - (d) % (a))
299#define	ALIGNUP(d, a)	((d) % (a) ? (d) - (d) % (a) + (a): (d))
300
301static int
302gpart_autofill_resize(struct gctl_req *req)
303{
304	struct gmesh mesh;
305	struct gclass *cp;
306	struct ggeom *gp;
307	struct gprovider *pp;
308	off_t last, size, start, new_size;
309	off_t lba, new_lba, alignment, offset;
310	const char *s;
311	int error, idx;
312
313	idx = (int)gctl_get_intmax(req, GPART_PARAM_INDEX);
314	if (idx < 1)
315		errx(EXIT_FAILURE, "invalid partition index");
316
317	error = geom_gettree(&mesh);
318	if (error)
319		return (error);
320	s = gctl_get_ascii(req, "class");
321	if (s == NULL)
322		abort();
323	cp = find_class(&mesh, s);
324	if (cp == NULL)
325		errx(EXIT_FAILURE, "Class %s not found.", s);
326	s = gctl_get_ascii(req, "arg0");
327	if (s == NULL)
328		abort();
329	gp = find_geom(cp, s);
330	if (gp == NULL)
331		errx(EXIT_FAILURE, "No such geom: %s.", s);
332	pp = LIST_FIRST(&gp->lg_consumer)->lg_provider;
333	if (pp == NULL)
334		errx(EXIT_FAILURE, "Provider for geom %s not found.", s);
335
336	s = gctl_get_ascii(req, "alignment");
337	alignment = 1;
338	if (*s != '*') {
339		error = g_parse_lba(s, pp->lg_sectorsize, &alignment);
340		if (error)
341			errc(EXIT_FAILURE, error, "Invalid alignment param");
342		if (alignment == 0)
343			errx(EXIT_FAILURE, "Invalid alignment param");
344		lba = pp->lg_stripesize / pp->lg_sectorsize;
345		if (lba % alignment)
346			alignment = g_lcm(lba, alignment);
347	}
348	error = gctl_delete_param(req, "alignment");
349	if (error)
350		errc(EXIT_FAILURE, error, "internal error");
351
352	s = gctl_get_ascii(req, "size");
353	if (*s == '*')
354		new_size = 0;
355	else {
356		error = g_parse_lba(s, pp->lg_sectorsize, &new_size);
357		if (error)
358			errc(EXIT_FAILURE, error, "Invalid size param");
359		/* no autofill necessary. */
360		if (alignment == 1)
361			goto done;
362	}
363
364	offset = pp->lg_stripeoffset / pp->lg_sectorsize;
365	last = (off_t)strtoimax(find_geomcfg(gp, "last"), NULL, 0);
366	LIST_FOREACH(pp, &gp->lg_provider, lg_provider) {
367		s = find_provcfg(pp, "index");
368		if (s == NULL)
369			continue;
370		if (atoi(s) == idx)
371			break;
372	}
373	if (pp == NULL)
374		errx(EXIT_FAILURE, "invalid partition index");
375
376	s = find_provcfg(pp, "start");
377	start = (off_t)strtoimax(s, NULL, 0);
378	s = find_provcfg(pp, "end");
379	lba = (off_t)strtoimax(s, NULL, 0);
380	size = lba - start + 1;
381
382	if (new_size > 0 && new_size <= size) {
383		/* The start offset may be not aligned, so we align the end
384		 * offset and then calculate the size.
385		 */
386		new_size = ALIGNDOWN(start + offset + new_size,
387		    alignment) - start - offset;
388		goto done;
389	}
390
391	pp = find_provider(gp, lba + 1);
392	if (pp == NULL) {
393		new_size = ALIGNDOWN(last + offset + 1, alignment) -
394		    start - offset;
395		if (new_size < size)
396			return (ENOSPC);
397	} else {
398		s = find_provcfg(pp, "start");
399		new_lba = (off_t)strtoimax(s, NULL, 0);
400		/*
401		 * Is there any free space between current and
402		 * next providers?
403		 */
404		new_lba = ALIGNDOWN(new_lba + offset, alignment) - offset;
405		if (new_lba > lba)
406			new_size = new_lba - start;
407		else {
408			geom_deletetree(&mesh);
409			return (ENOSPC);
410		}
411	}
412done:
413	snprintf(ssize, sizeof(ssize), "%jd", (intmax_t)new_size);
414	gctl_change_param(req, "size", -1, ssize);
415	geom_deletetree(&mesh);
416	return (0);
417}
418
419static int
420gpart_autofill(struct gctl_req *req)
421{
422	struct gmesh mesh;
423	struct gclass *cp;
424	struct ggeom *gp;
425	struct gprovider *pp;
426	off_t first, last, a_first;
427	off_t size, start, a_lba;
428	off_t lba, len, alignment, offset;
429	uintmax_t grade;
430	const char *s;
431	int error, has_size, has_start, has_alignment;
432
433	s = gctl_get_ascii(req, "verb");
434	if (strcmp(s, "resize") == 0)
435		return gpart_autofill_resize(req);
436	if (strcmp(s, "add") != 0)
437		return (0);
438
439	error = geom_gettree(&mesh);
440	if (error)
441		return (error);
442	s = gctl_get_ascii(req, "class");
443	if (s == NULL)
444		abort();
445	cp = find_class(&mesh, s);
446	if (cp == NULL)
447		errx(EXIT_FAILURE, "Class %s not found.", s);
448	s = gctl_get_ascii(req, "arg0");
449	if (s == NULL)
450		abort();
451	gp = find_geom(cp, s);
452	if (gp == NULL)
453		errx(EXIT_FAILURE, "No such geom: %s.", s);
454	pp = LIST_FIRST(&gp->lg_consumer)->lg_provider;
455	if (pp == NULL)
456		errx(EXIT_FAILURE, "Provider for geom %s not found.", s);
457
458	s = gctl_get_ascii(req, "alignment");
459	has_alignment = (*s == '*') ? 0 : 1;
460	alignment = 1;
461	if (has_alignment) {
462		error = g_parse_lba(s, pp->lg_sectorsize, &alignment);
463		if (error)
464			errc(EXIT_FAILURE, error, "Invalid alignment param");
465		if (alignment == 0)
466			errx(EXIT_FAILURE, "Invalid alignment param");
467	}
468	error = gctl_delete_param(req, "alignment");
469	if (error)
470		errc(EXIT_FAILURE, error, "internal error");
471
472	s = gctl_get_ascii(req, "size");
473	has_size = (*s == '*') ? 0 : 1;
474	size = 0;
475	if (has_size) {
476		error = g_parse_lba(s, pp->lg_sectorsize, &size);
477		if (error)
478			errc(EXIT_FAILURE, error, "Invalid size param");
479	}
480
481	s = gctl_get_ascii(req, "start");
482	has_start = (*s == '*') ? 0 : 1;
483	start = 0ULL;
484	if (has_start) {
485		error = g_parse_lba(s, pp->lg_sectorsize, &start);
486		if (error)
487			errc(EXIT_FAILURE, error, "Invalid start param");
488	}
489
490	/* No autofill necessary. */
491	if (has_size && has_start && !has_alignment)
492		goto done;
493
494	/*
495	 * If stripesize is not zero, then recalculate alignment value.
496	 * Use LCM from stripesize and user specified alignment.
497	 */
498	len = pp->lg_stripesize / pp->lg_sectorsize;
499	if (len % alignment)
500		alignment = g_lcm(len, alignment);
501
502	/* Adjust parameters to stripeoffset */
503	offset = pp->lg_stripeoffset / pp->lg_sectorsize;
504	start = ALIGNUP(start + offset, alignment);
505	if (size + offset > alignment)
506		size = ALIGNDOWN(size + offset, alignment);
507
508	first = (off_t)strtoimax(find_geomcfg(gp, "first"), NULL, 0);
509	last = (off_t)strtoimax(find_geomcfg(gp, "last"), NULL, 0);
510	grade = ~0ULL;
511	a_first = ALIGNUP(first + offset, alignment);
512	last = ALIGNDOWN(last + offset, alignment);
513	while ((pp = find_provider(gp, first)) != NULL) {
514		s = find_provcfg(pp, "start");
515		lba = (off_t)strtoimax(s, NULL, 0);
516		a_lba = ALIGNDOWN(lba + offset, alignment);
517		if (first < a_lba && a_first < a_lba) {
518			/* Free space [first, lba> */
519			len = a_lba - a_first;
520			if (has_size) {
521				if (len >= size &&
522				    (uintmax_t)(len - size) < grade) {
523					start = a_first;
524					grade = len - size;
525				}
526			} else if (has_start) {
527				if (start >= a_first && start < a_lba) {
528					size = a_lba - start;
529					grade = start - a_first;
530				}
531			} else {
532				if (grade == ~0ULL || len > size) {
533					start = a_first;
534					size = len;
535					grade = 0;
536				}
537			}
538		}
539
540		s = find_provcfg(pp, "end");
541		first = (off_t)strtoimax(s, NULL, 0) + 1;
542		a_first = ALIGNUP(first + offset, alignment);
543	}
544	if (a_first <= last) {
545		/* Free space [first-last] */
546		len = ALIGNDOWN(last - a_first + 1, alignment);
547		if (has_size) {
548			if (len >= size &&
549			    (uintmax_t)(len - size) < grade) {
550				start = a_first;
551				grade = len - size;
552			}
553		} else if (has_start) {
554			if (start >= a_first && start <= last) {
555				size = ALIGNDOWN(last - start + 1, alignment);
556				grade = start - a_first;
557			}
558		} else {
559			if (grade == ~0ULL || len > size) {
560				start = a_first;
561				size = len;
562				grade = 0;
563			}
564		}
565	}
566	if (grade == ~0ULL) {
567		geom_deletetree(&mesh);
568		return (ENOSPC);
569	}
570	start -= offset;	/* Return back to real offset */
571done:
572	snprintf(ssize, sizeof(ssize), "%jd", (intmax_t)size);
573	gctl_change_param(req, "size", -1, ssize);
574	snprintf(sstart, sizeof(sstart), "%jd", (intmax_t)start);
575	gctl_change_param(req, "start", -1, sstart);
576	geom_deletetree(&mesh);
577	return (0);
578}
579
580static void
581gpart_show_geom(struct ggeom *gp, const char *element, int show_providers)
582{
583	struct gprovider *pp;
584	const char *s, *scheme;
585	off_t first, last, sector, end;
586	off_t length, secsz;
587	int idx, wblocks, wname, wmax;
588
589	scheme = find_geomcfg(gp, "scheme");
590	s = find_geomcfg(gp, "first");
591	first = (off_t)strtoimax(s, NULL, 0);
592	s = find_geomcfg(gp, "last");
593	last = (off_t)strtoimax(s, NULL, 0);
594	wblocks = strlen(s);
595	s = find_geomcfg(gp, "state");
596	if (s != NULL && *s != 'C')
597		s = NULL;
598	wmax = strlen(gp->lg_name);
599	if (show_providers) {
600		LIST_FOREACH(pp, &gp->lg_provider, lg_provider) {
601			wname = strlen(pp->lg_name);
602			if (wname > wmax)
603				wmax = wname;
604		}
605	}
606	wname = wmax;
607	pp = LIST_FIRST(&gp->lg_consumer)->lg_provider;
608	secsz = pp->lg_sectorsize;
609	printf("=>%*jd  %*jd  %*s  %s  (%s)%s\n",
610	    wblocks, (intmax_t)first, wblocks, (intmax_t)(last - first + 1),
611	    wname, gp->lg_name,
612	    scheme, fmtsize(pp->lg_mediasize),
613	    s ? " [CORRUPT]": "");
614
615	while ((pp = find_provider(gp, first)) != NULL) {
616		s = find_provcfg(pp, "start");
617		sector = (off_t)strtoimax(s, NULL, 0);
618
619		s = find_provcfg(pp, "end");
620		end = (off_t)strtoimax(s, NULL, 0);
621		length = end - sector + 1;
622
623		s = find_provcfg(pp, "index");
624		idx = atoi(s);
625		if (first < sector) {
626			printf("  %*jd  %*jd  %*s  - free -  (%s)\n",
627			    wblocks, (intmax_t)first, wblocks,
628			    (intmax_t)(sector - first), wname, "",
629			    fmtsize((sector - first) * secsz));
630		}
631		if (show_providers) {
632			printf("  %*jd  %*jd  %*s  %s %s (%s)\n",
633			    wblocks, (intmax_t)sector, wblocks,
634			    (intmax_t)length, wname, pp->lg_name,
635			    find_provcfg(pp, element), fmtattrib(pp),
636			    fmtsize(pp->lg_mediasize));
637		} else
638			printf("  %*jd  %*jd  %*d  %s %s (%s)\n",
639			    wblocks, (intmax_t)sector, wblocks,
640			    (intmax_t)length, wname, idx,
641			    find_provcfg(pp, element), fmtattrib(pp),
642			    fmtsize(pp->lg_mediasize));
643		first = end + 1;
644	}
645	if (first <= last) {
646		length = last - first + 1;
647		printf("  %*jd  %*jd  %*s  - free -  (%s)\n",
648		    wblocks, (intmax_t)first, wblocks, (intmax_t)length,
649		    wname, "",
650		    fmtsize(length * secsz));
651	}
652	printf("\n");
653}
654
655static int
656gpart_show_hasopt(struct gctl_req *req, const char *opt, const char *elt)
657{
658
659	if (!gctl_get_int(req, "%s", opt))
660		return (0);
661
662	if (elt != NULL)
663		errx(EXIT_FAILURE, "-l and -r are mutually exclusive");
664
665	return (1);
666}
667
668static void
669gpart_show(struct gctl_req *req, unsigned int fl __unused)
670{
671	struct gmesh mesh;
672	struct gclass *classp;
673	struct ggeom *gp;
674	const char *element, *name;
675	int error, i, nargs, show_providers;
676
677	element = NULL;
678	if (gpart_show_hasopt(req, "show_label", element))
679		element = "label";
680	if (gpart_show_hasopt(req, "show_rawtype", element))
681		element = "rawtype";
682	if (element == NULL)
683		element = "type";
684
685	name = gctl_get_ascii(req, "class");
686	if (name == NULL)
687		abort();
688	error = geom_gettree(&mesh);
689	if (error != 0)
690		errc(EXIT_FAILURE, error, "Cannot get GEOM tree");
691	classp = find_class(&mesh, name);
692	if (classp == NULL) {
693		geom_deletetree(&mesh);
694		errx(EXIT_FAILURE, "Class %s not found.", name);
695	}
696	show_providers = gctl_get_int(req, "show_providers");
697	nargs = gctl_get_int(req, "nargs");
698	if (nargs > 0) {
699		for (i = 0; i < nargs; i++) {
700			name = gctl_get_ascii(req, "arg%d", i);
701			gp = find_geom(classp, name);
702			if (gp != NULL)
703				gpart_show_geom(gp, element, show_providers);
704			else
705				errx(EXIT_FAILURE, "No such geom: %s.", name);
706		}
707	} else {
708		LIST_FOREACH(gp, &classp->lg_geom, lg_geom) {
709			gpart_show_geom(gp, element, show_providers);
710		}
711	}
712	geom_deletetree(&mesh);
713}
714
715static void
716gpart_backup(struct gctl_req *req, unsigned int fl __unused)
717{
718	struct gmesh mesh;
719	struct gclass *classp;
720	struct gprovider *pp;
721	struct ggeom *gp;
722	const char *s, *scheme;
723	off_t sector, end;
724	off_t length, secsz;
725	int error, i, windex, wblocks, wtype;
726
727	if (gctl_get_int(req, "nargs") != 1)
728		errx(EXIT_FAILURE, "Invalid number of arguments.");
729	error = geom_gettree(&mesh);
730	if (error != 0)
731		errc(EXIT_FAILURE, error, "Cannot get GEOM tree");
732	s = gctl_get_ascii(req, "class");
733	if (s == NULL)
734		abort();
735	classp = find_class(&mesh, s);
736	if (classp == NULL) {
737		geom_deletetree(&mesh);
738		errx(EXIT_FAILURE, "Class %s not found.", s);
739	}
740	s = gctl_get_ascii(req, "arg0");
741	if (s == NULL)
742		abort();
743	gp = find_geom(classp, s);
744	if (gp == NULL)
745		errx(EXIT_FAILURE, "No such geom: %s.", s);
746	scheme = find_geomcfg(gp, "scheme");
747	if (scheme == NULL)
748		abort();
749	pp = LIST_FIRST(&gp->lg_consumer)->lg_provider;
750	secsz = pp->lg_sectorsize;
751	s = find_geomcfg(gp, "last");
752	wblocks = strlen(s);
753	wtype = 0;
754	LIST_FOREACH(pp, &gp->lg_provider, lg_provider) {
755		s = find_provcfg(pp, "type");
756		i = strlen(s);
757		if (i > wtype)
758			wtype = i;
759	}
760	s = find_geomcfg(gp, "entries");
761	windex = strlen(s);
762	printf("%s %s\n", scheme, s);
763	LIST_FOREACH(pp, &gp->lg_provider, lg_provider) {
764		s = find_provcfg(pp, "start");
765		sector = (off_t)strtoimax(s, NULL, 0);
766
767		s = find_provcfg(pp, "end");
768		end = (off_t)strtoimax(s, NULL, 0);
769		length = end - sector + 1;
770
771		s = find_provcfg(pp, "label");
772		printf("%-*s %*s %*jd %*jd %s %s\n",
773		    windex, find_provcfg(pp, "index"),
774		    wtype, find_provcfg(pp, "type"),
775		    wblocks, (intmax_t)sector,
776		    wblocks, (intmax_t)length,
777		    (s != NULL) ? s: "", fmtattrib(pp));
778	}
779	geom_deletetree(&mesh);
780}
781
782static int
783skip_line(const char *p)
784{
785
786	while (*p != '\0') {
787		if (*p == '#')
788			return (1);
789		if (isspace(*p) == 0)
790			return (0);
791		p++;
792	}
793	return (1);
794}
795
796static void
797gpart_sighndl(int sig __unused)
798{
799	undo_restore = 1;
800}
801
802static void
803gpart_restore(struct gctl_req *req, unsigned int fl __unused)
804{
805	struct gmesh mesh;
806	struct gclass *classp;
807	struct gctl_req *r;
808	struct ggeom *gp;
809	struct sigaction si_sa;
810	const char *s, *flags, *errstr, *label;
811	char **ap, *argv[6], line[BUFSIZ], *pline;
812	int error, forced, i, l, nargs, created, rl;
813	intmax_t n;
814
815	nargs = gctl_get_int(req, "nargs");
816	if (nargs < 1)
817		errx(EXIT_FAILURE, "Invalid number of arguments.");
818
819	forced = gctl_get_int(req, "force");
820	flags = gctl_get_ascii(req, "flags");
821	rl = gctl_get_int(req, "restore_labels");
822	s = gctl_get_ascii(req, "class");
823	if (s == NULL)
824		abort();
825	error = geom_gettree(&mesh);
826	if (error != 0)
827		errc(EXIT_FAILURE, error, "Cannot get GEOM tree");
828	classp = find_class(&mesh, s);
829	if (classp == NULL) {
830		geom_deletetree(&mesh);
831		errx(EXIT_FAILURE, "Class %s not found.", s);
832	}
833
834	sigemptyset(&si_sa.sa_mask);
835	si_sa.sa_flags = 0;
836	si_sa.sa_handler = gpart_sighndl;
837	if (sigaction(SIGINT, &si_sa, 0) == -1)
838		err(EXIT_FAILURE, "sigaction SIGINT");
839
840	if (forced) {
841		/* destroy existent partition table before restore */
842		for (i = 0; i < nargs; i++) {
843			s = gctl_get_ascii(req, "arg%d", i);
844			gp = find_geom(classp, s);
845			if (gp != NULL) {
846				r = gctl_get_handle();
847				gctl_ro_param(r, "class", -1,
848				    classp->lg_name);
849				gctl_ro_param(r, "verb", -1, "destroy");
850				gctl_ro_param(r, "flags", -1, "restore");
851				gctl_ro_param(r, "force", sizeof(forced),
852				    &forced);
853				gctl_ro_param(r, "arg0", -1, s);
854				errstr = gctl_issue(r);
855				if (errstr != NULL && errstr[0] != '\0') {
856					gpart_print_error(errstr);
857					gctl_free(r);
858					goto backout;
859				}
860				gctl_free(r);
861			}
862		}
863	}
864	created = 0;
865	while (undo_restore == 0 &&
866	    fgets(line, sizeof(line) - 1, stdin) != NULL) {
867		/* Format of backup entries:
868		 * <scheme name> <number of entries>
869		 * <index> <type> <start> <size> [label] ['['attrib[,attrib]']']
870		 */
871		pline = (char *)line;
872		pline[strlen(line) - 1] = 0;
873		if (skip_line(pline))
874			continue;
875		for (ap = argv;
876		    (*ap = strsep(&pline, " \t")) != NULL;)
877			if (**ap != '\0' && ++ap >= &argv[6])
878				break;
879		l = ap - &argv[0];
880		label = pline = NULL;
881		if (l == 1 || l == 2) { /* create table */
882			if (created)
883				errx(EXIT_FAILURE, "Incorrect backup format.");
884			if (l == 2)
885				n = strtoimax(argv[1], NULL, 0);
886			for (i = 0; i < nargs; i++) {
887				s = gctl_get_ascii(req, "arg%d", i);
888				r = gctl_get_handle();
889				gctl_ro_param(r, "class", -1,
890				    classp->lg_name);
891				gctl_ro_param(r, "verb", -1, "create");
892				gctl_ro_param(r, "scheme", -1, argv[0]);
893				if (l == 2)
894					gctl_ro_param(r, "entries",
895					    sizeof(n), &n);
896				gctl_ro_param(r, "flags", -1, "restore");
897				gctl_ro_param(r, "arg0", -1, s);
898				errstr = gctl_issue(r);
899				if (errstr != NULL && errstr[0] != '\0') {
900					gpart_print_error(errstr);
901					gctl_free(r);
902					goto backout;
903				}
904				gctl_free(r);
905			}
906			created = 1;
907			continue;
908		} else if (l < 4 || created == 0)
909			errx(EXIT_FAILURE, "Incorrect backup format.");
910		else if (l == 5) {
911			if (strchr(argv[4], '[') == NULL)
912				label = argv[4];
913			else
914				pline = argv[4];
915		} else if (l == 6) {
916			label = argv[4];
917			pline = argv[5];
918		}
919		/* Add partitions to each table */
920		for (i = 0; i < nargs; i++) {
921			s = gctl_get_ascii(req, "arg%d", i);
922			r = gctl_get_handle();
923			n = strtoimax(argv[0], NULL, 0);
924			gctl_ro_param(r, "class", -1, classp->lg_name);
925			gctl_ro_param(r, "verb", -1, "add");
926			gctl_ro_param(r, "flags", -1, "restore");
927			gctl_ro_param(r, GPART_PARAM_INDEX, sizeof(n), &n);
928			gctl_ro_param(r, "type", -1, argv[1]);
929			gctl_ro_param(r, "start", -1, argv[2]);
930			gctl_ro_param(r, "size", -1, argv[3]);
931			if (rl != 0 && label != NULL)
932				gctl_ro_param(r, "label", -1, argv[4]);
933			gctl_ro_param(r, "arg0", -1, s);
934			error = gpart_autofill(r);
935			if (error != 0)
936				errc(EXIT_FAILURE, error, "autofill");
937			errstr = gctl_issue(r);
938			if (errstr != NULL && errstr[0] != '\0') {
939				gpart_print_error(errstr);
940				gctl_free(r);
941				goto backout;
942			}
943			gctl_free(r);
944		}
945		if (pline == NULL || *pline != '[')
946			continue;
947		/* set attributes */
948		pline++;
949		for (ap = argv;
950		    (*ap = strsep(&pline, ",]")) != NULL;)
951			if (**ap != '\0' && ++ap >= &argv[6])
952				break;
953		for (i = 0; i < nargs; i++) {
954			l = ap - &argv[0];
955			s = gctl_get_ascii(req, "arg%d", i);
956			while (l > 0) {
957				r = gctl_get_handle();
958				gctl_ro_param(r, "class", -1, classp->lg_name);
959				gctl_ro_param(r, "verb", -1, "set");
960				gctl_ro_param(r, "flags", -1, "restore");
961				gctl_ro_param(r, GPART_PARAM_INDEX,
962				    sizeof(n), &n);
963				gctl_ro_param(r, "attrib", -1, argv[--l]);
964				gctl_ro_param(r, "arg0", -1, s);
965				errstr = gctl_issue(r);
966				if (errstr != NULL && errstr[0] != '\0') {
967					gpart_print_error(errstr);
968					gctl_free(r);
969					goto backout;
970				}
971				gctl_free(r);
972			}
973		}
974	}
975	if (undo_restore)
976		goto backout;
977	/* commit changes if needed */
978	if (strchr(flags, 'C') != NULL) {
979		for (i = 0; i < nargs; i++) {
980			s = gctl_get_ascii(req, "arg%d", i);
981			r = gctl_get_handle();
982			gctl_ro_param(r, "class", -1, classp->lg_name);
983			gctl_ro_param(r, "verb", -1, "commit");
984			gctl_ro_param(r, "arg0", -1, s);
985			errstr = gctl_issue(r);
986			if (errstr != NULL && errstr[0] != '\0') {
987				gpart_print_error(errstr);
988				gctl_free(r);
989				goto backout;
990			}
991			gctl_free(r);
992		}
993	}
994	gctl_free(req);
995	geom_deletetree(&mesh);
996	exit(EXIT_SUCCESS);
997
998backout:
999	for (i = 0; i < nargs; i++) {
1000		s = gctl_get_ascii(req, "arg%d", i);
1001		r = gctl_get_handle();
1002		gctl_ro_param(r, "class", -1, classp->lg_name);
1003		gctl_ro_param(r, "verb", -1, "undo");
1004		gctl_ro_param(r, "arg0", -1, s);
1005		gctl_issue(r);
1006		gctl_free(r);
1007	}
1008	gctl_free(req);
1009	geom_deletetree(&mesh);
1010	exit(EXIT_FAILURE);
1011}
1012
1013static void *
1014gpart_bootfile_read(const char *bootfile, ssize_t *size)
1015{
1016	struct stat sb;
1017	void *code;
1018	int fd;
1019
1020	if (stat(bootfile, &sb) == -1)
1021		err(EXIT_FAILURE, "%s", bootfile);
1022	if (!S_ISREG(sb.st_mode))
1023		errx(EXIT_FAILURE, "%s: not a regular file", bootfile);
1024	if (sb.st_size == 0)
1025		errx(EXIT_FAILURE, "%s: empty file", bootfile);
1026	if (*size > 0 && sb.st_size > *size)
1027		errx(EXIT_FAILURE, "%s: file too big (%zu limit)", bootfile,
1028		    *size);
1029
1030	*size = sb.st_size;
1031
1032	fd = open(bootfile, O_RDONLY);
1033	if (fd == -1)
1034		err(EXIT_FAILURE, "%s", bootfile);
1035	code = malloc(*size);
1036	if (code == NULL)
1037		err(EXIT_FAILURE, NULL);
1038	if (read(fd, code, *size) != *size)
1039		err(EXIT_FAILURE, "%s", bootfile);
1040	close(fd);
1041
1042	return (code);
1043}
1044
1045static void
1046gpart_write_partcode(struct ggeom *gp, int idx, void *code, ssize_t size)
1047{
1048	char dsf[128];
1049	struct gprovider *pp;
1050	const char *s;
1051	char *buf;
1052	off_t bsize;
1053	int fd;
1054
1055	LIST_FOREACH(pp, &gp->lg_provider, lg_provider) {
1056		s = find_provcfg(pp, "index");
1057		if (s == NULL)
1058			continue;
1059		if (atoi(s) == idx)
1060			break;
1061	}
1062
1063	if (pp != NULL) {
1064		snprintf(dsf, sizeof(dsf), "/dev/%s", pp->lg_name);
1065		fd = open(dsf, O_WRONLY);
1066		if (fd == -1)
1067			err(EXIT_FAILURE, "%s", dsf);
1068		if (lseek(fd, size, SEEK_SET) != size)
1069			errx(EXIT_FAILURE, "%s: not enough space", dsf);
1070		if (lseek(fd, 0, SEEK_SET) != 0)
1071			err(EXIT_FAILURE, "%s", dsf);
1072
1073		/*
1074		 * When writing to a disk device, the write must be
1075		 * sector aligned and not write to any partial sectors,
1076		 * so round up the buffer size to the next sector and zero it.
1077		 */
1078		bsize = (size + pp->lg_sectorsize - 1) /
1079		    pp->lg_sectorsize * pp->lg_sectorsize;
1080		buf = calloc(1, bsize);
1081		if (buf == NULL)
1082			err(EXIT_FAILURE, "%s", dsf);
1083		bcopy(code, buf, size);
1084		if (write(fd, buf, bsize) != bsize)
1085			err(EXIT_FAILURE, "%s", dsf);
1086		free(buf);
1087		close(fd);
1088	} else
1089		errx(EXIT_FAILURE, "invalid partition index");
1090}
1091
1092static void
1093gpart_write_partcode_vtoc8(struct ggeom *gp, int idx, void *code)
1094{
1095	char dsf[128];
1096	struct gprovider *pp;
1097	const char *s;
1098	int installed, fd;
1099
1100	installed = 0;
1101	LIST_FOREACH(pp, &gp->lg_provider, lg_provider) {
1102		s = find_provcfg(pp, "index");
1103		if (s == NULL)
1104			continue;
1105		if (idx != 0 && atoi(s) != idx)
1106			continue;
1107		snprintf(dsf, sizeof(dsf), "/dev/%s", pp->lg_name);
1108		if (pp->lg_sectorsize != sizeof(struct vtoc8))
1109			errx(EXIT_FAILURE, "%s: unexpected sector "
1110			    "size (%d)\n", dsf, pp->lg_sectorsize);
1111		fd = open(dsf, O_WRONLY);
1112		if (fd == -1)
1113			err(EXIT_FAILURE, "%s", dsf);
1114		if (lseek(fd, VTOC_BOOTSIZE, SEEK_SET) != VTOC_BOOTSIZE)
1115			continue;
1116		/*
1117		 * We ignore the first VTOC_BOOTSIZE bytes of boot code in
1118		 * order to avoid overwriting the label.
1119		 */
1120		if (lseek(fd, sizeof(struct vtoc8), SEEK_SET) !=
1121		    sizeof(struct vtoc8))
1122			err(EXIT_FAILURE, "%s", dsf);
1123		if (write(fd, (caddr_t)code + sizeof(struct vtoc8),
1124		    VTOC_BOOTSIZE - sizeof(struct vtoc8)) != VTOC_BOOTSIZE -
1125		    sizeof(struct vtoc8))
1126			err(EXIT_FAILURE, "%s", dsf);
1127		installed++;
1128		close(fd);
1129		if (idx != 0 && atoi(s) == idx)
1130			break;
1131	}
1132	if (installed == 0)
1133		errx(EXIT_FAILURE, "%s: no partitions", gp->lg_name);
1134}
1135
1136static void
1137gpart_bootcode(struct gctl_req *req, unsigned int fl)
1138{
1139	struct gmesh mesh;
1140	struct gclass *classp;
1141	struct ggeom *gp;
1142	const char *s;
1143	void *bootcode, *partcode;
1144	size_t bootsize, partsize;
1145	int error, idx, vtoc8;
1146
1147	if (gctl_has_param(req, GPART_PARAM_BOOTCODE)) {
1148		s = gctl_get_ascii(req, GPART_PARAM_BOOTCODE);
1149		bootsize = 800 * 1024;		/* Arbitrary limit. */
1150		bootcode = gpart_bootfile_read(s, &bootsize);
1151		error = gctl_change_param(req, GPART_PARAM_BOOTCODE, bootsize,
1152		    bootcode);
1153		if (error)
1154			errc(EXIT_FAILURE, error, "internal error");
1155	} else {
1156		bootcode = NULL;
1157		bootsize = 0;
1158	}
1159
1160	s = gctl_get_ascii(req, "class");
1161	if (s == NULL)
1162		abort();
1163	error = geom_gettree(&mesh);
1164	if (error != 0)
1165		errc(EXIT_FAILURE, error, "Cannot get GEOM tree");
1166	classp = find_class(&mesh, s);
1167	if (classp == NULL) {
1168		geom_deletetree(&mesh);
1169		errx(EXIT_FAILURE, "Class %s not found.", s);
1170	}
1171	if (gctl_get_int(req, "nargs") != 1)
1172		errx(EXIT_FAILURE, "Invalid number of arguments.");
1173	s = gctl_get_ascii(req, "arg0");
1174	if (s == NULL)
1175		abort();
1176	gp = find_geom(classp, s);
1177	if (gp == NULL)
1178		errx(EXIT_FAILURE, "No such geom: %s.", s);
1179	s = find_geomcfg(gp, "scheme");
1180	vtoc8 = 0;
1181	if (strcmp(s, "VTOC8") == 0)
1182		vtoc8 = 1;
1183
1184	if (gctl_has_param(req, GPART_PARAM_PARTCODE)) {
1185		s = gctl_get_ascii(req, GPART_PARAM_PARTCODE);
1186		partsize = vtoc8 != 0 ? VTOC_BOOTSIZE : bootsize * 1024;
1187		partcode = gpart_bootfile_read(s, &partsize);
1188		error = gctl_delete_param(req, GPART_PARAM_PARTCODE);
1189		if (error)
1190			errc(EXIT_FAILURE, error, "internal error");
1191	} else {
1192		partcode = NULL;
1193		partsize = 0;
1194	}
1195
1196	if (gctl_has_param(req, GPART_PARAM_INDEX)) {
1197		if (partcode == NULL)
1198			errx(EXIT_FAILURE, "-i is only valid with -p");
1199		idx = (int)gctl_get_intmax(req, GPART_PARAM_INDEX);
1200		if (idx < 1)
1201			errx(EXIT_FAILURE, "invalid partition index");
1202		error = gctl_delete_param(req, GPART_PARAM_INDEX);
1203		if (error)
1204			errc(EXIT_FAILURE, error, "internal error");
1205	} else
1206		idx = 0;
1207
1208	if (partcode != NULL) {
1209		if (vtoc8 == 0) {
1210			if (idx == 0)
1211				errx(EXIT_FAILURE, "missing -i option");
1212			gpart_write_partcode(gp, idx, partcode, partsize);
1213		} else
1214			gpart_write_partcode_vtoc8(gp, idx, partcode);
1215	} else
1216		if (bootcode == NULL)
1217			errx(EXIT_FAILURE, "no -b nor -p");
1218
1219	if (bootcode != NULL)
1220		gpart_issue(req, fl);
1221
1222	geom_deletetree(&mesh);
1223}
1224
1225static void
1226gpart_print_error(const char *errstr)
1227{
1228	char *errmsg;
1229	int error;
1230
1231	error = strtol(errstr, &errmsg, 0);
1232	if (errmsg != errstr) {
1233		while (errmsg[0] == ' ')
1234			errmsg++;
1235		if (errmsg[0] != '\0')
1236			warnc(error, "%s", errmsg);
1237		else
1238			warnc(error, NULL);
1239	} else
1240		warnx("%s", errmsg);
1241}
1242
1243static void
1244gpart_issue(struct gctl_req *req, unsigned int fl __unused)
1245{
1246	char buf[4096];
1247	const char *errstr;
1248	int error, status;
1249
1250	if (gctl_get_int(req, "nargs") != 1)
1251		errx(EXIT_FAILURE, "Invalid number of arguments.");
1252	(void)gctl_delete_param(req, "nargs");
1253
1254	/* autofill parameters (if applicable). */
1255	error = gpart_autofill(req);
1256	if (error) {
1257		warnc(error, "autofill");
1258		status = EXIT_FAILURE;
1259		goto done;
1260	}
1261
1262	bzero(buf, sizeof(buf));
1263	gctl_rw_param(req, "output", sizeof(buf), buf);
1264	errstr = gctl_issue(req);
1265	if (errstr == NULL || errstr[0] == '\0') {
1266		if (buf[0] != '\0')
1267			printf("%s", buf);
1268		status = EXIT_SUCCESS;
1269		goto done;
1270	}
1271
1272	gpart_print_error(errstr);
1273	status = EXIT_FAILURE;
1274
1275 done:
1276	gctl_free(req);
1277	exit(status);
1278}
1279