g_part_apm.c revision 332640
1/*-
2 * Copyright (c) 2006-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 *
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#include <sys/cdefs.h>
28__FBSDID("$FreeBSD: stable/11/sys/geom/part/g_part_apm.c 332640 2018-04-17 02:18:04Z kevans $");
29
30#include <sys/param.h>
31#include <sys/apm.h>
32#include <sys/bio.h>
33#include <sys/endian.h>
34#include <sys/kernel.h>
35#include <sys/kobj.h>
36#include <sys/limits.h>
37#include <sys/lock.h>
38#include <sys/malloc.h>
39#include <sys/mutex.h>
40#include <sys/queue.h>
41#include <sys/sbuf.h>
42#include <sys/systm.h>
43#include <sys/sysctl.h>
44#include <geom/geom.h>
45#include <geom/geom_int.h>
46#include <geom/part/g_part.h>
47
48#include "g_part_if.h"
49
50FEATURE(geom_part_apm, "GEOM partitioning class for Apple-style partitions");
51
52struct g_part_apm_table {
53	struct g_part_table	base;
54	struct apm_ddr		ddr;
55	struct apm_ent		self;
56	int			tivo_series1;
57};
58
59struct g_part_apm_entry {
60	struct g_part_entry	base;
61	struct apm_ent		ent;
62};
63
64static int g_part_apm_add(struct g_part_table *, struct g_part_entry *,
65    struct g_part_parms *);
66static int g_part_apm_create(struct g_part_table *, struct g_part_parms *);
67static int g_part_apm_destroy(struct g_part_table *, struct g_part_parms *);
68static void g_part_apm_dumpconf(struct g_part_table *, struct g_part_entry *,
69    struct sbuf *, const char *);
70static int g_part_apm_dumpto(struct g_part_table *, struct g_part_entry *);
71static int g_part_apm_modify(struct g_part_table *, struct g_part_entry *,
72    struct g_part_parms *);
73static const char *g_part_apm_name(struct g_part_table *, struct g_part_entry *,
74    char *, size_t);
75static int g_part_apm_probe(struct g_part_table *, struct g_consumer *);
76static int g_part_apm_read(struct g_part_table *, struct g_consumer *);
77static const char *g_part_apm_type(struct g_part_table *, struct g_part_entry *,
78    char *, size_t);
79static int g_part_apm_write(struct g_part_table *, struct g_consumer *);
80static int g_part_apm_resize(struct g_part_table *, struct g_part_entry *,
81    struct g_part_parms *);
82
83static kobj_method_t g_part_apm_methods[] = {
84	KOBJMETHOD(g_part_add,		g_part_apm_add),
85	KOBJMETHOD(g_part_create,	g_part_apm_create),
86	KOBJMETHOD(g_part_destroy,	g_part_apm_destroy),
87	KOBJMETHOD(g_part_dumpconf,	g_part_apm_dumpconf),
88	KOBJMETHOD(g_part_dumpto,	g_part_apm_dumpto),
89	KOBJMETHOD(g_part_modify,	g_part_apm_modify),
90	KOBJMETHOD(g_part_resize,	g_part_apm_resize),
91	KOBJMETHOD(g_part_name,		g_part_apm_name),
92	KOBJMETHOD(g_part_probe,	g_part_apm_probe),
93	KOBJMETHOD(g_part_read,		g_part_apm_read),
94	KOBJMETHOD(g_part_type,		g_part_apm_type),
95	KOBJMETHOD(g_part_write,	g_part_apm_write),
96	{ 0, 0 }
97};
98
99static struct g_part_scheme g_part_apm_scheme = {
100	"APM",
101	g_part_apm_methods,
102	sizeof(struct g_part_apm_table),
103	.gps_entrysz = sizeof(struct g_part_apm_entry),
104	.gps_minent = 16,
105	.gps_maxent = 4096,
106};
107G_PART_SCHEME_DECLARE(g_part_apm);
108MODULE_VERSION(geom_part_apm, 0);
109
110static void
111swab(char *buf, size_t bufsz)
112{
113	int i;
114	char ch;
115
116	for (i = 0; i < bufsz; i += 2) {
117		ch = buf[i];
118		buf[i] = buf[i + 1];
119		buf[i + 1] = ch;
120	}
121}
122
123static int
124apm_parse_type(const char *type, char *buf, size_t bufsz)
125{
126	const char *alias;
127
128	if (type[0] == '!') {
129		type++;
130		if (strlen(type) > bufsz)
131			return (EINVAL);
132		if (!strcmp(type, APM_ENT_TYPE_SELF) ||
133		    !strcmp(type, APM_ENT_TYPE_UNUSED))
134			return (EINVAL);
135		strncpy(buf, type, bufsz);
136		return (0);
137	}
138	alias = g_part_alias_name(G_PART_ALIAS_APPLE_BOOT);
139	if (!strcasecmp(type, alias)) {
140		strcpy(buf, APM_ENT_TYPE_APPLE_BOOT);
141		return (0);
142	}
143	alias = g_part_alias_name(G_PART_ALIAS_APPLE_HFS);
144	if (!strcasecmp(type, alias)) {
145		strcpy(buf, APM_ENT_TYPE_APPLE_HFS);
146		return (0);
147	}
148	alias = g_part_alias_name(G_PART_ALIAS_APPLE_UFS);
149	if (!strcasecmp(type, alias)) {
150		strcpy(buf, APM_ENT_TYPE_APPLE_UFS);
151		return (0);
152	}
153	alias = g_part_alias_name(G_PART_ALIAS_FREEBSD);
154	if (!strcasecmp(type, alias)) {
155		strcpy(buf, APM_ENT_TYPE_FREEBSD);
156		return (0);
157	}
158	alias = g_part_alias_name(G_PART_ALIAS_FREEBSD_NANDFS);
159	if (!strcasecmp(type, alias)) {
160		strcpy(buf, APM_ENT_TYPE_FREEBSD_NANDFS);
161		return (0);
162	}
163	alias = g_part_alias_name(G_PART_ALIAS_FREEBSD_SWAP);
164	if (!strcasecmp(type, alias)) {
165		strcpy(buf, APM_ENT_TYPE_FREEBSD_SWAP);
166		return (0);
167	}
168	alias = g_part_alias_name(G_PART_ALIAS_FREEBSD_UFS);
169	if (!strcasecmp(type, alias)) {
170		strcpy(buf, APM_ENT_TYPE_FREEBSD_UFS);
171		return (0);
172	}
173	alias = g_part_alias_name(G_PART_ALIAS_FREEBSD_VINUM);
174	if (!strcasecmp(type, alias)) {
175		strcpy(buf, APM_ENT_TYPE_FREEBSD_VINUM);
176		return (0);
177	}
178	alias = g_part_alias_name(G_PART_ALIAS_FREEBSD_ZFS);
179	if (!strcasecmp(type, alias)) {
180		strcpy(buf, APM_ENT_TYPE_FREEBSD_ZFS);
181		return (0);
182	}
183	return (EINVAL);
184}
185
186static int
187apm_read_ent(struct g_consumer *cp, uint32_t blk, struct apm_ent *ent,
188    int tivo_series1)
189{
190	struct g_provider *pp;
191	char *buf;
192	int error;
193
194	pp = cp->provider;
195	buf = g_read_data(cp, pp->sectorsize * blk, pp->sectorsize, &error);
196	if (buf == NULL)
197		return (error);
198	if (tivo_series1)
199		swab(buf, pp->sectorsize);
200	ent->ent_sig = be16dec(buf);
201	ent->ent_pmblkcnt = be32dec(buf + 4);
202	ent->ent_start = be32dec(buf + 8);
203	ent->ent_size = be32dec(buf + 12);
204	bcopy(buf + 16, ent->ent_name, sizeof(ent->ent_name));
205	bcopy(buf + 48, ent->ent_type, sizeof(ent->ent_type));
206	g_free(buf);
207	return (0);
208}
209
210static int
211g_part_apm_add(struct g_part_table *basetable, struct g_part_entry *baseentry,
212    struct g_part_parms *gpp)
213{
214	struct g_part_apm_entry *entry;
215	struct g_part_apm_table *table;
216	int error;
217
218	entry = (struct g_part_apm_entry *)baseentry;
219	table = (struct g_part_apm_table *)basetable;
220	entry->ent.ent_sig = APM_ENT_SIG;
221	entry->ent.ent_pmblkcnt = table->self.ent_pmblkcnt;
222	entry->ent.ent_start = gpp->gpp_start;
223	entry->ent.ent_size = gpp->gpp_size;
224	if (baseentry->gpe_deleted) {
225		bzero(entry->ent.ent_type, sizeof(entry->ent.ent_type));
226		bzero(entry->ent.ent_name, sizeof(entry->ent.ent_name));
227	}
228	error = apm_parse_type(gpp->gpp_type, entry->ent.ent_type,
229	    sizeof(entry->ent.ent_type));
230	if (error)
231		return (error);
232	if (gpp->gpp_parms & G_PART_PARM_LABEL) {
233		if (strlen(gpp->gpp_label) > sizeof(entry->ent.ent_name))
234			return (EINVAL);
235		strncpy(entry->ent.ent_name, gpp->gpp_label,
236		    sizeof(entry->ent.ent_name));
237	}
238	if (baseentry->gpe_index >= table->self.ent_pmblkcnt)
239		table->self.ent_pmblkcnt = baseentry->gpe_index + 1;
240	KASSERT(table->self.ent_size >= table->self.ent_pmblkcnt,
241	    ("%s", __func__));
242	KASSERT(table->self.ent_size > baseentry->gpe_index,
243	    ("%s", __func__));
244	return (0);
245}
246
247static int
248g_part_apm_create(struct g_part_table *basetable, struct g_part_parms *gpp)
249{
250	struct g_provider *pp;
251	struct g_part_apm_table *table;
252	uint32_t last;
253
254	/* We don't nest, which means that our depth should be 0. */
255	if (basetable->gpt_depth != 0)
256		return (ENXIO);
257
258	table = (struct g_part_apm_table *)basetable;
259	pp = gpp->gpp_provider;
260	if (pp->sectorsize != 512 ||
261	    pp->mediasize < (2 + 2 * basetable->gpt_entries) * pp->sectorsize)
262		return (ENOSPC);
263
264	/* APM uses 32-bit LBAs. */
265	last = MIN(pp->mediasize / pp->sectorsize, UINT32_MAX) - 1;
266
267	basetable->gpt_first = 2 + basetable->gpt_entries;
268	basetable->gpt_last = last;
269
270	table->ddr.ddr_sig = APM_DDR_SIG;
271	table->ddr.ddr_blksize = pp->sectorsize;
272	table->ddr.ddr_blkcount = last + 1;
273
274	table->self.ent_sig = APM_ENT_SIG;
275	table->self.ent_pmblkcnt = basetable->gpt_entries + 1;
276	table->self.ent_start = 1;
277	table->self.ent_size = table->self.ent_pmblkcnt;
278	strcpy(table->self.ent_name, "Apple");
279	strcpy(table->self.ent_type, APM_ENT_TYPE_SELF);
280	return (0);
281}
282
283static int
284g_part_apm_destroy(struct g_part_table *basetable, struct g_part_parms *gpp)
285{
286
287	/* Wipe the first 2 sectors to clear the partitioning. */
288	basetable->gpt_smhead |= 3;
289	return (0);
290}
291
292static void
293g_part_apm_dumpconf(struct g_part_table *table, struct g_part_entry *baseentry,
294    struct sbuf *sb, const char *indent)
295{
296	union {
297		char name[APM_ENT_NAMELEN + 1];
298		char type[APM_ENT_TYPELEN + 1];
299	} u;
300	struct g_part_apm_entry *entry;
301
302	entry = (struct g_part_apm_entry *)baseentry;
303	if (indent == NULL) {
304		/* conftxt: libdisk compatibility */
305		sbuf_printf(sb, " xs APPLE xt %s", entry->ent.ent_type);
306	} else if (entry != NULL) {
307		/* confxml: partition entry information */
308		strncpy(u.name, entry->ent.ent_name, APM_ENT_NAMELEN);
309		u.name[APM_ENT_NAMELEN] = '\0';
310		sbuf_printf(sb, "%s<label>", indent);
311		g_conf_printf_escaped(sb, "%s", u.name);
312		sbuf_printf(sb, "</label>\n");
313		strncpy(u.type, entry->ent.ent_type, APM_ENT_TYPELEN);
314		u.type[APM_ENT_TYPELEN] = '\0';
315		sbuf_printf(sb, "%s<rawtype>", indent);
316		g_conf_printf_escaped(sb, "%s", u.type);
317		sbuf_printf(sb, "</rawtype>\n");
318	} else {
319		/* confxml: scheme information */
320	}
321}
322
323static int
324g_part_apm_dumpto(struct g_part_table *table, struct g_part_entry *baseentry)
325{
326	struct g_part_apm_entry *entry;
327
328	entry = (struct g_part_apm_entry *)baseentry;
329	return ((!strcmp(entry->ent.ent_type, APM_ENT_TYPE_FREEBSD_SWAP))
330	    ? 1 : 0);
331}
332
333static int
334g_part_apm_modify(struct g_part_table *basetable,
335    struct g_part_entry *baseentry, struct g_part_parms *gpp)
336{
337	struct g_part_apm_entry *entry;
338	int error;
339
340	entry = (struct g_part_apm_entry *)baseentry;
341	if (gpp->gpp_parms & G_PART_PARM_LABEL) {
342		if (strlen(gpp->gpp_label) > sizeof(entry->ent.ent_name))
343			return (EINVAL);
344	}
345	if (gpp->gpp_parms & G_PART_PARM_TYPE) {
346		error = apm_parse_type(gpp->gpp_type, entry->ent.ent_type,
347		    sizeof(entry->ent.ent_type));
348		if (error)
349			return (error);
350	}
351	if (gpp->gpp_parms & G_PART_PARM_LABEL) {
352		strncpy(entry->ent.ent_name, gpp->gpp_label,
353		    sizeof(entry->ent.ent_name));
354	}
355	return (0);
356}
357
358static int
359g_part_apm_resize(struct g_part_table *basetable,
360    struct g_part_entry *baseentry, struct g_part_parms *gpp)
361{
362	struct g_part_apm_entry *entry;
363	struct g_provider *pp;
364
365	if (baseentry == NULL) {
366		pp = LIST_FIRST(&basetable->gpt_gp->consumer)->provider;
367		basetable->gpt_last = MIN(pp->mediasize / pp->sectorsize,
368		    UINT32_MAX) - 1;
369		return (0);
370	}
371
372	entry = (struct g_part_apm_entry *)baseentry;
373	baseentry->gpe_end = baseentry->gpe_start + gpp->gpp_size - 1;
374	entry->ent.ent_size = gpp->gpp_size;
375
376	return (0);
377}
378
379static const char *
380g_part_apm_name(struct g_part_table *table, struct g_part_entry *baseentry,
381    char *buf, size_t bufsz)
382{
383
384	snprintf(buf, bufsz, "s%d", baseentry->gpe_index + 1);
385	return (buf);
386}
387
388static int
389g_part_apm_probe(struct g_part_table *basetable, struct g_consumer *cp)
390{
391	struct g_provider *pp;
392	struct g_part_apm_table *table;
393	char *buf;
394	int error;
395
396	/* We don't nest, which means that our depth should be 0. */
397	if (basetable->gpt_depth != 0)
398		return (ENXIO);
399
400	table = (struct g_part_apm_table *)basetable;
401	table->tivo_series1 = 0;
402	pp = cp->provider;
403
404	/* Sanity-check the provider. */
405	if (pp->mediasize < 4 * pp->sectorsize)
406		return (ENOSPC);
407
408	/* Check that there's a Driver Descriptor Record (DDR). */
409	buf = g_read_data(cp, 0L, pp->sectorsize, &error);
410	if (buf == NULL)
411		return (error);
412	if (be16dec(buf) == APM_DDR_SIG) {
413		/* Normal Apple DDR */
414		table->ddr.ddr_sig = be16dec(buf);
415		table->ddr.ddr_blksize = be16dec(buf + 2);
416		table->ddr.ddr_blkcount = be32dec(buf + 4);
417		g_free(buf);
418		if (table->ddr.ddr_blksize != pp->sectorsize)
419			return (ENXIO);
420		if (table->ddr.ddr_blkcount > pp->mediasize / pp->sectorsize)
421			return (ENXIO);
422	} else {
423		/*
424		 * Check for Tivo drives, which have no DDR and a different
425		 * signature.  Those whose first two bytes are 14 92 are
426		 * Series 2 drives, and aren't supported.  Those that start
427		 * with 92 14 are series 1 drives and are supported.
428		 */
429		if (be16dec(buf) != 0x9214) {
430			/* If this is 0x1492 it could be a series 2 drive */
431			g_free(buf);
432			return (ENXIO);
433		}
434		table->ddr.ddr_sig = APM_DDR_SIG;		/* XXX */
435		table->ddr.ddr_blksize = pp->sectorsize;	/* XXX */
436		table->ddr.ddr_blkcount =
437		    MIN(pp->mediasize / pp->sectorsize, UINT32_MAX);
438		table->tivo_series1 = 1;
439		g_free(buf);
440	}
441
442	/* Check that there's a Partition Map. */
443	error = apm_read_ent(cp, 1, &table->self, table->tivo_series1);
444	if (error)
445		return (error);
446	if (table->self.ent_sig != APM_ENT_SIG)
447		return (ENXIO);
448	if (strcmp(table->self.ent_type, APM_ENT_TYPE_SELF))
449		return (ENXIO);
450	if (table->self.ent_pmblkcnt >= table->ddr.ddr_blkcount)
451		return (ENXIO);
452	return (G_PART_PROBE_PRI_NORM);
453}
454
455static int
456g_part_apm_read(struct g_part_table *basetable, struct g_consumer *cp)
457{
458	struct apm_ent ent;
459	struct g_part_apm_entry *entry;
460	struct g_part_apm_table *table;
461	int error, index;
462
463	table = (struct g_part_apm_table *)basetable;
464
465	basetable->gpt_first = table->self.ent_size + 1;
466	basetable->gpt_last = table->ddr.ddr_blkcount - 1;
467	basetable->gpt_entries = table->self.ent_size - 1;
468
469	for (index = table->self.ent_pmblkcnt - 1; index > 0; index--) {
470		error = apm_read_ent(cp, index + 1, &ent, table->tivo_series1);
471		if (error)
472			continue;
473		if (!strcmp(ent.ent_type, APM_ENT_TYPE_UNUSED))
474			continue;
475		entry = (struct g_part_apm_entry *)g_part_new_entry(basetable,
476		    index, ent.ent_start, ent.ent_start + ent.ent_size - 1);
477		entry->ent = ent;
478	}
479
480	return (0);
481}
482
483static const char *
484g_part_apm_type(struct g_part_table *basetable, struct g_part_entry *baseentry,
485    char *buf, size_t bufsz)
486{
487	struct g_part_apm_entry *entry;
488	const char *type;
489	size_t len;
490
491	entry = (struct g_part_apm_entry *)baseentry;
492	type = entry->ent.ent_type;
493	if (!strcmp(type, APM_ENT_TYPE_APPLE_BOOT))
494		return (g_part_alias_name(G_PART_ALIAS_APPLE_BOOT));
495	if (!strcmp(type, APM_ENT_TYPE_APPLE_HFS))
496		return (g_part_alias_name(G_PART_ALIAS_APPLE_HFS));
497	if (!strcmp(type, APM_ENT_TYPE_APPLE_UFS))
498		return (g_part_alias_name(G_PART_ALIAS_APPLE_UFS));
499	if (!strcmp(type, APM_ENT_TYPE_FREEBSD))
500		return (g_part_alias_name(G_PART_ALIAS_FREEBSD));
501	if (!strcmp(type, APM_ENT_TYPE_FREEBSD_NANDFS))
502		return (g_part_alias_name(G_PART_ALIAS_FREEBSD_NANDFS));
503	if (!strcmp(type, APM_ENT_TYPE_FREEBSD_SWAP))
504		return (g_part_alias_name(G_PART_ALIAS_FREEBSD_SWAP));
505	if (!strcmp(type, APM_ENT_TYPE_FREEBSD_UFS))
506		return (g_part_alias_name(G_PART_ALIAS_FREEBSD_UFS));
507	if (!strcmp(type, APM_ENT_TYPE_FREEBSD_VINUM))
508		return (g_part_alias_name(G_PART_ALIAS_FREEBSD_VINUM));
509	if (!strcmp(type, APM_ENT_TYPE_FREEBSD_ZFS))
510		return (g_part_alias_name(G_PART_ALIAS_FREEBSD_ZFS));
511	buf[0] = '!';
512	len = MIN(sizeof(entry->ent.ent_type), bufsz - 2);
513	bcopy(type, buf + 1, len);
514	buf[len + 1] = '\0';
515	return (buf);
516}
517
518static int
519g_part_apm_write(struct g_part_table *basetable, struct g_consumer *cp)
520{
521	struct g_provider *pp;
522	struct g_part_entry *baseentry;
523	struct g_part_apm_entry *entry;
524	struct g_part_apm_table *table;
525	char *buf, *ptr;
526	uint32_t index;
527	int error;
528	size_t tblsz;
529
530	pp = cp->provider;
531	table = (struct g_part_apm_table *)basetable;
532	/*
533	 * Tivo Series 1 disk partitions are currently read-only.
534	 */
535	if (table->tivo_series1)
536		return (EOPNOTSUPP);
537
538	/* Write the DDR only when we're newly created. */
539	if (basetable->gpt_created) {
540		buf = g_malloc(pp->sectorsize, M_WAITOK | M_ZERO);
541		be16enc(buf, table->ddr.ddr_sig);
542		be16enc(buf + 2, table->ddr.ddr_blksize);
543		be32enc(buf + 4, table->ddr.ddr_blkcount);
544		error = g_write_data(cp, 0, buf, pp->sectorsize);
545		g_free(buf);
546		if (error)
547			return (error);
548	}
549
550	/* Allocate the buffer for all entries */
551	tblsz = table->self.ent_pmblkcnt;
552	buf = g_malloc(tblsz * pp->sectorsize, M_WAITOK | M_ZERO);
553
554	/* Fill the self entry */
555	be16enc(buf, APM_ENT_SIG);
556	be32enc(buf + 4, table->self.ent_pmblkcnt);
557	be32enc(buf + 8, table->self.ent_start);
558	be32enc(buf + 12, table->self.ent_size);
559	bcopy(table->self.ent_name, buf + 16, sizeof(table->self.ent_name));
560	bcopy(table->self.ent_type, buf + 48, sizeof(table->self.ent_type));
561
562	baseentry = LIST_FIRST(&basetable->gpt_entry);
563	for (index = 1; index < tblsz; index++) {
564		entry = (baseentry != NULL && index == baseentry->gpe_index)
565		    ? (struct g_part_apm_entry *)baseentry : NULL;
566		ptr = buf + index * pp->sectorsize;
567		be16enc(ptr, APM_ENT_SIG);
568		be32enc(ptr + 4, table->self.ent_pmblkcnt);
569		if (entry != NULL && !baseentry->gpe_deleted) {
570			be32enc(ptr + 8, entry->ent.ent_start);
571			be32enc(ptr + 12, entry->ent.ent_size);
572			bcopy(entry->ent.ent_name, ptr + 16,
573			    sizeof(entry->ent.ent_name));
574			bcopy(entry->ent.ent_type, ptr + 48,
575			    sizeof(entry->ent.ent_type));
576		} else {
577			strcpy(ptr + 48, APM_ENT_TYPE_UNUSED);
578		}
579		if (entry != NULL)
580			baseentry = LIST_NEXT(baseentry, gpe_entry);
581	}
582
583	for (index = 0; index < tblsz; index += MAXPHYS / pp->sectorsize) {
584		error = g_write_data(cp, (1 + index) * pp->sectorsize,
585		    buf + index * pp->sectorsize,
586		    (tblsz - index > MAXPHYS / pp->sectorsize) ? MAXPHYS:
587		    (tblsz - index) * pp->sectorsize);
588		if (error) {
589			g_free(buf);
590			return (error);
591		}
592	}
593	g_free(buf);
594	return (0);
595}
596