1/* 2 * tclUnixThrd.c -- 3 * 4 * This file implements the UNIX-specific thread support. 5 * 6 * Copyright (c) 1991-1994 The Regents of the University of California. 7 * Copyright (c) 1994-1997 Sun Microsystems, Inc. 8 * 9 * See the file "license.terms" for information on usage and redistribution of 10 * this file, and for a DISCLAIMER OF ALL WARRANTIES. 11 * 12 * RCS: @(#) $Id: tclUnixThrd.c,v 1.57 2008/01/11 11:53:02 msofer Exp $ 13 */ 14 15#include "tclInt.h" 16 17#ifdef TCL_THREADS 18 19#include "pthread.h" 20 21typedef struct ThreadSpecificData { 22 char nabuf[16]; 23} ThreadSpecificData; 24 25static Tcl_ThreadDataKey dataKey; 26 27/* 28 * masterLock is used to serialize creation of mutexes, condition variables, 29 * and thread local storage. This is the only place that can count on the 30 * ability to statically initialize the mutex. 31 */ 32 33static pthread_mutex_t masterLock = PTHREAD_MUTEX_INITIALIZER; 34 35/* 36 * initLock is used to serialize initialization and finalization of Tcl. It 37 * cannot use any dyamically allocated storage. 38 */ 39 40static pthread_mutex_t initLock = PTHREAD_MUTEX_INITIALIZER; 41 42/* 43 * allocLock is used by Tcl's version of malloc for synchronization. For 44 * obvious reasons, cannot use any dyamically allocated storage. 45 */ 46 47static pthread_mutex_t allocLock = PTHREAD_MUTEX_INITIALIZER; 48static pthread_mutex_t *allocLockPtr = &allocLock; 49 50/* 51 * These are for the critical sections inside this file. 52 */ 53 54#define MASTER_LOCK pthread_mutex_lock(&masterLock) 55#define MASTER_UNLOCK pthread_mutex_unlock(&masterLock) 56 57#endif /* TCL_THREADS */ 58 59 60/* 61 *---------------------------------------------------------------------- 62 * 63 * TclpThreadCreate -- 64 * 65 * This procedure creates a new thread. 66 * 67 * Results: 68 * TCL_OK if the thread could be created. The thread ID is returned in a 69 * parameter. 70 * 71 * Side effects: 72 * A new thread is created. 73 * 74 *---------------------------------------------------------------------- 75 */ 76 77int 78TclpThreadCreate( 79 Tcl_ThreadId *idPtr, /* Return, the ID of the thread */ 80 Tcl_ThreadCreateProc proc, /* Main() function of the thread */ 81 ClientData clientData, /* The one argument to Main() */ 82 int stackSize, /* Size of stack for the new thread */ 83 int flags) /* Flags controlling behaviour of the new 84 * thread. */ 85{ 86#ifdef TCL_THREADS 87 pthread_attr_t attr; 88 pthread_t theThread; 89 int result; 90 91 pthread_attr_init(&attr); 92 pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM); 93 94#ifdef HAVE_PTHREAD_ATTR_SETSTACKSIZE 95 if (stackSize != TCL_THREAD_STACK_DEFAULT) { 96 pthread_attr_setstacksize(&attr, (size_t) stackSize); 97#ifdef TCL_THREAD_STACK_MIN 98 } else { 99 /* 100 * Certain systems define a thread stack size that by default is too 101 * small for many operations. The user has the option of defining 102 * TCL_THREAD_STACK_MIN to a value large enough to work for their 103 * needs. This would look like (for 128K min stack): 104 * make MEM_DEBUG_FLAGS=-DTCL_THREAD_STACK_MIN=131072L 105 * 106 * This solution is not optimal, as we should allow the user to 107 * specify a size at runtime, but we don't want to slow this function 108 * down, and that would still leave the main thread at the default. 109 */ 110 111 size_t size; 112 result = pthread_attr_getstacksize(&attr, &size); 113 if (!result && (size < TCL_THREAD_STACK_MIN)) { 114 pthread_attr_setstacksize(&attr, (size_t) TCL_THREAD_STACK_MIN); 115 } 116#endif 117 } 118#endif 119 if (! (flags & TCL_THREAD_JOINABLE)) { 120 pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED); 121 } 122 123 124 if (pthread_create(&theThread, &attr, 125 (void * (*)(void *))proc, (void *)clientData) && 126 pthread_create(&theThread, NULL, 127 (void * (*)(void *))proc, (void *)clientData)) { 128 result = TCL_ERROR; 129 } else { 130 *idPtr = (Tcl_ThreadId)theThread; 131 result = TCL_OK; 132 } 133 pthread_attr_destroy(&attr); 134 return result; 135#else 136 return TCL_ERROR; 137#endif /* TCL_THREADS */ 138} 139 140/* 141 *---------------------------------------------------------------------- 142 * 143 * Tcl_JoinThread -- 144 * 145 * This procedure waits upon the exit of the specified thread. 146 * 147 * Results: 148 * TCL_OK if the wait was successful, TCL_ERROR else. 149 * 150 * Side effects: 151 * The result area is set to the exit code of the thread we waited upon. 152 * 153 *---------------------------------------------------------------------- 154 */ 155 156int 157Tcl_JoinThread( 158 Tcl_ThreadId threadId, /* Id of the thread to wait upon. */ 159 int *state) /* Reference to the storage the result of the 160 * thread we wait upon will be written into. 161 * May be NULL. */ 162{ 163#ifdef TCL_THREADS 164 int result; 165 unsigned long retcode, *retcodePtr = &retcode; 166 167 result = pthread_join((pthread_t) threadId, (void**) retcodePtr); 168 if (state) { 169 *state = (int) retcode; 170 } 171 return (result == 0) ? TCL_OK : TCL_ERROR; 172#else 173 return TCL_ERROR; 174#endif 175} 176 177#ifdef TCL_THREADS 178/* 179 *---------------------------------------------------------------------- 180 * 181 * TclpThreadExit -- 182 * 183 * This procedure terminates the current thread. 184 * 185 * Results: 186 * None. 187 * 188 * Side effects: 189 * This procedure terminates the current thread. 190 * 191 *---------------------------------------------------------------------- 192 */ 193 194void 195TclpThreadExit( 196 int status) 197{ 198 pthread_exit(INT2PTR(status)); 199} 200#endif /* TCL_THREADS */ 201 202#ifdef TCL_THREADS 203/* 204 *---------------------------------------------------------------------- 205 * 206 * TclpThreadGetStackSize -- 207 * 208 * This procedure returns the size of the current thread's stack. 209 * 210 * Results: 211 * Stack size (in bytes?) or -1 for error or 0 for undeterminable. 212 * 213 * Side effects: 214 * None. 215 * 216 *---------------------------------------------------------------------- 217 */ 218 219size_t 220TclpThreadGetStackSize(void) 221{ 222 size_t stackSize = 0; 223#if defined(HAVE_PTHREAD_ATTR_SETSTACKSIZE) && defined(TclpPthreadGetAttrs) 224 pthread_attr_t threadAttr; /* This will hold the thread attributes for 225 * the current thread. */ 226#ifdef __GLIBC__ 227 /* 228 * Fix for [Bug 1815573] 229 * 230 * DESCRIPTION: 231 * On linux TclpPthreadGetAttrs (which is pthread_attr_get_np) may return 232 * bogus values on the initial thread. 233 * 234 * ASSUMPTIONS: 235 * There seems to be no api to determine if we are on the initial 236 * thread. The simple scheme implemented here assumes: 237 * 1. The first Tcl interp to be created lives in the initial thread. If 238 * this assumption is not true, the fix is to call 239 * TclpThreadGetStackSize from the initial thread previous to 240 * creating any Tcl interpreter. In this case, especially if another 241 * Tcl interpreter may be created in the initial thread, it might be 242 * better to enable the second branch in the #if below 243 * 2. There will be no races in creating the first Tcl interp - ie, the 244 * second Tcl interp will be created only after the first call to 245 * Tcl_CreateInterp returns. 246 * 247 * These assumptions are satisfied by tclsh. Embedders on linux may want 248 * to check their validity, and possibly adapt the code on failing to meet 249 * them. 250 */ 251 252 static int initialized = 0; 253 254 if (!initialized) { 255 initialized = 1; 256 return 0; 257 } else { 258#else 259 { 260#endif 261 if (pthread_attr_init(&threadAttr) != 0) { 262 return -1; 263 } 264 if (TclpPthreadGetAttrs(pthread_self(), &threadAttr) != 0) { 265 pthread_attr_destroy(&threadAttr); 266 return (size_t)-1; 267 } 268 } 269 270 271 if (pthread_attr_getstacksize(&threadAttr, &stackSize) != 0) { 272 pthread_attr_destroy(&threadAttr); 273 return (size_t)-1; 274 } 275 pthread_attr_destroy(&threadAttr); 276#elif defined(HAVE_PTHREAD_GET_STACKSIZE_NP) 277#ifdef __APPLE__ 278 /* 279 * On Darwin, the API below does not return the correct stack size for the 280 * main thread (which is not a real pthread), so fallback to getrlimit(). 281 */ 282 if (!pthread_main_np()) 283#endif 284 stackSize = pthread_get_stacksize_np(pthread_self()); 285#else 286 /* 287 * Cannot determine the real stack size of this thread. The caller might 288 * want to try looking at the process accounting limits instead. 289 */ 290#endif 291 return stackSize; 292} 293#endif /* TCL_THREADS */ 294 295/* 296 *---------------------------------------------------------------------- 297 * 298 * Tcl_GetCurrentThread -- 299 * 300 * This procedure returns the ID of the currently running thread. 301 * 302 * Results: 303 * A thread ID. 304 * 305 * Side effects: 306 * None. 307 * 308 *---------------------------------------------------------------------- 309 */ 310 311Tcl_ThreadId 312Tcl_GetCurrentThread(void) 313{ 314#ifdef TCL_THREADS 315 return (Tcl_ThreadId) pthread_self(); 316#else 317 return (Tcl_ThreadId) 0; 318#endif 319} 320 321/* 322 *---------------------------------------------------------------------- 323 * 324 * TclpInitLock 325 * 326 * This procedure is used to grab a lock that serializes initialization 327 * and finalization of Tcl. On some platforms this may also initialize 328 * the mutex used to serialize creation of more mutexes and thread local 329 * storage keys. 330 * 331 * Results: 332 * None. 333 * 334 * Side effects: 335 * Acquire the initialization mutex. 336 * 337 *---------------------------------------------------------------------- 338 */ 339 340void 341TclpInitLock(void) 342{ 343#ifdef TCL_THREADS 344 pthread_mutex_lock(&initLock); 345#endif 346} 347 348/* 349 *---------------------------------------------------------------------- 350 * 351 * TclpFinalizeLock 352 * 353 * This procedure is used to destroy all private resources used in this 354 * file. 355 * 356 * Results: 357 * None. 358 * 359 * Side effects: 360 * Destroys everything private. TclpInitLock must be held entering this 361 * function. 362 * 363 *---------------------------------------------------------------------- 364 */ 365 366void 367TclFinalizeLock(void) 368{ 369#ifdef TCL_THREADS 370 /* 371 * You do not need to destroy mutexes that were created with the 372 * PTHREAD_MUTEX_INITIALIZER macro. These mutexes do not need any 373 * destruction: masterLock, allocLock, and initLock. 374 */ 375 376 pthread_mutex_unlock(&initLock); 377#endif 378} 379 380/* 381 *---------------------------------------------------------------------- 382 * 383 * TclpInitUnlock 384 * 385 * This procedure is used to release a lock that serializes 386 * initialization and finalization of Tcl. 387 * 388 * Results: 389 * None. 390 * 391 * Side effects: 392 * Release the initialization mutex. 393 * 394 *---------------------------------------------------------------------- 395 */ 396 397void 398TclpInitUnlock(void) 399{ 400#ifdef TCL_THREADS 401 pthread_mutex_unlock(&initLock); 402#endif 403} 404 405/* 406 *---------------------------------------------------------------------- 407 * 408 * TclpMasterLock 409 * 410 * This procedure is used to grab a lock that serializes creation and 411 * finalization of serialization objects. This interface is only needed 412 * in finalization; it is hidden during creation of the objects. 413 * 414 * This lock must be different than the initLock because the initLock is 415 * held during creation of syncronization objects. 416 * 417 * Results: 418 * None. 419 * 420 * Side effects: 421 * Acquire the master mutex. 422 * 423 *---------------------------------------------------------------------- 424 */ 425 426void 427TclpMasterLock(void) 428{ 429#ifdef TCL_THREADS 430 pthread_mutex_lock(&masterLock); 431#endif 432} 433 434 435/* 436 *---------------------------------------------------------------------- 437 * 438 * TclpMasterUnlock 439 * 440 * This procedure is used to release a lock that serializes creation and 441 * finalization of synchronization objects. 442 * 443 * Results: 444 * None. 445 * 446 * Side effects: 447 * Release the master mutex. 448 * 449 *---------------------------------------------------------------------- 450 */ 451 452void 453TclpMasterUnlock(void) 454{ 455#ifdef TCL_THREADS 456 pthread_mutex_unlock(&masterLock); 457#endif 458} 459 460 461/* 462 *---------------------------------------------------------------------- 463 * 464 * Tcl_GetAllocMutex 465 * 466 * This procedure returns a pointer to a statically initialized mutex for 467 * use by the memory allocator. The alloctor must use this lock, because 468 * all other locks are allocated... 469 * 470 * Results: 471 * A pointer to a mutex that is suitable for passing to Tcl_MutexLock and 472 * Tcl_MutexUnlock. 473 * 474 * Side effects: 475 * None. 476 * 477 *---------------------------------------------------------------------- 478 */ 479 480Tcl_Mutex * 481Tcl_GetAllocMutex(void) 482{ 483#ifdef TCL_THREADS 484 pthread_mutex_t **allocLockPtrPtr = &allocLockPtr; 485 return (Tcl_Mutex *) allocLockPtrPtr; 486#else 487 return NULL; 488#endif 489} 490 491#ifdef TCL_THREADS 492 493/* 494 *---------------------------------------------------------------------- 495 * 496 * Tcl_MutexLock -- 497 * 498 * This procedure is invoked to lock a mutex. This procedure handles 499 * initializing the mutex, if necessary. The caller can rely on the fact 500 * that Tcl_Mutex is an opaque pointer. This routine will change that 501 * pointer from NULL after first use. 502 * 503 * Results: 504 * None. 505 * 506 * Side effects: 507 * May block the current thread. The mutex is aquired when this returns. 508 * Will allocate memory for a pthread_mutex_t and initialize this the 509 * first time this Tcl_Mutex is used. 510 * 511 *---------------------------------------------------------------------- 512 */ 513 514void 515Tcl_MutexLock( 516 Tcl_Mutex *mutexPtr) /* Really (pthread_mutex_t **) */ 517{ 518 pthread_mutex_t *pmutexPtr; 519 if (*mutexPtr == NULL) { 520 MASTER_LOCK; 521 if (*mutexPtr == NULL) { 522 /* 523 * Double inside master lock check to avoid a race condition. 524 */ 525 526 pmutexPtr = (pthread_mutex_t *)ckalloc(sizeof(pthread_mutex_t)); 527 pthread_mutex_init(pmutexPtr, NULL); 528 *mutexPtr = (Tcl_Mutex)pmutexPtr; 529 TclRememberMutex(mutexPtr); 530 } 531 MASTER_UNLOCK; 532 } 533 pmutexPtr = *((pthread_mutex_t **)mutexPtr); 534 pthread_mutex_lock(pmutexPtr); 535} 536 537/* 538 *---------------------------------------------------------------------- 539 * 540 * Tcl_MutexUnlock -- 541 * 542 * This procedure is invoked to unlock a mutex. The mutex must have been 543 * locked by Tcl_MutexLock. 544 * 545 * Results: 546 * None. 547 * 548 * Side effects: 549 * The mutex is released when this returns. 550 * 551 *---------------------------------------------------------------------- 552 */ 553 554void 555Tcl_MutexUnlock( 556 Tcl_Mutex *mutexPtr) /* Really (pthread_mutex_t **) */ 557{ 558 pthread_mutex_t *pmutexPtr = *(pthread_mutex_t **)mutexPtr; 559 pthread_mutex_unlock(pmutexPtr); 560} 561 562/* 563 *---------------------------------------------------------------------- 564 * 565 * TclpFinalizeMutex -- 566 * 567 * This procedure is invoked to clean up one mutex. This is only safe to 568 * call at the end of time. 569 * 570 * This assumes the Master Lock is held. 571 * 572 * Results: 573 * None. 574 * 575 * Side effects: 576 * The mutex list is deallocated. 577 * 578 *---------------------------------------------------------------------- 579 */ 580 581void 582TclpFinalizeMutex( 583 Tcl_Mutex *mutexPtr) 584{ 585 pthread_mutex_t *pmutexPtr = *(pthread_mutex_t **)mutexPtr; 586 if (pmutexPtr != NULL) { 587 pthread_mutex_destroy(pmutexPtr); 588 ckfree((char *) pmutexPtr); 589 *mutexPtr = NULL; 590 } 591} 592 593/* 594 *---------------------------------------------------------------------- 595 * 596 * Tcl_ConditionWait -- 597 * 598 * This procedure is invoked to wait on a condition variable. The mutex 599 * is automically released as part of the wait, and automatically grabbed 600 * when the condition is signaled. 601 * 602 * The mutex must be held when this procedure is called. 603 * 604 * Results: 605 * None. 606 * 607 * Side effects: 608 * May block the current thread. The mutex is aquired when this returns. 609 * Will allocate memory for a pthread_mutex_t and initialize this the 610 * first time this Tcl_Mutex is used. 611 * 612 *---------------------------------------------------------------------- 613 */ 614 615void 616Tcl_ConditionWait( 617 Tcl_Condition *condPtr, /* Really (pthread_cond_t **) */ 618 Tcl_Mutex *mutexPtr, /* Really (pthread_mutex_t **) */ 619 Tcl_Time *timePtr) /* Timeout on waiting period */ 620{ 621 pthread_cond_t *pcondPtr; 622 pthread_mutex_t *pmutexPtr; 623 struct timespec ptime; 624 625 if (*condPtr == NULL) { 626 MASTER_LOCK; 627 628 /* 629 * Double check inside mutex to avoid race, then initialize condition 630 * variable if necessary. 631 */ 632 633 if (*condPtr == NULL) { 634 pcondPtr = (pthread_cond_t *) ckalloc(sizeof(pthread_cond_t)); 635 pthread_cond_init(pcondPtr, NULL); 636 *condPtr = (Tcl_Condition)pcondPtr; 637 TclRememberCondition(condPtr); 638 } 639 MASTER_UNLOCK; 640 } 641 pmutexPtr = *((pthread_mutex_t **)mutexPtr); 642 pcondPtr = *((pthread_cond_t **)condPtr); 643 if (timePtr == NULL) { 644 pthread_cond_wait(pcondPtr, pmutexPtr); 645 } else { 646 Tcl_Time now; 647 648 /* 649 * Make sure to take into account the microsecond component of the 650 * current time, including possible overflow situations. [Bug #411603] 651 */ 652 653 Tcl_GetTime(&now); 654 ptime.tv_sec = timePtr->sec + now.sec + 655 (timePtr->usec + now.usec) / 1000000; 656 ptime.tv_nsec = 1000 * ((timePtr->usec + now.usec) % 1000000); 657 pthread_cond_timedwait(pcondPtr, pmutexPtr, &ptime); 658 } 659} 660 661/* 662 *---------------------------------------------------------------------- 663 * 664 * Tcl_ConditionNotify -- 665 * 666 * This procedure is invoked to signal a condition variable. 667 * 668 * The mutex must be held during this call to avoid races, but this 669 * interface does not enforce that. 670 * 671 * Results: 672 * None. 673 * 674 * Side effects: 675 * May unblock another thread. 676 * 677 *---------------------------------------------------------------------- 678 */ 679 680void 681Tcl_ConditionNotify( 682 Tcl_Condition *condPtr) 683{ 684 pthread_cond_t *pcondPtr = *((pthread_cond_t **)condPtr); 685 if (pcondPtr != NULL) { 686 pthread_cond_broadcast(pcondPtr); 687 } else { 688 /* 689 * Noone has used the condition variable, so there are no waiters. 690 */ 691 } 692} 693 694/* 695 *---------------------------------------------------------------------- 696 * 697 * TclpFinalizeCondition -- 698 * 699 * This procedure is invoked to clean up a condition variable. This is 700 * only safe to call at the end of time. 701 * 702 * This assumes the Master Lock is held. 703 * 704 * Results: 705 * None. 706 * 707 * Side effects: 708 * The condition variable is deallocated. 709 * 710 *---------------------------------------------------------------------- 711 */ 712 713void 714TclpFinalizeCondition( 715 Tcl_Condition *condPtr) 716{ 717 pthread_cond_t *pcondPtr = *(pthread_cond_t **)condPtr; 718 if (pcondPtr != NULL) { 719 pthread_cond_destroy(pcondPtr); 720 ckfree((char *) pcondPtr); 721 *condPtr = NULL; 722 } 723} 724#endif /* TCL_THREADS */ 725 726/* 727 *---------------------------------------------------------------------- 728 * 729 * TclpReaddir, TclpLocaltime, TclpGmtime, TclpInetNtoa -- 730 * 731 * These procedures replace core C versions to be used in a threaded 732 * environment. 733 * 734 * Results: 735 * See documentation of C functions. 736 * 737 * Side effects: 738 * See documentation of C functions. 739 * 740 * Notes: 741 * TclpReaddir is no longer used by the core (see 1095909), but it 742 * appears in the internal stubs table (see #589526). 743 * 744 *---------------------------------------------------------------------- 745 */ 746 747Tcl_DirEntry * 748TclpReaddir( 749 DIR * dir) 750{ 751 return TclOSreaddir(dir); 752} 753 754char * 755TclpInetNtoa( 756 struct in_addr addr) 757{ 758#ifdef TCL_THREADS 759 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); 760 unsigned char *b = (unsigned char*) &addr.s_addr; 761 762 sprintf(tsdPtr->nabuf, "%u.%u.%u.%u", b[0], b[1], b[2], b[3]); 763 return tsdPtr->nabuf; 764#else 765 return inet_ntoa(addr); 766#endif 767} 768 769#ifdef TCL_THREADS 770/* 771 * Additions by AOL for specialized thread memory allocator. 772 */ 773 774#ifdef USE_THREAD_ALLOC 775static volatile int initialized = 0; 776static pthread_key_t key; 777 778typedef struct allocMutex { 779 Tcl_Mutex tlock; 780 pthread_mutex_t plock; 781} allocMutex; 782 783Tcl_Mutex * 784TclpNewAllocMutex(void) 785{ 786 struct allocMutex *lockPtr; 787 register pthread_mutex_t *plockPtr; 788 789 lockPtr = malloc(sizeof(struct allocMutex)); 790 if (lockPtr == NULL) { 791 Tcl_Panic("could not allocate lock"); 792 } 793 plockPtr = &lockPtr->plock; 794 lockPtr->tlock = (Tcl_Mutex) plockPtr; 795 pthread_mutex_init(&lockPtr->plock, NULL); 796 return &lockPtr->tlock; 797} 798 799void 800TclpFreeAllocMutex( 801 Tcl_Mutex *mutex) /* The alloc mutex to free. */ 802{ 803 allocMutex* lockPtr = (allocMutex*) mutex; 804 if (!lockPtr) { 805 return; 806 } 807 pthread_mutex_destroy(&lockPtr->plock); 808 free(lockPtr); 809} 810 811void 812TclpFreeAllocCache( 813 void *ptr) 814{ 815 if (ptr != NULL) { 816 /* 817 * Called by the pthread lib when a thread exits 818 */ 819 820 TclFreeAllocCache(ptr); 821 822 } else if (initialized) { 823 /* 824 * Called by us in TclFinalizeThreadAlloc() during the library 825 * finalization initiated from Tcl_Finalize() 826 */ 827 828 pthread_key_delete(key); 829 initialized = 0; 830 } 831} 832 833void * 834TclpGetAllocCache(void) 835{ 836 if (!initialized) { 837 pthread_mutex_lock(allocLockPtr); 838 if (!initialized) { 839 pthread_key_create(&key, TclpFreeAllocCache); 840 initialized = 1; 841 } 842 pthread_mutex_unlock(allocLockPtr); 843 } 844 return pthread_getspecific(key); 845} 846 847void 848TclpSetAllocCache( 849 void *arg) 850{ 851 pthread_setspecific(key, arg); 852} 853#endif /* USE_THREAD_ALLOC */ 854#endif /* TCL_THREADS */ 855 856/* 857 * Local Variables: 858 * mode: c 859 * c-basic-offset: 4 860 * fill-column: 78 861 * End: 862 */ 863