OSDN Git Service

* sysv_msg.cc: Add fix from upstream version 1.65.
[pf3gnuchains/pf3gnuchains4x.git] / winsup / cygserver / bsd_helper.cc
1 /* bsd_helper.cc
2
3    Copyright 2003, 2004, 2005, 2007 Red Hat Inc.
4
5 This file is part of Cygwin.
6
7 This software is a copyrighted work licensed under the terms of the
8 Cygwin license.  Please consult the file "CYGWIN_LICENSE" for
9 details. */
10 #ifdef __OUTSIDE_CYGWIN__
11 #include "woutsup.h"
12 #include <errno.h>
13 #define _KERNEL 1
14 #define __BSD_VISIBLE 1
15 #include <sys/smallprint.h>
16 #include <sys/cygwin.h>
17 #include <sys/ipc.h>
18 #include <sys/param.h>
19 #include <sys/msg.h>
20 #include <sys/queue.h>
21 #include <malloc.h>
22 #include <unistd.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <ctype.h>
26
27 #include "cygserver.h"
28 #include "process.h"
29 #include "cygserver_ipc.h"
30 #include "cygserver_msg.h"
31 #include "cygserver_sem.h"
32 #include "cygserver_shm.h"
33
34 /*
35  * Copy a piece of memory from the client process into the server process.
36  * Returns an error code.
37  */
38 int
39 win_copyin (struct thread *td, const void *client_src,
40             void *server_tgt, size_t len)
41 {
42   if (!ReadProcessMemory (td->client->handle (), client_src, server_tgt,
43                           len, NULL))
44     return cygwin_internal (CW_GET_ERRNO_FROM_WINERROR,
45                             GetLastError (), EINVAL);
46   return 0;
47 }
48
49 /*
50  * Copy a piece of memory from the server process into the client process.
51  * Returns an error code.
52  */
53 int
54 win_copyout (struct thread *td, const void *server_src,
55              void *client_tgt, size_t len)
56 {
57   if (!WriteProcessMemory (td->client->handle (), client_tgt, server_src,
58                            len, NULL))
59     return cygwin_internal (CW_GET_ERRNO_FROM_WINERROR,
60                             GetLastError (), EINVAL);
61   return 0;
62 }
63
64 #define enter_critical_section(c) _enter_critical_section((c),__FILE__,__LINE__)
65 static void
66 _enter_critical_section (LPCRITICAL_SECTION pcs, const char *file, int line)
67 {
68   _log (file, line, LOG_DEBUG, "Try enter critical section(%p)", pcs);
69   EnterCriticalSection (pcs);
70   _log (file, line, LOG_DEBUG, "Entered   critical section(%p)", pcs);
71 }
72
73 #define leave_critical_section(c) _leave_critical_section((c),__FILE__,__LINE__)
74 static void
75 _leave_critical_section (LPCRITICAL_SECTION pcs, const char *file, int line)
76 {
77   LeaveCriticalSection (pcs);
78   _log (file, line, LOG_DEBUG, "Left      critical section(%p)", pcs);
79 }
80
81 CRITICAL_SECTION ipcht_cs;
82
83 struct ipc_hookthread_storage {
84   HANDLE process_hdl;
85   proc ipcblk;
86 };
87
88 struct ipc_hookthread {
89   SLIST_ENTRY (ipc_hookthread) sht_next;
90   HANDLE thread;
91   DWORD  winpid;
92   struct vmspace vmspace;
93 };
94 static SLIST_HEAD (, ipc_hookthread) ipcht_list; /* list of hook threads */
95
96 static HANDLE ipcexit_event;
97
98 struct vmspace *
99 ipc_p_vmspace (struct proc *proc)
100 {
101   struct vmspace *ret = NULL;
102   ipc_hookthread *ipcht_entry;
103   enter_critical_section (&ipcht_cs);
104   SLIST_FOREACH (ipcht_entry, &ipcht_list, sht_next)
105     {
106       if (ipcht_entry->winpid == proc->winpid)
107         {
108           ret = proc->p_vmspace = &ipcht_entry->vmspace;
109           break;
110         }
111     }
112   leave_critical_section (&ipcht_cs);
113   return ret;
114 }
115
116 static DWORD WINAPI
117 ipcexit_hookthread (const LPVOID param)
118 {
119   ipc_hookthread_storage *shs = (ipc_hookthread_storage *) param;
120   HANDLE obj[2] = { ipcexit_event, shs->process_hdl };
121   switch (WaitForMultipleObjects (2, obj, FALSE, INFINITE))
122     {
123       case WAIT_OBJECT_0:
124         /* Cygserver shutdown. */
125         /*FALLTHRU*/
126       case WAIT_OBJECT_0 + 1:
127         /* Process exited.  Call semexit_myhook to handle SEM_UNDOs for the
128            exiting process and shmexit_myhook to keep track of shared
129            memory. */
130         if (Giant.owner == shs->ipcblk.winpid)
131           mtx_unlock (&Giant);
132         if (support_semaphores == TUN_TRUE)
133           semexit_myhook (NULL, &shs->ipcblk);
134         if (support_sharedmem == TUN_TRUE)
135           {
136             _mtx_lock (&Giant, shs->ipcblk.winpid, __FILE__, __LINE__);
137             ipc_p_vmspace (&shs->ipcblk);
138             shmexit_myhook (shs->ipcblk.p_vmspace);
139             mtx_unlock (&Giant);
140           }
141         break;
142       default:
143         /* FIXME: Panic? */
144         break;
145     }
146   CloseHandle (shs->process_hdl);
147   ipc_hookthread *ipcht_entry, *sav_entry;
148   enter_critical_section (&ipcht_cs);
149   SLIST_FOREACH_SAFE (ipcht_entry, &ipcht_list, sht_next, sav_entry)
150     {
151       if (ipcht_entry->winpid == shs->ipcblk.winpid)
152         {
153           SLIST_REMOVE (&ipcht_list, ipcht_entry, ipc_hookthread, sht_next);
154           CloseHandle (ipcht_entry->thread);
155           delete ipcht_entry;
156         }
157     }
158   leave_critical_section (&ipcht_cs);
159   delete shs;
160   return 0;
161 }
162
163 /* Deletes all pending hook threads.  Called by ipcunload() which in turn
164    is called by the cygserver main routine. */
165 static void
166 ipcexit_dispose_hookthreads (void)
167 {
168   SetEvent (ipcexit_event);
169   ipc_hookthread *ipcht_entry;
170   enter_critical_section (&ipcht_cs);
171   SLIST_FOREACH (ipcht_entry, &ipcht_list, sht_next)
172     {
173       WaitForSingleObject (ipcht_entry->thread, 1000);
174       /* Don't bother removing the linked list on cygserver shutdown. */
175       /* FIXME: Error handling? */
176     }
177   leave_critical_section (&ipcht_cs);
178 }
179
180 /* Creates the per process wait thread.  Called by semget() under locked
181    Giant mutex condition. */
182 int
183 ipcexit_creat_hookthread (struct thread *td)
184 {
185   ipc_hookthread *ipcht_entry;
186   int ret = -1;
187   enter_critical_section (&ipcht_cs);
188   SLIST_FOREACH (ipcht_entry, &ipcht_list, sht_next)
189     {
190       if (ipcht_entry->winpid == td->ipcblk->winpid)
191         ret = 0;
192     }
193   leave_critical_section (&ipcht_cs);
194   if (!ret)
195     return 0;
196
197   DWORD tid;
198   ipc_hookthread_storage *shs = new ipc_hookthread_storage;
199   if (!DuplicateHandle (GetCurrentProcess (), td->client->handle (),
200                         GetCurrentProcess (), &shs->process_hdl,
201                         0, FALSE, DUPLICATE_SAME_ACCESS))
202     {
203       log (LOG_CRIT, "failed to duplicate process handle, error = %lu",
204                       GetLastError ());
205       return cygwin_internal (CW_GET_ERRNO_FROM_WINERROR,
206                               GetLastError (), ENOMEM);
207     }
208   shs->ipcblk = *td->ipcblk;
209   HANDLE thread = CreateThread (NULL, 0, ipcexit_hookthread, shs, 0, &tid);
210   if (!thread)
211     {
212       log (LOG_CRIT, "failed to create thread, error = %lu", GetLastError ());
213       return cygwin_internal (CW_GET_ERRNO_FROM_WINERROR,
214                               GetLastError (), ENOMEM);
215     }
216   ipcht_entry = new ipc_hookthread;
217   ipcht_entry->thread = thread;
218   ipcht_entry->winpid = td->ipcblk->winpid;
219   ipcht_entry->vmspace.vm_map = NULL;
220   ipcht_entry->vmspace.vm_shm = NULL;
221   enter_critical_section (&ipcht_cs);
222   SLIST_INSERT_HEAD (&ipcht_list, ipcht_entry, sht_next);
223   leave_critical_section (&ipcht_cs);
224   return 0;
225 }
226
227 /*
228  * Need the admins group SID to compare with groups in client token.
229  */
230 PSID admininstrator_group_sid;
231
232 static void
233 init_admin_sid (void)
234 {
235   SID_IDENTIFIER_AUTHORITY nt_auth = {SECURITY_NT_AUTHORITY};
236   if (! AllocateAndInitializeSid (&nt_auth, 2, 32, 544, 0, 0, 0, 0, 0, 0,
237                                   &admininstrator_group_sid))
238     panic ("failed to create well known sids, error = %lu",
239            GetLastError ());
240 }
241
242 SECURITY_DESCRIPTOR sec_all_nih_sd;
243 SECURITY_ATTRIBUTES sec_all_nih = { sizeof (SECURITY_ATTRIBUTES),
244                                     &sec_all_nih_sd,
245                                     FALSE };
246
247 void
248 securityinit ()
249 {
250   InitializeSecurityDescriptor (&sec_all_nih_sd, SECURITY_DESCRIPTOR_REVISION);
251   SetSecurityDescriptorDacl (&sec_all_nih_sd, TRUE, 0, FALSE);
252   init_admin_sid ();
253 }
254
255 /* Global vars, determining whether the IPC stuff should be started or not. */
256 tun_bool_t support_sharedmem = TUN_UNDEF;
257 tun_bool_t support_msgqueues = TUN_UNDEF;
258 tun_bool_t support_semaphores = TUN_UNDEF;
259
260 void
261 ipcinit ()
262 {
263   mtx_init (&Giant, "Giant", NULL, MTX_DEF);
264   msleep_init ();
265   ipcexit_event = CreateEvent (NULL, TRUE, FALSE, NULL);
266   if (!ipcexit_event)
267     panic ("Failed to create ipcexit event object");
268   InitializeCriticalSection (&ipcht_cs);
269   if (support_msgqueues == TUN_TRUE)
270     msginit ();
271   if (support_semaphores == TUN_TRUE)
272     seminit ();
273   if (support_sharedmem == TUN_TRUE)
274     shminit ();
275 }
276
277 int
278 ipcunload ()
279 {
280   ipcexit_dispose_hookthreads ();
281   CloseHandle (ipcexit_event);
282   wakeup_all ();
283   if (support_semaphores == TUN_TRUE)
284     semunload ();
285   if (support_sharedmem == TUN_TRUE)
286     shmunload ();
287   if (support_msgqueues == TUN_TRUE)
288     msgunload ();
289   mtx_destroy (&Giant);
290   return 0;
291 }
292
293 /*
294  * Helper function to find a gid in a list of gids.
295  */
296 static bool
297 is_grp_member (gid_t grp, gid_t *grplist, int listsize)
298 {
299   if (grplist)
300     for (; listsize > 0; --listsize)
301       if (grp == grplist[listsize - 1])
302         return true;
303   return false;
304 }
305
306 /*
307  * Helper function to get a specific token information from a token.
308  * This function mallocs the necessary buffer spcae by itself.  It
309  * must be free'd by the calling function.
310  */
311 static void *
312 get_token_info (HANDLE tok, TOKEN_INFORMATION_CLASS tic)
313 {
314   void *buf;
315   DWORD size;
316
317   if (!GetTokenInformation (tok, tic, NULL, 0, &size)
318       && GetLastError () != ERROR_INSUFFICIENT_BUFFER)
319     return NULL;
320   if (!(buf = malloc (size)))
321     return NULL;
322   if (!GetTokenInformation (tok, tic, buf, size, &size))
323     {
324       free (buf);
325       return NULL;
326     }
327   return buf;
328 }
329
330 /*
331  * Check if client user helds "mode" permission when accessing object
332  * associated with "perm" permission record.
333  * Returns an error code.
334  */
335 int
336 ipcperm (struct thread *td, ipc_perm *perm, unsigned int mode)
337 {
338   proc *p = td->ipcblk;
339
340   if (!suser (td))
341     return 0;
342   if (mode & IPC_M)
343     {
344       return (p->uid != perm->cuid && p->uid != perm->uid)
345              ? EACCES : 0;
346     }
347   if (p->uid != perm->cuid && p->uid != perm->uid)
348     {
349       /* If the user is a member of the creator or owner group, test
350          against group bits, otherwise against other bits. */
351       mode >>= p->gid != perm->gid && p->gid != perm->cgid
352                && !is_grp_member (perm->gid, p->gidlist, p->gidcnt)
353                && !is_grp_member (perm->cgid, p->gidlist, p->gidcnt)
354                ? 6 : 3;
355     }
356   return (mode & perm->mode) != mode ? EACCES : 0;
357 }
358
359 /*
360  * Check for client user being superuser.
361  * Returns an error code.
362  */
363 int
364 suser (struct thread *td)
365 {
366   /* This value has been set at ImpersonateNamedPipeClient() time
367      using the token information.  See adjust_identity_info() below. */
368   return td->ipcblk->is_admin ? 0 : EACCES;
369 }
370
371 /*
372  * Retrieves user and group info from impersonated token and creates the
373  * correct uid, gid, gidlist and is_admin entries in p from that.
374  */
375 bool
376 adjust_identity_info (struct proc *p)
377 {
378   HANDLE tok;
379
380   if (!OpenThreadToken (GetCurrentThread (), TOKEN_READ, TRUE, &tok))
381     {
382       debug ("Failed to open worker thread access token for pid %d, winpid %d",
383              p->cygpid, p->winpid);
384       return false;
385     }
386
387   /* Get uid from user SID in token. */
388   PTOKEN_USER user;
389   if (!(user = (PTOKEN_USER)get_token_info (tok, TokenUser)))
390     goto faulty;
391   p->uid = cygwin_internal (CW_GET_UID_FROM_SID, user->User.Sid);
392   free (user);
393   if (p->uid == (uid_t)-1)
394     log (LOG_WARNING, "WARNING: User not found in /etc/passwd! Using uid -1!");
395
396   /* Get gid from primary group SID in token. */
397   PTOKEN_PRIMARY_GROUP pgrp;
398   if (!(pgrp = (PTOKEN_PRIMARY_GROUP)get_token_info (tok, TokenPrimaryGroup)))
399     goto faulty;
400   p->gid = cygwin_internal (CW_GET_GID_FROM_SID, pgrp->PrimaryGroup);
401   free (pgrp);
402   if (p->gid == (gid_t)-1)
403     log (LOG_WARNING,"WARNING: Group not found in /etc/passwd! Using gid -1!");
404
405   /* Generate gid list from token group's SID list.  Also look if the token
406      has an enabled admin group SID.  That means, the process has admin
407      privileges.  That knowledge is used in suser(). */
408   PTOKEN_GROUPS gsids;
409   if (!(gsids = (PTOKEN_GROUPS)get_token_info (tok, TokenGroups)))
410     goto faulty;
411   if (gsids->GroupCount)
412     {
413       p->gidlist = (gid_t *) calloc (gsids->GroupCount, sizeof (gid_t));
414       if (p->gidlist)
415         p->gidcnt = gsids->GroupCount;
416     }
417   for (DWORD i = 0; i < gsids->GroupCount; ++i)
418     {
419       if (p->gidlist)
420         p->gidlist[i] = cygwin_internal (CW_GET_GID_FROM_SID,
421                                          gsids->Groups[i].Sid);
422       if (EqualSid (gsids->Groups[i].Sid, admininstrator_group_sid)
423           && (gsids->Groups[i].Attributes & SE_GROUP_ENABLED))
424         p->is_admin = true;
425     }
426   free (gsids);
427
428   CloseHandle (tok);
429   return true;
430
431 faulty:
432   CloseHandle (tok);
433   log (LOG_CRIT, "Failed to get token information for pid %d, winpid %d",
434                   p->cygpid, p->winpid);
435   return false;
436 }
437
438 /*
439  * Windows wrapper implementation of the VM functions called by sysv_shm.cc.
440  */
441
442 vm_object_t
443 _vm_pager_allocate (int size, int shmflg)
444 {
445   /* Create the file mapping object with full access for everyone.  This is
446      necessary to allow later calls to shmctl(..., IPC_SET,...) to
447      change the access rights and ownership of a shared memory region.
448      The access rights are tested at the beginning of every shm... function.
449      Note that this does not influence the actual read or write access
450      defined in a call to shmat. */
451   vm_object_t object = CreateFileMapping (INVALID_HANDLE_VALUE, &sec_all_nih,
452                                           PAGE_READWRITE, 0, size, NULL);
453   if (!object)
454     panic ("CreateFileMapping in _vm_pager_allocate failed, %lu", GetLastError ());
455   return object;
456 }
457
458 vm_object_t
459 vm_object_duplicate (struct thread *td, vm_object_t object)
460 {
461   vm_object_t dup_object;
462   if (!DuplicateHandle (GetCurrentProcess (), object,
463                         td->client->handle (), &dup_object,
464                         0, TRUE, DUPLICATE_SAME_ACCESS))
465     panic ("!DuplicateHandle in vm_object_duplicate failed, %lu", GetLastError ());
466   return dup_object;
467 }
468
469 void
470 vm_object_deallocate (vm_object_t object)
471 {
472   if (object)
473     CloseHandle (object);
474 }
475
476 /*
477  * Tunable parameters are read from a system wide cygserver.conf file.
478  * On the first call to tunable_int_fetch, the file is read and the
479  * parameters are set accordingly.  Each parameter has default, max and
480  * min settings.
481  */
482
483 enum tun_params_type {
484   TUN_NULL,
485   TUN_INT,
486   TUN_BOOL
487 };
488
489 union tun_value {
490   long ival;
491   tun_bool_t bval;
492 };
493
494 struct tun_struct {
495   const char *name;
496   tun_params_type type;
497   union tun_value value;
498   union tun_value min;
499   union tun_value max;
500   void (*check_func)(tun_struct *, char *, const char *);
501 };
502
503 static void
504 default_tun_check (tun_struct *that, char *value, const char *fname)
505 {
506   char *c = NULL;
507   tun_value val;
508   switch (that->type)
509     {
510       case TUN_INT:
511         val.ival = strtoul (value, &c, 10);
512         if (!val.ival || (c && *c))
513           panic ("Error in config file %s: Value of parameter %s malformed",
514                  fname, that->name);
515         if (val.ival < that->min.ival || val.ival > that->max.ival)
516           panic ("Error in config file %s: Value of parameter %s must be "
517                  "between %lu and %lu",
518                  fname, that->name, that->min.ival, that->max.ival);
519         if (that->value.ival)
520           panic ("Error in config file %s: Parameter %s set twice.\n",
521                  fname, that->name);
522         that->value.ival = val.ival;
523         break;
524       case TUN_BOOL:
525         if (!strcasecmp (value, "no") || !strcasecmp (value, "n")
526             || !strcasecmp (value, "false") || !strcasecmp (value, "f")
527             || !strcasecmp (value, "0"))
528           val.bval = TUN_FALSE;
529         else if (!strcasecmp (value, "yes") || !strcasecmp (value, "y")
530                  || !strcasecmp (value, "true") || !strcasecmp (value, "t")
531                  || !strcasecmp (value, "1"))
532           val.bval = TUN_TRUE;
533         else
534           panic ("Error in config file %s: Value of parameter %s malformed\n"
535                  "Allowed values: \"yes\", \"no\", \"y\", \"n\", \"true\", \"false\", \"t\", \"f\", \"1\" and \"0\"", fname, that->name);
536         that->value.bval = val.bval;
537         break;
538       default:
539         /* Shouldn't happen. */
540         panic ("Internal error: Wrong type of tunable parameter");
541         break;
542     }
543 }
544
545 static tun_struct tunable_params[] =
546 {
547   /* SRV */
548   { "kern.srv.cleanup_threads", TUN_INT, {0}, {1}, {32}, default_tun_check},
549   { "kern.srv.request_threads", TUN_INT, {0}, {1}, {310}, default_tun_check},
550   { "kern.srv.process_cache_size", TUN_INT, {0}, {1}, {310}, default_tun_check},
551   { "kern.srv.sharedmem", TUN_BOOL, {TUN_UNDEF}, {TUN_FALSE}, {TUN_TRUE}, default_tun_check},
552   { "kern.srv.msgqueues", TUN_BOOL, {TUN_UNDEF}, {TUN_FALSE}, {TUN_TRUE}, default_tun_check},
553   { "kern.srv.semaphores", TUN_BOOL, {TUN_UNDEF}, {TUN_FALSE}, {TUN_TRUE}, default_tun_check},
554
555   /* LOG */
556   { "kern.log.syslog", TUN_BOOL, {TUN_UNDEF}, {TUN_FALSE}, {TUN_TRUE}, default_tun_check},
557   { "kern.log.stderr", TUN_BOOL, {TUN_UNDEF}, {TUN_FALSE}, {TUN_TRUE}, default_tun_check},
558   { "kern.log.debug", TUN_BOOL, {TUN_UNDEF}, {TUN_FALSE}, {TUN_TRUE}, default_tun_check},
559   { "kern.log.level", TUN_INT, {0}, {1}, {7}, default_tun_check},
560
561   /* MSG */
562   { "kern.ipc.msgseg", TUN_INT, {0}, {256}, {65535}, default_tun_check},
563   { "kern.ipc.msgssz", TUN_INT, {0}, {8}, {1024}, default_tun_check},
564   { "kern.ipc.msgmnb", TUN_INT, {0}, {1}, {65535}, default_tun_check},
565   { "kern.ipc.msgmni", TUN_INT, {0}, {1}, {1024}, default_tun_check},
566   { "kern.ipc.msgtql", TUN_INT, {0}, {1}, {1024}, default_tun_check},
567
568   /* SEM */
569   //{ "kern.ipc.semmap", TUN_INT, {0}, {1}, {1024}, default_tun_check},
570   { "kern.ipc.semmni", TUN_INT, {0}, {1}, {1024}, default_tun_check},
571   { "kern.ipc.semmns", TUN_INT, {0}, {1}, {1024}, default_tun_check},
572   { "kern.ipc.semmnu", TUN_INT, {0}, {1}, {1024}, default_tun_check},
573   { "kern.ipc.semmsl", TUN_INT, {0}, {1}, {1024}, default_tun_check},
574   { "kern.ipc.semopm", TUN_INT, {0}, {1}, {1024}, default_tun_check},
575   { "kern.ipc.semume", TUN_INT, {0}, {1}, {1024}, default_tun_check},
576   //{ "kern.ipc.semusz", TUN_INT, {0}, {1}, {1024}, default_tun_check},
577   { "kern.ipc.semvmx", TUN_INT, {0}, {1}, {32767}, default_tun_check},
578   { "kern.ipc.semaem", TUN_INT, {0}, {1}, {32767}, default_tun_check},
579
580   /* SHM */
581   { "kern.ipc.shmmaxpgs", TUN_INT, {0}, {1}, {32767}, default_tun_check},
582   //{ "kern.ipc.shmmin", TUN_INT, {0}, {1}, {32767}, default_tun_check},
583   { "kern.ipc.shmmni", TUN_INT, {0}, {1}, {32767}, default_tun_check},
584   { "kern.ipc.shmseg", TUN_INT, {0}, {1}, {32767}, default_tun_check},
585   { "kern.ipc.shm_allow_removed", TUN_BOOL, {TUN_UNDEF}, {TUN_FALSE}, {TUN_TRUE}, default_tun_check},
586   //{ "kern.ipc.shm_use_phys", TUN_INT, {0}, {1}, {32767}, default_tun_check},
587   { NULL, TUN_NULL, {0}, {0}, {0}, NULL}
588 };
589
590 #define skip_whitespace(c)      while (*(c) && isspace (*(c))) ++(c)
591 #define skip_nonwhitespace(c)   while (*(c) && !isspace (*(c)) && *(c) != '#') ++(c)
592 #define end_of_content(c)       (!*(c) || *(c) == '#')
593
594 void
595 tunable_param_init (const char *config_file, bool force)
596 {
597   FILE *fp = fopen (config_file, "rt");
598   if (!fp)
599     {
600       if (force)
601         panic ("can't open config file %s\n", config_file);
602       return;
603     }
604   char line[1024];
605   while (fgets (line, 1024, fp))
606     {
607       char *c = strrchr (line, '\n');
608       if (!c)
609         panic ("Line too long in confg file %s\n", config_file);
610       /* Overwrite trailing NL. */
611       *c = '\0';
612       c = line;
613       skip_whitespace (c);
614       if (end_of_content (c))
615         continue;
616       /* So we are on the first character of a parameter name. */
617       char *name = c;
618       /* Find end of name. */
619       skip_nonwhitespace (c);
620       if (end_of_content (c))
621         {
622           *c++ = '\0';
623           panic ("Error in config file %s: Parameter %s has no value.\n",
624                  config_file, name);
625         }
626       /* Mark end of name. */
627       *c++ = '\0';
628       skip_whitespace (c);
629       if (end_of_content (c))
630         panic ("Error in config file %s: Parameter %s has no value.\n",
631                config_file, name);
632       /* Now we are on the first character of a parameter's value. */
633       char *value = c;
634       /* This only works for simple parameters.  If complex string parameters
635          are added at one point, the scanning routine must be changed here. */
636       /* Find end of value. */
637       skip_nonwhitespace (c);
638       /* Mark end of value. */
639       *c++ = '\0';
640       /* Now look if name is one from our list. */
641       tun_struct *s;
642       for (s = &tunable_params[0]; s->name; ++s)
643         if (!strcmp (name, s->name))
644           {
645             /* Now read value and check for validity.  check_func doesn't
646                return on error. */
647             s->check_func (s, value, config_file);
648             break;
649           }
650       if (!s->name)
651         panic ("Error in config file %s: Unknown parameter %s.\n",
652                config_file, name);
653     }
654   fclose (fp);
655 }
656
657 void
658 tunable_int_fetch (const char *name, long *tunable_target)
659 {
660   tun_struct *s;
661   for (s = &tunable_params[0]; s->name; ++s)
662     if (!strcmp (name, s->name))
663       break;
664   if (!s)                       /* Not found */
665     return;
666   if (s->type != TUN_INT)       /* Wrong type */
667     return;
668   if (!s->value.ival)           /* Not set in config file */
669     return;
670   *tunable_target = s->value.ival;
671   debug ("\nSet %s to %lu\n", name, *tunable_target);
672 }
673
674 void
675 tunable_bool_fetch (const char *name, tun_bool_t *tunable_target)
676 {
677   tun_struct *s;
678   const char *tun_bool_val_string[] = { "undefined", "no", "yes" };
679   for (s = &tunable_params[0]; s->name; ++s)
680     if (!strcmp (name, s->name))
681       break;
682   if (!s)                       /* Not found */
683     return;
684   if (s->type != TUN_BOOL)      /* Wrong type */
685     return;
686   if (!s->value.ival)           /* Not set in config file */
687     return;
688   *tunable_target = s->value.bval;
689   debug ("\nSet %s to %s\n", name, tun_bool_val_string[*tunable_target]);
690 }
691 #endif /* __OUTSIDE_CYGWIN__ */