blob: 2718dd5f6ac71d7fa852658bdc831499b8fadd74 [file] [log] [blame]
sewardjaf44c822007-11-25 14:01:38 +00001/*
2 This file is part of drd, a data race detector.
3
sewardj85642922008-01-14 11:54:56 +00004 Copyright (C) 2006-2008 Bart Van Assche
sewardjaf44c822007-11-25 14:01:38 +00005 bart.vanassche@gmail.com
6
7 This program is free software; you can redistribute it and/or
8 modify it under the terms of the GNU General Public License as
9 published by the Free Software Foundation; either version 2 of the
10 License, or (at your option) any later version.
11
12 This program is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
20 02111-1307, USA.
21
22 The GNU General Public License is contained in the file COPYING.
23*/
24
25
bart4bb53d82008-02-28 19:06:34 +000026#include "drd_clientobj.h"
sewardjaf44c822007-11-25 14:01:38 +000027#include "drd_error.h"
28#include "drd_mutex.h"
sewardj721ad7b2007-11-30 08:30:29 +000029#include "priv_drd_clientreq.h"
sewardjaf44c822007-11-25 14:01:38 +000030#include "pub_tool_errormgr.h" // VG_(maybe_record_error)()
31#include "pub_tool_libcassert.h" // tl_assert()
bart4bb53d82008-02-28 19:06:34 +000032#include "pub_tool_libcprint.h" // VG_(message)()
sewardjaf44c822007-11-25 14:01:38 +000033#include "pub_tool_machine.h" // VG_(get_IP)()
34#include "pub_tool_threadstate.h" // VG_(get_running_tid)()
35
36
sewardj347eeba2008-01-21 14:19:07 +000037// Local functions.
38
bart46d5f172008-02-28 19:49:37 +000039static void mutex_cleanup(struct mutex_info* p);
bart5357fcb2008-02-27 15:46:00 +000040static Bool mutex_is_locked(struct mutex_info* const p);
sewardj347eeba2008-01-21 14:19:07 +000041static void mutex_destroy(struct mutex_info* const p);
42
43
sewardjaf44c822007-11-25 14:01:38 +000044// Local variables.
45
46static Bool s_trace_mutex;
47static ULong s_mutex_lock_count;
sewardjaf44c822007-11-25 14:01:38 +000048
49
50// Function definitions.
51
52void mutex_set_trace(const Bool trace_mutex)
53{
54 tl_assert(!! trace_mutex == trace_mutex);
55 s_trace_mutex = trace_mutex;
56}
57
58static
59void mutex_initialize(struct mutex_info* const p,
60 const Addr mutex,
sewardj721ad7b2007-11-30 08:30:29 +000061 const SizeT size,
62 const MutexT mutex_type)
sewardjaf44c822007-11-25 14:01:38 +000063{
64 tl_assert(mutex != 0);
65 tl_assert(size > 0);
66
bart4bb53d82008-02-28 19:06:34 +000067 tl_assert(p->a1 == mutex);
68 tl_assert(p->a2 == mutex + size);
bart46d5f172008-02-28 19:49:37 +000069 p->cleanup = (void(*)(DrdClientobj*))&mutex_cleanup;
sewardj721ad7b2007-11-30 08:30:29 +000070 p->mutex_type = mutex_type;
sewardjaf44c822007-11-25 14:01:38 +000071 p->recursion_count = 0;
72 p->owner = DRD_INVALID_THREADID;
73 vc_init(&p->vc, 0, 0);
74}
75
bart46d5f172008-02-28 19:49:37 +000076/** Deallocate the memory that was allocated by mutex_initialize(). */
77static void mutex_cleanup(struct mutex_info* p)
78{
bartb78312c2008-02-29 11:00:17 +000079 if (s_trace_mutex)
80 {
81 const ThreadId vg_tid = VG_(get_running_tid)();
82 const DrdThreadId drd_tid = VgThreadIdToDrdThreadId(vg_tid);
83 VG_(message)(Vg_DebugMsg,
84 "drd_pre_mutex_destroy tid = %d/%d, %s 0x%lx",
85 vg_tid, drd_tid,
86 mutex_get_typename(p),
87 p->a1);
88 }
89
bart46d5f172008-02-28 19:49:37 +000090 if (mutex_is_locked(p))
91 {
92 MutexErrInfo MEI = { p->a1, p->recursion_count, p->owner };
93 VG_(maybe_record_error)(VG_(get_running_tid)(),
94 MutexErr,
95 VG_(get_IP)(VG_(get_running_tid)()),
96 "Destroying locked mutex",
97 &MEI);
98 }
99
100 vc_cleanup(&p->vc);
101}
102
sewardjaf44c822007-11-25 14:01:38 +0000103static
sewardj721ad7b2007-11-30 08:30:29 +0000104struct mutex_info*
105mutex_get_or_allocate(const Addr mutex,
106 const SizeT size,
107 const MutexT mutex_type)
sewardjaf44c822007-11-25 14:01:38 +0000108{
bart4bb53d82008-02-28 19:06:34 +0000109 struct mutex_info* p;
sewardj721ad7b2007-11-30 08:30:29 +0000110
bart4bb53d82008-02-28 19:06:34 +0000111 tl_assert(offsetof(DrdClientobj, mutex) == 0);
112 p = &drd_clientobj_get(mutex, ClientMutex)->mutex;
113 if (p)
114 {
115 tl_assert(p->mutex_type == mutex_type);
116 tl_assert(p->a2 - p->a1 == size);
117 return p;
118 }
sewardj721ad7b2007-11-30 08:30:29 +0000119
bart4bb53d82008-02-28 19:06:34 +0000120 if (drd_clientobj_present(mutex, mutex + size))
sewardj721ad7b2007-11-30 08:30:29 +0000121 {
bart4bb53d82008-02-28 19:06:34 +0000122 GenericErrInfo GEI;
123 VG_(maybe_record_error)(VG_(get_running_tid)(),
124 GenericErr,
125 VG_(get_IP)(VG_(get_running_tid)()),
126 "Not a mutex",
127 &GEI);
128 return 0;
sewardj721ad7b2007-11-30 08:30:29 +0000129 }
bart4bb53d82008-02-28 19:06:34 +0000130
131 p = &drd_clientobj_add(mutex, mutex + size, ClientMutex)->mutex;
132 mutex_initialize(p, mutex, size, mutex_type);
133 return p;
sewardjaf44c822007-11-25 14:01:38 +0000134}
135
sewardj721ad7b2007-11-30 08:30:29 +0000136struct mutex_info*
137mutex_init(const Addr mutex, const SizeT size, const MutexT mutex_type)
sewardjaf44c822007-11-25 14:01:38 +0000138{
139 struct mutex_info* mutex_p;
140
sewardjaf44c822007-11-25 14:01:38 +0000141 if (s_trace_mutex)
142 {
143 const ThreadId vg_tid = VG_(get_running_tid)();
144 const DrdThreadId drd_tid = VgThreadIdToDrdThreadId(vg_tid);
145 VG_(message)(Vg_DebugMsg,
146 "drd_post_mutex_init tid = %d/%d, %s 0x%lx",
147 vg_tid, drd_tid,
sewardj347eeba2008-01-21 14:19:07 +0000148 mutex_type_name(mutex_type),
sewardjaf44c822007-11-25 14:01:38 +0000149 mutex);
150 }
151
sewardj347eeba2008-01-21 14:19:07 +0000152 mutex_p = mutex_get(mutex);
153 if (mutex_p)
154 {
155 const ThreadId vg_tid = VG_(get_running_tid)();
156 MutexErrInfo MEI
bart4bb53d82008-02-28 19:06:34 +0000157 = { mutex_p->a1, mutex_p->recursion_count, mutex_p->owner };
sewardj347eeba2008-01-21 14:19:07 +0000158 VG_(maybe_record_error)(vg_tid,
159 MutexErr,
160 VG_(get_IP)(vg_tid),
161 "Mutex reinitialization",
162 &MEI);
bartb78312c2008-02-29 11:00:17 +0000163 return mutex_p;
sewardj347eeba2008-01-21 14:19:07 +0000164 }
165 mutex_p = mutex_get_or_allocate(mutex, size, mutex_type);
166
sewardjaf44c822007-11-25 14:01:38 +0000167 return mutex_p;
168}
169
sewardj347eeba2008-01-21 14:19:07 +0000170static void mutex_destroy(struct mutex_info* const p)
sewardjaf44c822007-11-25 14:01:38 +0000171{
bart4bb53d82008-02-28 19:06:34 +0000172 drd_clientobj_remove(p->a1);
sewardjaf44c822007-11-25 14:01:38 +0000173}
174
bart46d5f172008-02-28 19:49:37 +0000175/** Called after pthread_mutex_destroy(). */
sewardj347eeba2008-01-21 14:19:07 +0000176void mutex_post_destroy(const Addr mutex)
177{
178 struct mutex_info* p;
179
180 p = mutex_get(mutex);
181 tl_assert(p);
182 if (p)
183 {
bart46d5f172008-02-28 19:49:37 +0000184 mutex_destroy(p);
sewardj347eeba2008-01-21 14:19:07 +0000185 }
186}
187
sewardjaf44c822007-11-25 14:01:38 +0000188struct mutex_info* mutex_get(const Addr mutex)
189{
bart4bb53d82008-02-28 19:06:34 +0000190 tl_assert(offsetof(DrdClientobj, mutex) == 0);
191 return &drd_clientobj_get(mutex, ClientMutex)->mutex;
sewardjaf44c822007-11-25 14:01:38 +0000192}
193
bart8bba1f72008-02-27 16:13:05 +0000194/** Called before pthread_mutex_lock() is invoked. If a data structure for
195 * the client-side object was not yet created, do this now. Also check whether
196 * an attempt is made to lock recursively a synchronization object that must
197 * not be locked recursively.
198 */
199void mutex_pre_lock(const Addr mutex, const SizeT size, MutexT mutex_type)
200{
bart635cb162008-02-28 08:30:43 +0000201 struct mutex_info* p;
202
203 p = mutex_get(mutex);
bart8bba1f72008-02-27 16:13:05 +0000204 if (p == 0)
205 {
206 mutex_init(mutex, size, mutex_type);
207 p = mutex_get(mutex);
208 }
bart635cb162008-02-28 08:30:43 +0000209
bart8bba1f72008-02-27 16:13:05 +0000210 tl_assert(p);
211
212 if (p->owner == thread_get_running_tid()
213 && p->recursion_count >= 1
214 && mutex_type != mutex_type_recursive_mutex)
215 {
bart4bb53d82008-02-28 19:06:34 +0000216 MutexErrInfo MEI = { p->a1, p->recursion_count, p->owner };
bart8bba1f72008-02-27 16:13:05 +0000217 VG_(maybe_record_error)(VG_(get_running_tid)(),
218 MutexErr,
219 VG_(get_IP)(VG_(get_running_tid)()),
220 "Recursive locking not allowed",
221 &MEI);
222 }
223}
224
sewardjaf44c822007-11-25 14:01:38 +0000225/**
226 * Update mutex_info state when locking the pthread_mutex_t mutex.
227 * Note: this function must be called after pthread_mutex_lock() has been
228 * called, or a race condition is triggered !
229 */
bart8bba1f72008-02-27 16:13:05 +0000230int mutex_post_lock(const Addr mutex, const SizeT size, MutexT mutex_type)
sewardjaf44c822007-11-25 14:01:38 +0000231{
232 const DrdThreadId drd_tid = VgThreadIdToDrdThreadId(VG_(get_running_tid)());
sewardj721ad7b2007-11-30 08:30:29 +0000233 struct mutex_info* const p = mutex_get_or_allocate(mutex, size, mutex_type);
sewardjaf44c822007-11-25 14:01:38 +0000234
235 if (s_trace_mutex)
236 {
237 const ThreadId tid = DrdThreadIdToVgThreadId(drd_tid);
238 VG_(message)(Vg_DebugMsg,
239 "drd_post_mutex_lock tid = %d/%d, %s 0x%lx rc %d owner %d",
240 tid,
241 drd_tid,
242 mutex_get_typename(p),
243 mutex,
244 p ? p->recursion_count : 0,
245 p ? p->owner : VG_INVALID_THREADID);
246 }
247
bart635cb162008-02-28 08:30:43 +0000248 if (mutex_type == mutex_type_invalid_mutex)
249 {
250 GenericErrInfo GEI;
251 VG_(maybe_record_error)(VG_(get_running_tid)(),
252 GenericErr,
253 VG_(get_IP)(VG_(get_running_tid)()),
254 "Invalid mutex",
255 &GEI);
256 }
257
bartab7a6442008-02-25 19:46:14 +0000258 if (p == 0)
259 {
barte883bc82008-02-26 19:13:04 +0000260 GenericErrInfo GEI;
bartab7a6442008-02-25 19:46:14 +0000261 VG_(maybe_record_error)(VG_(get_running_tid)(),
barte883bc82008-02-26 19:13:04 +0000262 GenericErr,
bartab7a6442008-02-25 19:46:14 +0000263 VG_(get_IP)(VG_(get_running_tid)()),
264 "Not a mutex",
barte883bc82008-02-26 19:13:04 +0000265 &GEI);
bartab7a6442008-02-25 19:46:14 +0000266 return 0;
267 }
268
bart5357fcb2008-02-27 15:46:00 +0000269#if 0
sewardj721ad7b2007-11-30 08:30:29 +0000270 tl_assert(mutex_type == mutex_type_mutex
271 || mutex_type == mutex_type_spinlock);
bart5357fcb2008-02-27 15:46:00 +0000272#endif
273
sewardj721ad7b2007-11-30 08:30:29 +0000274 tl_assert(p->mutex_type == mutex_type);
bart4bb53d82008-02-28 19:06:34 +0000275 tl_assert(p->a2 - p->a1 == size);
sewardj721ad7b2007-11-30 08:30:29 +0000276
sewardjaf44c822007-11-25 14:01:38 +0000277 if (p->recursion_count == 0)
278 {
279 p->owner = drd_tid;
280 s_mutex_lock_count++;
281 }
282 else if (p->owner != drd_tid)
283 {
284 VG_(message)(Vg_DebugMsg,
285 "The impossible happened: mutex 0x%lx is locked"
286 " simultaneously by two threads (recursion count %d,"
287 " owners %d and %d) !",
bart4bb53d82008-02-28 19:06:34 +0000288 p->a1, p->recursion_count, p->owner, drd_tid);
sewardj347eeba2008-01-21 14:19:07 +0000289 p->owner = drd_tid;
sewardjaf44c822007-11-25 14:01:38 +0000290 }
291 p->recursion_count++;
292
293 if (p->recursion_count == 1)
294 {
bartab7a6442008-02-25 19:46:14 +0000295 const DrdThreadId last_owner = p->owner;
296
sewardjaf44c822007-11-25 14:01:38 +0000297 if (last_owner != drd_tid && last_owner != DRD_INVALID_THREADID)
298 thread_combine_vc2(drd_tid, mutex_get_last_vc(mutex));
299 thread_new_segment(drd_tid);
300 }
301
302 return p->recursion_count;
303}
304
305/**
306 * Update mutex_info state when unlocking the pthread_mutex_t mutex.
307 * Note: this function must be called before pthread_mutex_unlock() is called,
308 * or a race condition is triggered !
bartb78312c2008-02-29 11:00:17 +0000309 * @return New value of the mutex recursion count.
sewardjaf44c822007-11-25 14:01:38 +0000310 * @param mutex Pointer to pthread_mutex_t data structure in the client space.
311 * @param tid ThreadId of the thread calling pthread_mutex_unlock().
312 * @param vc Pointer to the current vector clock of thread tid.
313 */
sewardj721ad7b2007-11-30 08:30:29 +0000314int mutex_unlock(const Addr mutex, const MutexT mutex_type)
sewardjaf44c822007-11-25 14:01:38 +0000315{
bartb78312c2008-02-29 11:00:17 +0000316 const DrdThreadId drd_tid = thread_get_running_tid();
317 const ThreadId vg_tid = VG_(get_running_tid)();
sewardjaf44c822007-11-25 14:01:38 +0000318 const VectorClock* const vc = thread_get_vc(drd_tid);
319 struct mutex_info* const p = mutex_get(mutex);
320
bartb78312c2008-02-29 11:00:17 +0000321 if (s_trace_mutex && p != 0)
sewardjaf44c822007-11-25 14:01:38 +0000322 {
323 VG_(message)(Vg_DebugMsg,
324 "drd_pre_mutex_unlock tid = %d/%d, %s 0x%lx rc %d",
325 vg_tid, drd_tid,
326 mutex_get_typename(p),
327 mutex,
328 p->recursion_count,
329 p->owner);
330 }
331
bart635cb162008-02-28 08:30:43 +0000332 if (mutex_type == mutex_type_invalid_mutex)
333 {
334 GenericErrInfo GEI;
335 VG_(maybe_record_error)(VG_(get_running_tid)(),
336 GenericErr,
337 VG_(get_IP)(VG_(get_running_tid)()),
338 "Invalid mutex",
339 &GEI);
340 }
341
bart5357fcb2008-02-27 15:46:00 +0000342 if (p == 0)
bartab7a6442008-02-25 19:46:14 +0000343 {
barte883bc82008-02-26 19:13:04 +0000344 GenericErrInfo GEI;
bartab7a6442008-02-25 19:46:14 +0000345 VG_(maybe_record_error)(vg_tid,
barte883bc82008-02-26 19:13:04 +0000346 GenericErr,
bartab7a6442008-02-25 19:46:14 +0000347 VG_(get_IP)(vg_tid),
348 "Not a mutex",
barte883bc82008-02-26 19:13:04 +0000349 &GEI);
bartab7a6442008-02-25 19:46:14 +0000350 return 0;
351 }
352
bart5357fcb2008-02-27 15:46:00 +0000353 if (p->owner == DRD_INVALID_THREADID)
354 {
bart4bb53d82008-02-28 19:06:34 +0000355 MutexErrInfo MEI = { p->a1, p->recursion_count, p->owner };
bart5357fcb2008-02-27 15:46:00 +0000356 VG_(maybe_record_error)(vg_tid,
357 MutexErr,
358 VG_(get_IP)(vg_tid),
359 "Mutex not locked",
360 &MEI);
361 return 0;
362 }
363
sewardjaf44c822007-11-25 14:01:38 +0000364 tl_assert(p);
bart5357fcb2008-02-27 15:46:00 +0000365 if (p->mutex_type != mutex_type)
366 {
367 VG_(message)(Vg_DebugMsg, "??? mutex %p: type changed from %d into %d",
bart4bb53d82008-02-28 19:06:34 +0000368 p->a1, p->mutex_type, mutex_type);
bart5357fcb2008-02-27 15:46:00 +0000369 }
sewardj721ad7b2007-11-30 08:30:29 +0000370 tl_assert(p->mutex_type == mutex_type);
sewardjaf44c822007-11-25 14:01:38 +0000371 tl_assert(p->owner != DRD_INVALID_THREADID);
sewardj721ad7b2007-11-30 08:30:29 +0000372
sewardjaf44c822007-11-25 14:01:38 +0000373 if (p->owner != drd_tid)
374 {
bart4bb53d82008-02-28 19:06:34 +0000375 MutexErrInfo MEI = { p->a1, p->recursion_count, p->owner };
sewardjaf44c822007-11-25 14:01:38 +0000376 VG_(maybe_record_error)(vg_tid,
377 MutexErr,
378 VG_(get_IP)(vg_tid),
379 "Mutex not unlocked by owner thread",
380 &MEI);
381 }
382 p->recursion_count--;
sewardj347eeba2008-01-21 14:19:07 +0000383 if (p->recursion_count < 0)
384 {
385 MutexErrInfo MEI
bart4bb53d82008-02-28 19:06:34 +0000386 = { p->a1, p->recursion_count, p->owner };
sewardj347eeba2008-01-21 14:19:07 +0000387 VG_(maybe_record_error)(vg_tid,
388 MutexErr,
389 VG_(get_IP)(vg_tid),
390 "Attempt to unlock a mutex that is not locked",
391 &MEI);
392 p->recursion_count = 0;
393 }
394
sewardjaf44c822007-11-25 14:01:38 +0000395 if (p->recursion_count == 0)
396 {
397 /* This pthread_mutex_unlock() call really unlocks the mutex. Save the */
398 /* current vector clock of the thread such that it is available when */
399 /* this mutex is locked again. */
bart301c3112008-02-24 18:22:37 +0000400 vc_assign(&p->vc, vc);
sewardjaf44c822007-11-25 14:01:38 +0000401
402 thread_new_segment(drd_tid);
403 }
404 return p->recursion_count;
405}
406
407const char* mutex_get_typename(struct mutex_info* const p)
408{
409 tl_assert(p);
sewardj721ad7b2007-11-30 08:30:29 +0000410
sewardj347eeba2008-01-21 14:19:07 +0000411 return mutex_type_name(p->mutex_type);
412}
413
414const char* mutex_type_name(const MutexT mt)
415{
416 switch (mt)
sewardjaf44c822007-11-25 14:01:38 +0000417 {
bart635cb162008-02-28 08:30:43 +0000418 case mutex_type_invalid_mutex:
419 return "invalid mutex";
bart5357fcb2008-02-27 15:46:00 +0000420 case mutex_type_recursive_mutex:
421 return "recursive mutex";
422 case mutex_type_errorcheck_mutex:
423 return "error checking mutex";
424 case mutex_type_default_mutex:
sewardjaf44c822007-11-25 14:01:38 +0000425 return "mutex";
sewardj721ad7b2007-11-30 08:30:29 +0000426 case mutex_type_spinlock:
sewardjaf44c822007-11-25 14:01:38 +0000427 return "spinlock";
428 default:
429 tl_assert(0);
430 }
431 return "?";
432}
433
bart5357fcb2008-02-27 15:46:00 +0000434/** Return true if the specified mutex is locked by any thread. */
435static Bool mutex_is_locked(struct mutex_info* const p)
436{
437 tl_assert(p);
438 return (p->recursion_count > 0);
439}
440
sewardjaf44c822007-11-25 14:01:38 +0000441Bool mutex_is_locked_by(const Addr mutex, const DrdThreadId tid)
442{
443 struct mutex_info* const p = mutex_get(mutex);
sewardjaf44c822007-11-25 14:01:38 +0000444 if (p)
445 {
446 return (p->recursion_count > 0 && p->owner == tid);
447 }
448 return False;
449}
450
451const VectorClock* mutex_get_last_vc(const Addr mutex)
452{
453 struct mutex_info* const p = mutex_get(mutex);
454 return p ? &p->vc : 0;
455}
456
457int mutex_get_recursion_count(const Addr mutex)
458{
459 struct mutex_info* const p = mutex_get(mutex);
460 tl_assert(p);
461 return p->recursion_count;
462}
463
464/**
bart301c3112008-02-24 18:22:37 +0000465 * Call this function when thread tid stops to exist, such that the
sewardjaf44c822007-11-25 14:01:38 +0000466 * "last owner" field can be cleared if it still refers to that thread.
sewardjaf44c822007-11-25 14:01:38 +0000467 */
bart301c3112008-02-24 18:22:37 +0000468void mutex_thread_delete(const DrdThreadId tid)
sewardjaf44c822007-11-25 14:01:38 +0000469{
bart4bb53d82008-02-28 19:06:34 +0000470 struct mutex_info* p;
471
472 drd_clientobj_resetiter();
473 for ( ; (p = &drd_clientobj_next(ClientMutex)->mutex) != 0; )
sewardjaf44c822007-11-25 14:01:38 +0000474 {
bart4bb53d82008-02-28 19:06:34 +0000475 if (p->owner == tid && p->recursion_count > 0)
sewardjaf44c822007-11-25 14:01:38 +0000476 {
bart5357fcb2008-02-27 15:46:00 +0000477 MutexErrInfo MEI
bart4bb53d82008-02-28 19:06:34 +0000478 = { p->a1, p->recursion_count, p->owner };
bart5357fcb2008-02-27 15:46:00 +0000479 VG_(maybe_record_error)(VG_(get_running_tid)(),
480 MutexErr,
481 VG_(get_IP)(VG_(get_running_tid)()),
482 "Mutex still locked at thread exit",
483 &MEI);
sewardjaf44c822007-11-25 14:01:38 +0000484 p->owner = VG_INVALID_THREADID;
485 }
486 }
487}
488
sewardjaf44c822007-11-25 14:01:38 +0000489ULong get_mutex_lock_count(void)
490{
491 return s_mutex_lock_count;
492}
sewardj347eeba2008-01-21 14:19:07 +0000493
494
495/*
496 * Local variables:
497 * c-basic-offset: 3
498 * End:
499 */