1/* 2 * tclUnixCompat.c 3 * 4 * Written by: Zoran Vasiljevic (vasiljevic@users.sourceforge.net). 5 * 6 * See the file "license.terms" for information on usage and redistribution of 7 * this file, and for a DISCLAIMER OF ALL WARRANTIES. 8 * 9 * RCS: @(#) $Id: tclUnixCompat.c,v 1.15.2.1 2010/01/06 21:35:22 nijtmans Exp $ 10 * 11 */ 12 13#include "tclInt.h" 14#include <pwd.h> 15#include <grp.h> 16#include <errno.h> 17#include <string.h> 18 19/* See also: SC_BLOCKING_STYLE in unix/tcl.m4 20 */ 21#ifdef USE_FIONBIO 22# ifdef HAVE_SYS_FILIO_H 23# include <sys/filio.h> /* For FIONBIO. */ 24# endif 25# ifdef HAVE_SYS_IOCTL_H 26# include <sys/ioctl.h> 27# endif 28#endif /* USE_FIONBIO */ 29 30/* 31 *--------------------------------------------------------------------------- 32 * 33 * TclUnixSetBlockingMode -- 34 * 35 * Set the blocking mode of a file descriptor. 36 * 37 * Results: 38 * 39 * 0 on success, -1 (with errno set) on error. 40 * 41 *--------------------------------------------------------------------------- 42 */ 43int 44TclUnixSetBlockingMode( 45 int fd, /* File descriptor */ 46 int mode) /* TCL_MODE_BLOCKING or TCL_MODE_NONBLOCKING */ 47{ 48#ifndef USE_FIONBIO 49 int flags = fcntl(fd, F_GETFL); 50 51 if (mode == TCL_MODE_BLOCKING) { 52 flags &= ~O_NONBLOCK; 53 } else { 54 flags |= O_NONBLOCK; 55 } 56 return fcntl(fd, F_SETFL, flags); 57#else /* USE_FIONBIO */ 58 int state = (mode == TCL_MODE_NONBLOCKING); 59 return ioctl(fd, FIONBIO, &state); 60#endif /* !USE_FIONBIO */ 61} 62 63/* 64 * Used to pad structures at size'd boundaries 65 * 66 * This macro assumes that the pointer 'buffer' was created from an aligned 67 * pointer by adding the 'length'. If this 'length' was not a multiple of the 68 * 'size' the result is unaligned and PadBuffer corrects both the pointer, 69 * _and_ the 'length'. The latter means that future increments of 'buffer' by 70 * 'length' stay aligned. 71 */ 72 73#define PadBuffer(buffer, length, size) \ 74 if (((length) % (size))) { \ 75 (buffer) += ((size) - ((length) % (size))); \ 76 (length) += ((size) - ((length) % (size))); \ 77 } 78 79/* 80 * Per-thread private storage used to store values returned from MT-unsafe 81 * library calls. 82 */ 83 84#ifdef TCL_THREADS 85 86typedef struct ThreadSpecificData { 87 struct passwd pwd; 88 char pbuf[2048]; 89 90 struct group grp; 91 char gbuf[2048]; 92 93#if !defined(HAVE_MTSAFE_GETHOSTBYNAME) || !defined(HAVE_MTSAFE_GETHOSTBYADDR) 94 struct hostent hent; 95 char hbuf[2048]; 96#endif 97} ThreadSpecificData; 98static Tcl_ThreadDataKey dataKey; 99 100#if ((!defined(HAVE_GETHOSTBYNAME_R) || !defined(HAVE_GETHOSTBYADDR_R)) && \ 101 (!defined(HAVE_MTSAFE_GETHOSTBYNAME) || \ 102 !defined(HAVE_MTSAFE_GETHOSTBYADDR))) || \ 103 !defined(HAVE_GETPWNAM_R) || !defined(HAVE_GETPWUID_R) || \ 104 !defined(HAVE_GETGRNAM_R) || !defined(HAVE_GETGRGID_R) 105/* 106 * Mutex to lock access to MT-unsafe calls. This is just to protect our own 107 * usage. It does not protect us from others calling the same functions 108 * without (or using some different) lock. 109 */ 110 111static Tcl_Mutex compatLock; 112 113/* 114 * Helper function declarations. Note that these are only used if needed and 115 * only defined if used (via the NEED_* macros). 116 */ 117 118#undef NEED_COPYARRAY 119#undef NEED_COPYGRP 120#undef NEED_COPYHOSTENT 121#undef NEED_COPYPWD 122#undef NEED_COPYSTRING 123 124static int CopyArray(char **src, int elsize, char *buf, 125 int buflen); 126static int CopyGrp(struct group *tgtPtr, char *buf, int buflen); 127static int CopyHostent(struct hostent *tgtPtr, char *buf, 128 int buflen); 129static int CopyPwd(struct passwd *tgtPtr, char *buf, int buflen); 130static int CopyString(CONST char *src, char *buf, int buflen); 131 132#endif 133#endif /* TCL_THREADS */ 134 135/* 136 *--------------------------------------------------------------------------- 137 * 138 * TclpGetPwNam -- 139 * 140 * Thread-safe wrappers for getpwnam(). See "man getpwnam" for more 141 * details. 142 * 143 * Results: 144 * Pointer to struct passwd on success or NULL on error. 145 * 146 * Side effects: 147 * None. 148 * 149 *--------------------------------------------------------------------------- 150 */ 151 152struct passwd * 153TclpGetPwNam( 154 const char *name) 155{ 156#if !defined(TCL_THREADS) 157 return getpwnam(name); 158#else 159 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); 160 161#if defined(HAVE_GETPWNAM_R_5) 162 struct passwd *pwPtr = NULL; 163 164 return (getpwnam_r(name, &tsdPtr->pwd, tsdPtr->pbuf, sizeof(tsdPtr->pbuf), 165 &pwPtr) == 0 && pwPtr != NULL) ? &tsdPtr->pwd : NULL; 166 167#elif defined(HAVE_GETPWNAM_R_4) 168 return getpwnam_r(name, &tsdPtr->pwd, tsdPtr->pbuf, sizeof(tsdPtr->pbuf)); 169 170#else 171#define NEED_COPYPWD 1 172 struct passwd *pwPtr; 173 174 Tcl_MutexLock(&compatLock); 175 pwPtr = getpwnam(name); 176 if (pwPtr != NULL) { 177 tsdPtr->pwd = *pwPtr; 178 pwPtr = &tsdPtr->pwd; 179 if (CopyPwd(&tsdPtr->pwd, tsdPtr->pbuf, sizeof(tsdPtr->pbuf)) == -1) { 180 pwPtr = NULL; 181 } 182 } 183 Tcl_MutexUnlock(&compatLock); 184 return pwPtr; 185#endif 186 187 return NULL; /* Not reached. */ 188#endif /* TCL_THREADS */ 189} 190 191/* 192 *--------------------------------------------------------------------------- 193 * 194 * TclpGetPwUid -- 195 * 196 * Thread-safe wrappers for getpwuid(). See "man getpwuid" for more 197 * details. 198 * 199 * Results: 200 * Pointer to struct passwd on success or NULL on error. 201 * 202 * Side effects: 203 * None. 204 * 205 *--------------------------------------------------------------------------- 206 */ 207 208struct passwd * 209TclpGetPwUid( 210 uid_t uid) 211{ 212#if !defined(TCL_THREADS) 213 return getpwuid(uid); 214#else 215 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); 216 217#if defined(HAVE_GETPWUID_R_5) 218 struct passwd *pwPtr = NULL; 219 220 return (getpwuid_r(uid, &tsdPtr->pwd, tsdPtr->pbuf, sizeof(tsdPtr->pbuf), 221 &pwPtr) == 0 && pwPtr != NULL) ? &tsdPtr->pwd : NULL; 222 223#elif defined(HAVE_GETPWUID_R_4) 224 return getpwuid_r(uid, &tsdPtr->pwd, tsdPtr->pbuf, sizeof(tsdPtr->pbuf)); 225 226#else 227#define NEED_COPYPWD 1 228 struct passwd *pwPtr; 229 230 Tcl_MutexLock(&compatLock); 231 pwPtr = getpwuid(uid); 232 if (pwPtr != NULL) { 233 tsdPtr->pwd = *pwPtr; 234 pwPtr = &tsdPtr->pwd; 235 if (CopyPwd(&tsdPtr->pwd, tsdPtr->pbuf, sizeof(tsdPtr->pbuf)) == -1) { 236 pwPtr = NULL; 237 } 238 } 239 Tcl_MutexUnlock(&compatLock); 240 return pwPtr; 241#endif 242 243 return NULL; /* Not reached. */ 244#endif /* TCL_THREADS */ 245} 246 247/* 248 *--------------------------------------------------------------------------- 249 * 250 * TclpGetGrNam -- 251 * 252 * Thread-safe wrappers for getgrnam(). See "man getgrnam" for more 253 * details. 254 * 255 * Results: 256 * Pointer to struct group on success or NULL on error. 257 * 258 * Side effects: 259 * None. 260 * 261 *--------------------------------------------------------------------------- 262 */ 263 264struct group * 265TclpGetGrNam( 266 const char *name) 267{ 268#if !defined(TCL_THREADS) 269 return getgrnam(name); 270#else 271 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); 272 273#if defined(HAVE_GETGRNAM_R_5) 274 struct group *grPtr = NULL; 275 276 return (getgrnam_r(name, &tsdPtr->grp, tsdPtr->gbuf, sizeof(tsdPtr->gbuf), 277 &grPtr) == 0 && grPtr != NULL) ? &tsdPtr->grp : NULL; 278 279#elif defined(HAVE_GETGRNAM_R_4) 280 return getgrnam_r(name, &tsdPtr->grp, tsdPtr->gbuf, sizeof(tsdPtr->gbuf)); 281 282#else 283#define NEED_COPYGRP 1 284 struct group *grPtr; 285 286 Tcl_MutexLock(&compatLock); 287 grPtr = getgrnam(name); 288 if (grPtr != NULL) { 289 tsdPtr->grp = *grPtr; 290 grPtr = &tsdPtr->grp; 291 if (CopyGrp(&tsdPtr->grp, tsdPtr->gbuf, sizeof(tsdPtr->gbuf)) == -1) { 292 grPtr = NULL; 293 } 294 } 295 Tcl_MutexUnlock(&compatLock); 296 return grPtr; 297#endif 298 299 return NULL; /* Not reached. */ 300#endif /* TCL_THREADS */ 301} 302 303/* 304 *--------------------------------------------------------------------------- 305 * 306 * TclpGetGrGid -- 307 * 308 * Thread-safe wrappers for getgrgid(). See "man getgrgid" for more 309 * details. 310 * 311 * Results: 312 * Pointer to struct group on success or NULL on error. 313 * 314 * Side effects: 315 * None. 316 * 317 *--------------------------------------------------------------------------- 318 */ 319 320struct group * 321TclpGetGrGid( 322 gid_t gid) 323{ 324#if !defined(TCL_THREADS) 325 return getgrgid(gid); 326#else 327 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); 328 329#if defined(HAVE_GETGRGID_R_5) 330 struct group *grPtr = NULL; 331 332 return (getgrgid_r(gid, &tsdPtr->grp, tsdPtr->gbuf, sizeof(tsdPtr->gbuf), 333 &grPtr) == 0 && grPtr != NULL) ? &tsdPtr->grp : NULL; 334 335#elif defined(HAVE_GETGRGID_R_4) 336 return getgrgid_r(gid, &tsdPtr->grp, tsdPtr->gbuf, sizeof(tsdPtr->gbuf)); 337 338#else 339#define NEED_COPYGRP 1 340 struct group *grPtr; 341 342 Tcl_MutexLock(&compatLock); 343 grPtr = getgrgid(gid); 344 if (grPtr != NULL) { 345 tsdPtr->grp = *grPtr; 346 grPtr = &tsdPtr->grp; 347 if (CopyGrp(&tsdPtr->grp, tsdPtr->gbuf, sizeof(tsdPtr->gbuf)) == -1) { 348 grPtr = NULL; 349 } 350 } 351 Tcl_MutexUnlock(&compatLock); 352 return grPtr; 353#endif 354 355 return NULL; /* Not reached. */ 356#endif /* TCL_THREADS */ 357} 358 359/* 360 *--------------------------------------------------------------------------- 361 * 362 * TclpGetHostByName -- 363 * 364 * Thread-safe wrappers for gethostbyname(). See "man gethostbyname" for 365 * more details. 366 * 367 * Results: 368 * Pointer to struct hostent on success or NULL on error. 369 * 370 * Side effects: 371 * None. 372 * 373 *--------------------------------------------------------------------------- 374 */ 375 376struct hostent * 377TclpGetHostByName( 378 const char *name) 379{ 380#if !defined(TCL_THREADS) || defined(HAVE_MTSAFE_GETHOSTBYNAME) 381 return gethostbyname(name); 382#else 383 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); 384 385#if defined(HAVE_GETHOSTBYNAME_R_5) 386 int h_errno; 387 388 return gethostbyname_r(name, &tsdPtr->hent, tsdPtr->hbuf, 389 sizeof(tsdPtr->hbuf), &h_errno); 390 391#elif defined(HAVE_GETHOSTBYNAME_R_6) 392 struct hostent *hePtr = NULL; 393 int h_errno, result; 394 395 result = gethostbyname_r(name, &tsdPtr->hent, tsdPtr->hbuf, 396 sizeof(tsdPtr->hbuf), &hePtr, &h_errno); 397 return (result == 0) ? hePtr : NULL; 398 399#elif defined(HAVE_GETHOSTBYNAME_R_3) 400 struct hostent_data data; 401 402 return (gethostbyname_r(name, &tsdPtr->hent, &data) == 0) 403 ? &tsdPtr->hent : NULL; 404 405#else 406#define NEED_COPYHOSTENT 1 407 struct hostent *hePtr; 408 409 Tcl_MutexLock(&compatLock); 410 hePtr = gethostbyname(name); 411 if (hePtr != NULL) { 412 tsdPtr->hent = *hePtr; 413 hePtr = &tsdPtr->hent; 414 if (CopyHostent(&tsdPtr->hent, tsdPtr->hbuf, 415 sizeof(tsdPtr->hbuf)) == -1) { 416 hePtr = NULL; 417 } 418 } 419 Tcl_MutexUnlock(&compatLock); 420 return hePtr; 421#endif 422 423 return NULL; /* Not reached. */ 424#endif /* TCL_THREADS */ 425} 426 427/* 428 *--------------------------------------------------------------------------- 429 * 430 * TclpGetHostByAddr -- 431 * 432 * Thread-safe wrappers for gethostbyaddr(). See "man gethostbyaddr" for 433 * more details. 434 * 435 * Results: 436 * Pointer to struct hostent on success or NULL on error. 437 * 438 * Side effects: 439 * None. 440 * 441 *--------------------------------------------------------------------------- 442 */ 443 444struct hostent * 445TclpGetHostByAddr( 446 const char *addr, 447 int length, 448 int type) 449{ 450#if !defined(TCL_THREADS) || defined(HAVE_MTSAFE_GETHOSTBYADDR) 451 return gethostbyaddr(addr, length, type); 452#else 453 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); 454 455#if defined(HAVE_GETHOSTBYADDR_R_7) 456 int h_errno; 457 458 return gethostbyaddr_r(addr, length, type, &tsdPtr->hent, tsdPtr->hbuf, 459 sizeof(tsdPtr->hbuf), &h_errno); 460 461#elif defined(HAVE_GETHOSTBYADDR_R_8) 462 struct hostent *hePtr; 463 int h_errno; 464 465 return (gethostbyaddr_r(addr, length, type, &tsdPtr->hent, tsdPtr->hbuf, 466 sizeof(tsdPtr->hbuf), &hePtr, &h_errno) == 0) 467 ? &tsdPtr->hent : NULL; 468#else 469#define NEED_COPYHOSTENT 1 470 struct hostent *hePtr; 471 472 Tcl_MutexLock(&compatLock); 473 hePtr = gethostbyaddr(addr, length, type); 474 if (hePtr != NULL) { 475 tsdPtr->hent = *hePtr; 476 hePtr = &tsdPtr->hent; 477 if (CopyHostent(&tsdPtr->hent, tsdPtr->hbuf, 478 sizeof(tsdPtr->hbuf)) == -1) { 479 hePtr = NULL; 480 } 481 } 482 Tcl_MutexUnlock(&compatLock); 483 return hePtr; 484#endif 485 486 return NULL; /* Not reached. */ 487#endif /* TCL_THREADS */ 488} 489 490/* 491 *--------------------------------------------------------------------------- 492 * 493 * CopyGrp -- 494 * 495 * Copies string fields of the group structure to the private buffer, 496 * honouring the size of the buffer. 497 * 498 * Results: 499 * 0 on success or -1 on error (errno = ERANGE). 500 * 501 * Side effects: 502 * None. 503 * 504 *--------------------------------------------------------------------------- 505 */ 506 507#ifdef NEED_COPYGRP 508#define NEED_COPYARRAY 1 509#define NEED_COPYSTRING 1 510 511static int 512CopyGrp( 513 struct group *tgtPtr, 514 char *buf, 515 int buflen) 516{ 517 register char *p = buf; 518 register int copied, len = 0; 519 520 /* 521 * Copy username. 522 */ 523 524 copied = CopyString(tgtPtr->gr_name, p, buflen - len); 525 if (copied == -1) { 526 goto range; 527 } 528 tgtPtr->gr_name = (copied > 0) ? p : NULL; 529 len += copied; 530 p = buf + len; 531 532 /* 533 * Copy password. 534 */ 535 536 copied = CopyString(tgtPtr->gr_passwd, p, buflen - len); 537 if (copied == -1) { 538 goto range; 539 } 540 tgtPtr->gr_passwd = (copied > 0) ? p : NULL; 541 len += copied; 542 p = buf + len; 543 544 /* 545 * Copy group members. 546 */ 547 548 PadBuffer(p, len, sizeof(char *)); 549 copied = CopyArray((char **)tgtPtr->gr_mem, -1, p, buflen - len); 550 if (copied == -1) { 551 goto range; 552 } 553 tgtPtr->gr_mem = (copied > 0) ? (char **)p : NULL; 554 555 return 0; 556 557 range: 558 errno = ERANGE; 559 return -1; 560} 561#endif /* NEED_COPYGRP */ 562 563/* 564 *--------------------------------------------------------------------------- 565 * 566 * CopyHostent -- 567 * 568 * Copies string fields of the hostnent structure to the private buffer, 569 * honouring the size of the buffer. 570 * 571 * Results: 572 * Number of bytes copied on success or -1 on error (errno = ERANGE) 573 * 574 * Side effects: 575 * None 576 * 577 *--------------------------------------------------------------------------- 578 */ 579 580#ifdef NEED_COPYHOSTENT 581#define NEED_COPYSTRING 1 582#define NEED_COPYARRAY 1 583 584static int 585CopyHostent( 586 struct hostent *tgtPtr, 587 char *buf, 588 int buflen) 589{ 590 char *p = buf; 591 int copied, len = 0; 592 593 copied = CopyString(tgtPtr->h_name, p, buflen - len); 594 if (copied == -1) { 595 goto range; 596 } 597 tgtPtr->h_name = (copied > 0) ? p : NULL; 598 len += copied; 599 p = buf + len; 600 601 PadBuffer(p, len, sizeof(char *)); 602 copied = CopyArray(tgtPtr->h_aliases, -1, p, buflen - len); 603 if (copied == -1) { 604 goto range; 605 } 606 tgtPtr->h_aliases = (copied > 0) ? (char **)p : NULL; 607 len += copied; 608 p += len; 609 610 PadBuffer(p, len, sizeof(char *)); 611 copied = CopyArray(tgtPtr->h_addr_list, tgtPtr->h_length, p, buflen-len); 612 if (copied == -1) { 613 goto range; 614 } 615 tgtPtr->h_addr_list = (copied > 0) ? (char **)p : NULL; 616 617 return 0; 618 619 range: 620 errno = ERANGE; 621 return -1; 622} 623#endif /* NEED_COPYHOSTENT */ 624 625/* 626 *--------------------------------------------------------------------------- 627 * 628 * CopyPwd -- 629 * 630 * Copies string fields of the passwd structure to the private buffer, 631 * honouring the size of the buffer. 632 * 633 * Results: 634 * 0 on success or -1 on error (errno = ERANGE). 635 * 636 * Side effects: 637 * We are not copying the gecos field as it may not be supported on all 638 * platforms. 639 * 640 *--------------------------------------------------------------------------- 641 */ 642 643#ifdef NEED_COPYPWD 644#define NEED_COPYSTRING 1 645 646static int 647CopyPwd( 648 struct passwd *tgtPtr, 649 char *buf, 650 int buflen) 651{ 652 char *p = buf; 653 int copied, len = 0; 654 655 copied = CopyString(tgtPtr->pw_name, p, buflen - len); 656 if (copied == -1) { 657 range: 658 errno = ERANGE; 659 return -1; 660 } 661 tgtPtr->pw_name = (copied > 0) ? p : NULL; 662 len += copied; 663 p = buf + len; 664 665 copied = CopyString(tgtPtr->pw_passwd, p, buflen - len); 666 if (copied == -1) { 667 goto range; 668 } 669 tgtPtr->pw_passwd = (copied > 0) ? p : NULL; 670 len += copied; 671 p = buf + len; 672 673 copied = CopyString(tgtPtr->pw_dir, p, buflen - len); 674 if (copied == -1) { 675 goto range; 676 } 677 tgtPtr->pw_dir = (copied > 0) ? p : NULL; 678 len += copied; 679 p = buf + len; 680 681 copied = CopyString(tgtPtr->pw_shell, p, buflen - len); 682 if (copied == -1) { 683 goto range; 684 } 685 tgtPtr->pw_shell = (copied > 0) ? p : NULL; 686 687 return 0; 688} 689#endif /* NEED_COPYPWD */ 690 691/* 692 *--------------------------------------------------------------------------- 693 * 694 * CopyArray -- 695 * 696 * Copies array of NULL-terminated or fixed-length strings to the private 697 * buffer, honouring the size of the buffer. 698 * 699 * Results: 700 * Number of bytes copied on success or -1 on error (errno = ERANGE) 701 * 702 * Side effects: 703 * None. 704 * 705 *--------------------------------------------------------------------------- 706 */ 707 708#ifdef NEED_COPYARRAY 709static int 710CopyArray( 711 char **src, /* Array of elements to copy. */ 712 int elsize, /* Size of each element, or -1 to indicate 713 * that they are C strings of dynamic 714 * length. */ 715 char *buf, /* Buffer to copy into. */ 716 int buflen) /* Size of buffer. */ 717{ 718 int i, j, len = 0; 719 char *p, **new; 720 721 if (src == NULL) { 722 return 0; 723 } 724 725 for (i = 0; src[i] != NULL; i++) { 726 /* 727 * Empty loop to count how many. 728 */ 729 } 730 len = sizeof(char *) * (i + 1); /* Leave place for the array. */ 731 if (len > buflen) { 732 return -1; 733 } 734 735 new = (char **) buf; 736 p = buf + len; 737 738 for (j = 0; j < i; j++) { 739 int sz = (elsize<0 ? (int) strlen(src[j]) + 1 : elsize); 740 741 len += sz; 742 if (len > buflen) { 743 return -1; 744 } 745 memcpy(p, src[j], sz); 746 new[j] = p; 747 p = buf + len; 748 } 749 new[j] = NULL; 750 751 return len; 752} 753#endif /* NEED_COPYARRAY */ 754 755/* 756 *--------------------------------------------------------------------------- 757 * 758 * CopyString -- 759 * 760 * Copies a NULL-terminated string to the private buffer, honouring the 761 * size of the buffer 762 * 763 * Results: 764 * 0 success or -1 on error (errno = ERANGE) 765 * 766 * Side effects: 767 * None 768 * 769 *--------------------------------------------------------------------------- 770 */ 771 772#ifdef NEED_COPYSTRING 773static int 774CopyString( 775 CONST char *src, /* String to copy. */ 776 char *buf, /* Buffer to copy into. */ 777 int buflen) /* Size of buffer. */ 778{ 779 int len = 0; 780 781 if (src != NULL) { 782 len = strlen(src) + 1; 783 if (len > buflen) { 784 return -1; 785 } 786 memcpy(buf, src, len); 787 } 788 789 return len; 790} 791#endif /* NEED_COPYSTRING */ 792 793/* 794 * Local Variables: 795 * mode: c 796 * c-basic-offset: 4 797 * fill-column: 78 798 * End: 799 */ 800