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