1 /* 2 * Copyright (c) 2011 Alex Hornung <alex@alexhornung.com>. 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in 13 * the documentation and/or other materials provided with the 14 * distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 19 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 20 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 21 * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, 22 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 24 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 25 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 26 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 * SUCH DAMAGE. 28 */ 29 30 #include <stdlib.h> 31 #include <unistd.h> 32 #include <errno.h> 33 #include <string.h> 34 #include <stdio.h> 35 #include <stdarg.h> 36 37 #include "tcplay.h" 38 #include "tcplay_api.h" 39 #include "tcplay_api_internal.h" 40 41 42 int 43 tc_api_init(int verbose) 44 { 45 int error; 46 47 tc_internal_verbose = verbose; 48 49 if ((error = tc_play_init()) != 0) 50 return TC_ERR; 51 else 52 return TC_OK; 53 } 54 55 int 56 tc_api_uninit(void) 57 { 58 check_and_purge_safe_mem(); 59 return TC_OK; 60 } 61 62 63 static const char *_caps[] = { 64 "trim", 65 NULL 66 }; 67 68 int 69 tc_api_has(const char *feature) 70 { 71 const char *cap; 72 int i; 73 74 for (cap = _caps[0], i = 0; cap != NULL; cap = _caps[++i]) { 75 if ((strcmp(cap, feature)) == 0) 76 return TC_OK; 77 } 78 79 return TC_ERR_UNIMPL; 80 } 81 82 int 83 tc_api_cipher_iterate(tc_api_cipher_iterator_fn fn, void *priv) 84 { 85 int i; 86 struct tc_cipher_chain *chain; 87 int klen; 88 int length; 89 char buf[1024]; 90 91 if (fn == NULL) { 92 errno = EFAULT; 93 return TC_ERR; 94 } 95 96 for (i = 0, chain = tc_cipher_chains[0]; chain != NULL; 97 chain = tc_cipher_chains[++i]) { 98 tc_cipher_chain_sprint(buf, sizeof(buf), chain); 99 klen = tc_cipher_chain_klen(chain); 100 length = tc_cipher_chain_length(chain); 101 if ((fn(priv, buf, klen, length)) < 0) 102 break; 103 } 104 105 return TC_OK; 106 } 107 108 int 109 tc_api_prf_iterate(tc_api_prf_iterator_fn fn, void *priv) 110 { 111 int i; 112 113 if (fn == NULL) { 114 errno = EFAULT; 115 return TC_ERR; 116 } 117 118 for (i = 0; pbkdf_prf_algos[i].name != NULL; i++) { 119 /* Skip over sys PRFs */ 120 if (pbkdf_prf_algos[i].sys) 121 continue; 122 123 if ((fn(priv, pbkdf_prf_algos[i].name)) < 0) 124 break; 125 } 126 127 return TC_OK; 128 } 129 130 131 const char * 132 tc_api_task_get_error(tc_api_task task __unused) 133 { 134 return tc_internal_log_buffer; 135 } 136 137 138 #define _match(k, v) (strcmp(k, v) == 0) 139 140 tc_api_task 141 tc_api_task_init(const char *op) 142 { 143 tc_api_task task = NULL; 144 int fail = 1; 145 146 if ((task = alloc_safe_mem(sizeof(*task))) == NULL) { 147 errno = ENOMEM; 148 goto out; 149 } 150 151 if ((task->opts = opts_init()) == NULL) { 152 errno = ENOMEM; 153 goto out; 154 } 155 156 if (_match(op, "create")) { 157 task->op = TC_OP_CREATE; 158 } else if (_match(op, "map")) { 159 task->op = TC_OP_MAP; 160 } else if (_match(op, "unmap")) { 161 task->op = TC_OP_UNMAP; 162 } else if (_match(op, "info")) { 163 task->op = TC_OP_INFO; 164 } else if (_match(op, "info_mapped")) { 165 task->op = TC_OP_INFO_MAPPED; 166 } else if (_match(op, "modify")) { 167 task->op = TC_OP_MODIFY; 168 } else if (_match(op, "restore")) { 169 task->op = TC_OP_RESTORE; 170 } else { 171 errno = EINVAL; 172 goto out; 173 } 174 175 fail = 0; 176 177 out: 178 if (fail && task != NULL) { 179 if (task->opts != NULL) 180 opts_free(task->opts); 181 free_safe_mem(task); 182 } 183 184 return fail ? NULL : task; 185 } 186 187 int 188 tc_api_task_uninit(tc_api_task task) 189 { 190 if (task->last_info != NULL) 191 free_info(task->last_info); 192 opts_free(task->opts); 193 free_safe_mem(task); 194 195 return TC_OK; 196 } 197 198 199 #define _set_str(k) \ 200 do { \ 201 if ((opts->k = strdup_safe_mem(s)) == NULL) { \ 202 errno = ENOMEM; \ 203 r = TC_ERR; \ 204 goto out; \ 205 } \ 206 } while (0) 207 208 #define _clr_str(k) \ 209 do { \ 210 if (opts->k) \ 211 free_safe_mem(opts->k); \ 212 opts->k = NULL; \ 213 } while (0) 214 215 int 216 tc_api_task_set(tc_api_task task, const char *key, ...) 217 { 218 struct tcplay_opts *opts; 219 va_list ap; 220 const char *s; 221 int64_t i64; 222 int i; 223 tc_api_state_change_fn sc_fn; 224 void *vp; 225 int r = TC_OK; 226 227 if (task == NULL || key == NULL || ((opts = task->opts) == NULL)) { 228 errno = EFAULT; 229 return TC_ERR; 230 } 231 232 va_start(ap, key); 233 234 if (_match(key, "interactive")) { 235 i = va_arg(ap, int); 236 opts->interactive = i; 237 } else if (_match(key, "weak_keys_and_salt")) { 238 i = va_arg(ap, int); 239 opts->weak_keys_and_salt = i; 240 } else if (_match(key, "secure_erase")) { 241 i = va_arg(ap, int); 242 opts->secure_erase = i; 243 } else if (_match(key, "protect_hidden")) { 244 i = va_arg(ap, int); 245 opts->protect_hidden = i; 246 } else if (_match(key, "fde")) { 247 i = va_arg(ap, int); 248 if (i) 249 opts->flags |= TC_FLAG_FDE; 250 else 251 opts->flags &= ~TC_FLAG_FDE; 252 } else if (_match(key, "use_backup_header")) { 253 i = va_arg(ap, int); 254 if (i) 255 opts->flags |= TC_FLAG_BACKUP; 256 else 257 opts->flags &= ~TC_FLAG_BACKUP; 258 } else if (_match(key, "allow_trim")) { 259 i = va_arg(ap, int); 260 if (i) 261 opts->flags |= TC_FLAG_ALLOW_TRIM; 262 else 263 opts->flags &= ~TC_FLAG_ALLOW_TRIM; 264 } else if (_match(key, "hidden_size_bytes")) { 265 i64 = va_arg(ap, int64_t); 266 opts->hidden_size_bytes = (disksz_t)i64; 267 opts->hidden = (i64 > 0); 268 } else if (_match(key, "retries")) { 269 i = va_arg(ap, int); 270 opts->retries = i; 271 } else if (_match(key, "timeout")) { 272 i = va_arg(ap, int); 273 opts->timeout = (time_t)i; 274 } else if (_match(key, "save_header_to_file")) { 275 s = va_arg(ap, const char *); 276 if (s != NULL) { 277 _set_str(hdr_file_out); 278 opts->flags |= TC_FLAG_SAVE_TO_FILE; 279 } else { 280 _clr_str(hdr_file_out); 281 opts->flags &= ~TC_FLAG_SAVE_TO_FILE; 282 } 283 } else if (_match(key, "header_from_file")) { 284 s = va_arg(ap, const char *); 285 if (s != NULL) { 286 _set_str(hdr_file_in); 287 opts->flags |= TC_FLAG_HDR_FROM_FILE; 288 } else { 289 _clr_str(hdr_file_in); 290 opts->flags &= ~TC_FLAG_HDR_FROM_FILE; 291 } 292 } else if (_match(key, "hidden_header_from_file")) { 293 s = va_arg(ap, const char *); 294 if (s != NULL) { 295 _set_str(h_hdr_file_in); 296 opts->flags |= TC_FLAG_H_HDR_FROM_FILE; 297 } else { 298 _clr_str(h_hdr_file_in); 299 opts->flags &= ~TC_FLAG_H_HDR_FROM_FILE; 300 } 301 } else if (_match(key, "sys")) { 302 s = va_arg(ap, const char *); 303 if (s != NULL) { 304 _set_str(sys_dev); 305 opts->flags |= TC_FLAG_SYS; 306 } else { 307 _clr_str(sys_dev); 308 opts->flags &= ~TC_FLAG_SYS; 309 } 310 } else if (_match(key, "passphrase")) { 311 s = va_arg(ap, const char *); 312 if (s != NULL) { 313 _set_str(passphrase); 314 } else { 315 _clr_str(passphrase); 316 } 317 } else if (_match(key, "h_passphrase")) { 318 s = va_arg(ap, const char *); 319 if (s != NULL) { 320 _set_str(h_passphrase); 321 } else { 322 _clr_str(h_passphrase); 323 } 324 } else if (_match(key, "new_passphrase")) { 325 s = va_arg(ap, const char *); 326 if (s != NULL) { 327 _set_str(new_passphrase); 328 } else { 329 _clr_str(new_passphrase); 330 } 331 } else if (_match(key, "dev")) { 332 s = va_arg(ap, const char *); 333 if (s != NULL) { 334 _set_str(dev); 335 } else { 336 _clr_str(dev); 337 } 338 } else if (_match(key, "map_name")) { 339 s = va_arg(ap, const char *); 340 if (s != NULL) { 341 _set_str(map_name); 342 } else { 343 _clr_str(map_name); 344 } 345 } else if (_match(key, "keyfiles")) { 346 s = va_arg(ap, const char *); 347 if (s != NULL) { 348 opts_add_keyfile(opts, s); 349 } else { 350 opts_clear_keyfile(opts); 351 } 352 } else if (_match(key, "h_keyfiles")) { 353 s = va_arg(ap, const char *); 354 if (s != NULL) { 355 opts_add_keyfile_hidden(opts, s); 356 } else { 357 opts_clear_keyfile_hidden(opts); 358 } 359 } else if (_match(key, "new_keyfiles")) { 360 s = va_arg(ap, const char *); 361 if (s != NULL) { 362 opts_add_keyfile_new(opts, s); 363 } else { 364 opts_clear_keyfile_new(opts); 365 } 366 } else if (_match(key, "prf_algo")) { 367 s = va_arg(ap, const char *); 368 if (s != NULL) { 369 if ((opts->prf_algo = check_prf_algo(s, 0, 1)) == NULL) { 370 errno = ENOENT; 371 r = TC_ERR; 372 goto out; 373 } 374 } else { 375 opts->prf_algo = NULL; 376 } 377 } else if (_match(key, "h_prf_algo")) { 378 s = va_arg(ap, const char *); 379 if (s != NULL) { 380 if ((opts->h_prf_algo = check_prf_algo(s, 0, 1)) == NULL) { 381 errno = ENOENT; 382 r = TC_ERR; 383 goto out; 384 } 385 } else { 386 opts->h_prf_algo = NULL; 387 } 388 } else if (_match(key, "new_prf_algo")) { 389 s = va_arg(ap, const char *); 390 if (s != NULL) { 391 if ((opts->new_prf_algo = check_prf_algo(s, 0, 1)) == NULL) { 392 errno = ENOENT; 393 r = TC_ERR; 394 goto out; 395 } 396 } else { 397 opts->new_prf_algo = NULL; 398 } 399 } else if (_match(key, "cipher_chain")) { 400 s = va_arg(ap, const char *); 401 if (s != NULL) { 402 if ((opts->cipher_chain = check_cipher_chain(s, 1)) == NULL) { 403 errno = ENOENT; 404 r = TC_ERR; 405 goto out; 406 } 407 } else { 408 opts->cipher_chain = NULL; 409 } 410 } else if (_match(key, "h_cipher_chain")) { 411 s = va_arg(ap, const char *); 412 if (s != NULL) { 413 if ((opts->h_cipher_chain = check_cipher_chain(s, 1)) == NULL) { 414 errno = ENOENT; 415 r = TC_ERR; 416 goto out; 417 } 418 } else { 419 opts->h_cipher_chain = NULL; 420 } 421 } else if (_match(key, "state_change_fn")) { 422 sc_fn = va_arg(ap, tc_api_state_change_fn); 423 opts->state_change_fn = sc_fn; 424 vp = va_arg(ap, void *); 425 opts->api_ctx = vp; 426 } else { 427 r = TC_ERR_UNIMPL; 428 } 429 430 out: 431 va_end(ap); 432 433 return r; 434 } 435 436 #define _not_null(x) \ 437 if (opts->x == NULL) { \ 438 return -1; \ 439 } 440 441 #define _null(x) \ 442 if (opts->x != NULL) { \ 443 return -1; \ 444 } 445 446 #define _zero(x) \ 447 if (opts->x != 0) { \ 448 return -1; \ 449 } 450 451 #define _not_set(x) \ 452 if (TC_FLAG_SET(opts->flags, x)) { \ 453 return -1; \ 454 } 455 456 static 457 int 458 _opts_check_create(struct tcplay_opts *opts) 459 { 460 _not_null(dev); 461 _not_set(SYS); 462 _not_set(FDE); 463 _not_set(BACKUP); 464 _not_set(ONLY_RESTORE); 465 _not_set(ALLOW_TRIM); 466 _not_set(SAVE_TO_FILE); 467 _not_set(HDR_FROM_FILE); 468 _not_set(H_HDR_FROM_FILE); 469 470 _null(map_name); 471 _zero(protect_hidden); 472 _null(new_passphrase); 473 _null(new_prf_algo); 474 _zero(n_newkeyfiles); 475 476 if (opts->hidden_size_bytes && !opts->hidden) { 477 return -1; 478 } 479 480 return 0; 481 } 482 483 static 484 int 485 _opts_check_map(struct tcplay_opts *opts) 486 { 487 _not_null(dev); 488 _not_null(map_name); 489 _not_set(ONLY_RESTORE); 490 _not_set(SAVE_TO_FILE); 491 _zero(hidden); 492 _zero(hidden_size_bytes); 493 _null(new_passphrase); 494 _null(new_prf_algo); 495 _zero(n_newkeyfiles); 496 _null(prf_algo); 497 _null(h_prf_algo); 498 _null(cipher_chain); 499 _null(h_cipher_chain); 500 501 if (!opts->protect_hidden) { 502 _zero(n_hkeyfiles); 503 //_null(h_passphrase); 504 } 505 506 return 0; 507 } 508 509 static 510 int 511 _opts_check_unmap(struct tcplay_opts *opts) 512 { 513 _not_null(map_name); 514 /* XXX: _not_null(dev); ? */ 515 _zero(nkeyfiles); 516 _zero(n_hkeyfiles); 517 _null(prf_algo); 518 _null(cipher_chain); 519 _null(h_prf_algo); 520 _null(h_cipher_chain); 521 _null(passphrase); 522 _null(h_passphrase); 523 _zero(hidden); 524 _zero(protect_hidden); 525 _null(new_prf_algo); 526 _null(new_passphrase); 527 _zero(n_newkeyfiles); 528 _not_set(SYS); 529 _not_set(FDE); 530 _not_set(BACKUP); 531 _not_set(ONLY_RESTORE); 532 _not_set(ALLOW_TRIM); 533 _not_set(SAVE_TO_FILE); 534 _not_set(HDR_FROM_FILE); 535 _not_set(H_HDR_FROM_FILE); 536 537 return 0; 538 } 539 540 static 541 int 542 _opts_check_info(struct tcplay_opts *opts) 543 { 544 _not_null(dev); 545 _null(map_name); 546 _not_set(ONLY_RESTORE); 547 _not_set(SAVE_TO_FILE); 548 _zero(hidden); 549 _zero(hidden_size_bytes); 550 _null(new_passphrase); 551 _null(new_prf_algo); 552 _zero(n_newkeyfiles); 553 _null(prf_algo); 554 _null(h_prf_algo); 555 _null(cipher_chain); 556 _null(h_cipher_chain); 557 558 if (!opts->protect_hidden) { 559 _zero(n_hkeyfiles); 560 //_null(h_passphrase); 561 } 562 563 return 0; 564 } 565 566 static 567 int 568 _opts_check_info_mapped(struct tcplay_opts *opts) 569 { 570 _not_null(map_name); 571 /* XXX: _not_null(dev); ? */ 572 _zero(nkeyfiles); 573 _zero(n_hkeyfiles); 574 _null(prf_algo); 575 _null(cipher_chain); 576 _null(h_prf_algo); 577 _null(h_cipher_chain); 578 _null(passphrase); 579 _null(h_passphrase); 580 _zero(hidden); 581 _zero(protect_hidden); 582 _null(new_prf_algo); 583 _null(new_passphrase); 584 _zero(n_newkeyfiles); 585 _not_set(SYS); 586 _not_set(FDE); 587 _not_set(BACKUP); 588 _not_set(ONLY_RESTORE); 589 _not_set(ALLOW_TRIM); 590 _not_set(SAVE_TO_FILE); 591 _not_set(HDR_FROM_FILE); 592 _not_set(H_HDR_FROM_FILE); 593 594 return 0; 595 } 596 597 static 598 int 599 _opts_check_modify(struct tcplay_opts *opts) 600 { 601 _not_null(dev); 602 _null(map_name); 603 _zero(hidden); 604 _zero(hidden_size_bytes); 605 _null(prf_algo); 606 _null(h_prf_algo); 607 _null(cipher_chain); 608 _null(h_cipher_chain); 609 610 if (!opts->protect_hidden) { 611 _zero(n_hkeyfiles); 612 _null(h_passphrase); 613 } 614 615 return 0; 616 } 617 618 619 static 620 int 621 _opts_check_restore(struct tcplay_opts *opts) 622 { 623 if ((_opts_check_modify(opts)) < 0) 624 return -1; 625 626 _null(new_prf_algo); 627 _zero(n_newkeyfiles); 628 _null(new_passphrase); 629 630 return 0; 631 } 632 633 int 634 tc_api_task_do(tc_api_task task) 635 { 636 struct tcplay_opts *opts; 637 int r = TC_OK; 638 639 if (task == NULL || ((opts = task->opts) == NULL)) { 640 errno = EFAULT; 641 return TC_ERR; 642 } 643 644 if (task->last_info != NULL) { 645 free_info(task->last_info); 646 } 647 648 switch (task->op) { 649 case TC_OP_CREATE: 650 if ((r = _opts_check_create(task->opts)) != 0) { 651 errno = EINVAL; 652 return r; 653 } 654 r = create_volume(opts); 655 break; 656 657 case TC_OP_MAP: 658 if ((r = _opts_check_map(task->opts)) != 0) { 659 errno = EINVAL; 660 return r; 661 } 662 r = map_volume(opts); 663 break; 664 665 case TC_OP_UNMAP: 666 if ((r = _opts_check_unmap(task->opts)) != 0) { 667 errno = EINVAL; 668 return r; 669 } 670 r = dm_teardown(opts->map_name, opts->dev); 671 break; 672 673 case TC_OP_INFO: 674 if ((r = _opts_check_info(task->opts)) != 0) { 675 errno = EINVAL; 676 return r; 677 } 678 if ((task->last_info = info_map_common(opts, NULL)) == NULL) { 679 r = TC_ERR; 680 } 681 break; 682 683 case TC_OP_INFO_MAPPED: 684 if ((r = _opts_check_info_mapped(task->opts)) != 0) { 685 errno = EINVAL; 686 return r; 687 } 688 if ((task->last_info = dm_info_map(opts->map_name)) == NULL) { 689 r = TC_ERR; 690 } 691 break; 692 693 case TC_OP_MODIFY: 694 if ((r = _opts_check_modify(task->opts)) != 0) { 695 errno = EINVAL; 696 return r; 697 } 698 r = modify_volume(opts); 699 break; 700 701 case TC_OP_RESTORE: 702 if ((r = _opts_check_restore(task->opts)) != 0) { 703 errno = EINVAL; 704 return r; 705 } 706 opts->flags |= TC_FLAG_ONLY_RESTORE; 707 r = modify_volume(opts); 708 opts->flags &= ~TC_FLAG_ONLY_RESTORE; 709 break; 710 } 711 712 return r; 713 } 714 715 716 int 717 tc_api_task_info_get(tc_api_task task, const char *key, ...) 718 { 719 char buf[1024]; 720 va_list ap; 721 struct tcplay_info *info; 722 char *s; 723 int *ip; 724 int64_t *i64p; 725 int r = TC_OK; 726 size_t sz; 727 728 if (task == NULL || ((info = task->last_info) == NULL)) { 729 errno = EFAULT; 730 return TC_ERR; 731 } 732 733 va_start(ap, key); 734 sz = va_arg(ap, size_t); 735 if (sz < 1) { 736 errno = EINVAL; 737 r = TC_ERR; 738 goto out; 739 } 740 741 if (_match(key, "device")) { 742 s = va_arg(ap, char *); 743 strncpy(s, info->dev, sz); 744 s[sz-1] = '\0'; 745 } else if (_match(key, "cipher")) { 746 s = va_arg(ap, char *); 747 tc_cipher_chain_sprint(buf, sizeof(buf), info->cipher_chain); 748 strncpy(s, buf, sz); 749 s[sz-1] = '\0'; 750 } else if (_match(key, "prf")) { 751 s = va_arg(ap, char *); 752 if (info->pbkdf_prf) 753 strncpy(s, info->pbkdf_prf->name, sz); 754 else 755 strncpy(s, "(unknown)", sz); 756 s[sz-1] = '\0'; 757 } else if (_match(key, "key_bits")) { 758 if (sz != sizeof(int)) { 759 errno = EFAULT; 760 r = TC_ERR; 761 goto out; 762 } 763 ip = va_arg(ap, int *); 764 *ip = 8*tc_cipher_chain_klen(info->cipher_chain); 765 } else if (_match(key, "size")) { 766 if (sz != sizeof(int64_t)) { 767 errno = EFAULT; 768 r = TC_ERR; 769 goto out; 770 } 771 i64p = va_arg(ap, int64_t *); 772 if (info->hdr) 773 *i64p = (int64_t)info->size * (int64_t)info->hdr->sec_sz; 774 else 775 *i64p = (int64_t)info->size * (int64_t)info->blk_sz; 776 } else if (_match(key, "iv_offset")) { 777 if (sz != sizeof(int64_t)) { 778 errno = EFAULT; 779 r = TC_ERR; 780 goto out; 781 } 782 i64p = va_arg(ap, int64_t *); 783 if (info->hdr) 784 *i64p = (int64_t)info->skip * (int64_t)info->hdr->sec_sz; 785 else 786 *i64p = (int64_t)info->skip * (int64_t)info->blk_sz; 787 } else if (_match(key, "block_offset")) { 788 if (sz != sizeof(int64_t)) { 789 errno = EFAULT; 790 r = TC_ERR; 791 goto out; 792 } 793 i64p = va_arg(ap, int64_t *); 794 if (info->hdr) 795 *i64p = (int64_t)info->offset * (int64_t)info->hdr->sec_sz; 796 else 797 *i64p = (int64_t)info->offset * (int64_t)info->blk_sz; 798 } else { 799 r = TC_ERR_UNIMPL; 800 } 801 802 out: 803 va_end(ap); 804 805 return r; 806 } 807