1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21/*
22 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
23 * Use is subject to license terms.
24 */
25
26/*	Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T	*/
27/*	All Rights Reserved					*/
28
29/*
30 * Portions of this source code were derived from Berkeley 4.3 BSD
31 * under license from the Regents of the University of California.
32 */
33
34#include <stdio.h>
35#include <stdlib.h>
36#include <stdarg.h>
37#include <string.h>
38#include <sys/stat.h>
39#include <unistd.h>
40#include <ctype.h>
41#include <limits.h>
42#include <errno.h>
43#include <fcntl.h>
44
45#include "cpio.h"
46
47/*
48 * Allocation wrappers.  Used to centralize error handling for
49 * failed allocations.
50 */
51static void *
52e_alloc_fail(int flag)
53{
54	if (flag == E_EXIT)
55		msg(EXTN, "Out of memory");
56
57	return (NULL);
58}
59
60/*
61 *  Note: unlike the other e_*lloc functions, e_realloc does not zero out the
62 *  additional memory it returns.  Ensure that you do not trust its contents
63 *  when you call it.
64 */
65void *
66e_realloc(int flag, void *old, size_t newsize)
67{
68	void *ret = realloc(old, newsize);
69
70	if (ret == NULL) {
71		return (e_alloc_fail(flag));
72	}
73
74	return (ret);
75}
76
77char *
78e_strdup(int flag, const char *arg)
79{
80	char *ret = strdup(arg);
81
82	if (ret == NULL) {
83		return (e_alloc_fail(flag));
84	}
85
86	return (ret);
87}
88
89void *
90e_valloc(int flag, size_t size)
91{
92	void *ret = valloc(size);
93
94	if (ret == NULL) {
95		return (e_alloc_fail(flag));
96	}
97
98	return (ret);
99}
100
101void *
102e_zalloc(int flag, size_t size)
103{
104	void *ret = malloc(size);
105
106	if (ret == NULL) {
107		return (e_alloc_fail(flag));
108	}
109
110	(void) memset(ret, 0, size);
111	return (ret);
112}
113
114/*
115 * Simple printf() which only support "%s" conversion.
116 * We need secure version of printf since format string can be supplied
117 * from gettext().
118 */
119void
120str_fprintf(FILE *fp, const char *fmt, ...)
121{
122	const char *s = fmt;
123	va_list	ap;
124
125	va_start(ap, fmt);
126	while (*s != '\0') {
127		if (*s != '%') {
128			(void) fputc(*s++, fp);
129			continue;
130		}
131		s++;
132		if (*s != 's') {
133			(void) fputc(*(s - 1), fp);
134			(void) fputc(*s++, fp);
135			continue;
136		}
137		(void) fputs(va_arg(ap, char *), fp);
138		s++;
139	}
140	va_end(ap);
141}
142
143/*
144 * Step through a file discovering and recording pairs of data and hole
145 * offsets. Returns a linked list of data/hole offset pairs of a file.
146 * If there is no holes found, NULL is returned.
147 *
148 * Note: According to lseek(2), only filesystems which support
149 * fpathconf(_PC_MIN_HOLE_SIZE) support SEEK_HOLE.  For filesystems
150 * that do not supply information about holes, the file will be
151 * represented as one entire data region.
152 */
153static holes_list_t *
154get_holes_list(int fd, off_t filesz, size_t *countp)
155{
156	off_t	data, hole;
157	holes_list_t *hlh, *hl, **hlp;
158	size_t	cnt;
159
160	if (filesz == 0 || fpathconf(fd, _PC_MIN_HOLE_SIZE) < 0)
161		return (NULL);
162
163	cnt = 0;
164	hole = 0;
165	hlh = NULL;
166	hlp = &hlh;
167
168	while (hole < filesz) {
169		if ((data = lseek(fd, hole, SEEK_DATA)) == -1) {
170			/* no more data till the end of file */
171			if (errno == ENXIO) {
172				data = filesz;
173			} else {
174				/* assume data starts from the * beginning */
175				data = 0;
176			}
177		}
178		if ((hole = lseek(fd, data, SEEK_HOLE)) == -1) {
179			/* assume that data ends at the end of file */
180			hole = filesz;
181		}
182		if (data == 0 && hole == filesz) {
183			/* no holes */
184			break;
185		}
186		hl = e_zalloc(E_EXIT, sizeof (holes_list_t));
187		hl->hl_next = NULL;
188
189		/* set data and hole */
190		hl->hl_data = data;
191		hl->hl_hole = hole;
192
193		*hlp = hl;
194		hlp = &hl->hl_next;
195		cnt++;
196	}
197	if (countp != NULL)
198		*countp = cnt;
199
200	/*
201	 * reset to the beginning, otherwise subsequent read calls would
202	 * get EOF
203	 */
204	(void) lseek(fd, 0, SEEK_SET);
205
206	return (hlh);
207}
208
209/*
210 * Calculate the real data size in the sparse file.
211 */
212static off_t
213get_compressed_filesz(holes_list_t *hlh)
214{
215	holes_list_t *hl;
216	off_t	size;
217
218	size = 0;
219	for (hl = hlh; hl != NULL; hl = hl->hl_next) {
220		size += (hl->hl_hole - hl->hl_data);
221	}
222	return (size);
223}
224
225/*
226 * Convert val to digit string and put it in str. The next address
227 * of the last digit is returned.
228 */
229static char *
230put_value(off_t val, char *str)
231{
232	size_t	len;
233	char	*digp, dbuf[ULL_MAX_SIZE + 1];
234
235	dbuf[ULL_MAX_SIZE] = '\0';
236	digp = ulltostr((u_longlong_t)val, &dbuf[ULL_MAX_SIZE]);
237	len = &dbuf[ULL_MAX_SIZE] - digp;
238	(void) memcpy(str, digp, len);
239
240	return (str + len);
241}
242
243/*
244 * Put data/hole offset pair into string in the following
245 * sequence.
246 * <data> <sp> <hole> <sp>
247 */
248static void
249store_sparse_string(holes_list_t *hlh, char *str, size_t *szp)
250{
251	holes_list_t *hl;
252	char	*p;
253
254	p = str;
255	for (hl = hlh; hl != NULL; hl = hl->hl_next) {
256		p = put_value(hl->hl_data, p);
257		*p++ = ' ';
258		p = put_value(hl->hl_hole, p);
259		*p++ = ' ';
260	}
261	*--p = '\0';
262	if (szp != NULL)
263		*szp = p - str;
264}
265
266/*
267 * Convert decimal str into unsigned long long value. The end pointer
268 * is returned.
269 */
270static const char *
271get_ull_tok(const char *str, uint64_t *ulp)
272{
273	uint64_t ul;
274	char	*np;
275
276	while (isspace(*str))
277		str++;
278	if (!isdigit(*str))
279		return (NULL);
280
281	errno = 0;
282	ul = strtoull(str, &np, 10);
283	if (ul == ULLONG_MAX && errno == ERANGE)
284		return (NULL);		/* invalid value */
285	if (*np != ' ' && *np != '\0')
286		return (NULL);		/* invalid input */
287
288	*ulp = ul;
289	return (np);
290}
291
292static void
293free_holesdata(holes_info_t *hi)
294{
295	holes_list_t	*hl, *nhl;
296
297	for (hl = hi->holes_list; hl != NULL; hl = nhl) {
298		nhl = hl->hl_next;
299		free(hl);
300	}
301	hi->holes_list = NULL;
302
303	if (hi->holesdata != NULL)
304		free(hi->holesdata);
305	hi->holesdata = NULL;
306}
307
308/*
309 * When a hole is detected, non NULL holes_info pointer is returned.
310 * If we are in copy-out mode, holes_list is converted to string (holesdata)
311 * which will be prepended to file contents. The holesdata is a character
312 * string and in the format of:
313 *
314 * <data size(%10u)><SP><file size(%llu)><SP>
315 *   <SP><data off><SP><hole off><SP><data off><SP><hole off> ...
316 *
317 * This string is parsed by parse_holesholes() in copy-in mode to restore
318 * the sparse info.
319 */
320holes_info_t *
321get_holes_info(int fd, off_t filesz, boolean_t pass_mode)
322{
323	holes_info_t *hi;
324	holes_list_t *hl;
325	char	*str, hstr[MIN_HOLES_HDRSIZE + 1];
326	size_t	ninfo, len;
327
328	if ((hl = get_holes_list(fd, filesz, &ninfo)) == NULL)
329		return (NULL);
330
331	hi = e_zalloc(E_EXIT, sizeof (holes_info_t));
332	hi->holes_list = hl;
333
334	if (!pass_mode) {
335		str = e_zalloc(E_EXIT,
336		    MIN_HOLES_HDRSIZE + ninfo * (ULL_MAX_SIZE * 2));
337		/*
338		 * Convert into string data, and place it to after
339		 * the first 2 fixed entries.
340		 */
341		store_sparse_string(hl, str + MIN_HOLES_HDRSIZE, &len);
342
343		/*
344		 * Add the first two fixed entries. The size of holesdata
345		 * includes '\0' at the end of data
346		 */
347		(void) sprintf(hstr, "%10lu %20llu ",
348		    (ulong_t)MIN_HOLES_HDRSIZE + len + 1, filesz);
349		(void) memcpy(str, hstr, MIN_HOLES_HDRSIZE);
350
351		/* calc real file size without holes */
352		hi->data_size = get_compressed_filesz(hl);
353		hi->holesdata = str;
354		hi->holesdata_sz = MIN_HOLES_HDRSIZE + len + 1;
355	}
356	return (hi);
357}
358
359/*
360 * The holesdata information is in the following format:
361 * <data size(%10u)><SP><file size(%llu)><SP>
362 *   <SP><data off><SP><hole off><SP><data off><SP><hole off> ...
363 * read_holes_header() allocates holes_info_t, and read the first 2
364 * entries (data size and file size). The rest of holesdata is
365 * read by parse_holesdata().
366 */
367holes_info_t *
368read_holes_header(const char *str, off_t filesz)
369{
370	holes_info_t	*hi;
371	uint64_t	ull;
372
373	hi = e_zalloc(E_EXIT, sizeof (holes_info_t));
374
375	/* read prepended holes data size */
376	if ((str = get_ull_tok(str, &ull)) == NULL || *str != ' ') {
377bad:
378		free(hi);
379		return (NULL);
380	}
381	hi->holesdata_sz = (size_t)ull;
382
383	/* read original(expanded) file size */
384	if (get_ull_tok(str, &ull) == NULL)
385		goto bad;
386	hi->orig_size = (off_t)ull;
387
388	/* sanity check */
389	if (hi->holesdata_sz > filesz ||
390	    hi->holesdata_sz <= MIN_HOLES_HDRSIZE) {
391		goto bad;
392	}
393	return (hi);
394}
395
396int
397parse_holesdata(holes_info_t *hi, const char *str)
398{
399	holes_list_t	*hl, **hlp;
400	uint64_t	ull;
401	off_t		loff;
402
403	/* create hole list */
404	hlp = &hi->holes_list;
405	while (*str != '\0') {
406		hl = e_zalloc(E_EXIT, sizeof (holes_list_t));
407		/* link list */
408		hl->hl_next = NULL;
409		*hlp = hl;
410		hlp = &hl->hl_next;
411
412		/* read the string token for data */
413		if ((str = get_ull_tok(str, &ull)) == NULL)
414			goto bad;
415		hl->hl_data = (off_t)ull;
416
417		/* there must be single blank space in between */
418		if (*str != ' ')
419			goto bad;
420
421		/* read the string token for hole */
422		if ((str = get_ull_tok(str, &ull)) == NULL)
423			goto bad;
424		hl->hl_hole = (off_t)ull;
425	}
426
427	/* check to see if offset is in ascending order */
428	loff = -1;
429	for (hl = hi->holes_list; hl != NULL; hl = hl->hl_next) {
430		if (loff >= hl->hl_data)
431			goto bad;
432		loff = hl->hl_data;
433		/* data and hole can be equal */
434		if (loff > hl->hl_hole)
435			goto bad;
436		loff = hl->hl_hole;
437	}
438	/* The last hole offset should match original file size */
439	if (hi->orig_size != loff) {
440bad:
441		free_holesdata(hi);
442		return (1);
443	}
444
445	hi->data_size = get_compressed_filesz(hi->holes_list);
446
447	return (0);
448}
449
450void
451free_holes_info(holes_info_t *hi)
452{
453	free_holesdata(hi);
454	free(hi);
455}
456