1265574Smarcel/*-
2265574Smarcel * Copyright (c) 2014 Juniper Networks, Inc.
3265574Smarcel * All rights reserved.
4265574Smarcel *
5265574Smarcel * Redistribution and use in source and binary forms, with or without
6265574Smarcel * modification, are permitted provided that the following conditions
7265574Smarcel * are met:
8265574Smarcel * 1. Redistributions of source code must retain the above copyright
9265574Smarcel *    notice, this list of conditions and the following disclaimer.
10265574Smarcel * 2. Redistributions in binary form must reproduce the above copyright
11265574Smarcel *    notice, this list of conditions and the following disclaimer in the
12265574Smarcel *    documentation and/or other materials provided with the distribution.
13265574Smarcel *
14265574Smarcel * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15265574Smarcel * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16265574Smarcel * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17265574Smarcel * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18265574Smarcel * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19265574Smarcel * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20265574Smarcel * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21265574Smarcel * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22265574Smarcel * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23265574Smarcel * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24265574Smarcel * SUCH DAMAGE.
25265574Smarcel */
26265574Smarcel
27265574Smarcel#include <sys/cdefs.h>
28265574Smarcel__FBSDID("$FreeBSD$");
29265574Smarcel
30272775Smarcel#include <sys/mman.h>
31272775Smarcel#include <sys/queue.h>
32272775Smarcel#include <sys/stat.h>
33265574Smarcel#include <sys/types.h>
34265574Smarcel#include <assert.h>
35302924Smarkj#include <err.h>
36265574Smarcel#include <errno.h>
37268161Smarcel#include <limits.h>
38268161Smarcel#include <paths.h>
39272775Smarcel#include <stdint.h>
40268161Smarcel#include <stdio.h>
41265574Smarcel#include <stdlib.h>
42272775Smarcel#include <string.h>
43265574Smarcel#include <unistd.h>
44265574Smarcel
45265579Smarcel#include "image.h"
46265574Smarcel#include "mkimg.h"
47265574Smarcel
48272775Smarcelstruct chunk {
49272775Smarcel	STAILQ_ENTRY(chunk) ch_list;
50272775Smarcel	size_t	ch_size;		/* Size of chunk in bytes. */
51272775Smarcel	lba_t	ch_block;		/* Block address in image. */
52272775Smarcel	union {
53272775Smarcel		struct {
54272775Smarcel			off_t	ofs;	/* Offset in backing file. */
55272775Smarcel			int	fd;	/* FD of backing file. */
56272775Smarcel		} file;
57272775Smarcel		struct {
58272775Smarcel			void	*ptr;	/* Pointer to data in memory */
59272775Smarcel		} mem;
60272775Smarcel	} ch_u;
61272775Smarcel	u_int	ch_type;
62272775Smarcel#define	CH_TYPE_ZEROES		0	/* Chunk is a gap (no data). */
63272775Smarcel#define	CH_TYPE_FILE		1	/* File-backed chunk. */
64272775Smarcel#define	CH_TYPE_MEMORY		2	/* Memory-backed chunk */
65272775Smarcel};
66265574Smarcel
67272775Smarcelstatic STAILQ_HEAD(chunk_head, chunk) image_chunks;
68272775Smarcelstatic u_int image_nchunks;
69272775Smarcel
70272775Smarcelstatic char image_swap_file[PATH_MAX];
71272775Smarcelstatic int image_swap_fd = -1;
72272775Smarcelstatic u_int image_swap_pgsz;
73272775Smarcelstatic off_t image_swap_size;
74272775Smarcel
75265725Smarcelstatic lba_t image_size;
76265618Smarcel
77272775Smarcelstatic int
78272775Smarcelis_empty_sector(void *buf)
79265618Smarcel{
80272775Smarcel	uint64_t *p = buf;
81272775Smarcel	size_t n, max;
82265618Smarcel
83272775Smarcel	assert(((uintptr_t)p & 3) == 0);
84272775Smarcel
85272775Smarcel	max = secsz / sizeof(uint64_t);
86272775Smarcel	for (n = 0; n < max; n++) {
87272775Smarcel		if (p[n] != 0UL)
88272775Smarcel			return (0);
89272775Smarcel	}
90272775Smarcel	return (1);
91265618Smarcel}
92265618Smarcel
93272775Smarcel/*
94272775Smarcel * Swap file handlng.
95272775Smarcel */
96272775Smarcel
97272775Smarcelstatic off_t
98272775Smarcelimage_swap_alloc(size_t size)
99265574Smarcel{
100272775Smarcel	off_t ofs;
101272775Smarcel	size_t unit;
102272775Smarcel
103272775Smarcel	unit = (secsz > image_swap_pgsz) ? secsz : image_swap_pgsz;
104272775Smarcel	assert((unit & (unit - 1)) == 0);
105272775Smarcel
106272775Smarcel	size = (size + unit - 1) & ~(unit - 1);
107272775Smarcel
108272775Smarcel	ofs = image_swap_size;
109272775Smarcel	image_swap_size += size;
110272775Smarcel	if (ftruncate(image_swap_fd, image_swap_size) == -1) {
111272775Smarcel		image_swap_size = ofs;
112272775Smarcel		ofs = -1LL;
113272775Smarcel	}
114272775Smarcel	return (ofs);
115272775Smarcel}
116272775Smarcel
117272775Smarcel/*
118272775Smarcel * Image chunk handling.
119272775Smarcel */
120272775Smarcel
121272775Smarcelstatic struct chunk *
122272775Smarcelimage_chunk_find(lba_t blk)
123272775Smarcel{
124272775Smarcel	static struct chunk *last = NULL;
125272775Smarcel	struct chunk *ch;
126272775Smarcel
127272775Smarcel	ch = (last != NULL && last->ch_block <= blk)
128272775Smarcel	    ? last : STAILQ_FIRST(&image_chunks);
129272775Smarcel	while (ch != NULL) {
130272775Smarcel		if (ch->ch_block <= blk &&
131272775Smarcel		    (lba_t)(ch->ch_block + (ch->ch_size / secsz)) > blk) {
132272775Smarcel			last = ch;
133272775Smarcel			break;
134272775Smarcel		}
135272775Smarcel		ch = STAILQ_NEXT(ch, ch_list);
136272775Smarcel	}
137272775Smarcel	return (ch);
138272775Smarcel}
139272775Smarcel
140272775Smarcelstatic size_t
141272775Smarcelimage_chunk_grow(struct chunk *ch, size_t sz)
142272775Smarcel{
143272775Smarcel	size_t dsz, newsz;
144272775Smarcel
145272775Smarcel	newsz = ch->ch_size + sz;
146272775Smarcel	if (newsz > ch->ch_size) {
147272775Smarcel		ch->ch_size = newsz;
148272775Smarcel		return (0);
149272775Smarcel	}
150272775Smarcel	/* We would overflow -- create new chunk for remainder. */
151272775Smarcel	dsz = SIZE_MAX - ch->ch_size;
152272775Smarcel	assert(dsz < sz);
153272775Smarcel	ch->ch_size = SIZE_MAX;
154272775Smarcel	return (sz - dsz);
155272775Smarcel}
156272775Smarcel
157272775Smarcelstatic struct chunk *
158272775Smarcelimage_chunk_memory(struct chunk *ch, lba_t blk)
159272775Smarcel{
160272775Smarcel	struct chunk *new;
161272775Smarcel	void *ptr;
162272775Smarcel
163272775Smarcel	ptr = calloc(1, secsz);
164272775Smarcel	if (ptr == NULL)
165272775Smarcel		return (NULL);
166272775Smarcel
167272775Smarcel	if (ch->ch_block < blk) {
168272775Smarcel		new = malloc(sizeof(*new));
169272775Smarcel		if (new == NULL) {
170272775Smarcel			free(ptr);
171272775Smarcel			return (NULL);
172272775Smarcel		}
173272775Smarcel		memcpy(new, ch, sizeof(*new));
174272775Smarcel		ch->ch_size = (blk - ch->ch_block) * secsz;
175272775Smarcel		new->ch_block = blk;
176272775Smarcel		new->ch_size -= ch->ch_size;
177272775Smarcel		STAILQ_INSERT_AFTER(&image_chunks, ch, new, ch_list);
178272775Smarcel		image_nchunks++;
179272775Smarcel		ch = new;
180272775Smarcel	}
181272775Smarcel
182272775Smarcel	if (ch->ch_size > secsz) {
183272775Smarcel		new = malloc(sizeof(*new));
184272775Smarcel		if (new == NULL) {
185272775Smarcel			free(ptr);
186272775Smarcel			return (NULL);
187272775Smarcel		}
188272775Smarcel		memcpy(new, ch, sizeof(*new));
189272775Smarcel		ch->ch_size = secsz;
190272775Smarcel		new->ch_block++;
191272775Smarcel		new->ch_size -= secsz;
192272775Smarcel		STAILQ_INSERT_AFTER(&image_chunks, ch, new, ch_list);
193272775Smarcel		image_nchunks++;
194272775Smarcel	}
195272775Smarcel
196272775Smarcel	ch->ch_type = CH_TYPE_MEMORY;
197272775Smarcel	ch->ch_u.mem.ptr = ptr;
198272775Smarcel	return (ch);
199272775Smarcel}
200272775Smarcel
201272775Smarcelstatic int
202272775Smarcelimage_chunk_skipto(lba_t to)
203272775Smarcel{
204272775Smarcel	struct chunk *ch;
205272775Smarcel	lba_t from;
206272775Smarcel	size_t sz;
207272775Smarcel
208272775Smarcel	ch = STAILQ_LAST(&image_chunks, chunk, ch_list);
209272775Smarcel	from = (ch != NULL) ? ch->ch_block + (ch->ch_size / secsz) : 0LL;
210272775Smarcel
211272775Smarcel	assert(from <= to);
212272775Smarcel
213272775Smarcel	/* Nothing to do? */
214272775Smarcel	if (from == to)
215272775Smarcel		return (0);
216272775Smarcel	/* Avoid bugs due to overflows. */
217272775Smarcel	if ((uintmax_t)(to - from) > (uintmax_t)(SIZE_MAX / secsz))
218272775Smarcel		return (EFBIG);
219272775Smarcel	sz = (to - from) * secsz;
220272775Smarcel	if (ch != NULL && ch->ch_type == CH_TYPE_ZEROES) {
221272775Smarcel		sz = image_chunk_grow(ch, sz);
222272775Smarcel		if (sz == 0)
223272775Smarcel			return (0);
224272775Smarcel		from = ch->ch_block + (ch->ch_size / secsz);
225272775Smarcel	}
226272775Smarcel	ch = malloc(sizeof(*ch));
227272775Smarcel	if (ch == NULL)
228272775Smarcel		return (ENOMEM);
229272775Smarcel	memset(ch, 0, sizeof(*ch));
230272775Smarcel	ch->ch_block = from;
231272775Smarcel	ch->ch_size = sz;
232272775Smarcel	ch->ch_type = CH_TYPE_ZEROES;
233272775Smarcel	STAILQ_INSERT_TAIL(&image_chunks, ch, ch_list);
234272775Smarcel	image_nchunks++;
235272775Smarcel	return (0);
236272775Smarcel}
237272775Smarcel
238272775Smarcelstatic int
239272775Smarcelimage_chunk_append(lba_t blk, size_t sz, off_t ofs, int fd)
240272775Smarcel{
241272775Smarcel	struct chunk *ch;
242272775Smarcel
243272775Smarcel	ch = STAILQ_LAST(&image_chunks, chunk, ch_list);
244272775Smarcel	if (ch != NULL && ch->ch_type == CH_TYPE_FILE) {
245272775Smarcel		if (fd == ch->ch_u.file.fd &&
246272775Smarcel		    blk == (lba_t)(ch->ch_block + (ch->ch_size / secsz)) &&
247272775Smarcel		    ofs == (off_t)(ch->ch_u.file.ofs + ch->ch_size)) {
248272775Smarcel			sz = image_chunk_grow(ch, sz);
249272775Smarcel			if (sz == 0)
250272775Smarcel				return (0);
251272775Smarcel			blk = ch->ch_block + (ch->ch_size / secsz);
252272775Smarcel			ofs = ch->ch_u.file.ofs + ch->ch_size;
253272775Smarcel		}
254272775Smarcel	}
255272775Smarcel	ch = malloc(sizeof(*ch));
256272775Smarcel	if (ch == NULL)
257272775Smarcel		return (ENOMEM);
258272775Smarcel	memset(ch, 0, sizeof(*ch));
259272775Smarcel	ch->ch_block = blk;
260272775Smarcel	ch->ch_size = sz;
261272775Smarcel	ch->ch_type = CH_TYPE_FILE;
262272775Smarcel	ch->ch_u.file.ofs = ofs;
263272775Smarcel	ch->ch_u.file.fd = fd;
264272775Smarcel	STAILQ_INSERT_TAIL(&image_chunks, ch, ch_list);
265272775Smarcel	image_nchunks++;
266272775Smarcel	return (0);
267272775Smarcel}
268272775Smarcel
269272775Smarcelstatic int
270272775Smarcelimage_chunk_copyin(lba_t blk, void *buf, size_t sz, off_t ofs, int fd)
271272775Smarcel{
272272775Smarcel	uint8_t *p = buf;
273272775Smarcel	int error;
274272775Smarcel
275272775Smarcel	error = 0;
276272775Smarcel	sz = (sz + secsz - 1) & ~(secsz - 1);
277272775Smarcel	while (!error && sz > 0) {
278272775Smarcel		if (is_empty_sector(p))
279272775Smarcel			error = image_chunk_skipto(blk + 1);
280272775Smarcel		else
281272775Smarcel			error = image_chunk_append(blk, secsz, ofs, fd);
282272775Smarcel		blk++;
283272775Smarcel		p += secsz;
284272775Smarcel		sz -= secsz;
285272775Smarcel		ofs += secsz;
286272775Smarcel	}
287272775Smarcel	return (error);
288272775Smarcel}
289272775Smarcel
290272775Smarcel/*
291272775Smarcel * File mapping support.
292272775Smarcel */
293272775Smarcel
294272775Smarcelstatic void *
295272775Smarcelimage_file_map(int fd, off_t ofs, size_t sz)
296272775Smarcel{
297272775Smarcel	void *ptr;
298272775Smarcel	size_t unit;
299272775Smarcel	int flags, prot;
300272775Smarcel
301272775Smarcel	unit = (secsz > image_swap_pgsz) ? secsz : image_swap_pgsz;
302272775Smarcel	assert((unit & (unit - 1)) == 0);
303272775Smarcel
304272775Smarcel	flags = MAP_NOCORE | MAP_NOSYNC | MAP_SHARED;
305272775Smarcel	/* Allow writing to our swap file only. */
306272775Smarcel	prot = PROT_READ | ((fd == image_swap_fd) ? PROT_WRITE : 0);
307272775Smarcel	sz = (sz + unit - 1) & ~(unit - 1);
308272775Smarcel	ptr = mmap(NULL, sz, prot, flags, fd, ofs);
309272775Smarcel	return ((ptr == MAP_FAILED) ? NULL : ptr);
310272775Smarcel}
311272775Smarcel
312272775Smarcelstatic int
313272775Smarcelimage_file_unmap(void *buffer, size_t sz)
314272775Smarcel{
315272775Smarcel	size_t unit;
316272775Smarcel
317272775Smarcel	unit = (secsz > image_swap_pgsz) ? secsz : image_swap_pgsz;
318272775Smarcel	sz = (sz + unit - 1) & ~(unit - 1);
319302924Smarkj	if (madvise(buffer, sz, MADV_DONTNEED) != 0)
320302924Smarkj		warn("madvise");
321272775Smarcel	munmap(buffer, sz);
322272775Smarcel	return (0);
323272775Smarcel}
324272775Smarcel
325272775Smarcel/*
326272775Smarcel * Input/source file handling.
327272775Smarcel */
328272775Smarcel
329272775Smarcelstatic int
330272775Smarcelimage_copyin_stream(lba_t blk, int fd, uint64_t *sizep)
331272775Smarcel{
332265574Smarcel	char *buffer;
333265574Smarcel	uint64_t bytesize;
334272775Smarcel	off_t swofs;
335272775Smarcel	size_t iosz;
336272775Smarcel	ssize_t rdsz;
337272775Smarcel	int error;
338265574Smarcel
339272775Smarcel	/*
340272775Smarcel	 * This makes sure we're doing I/O in multiples of the page
341272775Smarcel	 * size as well as of the sector size. 2MB is the minimum
342272775Smarcel	 * by virtue of secsz at least 512 bytes and the page size
343272775Smarcel	 * at least 4K bytes.
344272775Smarcel	 */
345272775Smarcel	iosz = secsz * image_swap_pgsz;
346265574Smarcel
347265574Smarcel	bytesize = 0;
348272775Smarcel	do {
349272775Smarcel		swofs = image_swap_alloc(iosz);
350272775Smarcel		if (swofs == -1LL)
351272775Smarcel			return (errno);
352272775Smarcel		buffer = image_file_map(image_swap_fd, swofs, iosz);
353272775Smarcel		if (buffer == NULL)
354272775Smarcel			return (errno);
355272775Smarcel		rdsz = read(fd, buffer, iosz);
356272775Smarcel		if (rdsz > 0)
357272775Smarcel			error = image_chunk_copyin(blk, buffer, rdsz, swofs,
358272775Smarcel			    image_swap_fd);
359272775Smarcel		else if (rdsz < 0)
360272775Smarcel			error = errno;
361272775Smarcel		else
362272775Smarcel			error = 0;
363272775Smarcel		image_file_unmap(buffer, iosz);
364272775Smarcel		/* XXX should we relinguish unused swap space? */
365272775Smarcel		if (error)
366272775Smarcel			return (error);
367272775Smarcel
368272775Smarcel		bytesize += rdsz;
369272775Smarcel		blk += (rdsz + secsz - 1) / secsz;
370272775Smarcel	} while (rdsz > 0);
371272775Smarcel
372272775Smarcel	if (sizep != NULL)
373272775Smarcel		*sizep = bytesize;
374272775Smarcel	return (0);
375272775Smarcel}
376272775Smarcel
377272775Smarcelstatic int
378272775Smarcelimage_copyin_mapped(lba_t blk, int fd, uint64_t *sizep)
379272775Smarcel{
380272775Smarcel	off_t cur, data, end, hole, pos;
381272775Smarcel	void *buf;
382272775Smarcel	uint64_t bytesize;
383272775Smarcel	size_t iosz, sz;
384272775Smarcel	int error;
385272775Smarcel
386272775Smarcel	/*
387272775Smarcel	 * We'd like to know the size of the file and we must
388272775Smarcel	 * be able to seek in order to mmap(2). If this isn't
389272775Smarcel	 * possible, then treat the file as a stream/pipe.
390272775Smarcel	 */
391272775Smarcel	end = lseek(fd, 0L, SEEK_END);
392272775Smarcel	if (end == -1L)
393272775Smarcel		return (image_copyin_stream(blk, fd, sizep));
394272775Smarcel
395272775Smarcel	/*
396272775Smarcel	 * We need the file opened for the duration and our
397272775Smarcel	 * caller is going to close the file. Make a dup(2)
398272775Smarcel	 * so that control the faith of the descriptor.
399272775Smarcel	 */
400272775Smarcel	fd = dup(fd);
401272775Smarcel	if (fd == -1)
402272775Smarcel		return (errno);
403272775Smarcel
404272775Smarcel	iosz = secsz * image_swap_pgsz;
405272775Smarcel
406272775Smarcel	bytesize = 0;
407272775Smarcel	cur = pos = 0;
408272775Smarcel	error = 0;
409272775Smarcel	while (!error && cur < end) {
410272775Smarcel		hole = lseek(fd, cur, SEEK_HOLE);
411274634Smarcel		if (hole == -1)
412274634Smarcel			hole = end;
413272775Smarcel		data = lseek(fd, cur, SEEK_DATA);
414274634Smarcel		if (data == -1)
415274634Smarcel			data = end;
416272775Smarcel
417272775Smarcel		/*
418272775Smarcel		 * Treat the entire file as data if sparse files
419272775Smarcel		 * are not supported by the underlying file system.
420272775Smarcel		 */
421274634Smarcel		if (hole == end && data == end)
422272775Smarcel			data = cur;
423272775Smarcel
424272775Smarcel		if (cur == hole && data > hole) {
425272775Smarcel			hole = pos;
426272775Smarcel			pos = data & ~((uint64_t)secsz - 1);
427272775Smarcel
428272775Smarcel			blk += (pos - hole) / secsz;
429272775Smarcel			error = image_chunk_skipto(blk);
430272775Smarcel
431272775Smarcel			bytesize += pos - hole;
432272775Smarcel			cur = data;
433272775Smarcel		} else if (cur == data && hole > data) {
434272775Smarcel			data = pos;
435272775Smarcel			pos = (hole + secsz - 1) & ~((uint64_t)secsz - 1);
436272775Smarcel
437272775Smarcel			while (data < pos) {
438272775Smarcel				sz = (pos - data > (off_t)iosz)
439272775Smarcel				    ? iosz : (size_t)(pos - data);
440272775Smarcel
441272775Smarcel				buf = image_file_map(fd, data, sz);
442272775Smarcel				if (buf != NULL) {
443272775Smarcel					error = image_chunk_copyin(blk, buf,
444272775Smarcel					    sz, data, fd);
445272775Smarcel					image_file_unmap(buf, sz);
446272775Smarcel				} else
447272775Smarcel					error = errno;
448272775Smarcel
449272775Smarcel				blk += sz / secsz;
450272775Smarcel				bytesize += sz;
451272775Smarcel				data += sz;
452272775Smarcel			}
453272775Smarcel			cur = hole;
454272775Smarcel		} else {
455272775Smarcel			/*
456272775Smarcel			 * I don't know what this means or whether it
457272775Smarcel			 * can happen at all...
458272775Smarcel			 */
459272775Smarcel			error = EDOOFUS;
460265574Smarcel			break;
461265574Smarcel		}
462265574Smarcel	}
463272775Smarcel	if (error)
464272775Smarcel		close(fd);
465272775Smarcel	if (!error && sizep != NULL)
466265574Smarcel		*sizep = bytesize;
467265574Smarcel	return (error);
468265574Smarcel}
469265574Smarcel
470265574Smarcelint
471272775Smarcelimage_copyin(lba_t blk, int fd, uint64_t *sizep)
472272775Smarcel{
473272775Smarcel	struct stat sb;
474272775Smarcel	int error;
475272775Smarcel
476272775Smarcel	error = image_chunk_skipto(blk);
477272775Smarcel	if (!error) {
478272775Smarcel		if (fstat(fd, &sb) == -1 || !S_ISREG(sb.st_mode))
479272775Smarcel			error = image_copyin_stream(blk, fd, sizep);
480272775Smarcel		else
481272775Smarcel			error = image_copyin_mapped(blk, fd, sizep);
482272775Smarcel	}
483272775Smarcel	return (error);
484272775Smarcel}
485272775Smarcel
486272775Smarcel/*
487272775Smarcel * Output/sink file handling.
488272775Smarcel */
489272775Smarcel
490272775Smarcelint
491265618Smarcelimage_copyout(int fd)
492265574Smarcel{
493269177Smarcel	int error;
494269177Smarcel
495269177Smarcel	error = image_copyout_region(fd, 0, image_size);
496269177Smarcel	if (!error)
497269177Smarcel		error = image_copyout_done(fd);
498269177Smarcel	return (error);
499269177Smarcel}
500269177Smarcel
501269177Smarcelint
502269177Smarcelimage_copyout_done(int fd)
503269177Smarcel{
504269177Smarcel	off_t ofs;
505269177Smarcel	int error;
506269177Smarcel
507269177Smarcel	ofs = lseek(fd, 0L, SEEK_CUR);
508269177Smarcel	if (ofs == -1)
509269177Smarcel		return (0);
510269177Smarcel	error = (ftruncate(fd, ofs) == -1) ? errno : 0;
511269177Smarcel	return (error);
512269177Smarcel}
513269177Smarcel
514272775Smarcelstatic int
515272775Smarcelimage_copyout_memory(int fd, size_t size, void *ptr)
516272775Smarcel{
517272775Smarcel
518272775Smarcel	if (write(fd, ptr, size) == -1)
519272775Smarcel		return (errno);
520272775Smarcel	return (0);
521272775Smarcel}
522272775Smarcel
523287122Smarcelint
524287122Smarcelimage_copyout_zeroes(int fd, size_t count)
525272775Smarcel{
526272775Smarcel	static uint8_t *zeroes = NULL;
527272775Smarcel	size_t sz;
528272775Smarcel	int error;
529272775Smarcel
530287122Smarcel	if (lseek(fd, (off_t)count, SEEK_CUR) != -1)
531272775Smarcel		return (0);
532272775Smarcel
533272775Smarcel	/*
534272775Smarcel	 * If we can't seek, we must write.
535272775Smarcel	 */
536272775Smarcel
537272775Smarcel	if (zeroes == NULL) {
538272775Smarcel		zeroes = calloc(1, secsz);
539272775Smarcel		if (zeroes == NULL)
540272775Smarcel			return (ENOMEM);
541272775Smarcel	}
542272775Smarcel
543287122Smarcel	while (count > 0) {
544287122Smarcel		sz = (count > secsz) ? secsz : count;
545272775Smarcel		error = image_copyout_memory(fd, sz, zeroes);
546272775Smarcel		if (error)
547272775Smarcel			return (error);
548287122Smarcel		count -= sz;
549272775Smarcel	}
550272775Smarcel	return (0);
551272775Smarcel}
552272775Smarcel
553272775Smarcelstatic int
554272775Smarcelimage_copyout_file(int fd, size_t size, int ifd, off_t iofs)
555272775Smarcel{
556272775Smarcel	void *buf;
557272775Smarcel	size_t iosz, sz;
558272775Smarcel	int error;
559272775Smarcel
560272775Smarcel	iosz = secsz * image_swap_pgsz;
561272775Smarcel
562272775Smarcel	while (size > 0) {
563272775Smarcel		sz = (size > iosz) ? iosz : size;
564272775Smarcel		buf = image_file_map(ifd, iofs, sz);
565272775Smarcel		if (buf == NULL)
566272775Smarcel			return (errno);
567272775Smarcel		error = image_copyout_memory(fd, sz, buf);
568272775Smarcel		image_file_unmap(buf, sz);
569272775Smarcel		if (error)
570272775Smarcel			return (error);
571272775Smarcel		size -= sz;
572272775Smarcel		iofs += sz;
573272775Smarcel	}
574272775Smarcel	return (0);
575272775Smarcel}
576272775Smarcel
577269177Smarcelint
578269177Smarcelimage_copyout_region(int fd, lba_t blk, lba_t size)
579269177Smarcel{
580272775Smarcel	struct chunk *ch;
581272775Smarcel	size_t ofs, sz;
582265618Smarcel	int error;
583265574Smarcel
584272775Smarcel	size *= secsz;
585265618Smarcel
586269177Smarcel	while (size > 0) {
587272775Smarcel		ch = image_chunk_find(blk);
588272775Smarcel		if (ch == NULL)
589272775Smarcel			return (EINVAL);
590272775Smarcel		ofs = (blk - ch->ch_block) * secsz;
591272775Smarcel		sz = ch->ch_size - ofs;
592272775Smarcel		sz = ((lba_t)sz < size) ? sz : (size_t)size;
593272775Smarcel		switch (ch->ch_type) {
594272775Smarcel		case CH_TYPE_ZEROES:
595272775Smarcel			error = image_copyout_zeroes(fd, sz);
596265618Smarcel			break;
597272775Smarcel		case CH_TYPE_FILE:
598272775Smarcel			error = image_copyout_file(fd, sz, ch->ch_u.file.fd,
599272775Smarcel			    ch->ch_u.file.ofs + ofs);
600265618Smarcel			break;
601272775Smarcel		case CH_TYPE_MEMORY:
602272775Smarcel			error = image_copyout_memory(fd, sz, ch->ch_u.mem.ptr);
603272775Smarcel			break;
604272775Smarcel		default:
605272775Smarcel			return (EDOOFUS);
606265618Smarcel		}
607272775Smarcel		size -= sz;
608272775Smarcel		blk += sz / secsz;
609265618Smarcel	}
610272775Smarcel	return (0);
611265618Smarcel}
612265618Smarcel
613269177Smarcelint
614269177Smarcelimage_data(lba_t blk, lba_t size)
615269177Smarcel{
616272775Smarcel	struct chunk *ch;
617272775Smarcel	lba_t lim;
618269177Smarcel
619272775Smarcel	while (1) {
620272775Smarcel		ch = image_chunk_find(blk);
621272775Smarcel		if (ch == NULL)
622272775Smarcel			return (0);
623272775Smarcel		if (ch->ch_type != CH_TYPE_ZEROES)
624272775Smarcel			return (1);
625272775Smarcel		lim = ch->ch_block + (ch->ch_size / secsz);
626272775Smarcel		if (lim >= blk + size)
627272775Smarcel			return (0);
628272775Smarcel		size -= lim - blk;
629272775Smarcel		blk = lim;
630269177Smarcel	}
631272775Smarcel	/*NOTREACHED*/
632269177Smarcel}
633269177Smarcel
634265725Smarcellba_t
635265725Smarcelimage_get_size(void)
636265725Smarcel{
637265725Smarcel
638265725Smarcel	return (image_size);
639265725Smarcel}
640265725Smarcel
641265618Smarcelint
642265618Smarcelimage_set_size(lba_t blk)
643265618Smarcel{
644272775Smarcel	int error;
645265618Smarcel
646272775Smarcel	error = image_chunk_skipto(blk);
647272775Smarcel	if (!error)
648272775Smarcel		image_size = blk;
649272775Smarcel	return (error);
650265574Smarcel}
651265574Smarcel
652265574Smarcelint
653265618Smarcelimage_write(lba_t blk, void *buf, ssize_t len)
654265574Smarcel{
655272775Smarcel	struct chunk *ch;
656265574Smarcel
657272775Smarcel	while (len > 0) {
658272775Smarcel		if (!is_empty_sector(buf)) {
659272775Smarcel			ch = image_chunk_find(blk);
660272775Smarcel			if (ch == NULL)
661272775Smarcel				return (ENXIO);
662272775Smarcel			/* We may not be able to write to files. */
663272775Smarcel			if (ch->ch_type == CH_TYPE_FILE)
664272775Smarcel				return (EINVAL);
665272775Smarcel			if (ch->ch_type == CH_TYPE_ZEROES) {
666272775Smarcel				ch = image_chunk_memory(ch, blk);
667272775Smarcel				if (ch == NULL)
668272775Smarcel					return (ENOMEM);
669272775Smarcel			}
670272775Smarcel			assert(ch->ch_type == CH_TYPE_MEMORY);
671272775Smarcel			memcpy(ch->ch_u.mem.ptr, buf, secsz);
672272775Smarcel		}
673272775Smarcel		blk++;
674272775Smarcel		buf = (char *)buf + secsz;
675272775Smarcel		len--;
676272775Smarcel	}
677265574Smarcel	return (0);
678265574Smarcel}
679265618Smarcel
680272775Smarcelstatic void
681272775Smarcelimage_cleanup(void)
682272775Smarcel{
683272775Smarcel	struct chunk *ch;
684272775Smarcel
685272775Smarcel	while ((ch = STAILQ_FIRST(&image_chunks)) != NULL) {
686272775Smarcel		switch (ch->ch_type) {
687272775Smarcel		case CH_TYPE_FILE:
688272775Smarcel			/* We may be closing the same file multiple times. */
689272775Smarcel			if (ch->ch_u.file.fd != -1)
690272775Smarcel				close(ch->ch_u.file.fd);
691272775Smarcel			break;
692272775Smarcel		case CH_TYPE_MEMORY:
693272775Smarcel			free(ch->ch_u.mem.ptr);
694272775Smarcel			break;
695272775Smarcel		default:
696272775Smarcel			break;
697272775Smarcel		}
698272775Smarcel		STAILQ_REMOVE_HEAD(&image_chunks, ch_list);
699272775Smarcel		free(ch);
700272775Smarcel	}
701272775Smarcel	if (image_swap_fd != -1)
702272775Smarcel		close(image_swap_fd);
703272775Smarcel	unlink(image_swap_file);
704272775Smarcel}
705272775Smarcel
706265618Smarcelint
707265618Smarcelimage_init(void)
708265618Smarcel{
709268161Smarcel	const char *tmpdir;
710265618Smarcel
711272775Smarcel	STAILQ_INIT(&image_chunks);
712272775Smarcel	image_nchunks = 0;
713272775Smarcel
714272775Smarcel	image_swap_size = 0;
715272775Smarcel	image_swap_pgsz = getpagesize();
716272775Smarcel
717272775Smarcel	if (atexit(image_cleanup) == -1)
718265618Smarcel		return (errno);
719268161Smarcel	if ((tmpdir = getenv("TMPDIR")) == NULL || *tmpdir == '\0')
720268161Smarcel		tmpdir = _PATH_TMP;
721272775Smarcel	snprintf(image_swap_file, sizeof(image_swap_file), "%s/mkimg-XXXXXX",
722268161Smarcel	    tmpdir);
723272775Smarcel	image_swap_fd = mkstemp(image_swap_file);
724272775Smarcel	if (image_swap_fd == -1)
725265618Smarcel		return (errno);
726265618Smarcel	return (0);
727265618Smarcel}
728