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 *
e_alloc_fail(int flag)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 *
e_realloc(int flag,void * old,size_t newsize)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 *
e_strdup(int flag,const char * arg)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 *
e_valloc(int flag,size_t size)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 *
e_zalloc(int flag,size_t size)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
str_fprintf(FILE * fp,const char * fmt,...)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 *
get_holes_list(int fd,off_t filesz,size_t * countp)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
get_compressed_filesz(holes_list_t * hlh)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 *
put_value(off_t val,char * str)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
store_sparse_string(holes_list_t * hlh,char * str,size_t * szp)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 *
get_ull_tok(const char * str,uint64_t * ulp)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
free_holesdata(holes_info_t * hi)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 *
get_holes_info(int fd,off_t filesz,boolean_t pass_mode)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 *
read_holes_header(const char * str,off_t filesz)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
parse_holesdata(holes_info_t * hi,const char * str)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
free_holes_info(holes_info_t * hi)451 free_holes_info(holes_info_t *hi)
452 {
453 free_holesdata(hi);
454 free(hi);
455 }
456