1/*-
2 * Copyright (c) 2002 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 *
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
16 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
19 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
20 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#if HAVE_NBTOOL_CONFIG_H
28#include "nbtool_config.h"
29#endif
30
31#include <sys/cdefs.h>
32#ifdef __FBSDID
33__FBSDID("$FreeBSD: src/sbin/gpt/add.c,v 1.14 2006/06/22 22:05:28 marcel Exp $");
34#endif
35#ifdef __RCSID
36__RCSID("$NetBSD: resizedisk.c,v 1.21 2024/02/06 20:25:11 christos Exp $");
37#endif
38
39#include <sys/bootblock.h>
40#include <sys/types.h>
41
42#include <err.h>
43#include <stdbool.h>
44#include <stddef.h>
45#include <stdio.h>
46#include <stdlib.h>
47#include <string.h>
48#include <unistd.h>
49
50#include "map.h"
51#include "gpt.h"
52#include "gpt_private.h"
53
54
55static int cmd_resizedisk(gpt_t, int, char *[]);
56
57static const char *resizediskhelp[] = {
58	"[-s size] [-q]",
59};
60
61struct gpt_cmd c_resizedisk = {
62	"resizedisk",
63	cmd_resizedisk,
64	resizediskhelp, __arraycount(resizediskhelp),
65	GPT_OPTGPT,
66};
67
68#define usage() gpt_usage(NULL, &c_resizedisk)
69
70/*
71 * relocate the secondary GPT based on the following criteria:
72 * - size not specified
73 *   - disk has not changed size, do nothing
74 *   - disk has grown, relocate secondary
75 *   - disk has shrunk, create new secondary
76 * - size specified
77 *   - size is larger then disk or same as current location, do nothing
78 *   - relocate or create new secondary
79 * - when shrinking, verify that table fits
80 */
81static int
82resizedisk(gpt_t gpt, off_t sector, off_t size, bool quiet)
83{
84	map_t mbrmap;
85	struct gpt_hdr *hdr;
86	struct gpt_ent *ent;
87	struct mbr *mbr;
88	off_t last, oldloc, newloc, lastdata, gpt_size;
89	int i;
90
91	last = gpt->mediasz / gpt->secsz - 1;
92	lastdata = 0;
93	newloc = 0;
94
95	if (sector > last) {
96		gpt_warnx(gpt, "specified number of sectors %jd"
97		    " is larger then the disk %jd", (uintmax_t)sector,
98		    (uintmax_t)last);
99		return -1;
100	}
101
102        mbrmap = map_find(gpt, MAP_TYPE_PMBR);
103        if (mbrmap == NULL || mbrmap->map_start != 0) {
104                gpt_warnx(gpt, "No valid PMBR found");
105                return -1;
106        }
107        mbr = mbrmap->map_data;
108
109	gpt->gpt = map_find(gpt, MAP_TYPE_PRI_GPT_HDR);
110	if (gpt->gpt == NULL) {
111		gpt_warnx(gpt, "No primary GPT header; run create or recover");
112		return -1;
113	}
114
115	gpt->tbl = map_find(gpt, MAP_TYPE_PRI_GPT_TBL);
116	if (gpt->tbl == NULL) {
117		gpt_warnx(gpt, "No primary GPT table; Run recover");
118		return -1;
119	}
120
121	hdr = gpt->gpt->map_data;
122	oldloc = (off_t)le64toh((uint64_t)hdr->hdr_lba_alt);
123
124	gpt->tpg = map_find(gpt, MAP_TYPE_SEC_GPT_HDR);
125	gpt->lbt = map_find(gpt, MAP_TYPE_SEC_GPT_TBL);
126
127	if (gpt->tpg == NULL || gpt->lbt == NULL)
128		gpt_warnx(gpt, "No secondary GPT table");
129
130	gpt_size = gpt->tbl->map_size;
131	if (sector == oldloc) {
132		if (!quiet)
133			gpt_warnx(gpt, "Device is already the specified size");
134		return 0;
135	}
136
137	if (sector == 0 && last == oldloc) {
138		if (!quiet)
139			gpt_warnx(gpt, "Device hasn't changed size");
140		if (gpt->tpg != NULL && gpt->lbt != NULL)
141			return 0;
142	}
143
144	for (ent = gpt->tbl->map_data; ent <
145	    (struct gpt_ent *)((char *)gpt->tbl->map_data +
146	    le32toh(hdr->hdr_entries) * le32toh(hdr->hdr_entsz)); ent++) {
147		if (!gpt_uuid_is_nil(ent->ent_type) &&
148		    ((off_t)le64toh(ent->ent_lba_end) > lastdata)) {
149			lastdata = (off_t)le64toh((uint64_t)ent->ent_lba_end);
150		}
151	}
152
153	if (sector - gpt_size <= lastdata) {
154		gpt_warnx(gpt, "Not enough space at %" PRIu64
155		    " for secondary GPT table", sector);
156		return -1;
157	}
158
159	if (last - gpt_size <= lastdata) {
160		gpt_warnx(gpt, "Not enough space for new secondary GPT table");
161		return -1;
162	}
163
164	if (sector > oldloc)
165		newloc = sector;
166	if (sector > 0 && sector < oldloc && last >= oldloc)
167		newloc = sector;
168	if (sector == 0 && last > oldloc)
169		newloc = last;
170
171	if (newloc > 0 && gpt->tpg != NULL && gpt->lbt != NULL) {
172		if (!quiet)
173			gpt_msg(gpt, "Moving secondary GPT header");
174		gpt->tpg->map_start = newloc;
175		gpt->lbt->map_start = newloc - gpt_size;
176	} else {
177		if (!quiet)
178			gpt_msg(gpt, "Creating new secondary GPT header");
179		if (sector > 0)
180			newloc = sector;
181		else
182			newloc = last;
183
184		if (gpt_add_hdr(gpt, MAP_TYPE_SEC_GPT_HDR, newloc) == -1)
185			return -1;
186
187		gpt->lbt = map_add(gpt, newloc - gpt_size, gpt_size,
188		    MAP_TYPE_SEC_GPT_TBL, gpt->tbl->map_data, 0);
189		if (gpt->lbt == NULL) {
190			gpt_warn(gpt, "Error adding secondary GPT table");
191			return -1;
192		}
193		memcpy(gpt->tpg->map_data, gpt->gpt->map_data, gpt->secsz);
194	}
195
196	hdr = gpt->gpt->map_data;
197	hdr->hdr_lba_alt = htole64((uint64_t)gpt->tpg->map_start);
198	hdr->hdr_crc_self = 0;
199	hdr->hdr_lba_end = htole64((uint64_t)(gpt->lbt->map_start - 1));
200	hdr->hdr_crc_self =
201	    htole32(crc32(gpt->gpt->map_data, GPT_HDR_SIZE));
202	gpt_write(gpt, gpt->gpt);
203
204	hdr = gpt->tpg->map_data;
205	hdr->hdr_lba_self = htole64((uint64_t)gpt->tpg->map_start);
206	hdr->hdr_lba_alt = htole64((uint64_t)gpt->gpt->map_start);
207	hdr->hdr_lba_end = htole64((uint64_t)(gpt->lbt->map_start - 1));
208	hdr->hdr_lba_table = htole64((uint64_t)gpt->lbt->map_start);
209
210	if (gpt_write_backup(gpt) == -1)
211		return -1;
212
213	for (i = 0; i < 4; i++)
214		if (mbr->mbr_part[0].part_typ == MBR_PTYPE_PMBR)
215			break;
216	if (i == 4) {
217		gpt_warnx(gpt, "No valid PMBR partition found");
218		return -1;
219	}
220	if (last > 0xffffffff) {
221		mbr->mbr_part[0].part_size_lo = htole16(0xffff);
222		mbr->mbr_part[0].part_size_hi = htole16(0xffff);
223	} else {
224		mbr->mbr_part[0].part_size_lo = htole16((uint16_t)last);
225		mbr->mbr_part[0].part_size_hi = htole16((uint16_t)(last >> 16));
226	}
227	if (gpt_write(gpt, mbrmap) == -1) {
228		gpt_warnx(gpt, "Error writing PMBR");
229		return -1;
230	}
231
232	return 0;
233}
234
235static int
236cmd_resizedisk(gpt_t gpt, int argc, char *argv[])
237{
238	int ch;
239	off_t sector, size = gpt->mediasz;
240	bool quiet = false;
241
242	while ((ch = getopt(argc, argv, "s:q")) != -1) {
243		switch(ch) {
244		case 's':
245			if (gpt_add_ais(gpt, NULL, NULL, &size, ch) == -1)
246				return -1;
247			break;
248		case 'q':
249			quiet = true;
250			break;
251		default:
252			return usage();
253		}
254	}
255
256	if (argc != optind)
257		return usage();
258
259	if ((sector = gpt_check_ais(gpt, 0, (u_int)~0, size)) == -1)
260		return -1;
261
262	if (--sector == 0) {
263		gpt_warnx(gpt, "New size %ju too small", (uintmax_t)size);
264		return -1;
265	}
266
267	return resizedisk(gpt, sector, size, quiet);
268}
269