1 /* $OpenBSD: scsi.c,v 1.31 2021/06/22 14:51:29 jmc Exp $ */ 2 /* $FreeBSD: scsi.c,v 1.11 1996/04/06 11:00:28 joerg Exp $ */ 3 4 /* 5 * Written By Julian ELischer 6 * Copyright julian Elischer 1993. 7 * Permission is granted to use or redistribute this file in any way as long 8 * as this notice remains. Julian Elischer does not guarantee that this file 9 * is totally correct for any given task and users of this file must 10 * accept responsibility for any damage that occurs from the application of this 11 * file. 12 * 13 * (julian@tfs.com julian@dialix.oz.au) 14 * 15 * User SCSI hooks added by Peter Dufault: 16 * 17 * Copyright (c) 1994 HD Associates 18 * (contact: dufault@hda.com) 19 * All rights reserved. 20 * 21 * Redistribution and use in source and binary forms, with or without 22 * modification, are permitted provided that the following conditions 23 * are met: 24 * 1. Redistributions of source code must retain the above copyright 25 * notice, this list of conditions and the following disclaimer. 26 * 2. Redistributions in binary form must reproduce the above copyright 27 * notice, this list of conditions and the following disclaimer in the 28 * documentation and/or other materials provided with the distribution. 29 * 3. The name of HD Associates 30 * may not be used to endorse or promote products derived from this software 31 * without specific prior written permission. 32 * 33 * THIS SOFTWARE IS PROVIDED BY HD ASSOCIATES ``AS IS'' AND 34 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 35 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 36 * ARE DISCLAIMED. IN NO EVENT SHALL HD ASSOCIATES BE LIABLE 37 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 38 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 39 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 40 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 41 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 42 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 43 * SUCH DAMAGE. 44 */ 45 46 #include <sys/types.h> 47 #include <sys/wait.h> 48 49 #include <fcntl.h> 50 #include <stdio.h> 51 #include <string.h> 52 #include <stdlib.h> 53 #include <unistd.h> 54 #include <errno.h> 55 #include <sys/scsiio.h> 56 #include <ctype.h> 57 #include <signal.h> 58 #include <err.h> 59 #include <paths.h> 60 61 #include "libscsi.h" 62 63 int fd; 64 int debuglevel; 65 int debugflag; 66 int commandflag; 67 int verbose = 0; 68 69 int modeflag; 70 int editflag; 71 int modepage = 0; /* Read this mode page */ 72 int pagectl = 0; /* Mode sense page control */ 73 int seconds = 2; 74 75 void procargs(int *argc_p, char ***argv_p); 76 int iget(void *hook, char *name); 77 char *cget(void *hook, char *name); 78 void arg_put(void *hook, int letter, void *arg, int count, char *name); 79 void mode_sense(int fd, u_char *data, int len, int pc, int page); 80 void mode_select(int fd, u_char *data, int len, int perm); 81 int editit(const char *pathname); 82 83 static void 84 usage(void) 85 { 86 fprintf(stderr, 87 "usage: scsi -f device -d debug_level\n" 88 " scsi -f device -m page [-e] [-P pc]\n" 89 " scsi -f device [-v] [-s seconds] -c cmd_fmt [arg ...]" 90 " -o count out_fmt\n" 91 " [arg ...] -i count in_fmt [arg ...]\n"); 92 93 exit (1); 94 } 95 96 void 97 procargs(int *argc_p, char ***argv_p) 98 { 99 int argc = *argc_p; 100 char **argv = *argv_p; 101 int fflag, ch; 102 103 fflag = 0; 104 commandflag = 0; 105 debugflag = 0; 106 while ((ch = getopt(argc, argv, "cef:d:m:P:s:v")) != -1) { 107 switch (ch) { 108 case 'c': 109 commandflag = 1; 110 break; 111 case 'e': 112 editflag = 1; 113 break; 114 case 'f': 115 if ((fd = scsi_open(optarg, O_RDWR)) < 0) 116 err(1, "unable to open device %s", optarg); 117 fflag = 1; 118 break; 119 case 'd': 120 debuglevel = strtol(optarg, 0, 0); 121 debugflag = 1; 122 break; 123 case 'm': 124 modeflag = 1; 125 modepage = strtol(optarg, 0, 0); 126 break; 127 case 'P': 128 pagectl = strtol(optarg, 0, 0); 129 break; 130 case 's': 131 seconds = strtol(optarg, 0, 0); 132 break; 133 case 'v': 134 verbose = 1; 135 break; 136 case '?': 137 default: 138 usage(); 139 } 140 } 141 *argc_p = argc - optind; 142 *argv_p = argv + optind; 143 144 if (!fflag) usage(); 145 } 146 147 /* get_hook: Structure for evaluating args in a callback. 148 */ 149 struct get_hook 150 { 151 int argc; 152 char **argv; 153 int got; 154 }; 155 156 /* iget: Integer argument callback 157 */ 158 int 159 iget(void *hook, char *name) 160 { 161 struct get_hook *h = (struct get_hook *)hook; 162 int arg; 163 164 if (h->got >= h->argc) 165 { 166 fprintf(stderr, "Expecting an integer argument.\n"); 167 usage(); 168 } 169 arg = strtol(h->argv[h->got], 0, 0); 170 h->got++; 171 172 if (verbose && name && *name) 173 printf("%s: %d\n", name, arg); 174 175 return arg; 176 } 177 178 /* cget: char * argument callback 179 */ 180 char * 181 cget(void *hook, char *name) 182 { 183 struct get_hook *h = (struct get_hook *)hook; 184 char *arg; 185 186 if (h->got >= h->argc) 187 { 188 fprintf(stderr, "Expecting a character pointer argument.\n"); 189 usage(); 190 } 191 arg = h->argv[h->got]; 192 h->got++; 193 194 if (verbose && name) 195 printf("cget: %s: %s", name, arg); 196 197 return arg; 198 } 199 200 /* arg_put: "put argument" callback 201 */ 202 void arg_put(void *hook, int letter, void *arg, int count, char *name) 203 { 204 if (verbose && name && *name) 205 printf("%s: ", name); 206 207 switch(letter) 208 { 209 case 'i': 210 case 'b': 211 printf("%ld ", (long)arg); 212 break; 213 214 case 'c': 215 case 'z': 216 { 217 char *p = malloc(count + 1); 218 if (p == NULL) 219 err(1, NULL); 220 221 p[count] = 0; 222 strncpy(p, (char *)arg, count); 223 if (letter == 'z') 224 { 225 int i; 226 for (i = count - 1; i >= 0; i--) 227 if (p[i] == ' ') 228 p[i] = 0; 229 else 230 break; 231 } 232 printf("%s ", p); 233 free(p); 234 } 235 236 break; 237 238 default: 239 printf("Unknown format letter: '%c'\n", letter); 240 } 241 if (verbose) 242 putchar('\n'); 243 } 244 245 /* data_phase: SCSI bus data phase: DATA IN, DATA OUT, or no data transfer. 246 */ 247 enum data_phase {none = 0, in, out}; 248 249 /* do_cmd: Send a command to a SCSI device 250 */ 251 static void 252 do_cmd(int fd, char *fmt, int argc, char **argv) 253 { 254 struct get_hook h; 255 scsireq_t *scsireq = scsireq_new(); 256 enum data_phase data_phase; 257 int count, amount; 258 char *data_fmt, *bp; 259 260 h.argc = argc; 261 h.argv = argv; 262 h.got = 0; 263 264 scsireq_reset(scsireq); 265 266 scsireq_build_visit(scsireq, 0, 0, 0, fmt, iget, (void *)&h); 267 268 /* Three choices here: 269 * 1. We've used up all the args and have no data phase. 270 * 2. We have input data ("-i") 271 * 3. We have output data ("-o") 272 */ 273 274 if (h.got >= h.argc) 275 { 276 data_phase = none; 277 count = scsireq->datalen = 0; 278 } 279 else 280 { 281 char *flag = cget(&h, 0); 282 283 if (strcmp(flag, "-o") == 0) 284 { 285 data_phase = out; 286 scsireq->flags = SCCMD_WRITE; 287 } 288 else if (strcmp(flag, "-i") == 0) 289 { 290 data_phase = in; 291 scsireq->flags = SCCMD_READ; 292 } 293 else 294 { 295 fprintf(stderr, 296 "Need either \"-i\" or \"-o\" for data phase; not \"%s\".\n", flag); 297 usage(); 298 } 299 300 count = scsireq->datalen = iget(&h, 0); 301 if (count) { 302 data_fmt = cget(&h, 0); 303 304 scsireq->databuf = malloc(count); 305 if (scsireq->databuf == NULL) 306 err(1, NULL); 307 308 if (data_phase == out) { 309 if (strcmp(data_fmt, "-") == 0) { 310 bp = (char *)scsireq->databuf; 311 while (count > 0 && 312 (amount = read(STDIN_FILENO, 313 bp, count)) > 0) { 314 count -= amount; 315 bp += amount; 316 } 317 if (amount == -1) 318 err(1, "read"); 319 else if (amount == 0) { 320 /* early EOF */ 321 fprintf(stderr, 322 "Warning: only read %lu bytes out of %lu.\n", 323 scsireq->datalen - (u_long)count, 324 scsireq->datalen); 325 scsireq->datalen -= (u_long)count; 326 } 327 } 328 else 329 { 330 bzero(scsireq->databuf, count); 331 scsireq_encode_visit(scsireq, data_fmt, iget, (void *)&h); 332 } 333 } 334 } 335 } 336 337 338 scsireq->timeout = seconds * 1000; 339 340 if (scsireq_enter(fd, scsireq) == -1) 341 { 342 scsi_debug(stderr, -1, scsireq); 343 exit(1); 344 } 345 346 if (SCSIREQ_ERROR(scsireq)) 347 scsi_debug(stderr, 0, scsireq); 348 349 if (count && data_phase == in) 350 { 351 if (strcmp(data_fmt, "-") == 0) /* stdout */ 352 { 353 bp = (char *)scsireq->databuf; 354 while (count > 0 && (amount = write(STDOUT_FILENO, bp, count)) > 0) 355 { 356 count -= amount; 357 bp += amount; 358 } 359 if (amount < 0) 360 err(1, "write"); 361 else if (amount == 0) 362 fprintf(stderr, "Warning: wrote only %lu bytes out of %lu.\n", 363 scsireq->datalen - count, 364 scsireq->datalen); 365 366 } 367 else 368 { 369 scsireq_decode_visit(scsireq, data_fmt, arg_put, 0); 370 putchar('\n'); 371 } 372 } 373 } 374 375 void mode_sense(int fd, u_char *data, int len, int pc, int page) 376 { 377 scsireq_t *scsireq; 378 379 bzero(data, len); 380 381 scsireq = scsireq_new(); 382 383 if (scsireq_enter(fd, scsireq_build(scsireq, 384 len, data, SCCMD_READ, 385 "1A 0 v:2 {Page Control} v:6 {Page Code} 0 v:i1 {Allocation Length} 0", 386 pc, page, len)) == -1) /* Mode sense */ 387 { 388 scsi_debug(stderr, -1, scsireq); 389 exit(1); 390 } 391 392 if (SCSIREQ_ERROR(scsireq)) 393 { 394 scsi_debug(stderr, 0, scsireq); 395 exit(1); 396 } 397 398 free(scsireq); 399 } 400 401 void mode_select(int fd, u_char *data, int len, int perm) 402 { 403 scsireq_t *scsireq; 404 405 scsireq = scsireq_new(); 406 407 if (scsireq_enter(fd, scsireq_build(scsireq, 408 len, data, SCCMD_WRITE, 409 "15 0:7 v:1 {SP} 0 0 v:i1 {Allocation Length} 0", perm, len)) == -1) /* Mode select */ 410 { 411 scsi_debug(stderr, -1, scsireq); 412 exit(1); 413 } 414 415 if (SCSIREQ_ERROR(scsireq)) 416 { 417 scsi_debug(stderr, 0, scsireq); 418 exit(1); 419 } 420 421 free(scsireq); 422 } 423 424 425 #define START_ENTRY '{' 426 #define END_ENTRY '}' 427 428 static void 429 skipwhite(FILE *f) 430 { 431 int c; 432 433 skip_again: 434 435 while (isspace(c = getc(f))) 436 continue; 437 438 if (c == '#') { 439 while ((c = getc(f)) != '\n' && c != EOF) 440 continue; 441 goto skip_again; 442 } 443 444 ungetc(c, f); 445 } 446 447 /* mode_lookup: Lookup a format description for a given page. 448 */ 449 char *mode_db = "/usr/share/misc/scsi_modes"; 450 static char *mode_lookup(int page) 451 { 452 char *new_db; 453 FILE *modes; 454 int match, next, found, c; 455 static char fmt[1024]; /* XXX This should be with strealloc */ 456 int page_desc; 457 new_db = getenv("SCSI_MODES"); 458 459 if (new_db) 460 mode_db = new_db; 461 462 modes = fopen(mode_db, "r"); 463 if (modes == NULL) 464 return 0; 465 466 next = 0; 467 found = 0; 468 469 while (!found) { 470 471 skipwhite(modes); 472 473 if (fscanf(modes, "%i", &page_desc) != 1) 474 break; 475 476 if (page_desc == page) 477 found = 1; 478 479 skipwhite(modes); 480 if (getc(modes) != START_ENTRY) { 481 errx(1, "Expected %c", START_ENTRY); 482 } 483 484 match = 1; 485 while (match != 0) { 486 c = getc(modes); 487 if (c == EOF) 488 fprintf(stderr, "Expected %c.\n", END_ENTRY); 489 490 if (c == START_ENTRY) { 491 match++; 492 } 493 if (c == END_ENTRY) { 494 match--; 495 if (match == 0) 496 break; 497 } 498 if (found && c != '\n') { 499 if (next >= sizeof(fmt)) { 500 errx(1, "Stupid program: Buffer overflow.\n"); 501 } 502 503 fmt[next++] = (u_char)c; 504 } 505 } 506 } 507 fclose(modes); 508 fmt[next] = 0; 509 510 return (found) ? fmt : 0; 511 } 512 513 /* -------- edit: Mode Select Editor --------- 514 */ 515 struct editinfo 516 { 517 long can_edit; 518 long default_value; 519 } editinfo[64]; /* XXX Bogus fixed size */ 520 521 static int editind; 522 volatile int edit_opened; 523 static FILE *edit_file; 524 static char edit_name[L_tmpnam]; 525 526 static void 527 edit_rewind(void) 528 { 529 editind = 0; 530 } 531 532 static void 533 edit_done(void) 534 { 535 int opened; 536 537 sigset_t all, prev; 538 sigfillset(&all); 539 540 (void)sigprocmask(SIG_SETMASK, &all, &prev); 541 542 opened = (int)edit_opened; 543 edit_opened = 0; 544 545 (void)sigprocmask(SIG_SETMASK, &prev, 0); 546 547 if (opened) 548 { 549 if (fclose(edit_file)) 550 perror(edit_name); 551 if (unlink(edit_name)) 552 perror(edit_name); 553 } 554 } 555 556 static void 557 edit_init(void) 558 { 559 int fd; 560 561 edit_rewind(); 562 strlcpy(edit_name, "/var/tmp/scXXXXXXXX", sizeof edit_name); 563 if ((fd = mkstemp(edit_name)) == -1) 564 err(1, "mkstemp"); 565 if ( (edit_file = fdopen(fd, "w+")) == 0) 566 err(1, "fdopen"); 567 edit_opened = 1; 568 569 atexit(edit_done); 570 } 571 572 static void 573 edit_check(void *hook, int letter, void *arg, int count, char *name) 574 { 575 if (letter != 'i' && letter != 'b') { 576 errx(1, "Can't edit format %c.\n", letter); 577 } 578 579 if (editind >= sizeof(editinfo) / sizeof(editinfo[0])) { 580 errx(1, "edit table overflow"); 581 } 582 editinfo[editind].can_edit = ((long)arg != 0); 583 editind++; 584 } 585 586 static void 587 edit_defaults(void *hook, int letter, void *arg, int count, char *name) 588 { 589 if (letter != 'i' && letter != 'b') { 590 errx(1, "Can't edit format %c.\n", letter); 591 } 592 593 editinfo[editind].default_value = ((long)arg); 594 editind++; 595 } 596 597 static void 598 edit_report(void *hook, int letter, void *arg, int count, char *name) 599 { 600 if (editinfo[editind].can_edit) { 601 if (letter != 'i' && letter != 'b') { 602 errx(1, "Can't report format %c.\n", letter); 603 } 604 605 fprintf(edit_file, "%s: %ld\n", name, (long)arg); 606 } 607 608 editind++; 609 } 610 611 static int 612 edit_get(void *hook, char *name) 613 { 614 int arg = editinfo[editind].default_value; 615 616 if (editinfo[editind].can_edit) { 617 char line[80]; 618 size_t len; 619 if (fgets(line, sizeof(line), edit_file) == NULL) 620 err(1, "fgets"); 621 622 len = strlen(line); 623 if (len && line[len - 1] == '\n') 624 line[len - 1] = '\0'; 625 626 if (strncmp(name, line, strlen(name)) != 0) { 627 errx(1, "Expected \"%s\" and read \"%s\"\n", 628 name, line); 629 } 630 631 arg = strtoul(line + strlen(name) + 2, 0, 0); 632 } 633 634 editind++; 635 return arg; 636 } 637 638 int 639 editit(const char *pathname) 640 { 641 char *argp[] = {"sh", "-c", NULL, NULL}, *ed, *p; 642 sig_t sighup, sigint, sigquit; 643 pid_t pid; 644 int st; 645 646 ed = getenv("VISUAL"); 647 if (ed == NULL || ed[0] == '\0') 648 ed = getenv("EDITOR"); 649 if (ed == NULL || ed[0] == '\0') 650 ed = _PATH_VI; 651 if (asprintf(&p, "%s %s", ed, pathname) == -1) 652 return (-1); 653 argp[2] = p; 654 655 top: 656 sighup = signal(SIGHUP, SIG_IGN); 657 sigint = signal(SIGINT, SIG_IGN); 658 sigquit = signal(SIGQUIT, SIG_IGN); 659 if ((pid = fork()) == -1) { 660 int saved_errno = errno; 661 662 (void)signal(SIGHUP, sighup); 663 (void)signal(SIGINT, sigint); 664 (void)signal(SIGQUIT, sigquit); 665 if (saved_errno == EAGAIN) { 666 sleep(1); 667 goto top; 668 } 669 free(p); 670 errno = saved_errno; 671 return (-1); 672 } 673 if (pid == 0) { 674 execv(_PATH_BSHELL, argp); 675 _exit(127); 676 } 677 free(p); 678 for (;;) { 679 if (waitpid(pid, &st, 0) == -1) { 680 if (errno != EINTR) 681 return (-1); 682 } else 683 break; 684 } 685 (void)signal(SIGHUP, sighup); 686 (void)signal(SIGINT, sigint); 687 (void)signal(SIGQUIT, sigquit); 688 if (!WIFEXITED(st) || WEXITSTATUS(st) != 0) { 689 errno = ECHILD; 690 return (-1); 691 } 692 return (0); 693 } 694 695 static void 696 mode_edit(int fd, int page, int edit, int argc, char *argv[]) 697 { 698 int i; 699 u_char data[255]; 700 u_char *mode_pars; 701 struct mode_header 702 { 703 u_char mdl; /* Mode data length */ 704 u_char medium_type; 705 u_char dev_spec_par; 706 u_char bdl; /* Block descriptor length */ 707 }; 708 709 struct mode_page_header 710 { 711 u_char page_code; 712 u_char page_length; 713 }; 714 715 struct mode_header *mh; 716 struct mode_page_header *mph; 717 718 char *fmt = mode_lookup(page); 719 if (!fmt && verbose) { 720 fprintf(stderr, 721 "No mode data base entry in \"%s\" for page %d; binary %s only.\n", 722 mode_db, page, (edit ? "edit" : "display")); 723 } 724 725 if (edit) { 726 if (!fmt) { 727 errx(1, "Sorry: can't edit without a format.\n"); 728 } 729 730 if (pagectl != 0 && pagectl != 3) { 731 errx(1, 732 "It only makes sense to edit page 0 (current) or page 3 (saved values)\n"); 733 } 734 735 verbose = 1; 736 737 mode_sense(fd, data, sizeof(data), 1, page); 738 739 mh = (struct mode_header *)data; 740 mph = (struct mode_page_header *) 741 (((char *)mh) + sizeof(*mh) + mh->bdl); 742 743 mode_pars = (char *)mph + sizeof(*mph); 744 745 edit_init(); 746 scsireq_buff_decode_visit(mode_pars, mh->mdl, 747 fmt, edit_check, 0); 748 749 mode_sense(fd, data, sizeof(data), 0, page); 750 751 edit_rewind(); 752 scsireq_buff_decode_visit(mode_pars, mh->mdl, 753 fmt, edit_defaults, 0); 754 755 edit_rewind(); 756 scsireq_buff_decode_visit(mode_pars, mh->mdl, 757 fmt, edit_report, 0); 758 759 fclose(edit_file); 760 if (editit(edit_name) == -1 && errno != ECHILD) 761 err(1, "edit %s", edit_name); 762 if ((edit_file = fopen(edit_name, "r")) == NULL) 763 err(1, "open %s", edit_name); 764 765 edit_rewind(); 766 scsireq_buff_encode_visit(mode_pars, mh->mdl, 767 fmt, edit_get, 0); 768 769 /* Eliminate block descriptors: 770 */ 771 bcopy((char *)mph, ((char *)mh) + sizeof(*mh), 772 sizeof(*mph) + mph->page_length); 773 774 mh->bdl = 0; 775 mph = (struct mode_page_header *) (((char *)mh) + sizeof(*mh)); 776 mode_pars = ((char *)mph) + 2; 777 778 #if 0 779 /* Turn this on to see what you're sending to the 780 * device: 781 */ 782 edit_rewind(); 783 scsireq_buff_decode_visit(mode_pars, 784 mh->mdl, fmt, arg_put, 0); 785 #endif 786 787 edit_done(); 788 789 /* Make it permanent if pageselect is three. 790 */ 791 792 mph->page_code &= ~0xC0; /* Clear PS and RESERVED */ 793 mh->mdl = 0; /* Reserved for mode select */ 794 795 mode_select(fd, (char *)mh, 796 sizeof(*mh) + mh->bdl + sizeof(*mph) + mph->page_length, 797 (pagectl == 3)); 798 799 exit(0); 800 } 801 802 mode_sense(fd, data, sizeof(data), pagectl, page); 803 804 /* Skip over the block descriptors. 805 */ 806 mh = (struct mode_header *)data; 807 mph = (struct mode_page_header *)(((char *)mh) + sizeof(*mh) + mh->bdl); 808 mode_pars = (char *)mph + sizeof(*mph); 809 810 if (!fmt) { 811 for (i = 0; i < mh->mdl; i++) { 812 printf("%02x%c",mode_pars[i], 813 (((i + 1) % 8) == 0) ? '\n' : ' '); 814 } 815 putc('\n', stdout); 816 } else { 817 verbose = 1; 818 scsireq_buff_decode_visit(mode_pars, 819 mh->mdl, fmt, arg_put, 0); 820 } 821 } 822 823 int 824 main(int argc, char **argv) 825 { 826 procargs(&argc,&argv); 827 828 /* XXX This has grown to the point that it should be cleaned up. 829 */ 830 if (debugflag) { 831 if (ioctl(fd,SCIOCDEBUG,&debuglevel) == -1) 832 err(1, "SCIOCDEBUG"); 833 } else if (commandflag) { 834 char *fmt; 835 836 if (argc < 1) { 837 fprintf(stderr, "Need the command format string.\n"); 838 usage(); 839 } 840 841 842 fmt = argv[0]; 843 844 argc -= 1; 845 argv += 1; 846 847 do_cmd(fd, fmt, argc, argv); 848 } else if (modeflag) 849 mode_edit(fd, modepage, editflag, argc, argv); 850 851 exit(0); 852 } 853