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