xref: /illumos-gate/usr/src/cmd/cpio/utils.c (revision 6bb6b5762ca4b17cd5fb3c6c123f17489d5635aa)
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  */
51 static void *
52 e_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  */
65 void *
66 e_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 
77 char *
78 e_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 
89 void *
90 e_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 
101 void *
102 e_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  */
119 void
120 str_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  */
153 static holes_list_t *
154 get_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  */
212 static off_t
213 get_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  */
229 static char *
230 put_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  */
248 static void
249 store_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  */
270 static const char *
271 get_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 
292 static void
293 free_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  */
320 holes_info_t *
321 get_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  */
367 holes_info_t *
368 read_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 != ' ') {
377 bad:
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 
396 int
397 parse_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) {
440 bad:
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 
450 void
451 free_holes_info(holes_info_t *hi)
452 {
453 	free_holesdata(hi);
454 	free(hi);
455 }
456