]> git.pld-linux.org Git - packages/conserver.git/blob - conserver-locks.patch
- add UUCP lock file support
[packages/conserver.git] / conserver-locks.patch
1 diff -Naur conserver-8.1.9/configure.in conserver-8.1.9-p/configure.in
2 --- conserver-8.1.9/configure.in        Sun May 30 18:50:32 2004
3 +++ conserver-8.1.9-p/configure.in      Fri Aug  6 23:23:02 2004
4 @@ -16,6 +16,7 @@
5  AH_TEMPLATE([HAVE_OPENSSL], [have openssl support])
6  AH_TEMPLATE([HAVE_DMALLOC], [have dmalloc support])
7  AH_TEMPLATE([HAVE_SA_LEN],[Defined if sa_len member exists in struct sockaddr])
8 +AH_TEMPLATE([HAVE_MKSTEMP],[have mkstemp])
9  AH_TEMPLATE([TRUST_REVERSE_DNS],[Defined if we trust reverse DNS])
10  AH_TEMPLATE([USE_EXTENDED_MESSAGES],[Defined if we produce extended messages])
11  AH_TEMPLATE([USE_UNIX_DOMAIN_SOCKETS],[Defined if we use Unix domain sockets])
12 @@ -359,7 +360,13 @@
13         [AC_MSG_RESULT(yes)
14          AC_DEFINE(HAVE_SA_LEN)],
15         [AC_MSG_RESULT(no)])
16 -
17 +AC_MSG_CHECKING(for mkstemp)
18 +AC_TRY_COMPILE([#include <stdlib.h>],
19 +       [mkstemp("/tmp/XXXXXX");],
20 +       [AC_MSG_RESULT(yes)
21 +        AC_DEFINE(HAVE_MKSTEMP)],
22 +       [AC_MSG_RESULT(no)])
23 +       
24  
25  dnl ### Host specific checks. ######################################
26  AC_CANONICAL_HOST
27 diff -Naur conserver-8.1.9/conserver/Makefile.in conserver-8.1.9-p/conserver/Makefile.in
28 --- conserver-8.1.9/conserver/Makefile.in       Tue Feb 10 01:32:28 2004
29 +++ conserver-8.1.9-p/conserver/Makefile.in     Fri Aug  6 22:11:26 2004
30 @@ -28,11 +28,11 @@
31  ### Makefile rules - no user-servicable parts below
32  
33  CONSERVER_OBJS = access.o client.o consent.o group.o main.o master.o \
34 -                readcfg.o fallback.o cutil.o
35 +                readcfg.o fallback.o cutil.o locks.o
36  CONSERVER_HDRS = ../config.h $(top_srcdir)/compat.h $(srcdir)/access.h \
37                  $(srcdir)/client.h $(srcdir)/consent.h $(srcdir)/cutil.h \
38                  $(srcdir)/group.h $(srcdir)/main.h $(srcdir)/master.h \
39 -                $(srcdir)/readcfg.h $(srcdir)/version.h
40 +                $(srcdir)/readcfg.h $(srcdir)/version.h $(srcdir)/locks.h
41  
42  ALL = conserver convert
43  
44 diff -Naur conserver-8.1.9/conserver/consent.c conserver-8.1.9-p/conserver/consent.c
45 --- conserver-8.1.9/conserver/consent.c Thu Jun  3 23:53:59 2004
46 +++ conserver-8.1.9-p/conserver/consent.c       Sat Aug  7 00:20:34 2004
47 @@ -49,7 +49,7 @@
48  #include <access.h>
49  #include <readcfg.h>
50  #include <main.h>
51 -
52 +#include <locks.h>
53  
54  BAUD baud[] = {
55  #if defined(B115200)
56 @@ -655,6 +655,9 @@
57         close(pCE->execSlaveFD);
58         pCE->execSlaveFD = 0;
59      }
60 +    if (pCE->type == DEVICE && pCE->lock == FLAGTRUE) {
61 +       rmlocks(pCE->device);
62 +   }
63      pCE->fup = 0;
64      pCE->nolog = 0;
65      pCE->autoReUp = 0;
66 @@ -854,6 +857,21 @@
67             }
68             break;
69         case DEVICE:
70 +           if (pCE->lock == FLAGTRUE) {
71 +                   if (checklock(pCE->device) != NO_LOCK) {
72 +                           Error("[%s] checklock(%s): device already locked",
73 +                               pCE->server, pCE->device);
74 +                           ConsDown(pCE, FLAGTRUE, FLAGTRUE);
75 +                           return;
76 +                   }
77 +                   if (makelock(pCE->device) == FAIL) {
78 +                           Error("[%s] makelock(%s): could not lock device",
79 +                               pCE->server, pCE->device);
80 +                           ConsDown(pCE, FLAGTRUE, FLAGTRUE);
81 +                           return;
82 +                   }
83 +                   /* now we have the lock */
84 +           }
85             if (-1 ==
86                 (cofile = open(pCE->device, O_RDWR | O_NONBLOCK, 0600))) {
87  
88 diff -Naur conserver-8.1.9/conserver/consent.h conserver-8.1.9-p/conserver/consent.h
89 --- conserver-8.1.9/conserver/consent.h Wed Jun  2 01:45:47 2004
90 +++ conserver-8.1.9-p/conserver/consent.h       Fri Aug  6 22:41:11 2004
91 @@ -123,6 +123,7 @@
92      FLAG striphigh;            /* strip high-bit of console data       */
93      FLAG autoreinit;           /* auto-reinitialize if failed          */
94      FLAG unloved;              /* copy "unloved" data to stdout        */
95 +    FLAG lock;                 /* lock the device                      */
96  
97      /*** runtime settings ***/
98      CONSFILE *fdlog;           /* the local log file                   */
99 diff -Naur conserver-8.1.9/conserver/locks.c conserver-8.1.9-p/conserver/locks.c
100 --- conserver-8.1.9/conserver/locks.c   Thu Jan  1 01:00:00 1970
101 +++ conserver-8.1.9-p/conserver/locks.c Sat Aug  7 00:21:23 2004
102 @@ -0,0 +1,435 @@
103 +#ident "$Id$ Copyright (c) Gert Doering / Paul Sutcliffe Jr."
104 +
105 +/* large parts of the code in this module are taken from the
106 + * "getty kit 2.0" by Paul Sutcliffe, Jr., paul@devon.lns.pa.us,
107 + * and are used with permission here.
108 + * SVR4 style locking by Bodo Bauer, bodo@hal.nbg.sub.org.
109 + */
110 +/* adopted from mgetty 1.1.30 for conserver
111 + * by Sebastian Zagrodzki, s.zagrodzki@net.icm.edu.pl */
112 +
113 +#include <stdio.h>
114 +#include <unistd.h>
115 +#include <stdlib.h>
116 +#include <fcntl.h>
117 +#include <signal.h>
118 +#include <string.h>
119 +#include <sys/types.h>
120 +#include <sys/stat.h>
121 +#include <ctype.h>
122 +
123 +/* some OSes do include this in stdio.h, others don't... */
124 +#ifndef EEXIST
125 +#include <errno.h>
126 +#endif
127 +
128 +/* SVR4 uses a different locking mechanism. This is why we need this... */
129 +#ifdef SVR4 
130 +#include <sys/mkdev.h>
131
132 +#define LCK_NODEV    -1
133 +#define LCK_OPNFAIL  -2
134 +#endif
135 +
136 +#include "locks.h"
137 +
138 +int add_lock_to_list(char *device)
139 +{
140 +       s_ldev *temp_ldev, *ldev, *p_ldev = NULL;
141 +
142 +       Debug(2, "add_lock: %s", device);
143 +       for (ldev = locked_devices; ldev && strcmp(ldev->name, device);
144 +               p_ldev = ldev, ldev = ldev->next); 
145 +
146 +       if (ldev) { /* we had it already */
147 +               Msg("add_lock: That lock was already on my list");
148 +               return(SUCCESS);
149 +       }
150 +
151 +       if (!(temp_ldev = (s_ldev *) malloc(sizeof(s_ldev))))
152 +               OutOfMem();
153 +
154 +       if (!(temp_ldev->name = (char *) malloc(strlen(device)+1)))
155 +               OutOfMem();
156 +
157 +       strcpy(temp_ldev->name, device);
158 +       temp_ldev->next = NULL;
159 +       if (p_ldev)
160 +               p_ldev->next = temp_ldev;
161 +       else
162 +               locked_devices = temp_ldev;
163 +       return(SUCCESS);
164 +}
165 +       
166 +int remove_lock_from_list(char *device)
167 +{
168 +       s_ldev *ldev, *p_ldev = NULL;
169 +
170 +       Debug(2, "remove_lock: %s", device);
171 +       for (ldev = locked_devices; ldev && strcmp(ldev->name, device);
172 +               p_ldev = ldev, ldev = ldev->next);
173 +       if (!ldev) {
174 +               Msg("remove_lock: That lock wasn't on my list");
175 +               return(SUCCESS);
176 +       }
177 +       free(ldev->name);
178 +       if (p_ldev)
179 +               p_ldev->next = ldev->next;
180 +       else
181 +               locked_devices = ldev->next;
182 +       free(ldev);
183 +       return(SUCCESS);
184 +}
185 +               
186 +int lock_is_on_list(char *device)
187 +{
188 +       s_ldev *ldev;
189 +       for (ldev = locked_devices; ldev && strcmp(ldev->name, device);
190 +               ldev = ldev->next);
191 +       if (!ldev) {
192 +               Debug(2, "lock_is_on_list: not found: %s", device);
193 +               return(FALSE);
194 +       } else {
195 +               Debug(2, "lock_is_on_list: FOUND: %s", device);
196 +               return(TRUE);
197 +       }
198 +}
199 +               
200 +
201 +/*
202 + *     makelock() - attempt to create a lockfile
203 + *
204 + *     Returns FAIL if lock could not be made (line in use).
205 + */
206 +
207 +int makelock(char *device)
208 +{
209 +       int fd, pid;
210 +       char *temp, buf[MAXLINE+1];
211 +       int tries = 0;
212 +       char    lock[MAXLINE+1];
213 +
214 +       s_ldev *ldev, *temp_ldev;
215 +       Debug(2, "makelock: %s", device);
216 +       for (ldev = locked_devices; ldev; ldev = ldev->next) {
217 +               if (!strcmp(device, locked_devices->name)) {
218 +                       Msg("We already had a lock");
219 +                       return (SUCCESS);
220 +               }
221 +       }
222 +
223 +       if (get_lock_name(lock, device) == NULL) {
224 +               Error("Cannot get lock filename");
225 +               return (FAIL);
226 +       }
227 +       Debug(2, "makelock: lock='%s'", lock);
228 +
229 +       /* first make a temp file */
230 +
231 +#ifdef HAVE_MKSTEMP
232 +       /* secure, but not as portable */
233 +       temp=buf;
234 +       sprintf(buf, LOCK, "TM.XXXXXX");
235 +       if ((fd = mkstemp(temp)) == FAIL ) {
236 +               Error("cannot create tempfile (%s)", temp);
237 +               return(FAIL);
238 +       }
239 +#else
240 +       /* portable, but subject to some problems on some platforms */
241 +again:
242 +       sprintf(buf, LOCK, "TM.XXXXXX");
243 +       temp = mktemp(buf);
244 +       unlink(temp);
245 +       if ((fd = open(temp, O_CREAT|O_WRONLY|O_EXCL, 0644)) == FAIL) {
246 +               Error("cannot create tempfile (%s)", temp);
247 +               if ( errno == EEXIST && ++tries < 20 ) goto again;
248 +               return(FAIL);
249 +       }
250 +#endif
251 +
252 +       /* just in case some "umask" is set (errors are ignored) */
253 +       chmod( temp, 0644 );
254 +
255 +       /* put my pid in it */
256 +       if ( lock_write_pid( fd ) == FAIL)
257 +                               { unlink(temp); return FAIL; }
258 +
259 +       /* link it to the lock file */
260 +
261 +       while (link(temp, lock) == FAIL)
262 +       {
263 +               if (errno != EEXIST )
264 +               {
265 +                   Error("lock not made: link(temp,lock) failed" );
266 +               }
267 +
268 +               if (errno == EEXIST)            /* lock file already there */
269 +               {
270 +                   if ((pid = readlock(lock)) == FAIL)
271 +                   {
272 +                       if ( errno == ENOENT )  /* disappeared */
273 +                           continue;
274 +                       else
275 +                       {
276 +                           Debug(2, "cannot read lockfile" );
277 +                           unlink(temp);
278 +                           return FAIL;
279 +                       }
280 +                   }
281 +
282 +                   if (pid == getpid())        /* huh? WE locked the line!*/
283 +                   {
284 +                       Msg("we *have* the line!" );
285 +                       break;
286 +                   }
287 +
288 +                   if ((kill(pid, 0) == FAIL) && errno == ESRCH)
289 +                   {
290 +                       /* pid that created lockfile is gone */
291 +                       Debug(2, "stale lockfile, created by process %d, ignoring", pid );
292 +                       if ( unlink(lock) < 0 &&
293 +                                errno != EINTR && errno != ENOENT )
294 +                       {
295 +                           Error("unlink() failed, giving up" );
296 +                           unlink(temp);
297 +                           return FAIL;
298 +                       }
299 +                       continue;
300 +                   }
301 +                   
302 +                   Msg("lock not made: lock file exists (pid=%d)", pid);
303 +               }                               /* if (errno == EEXIST) */
304 +               
305 +               (void) unlink(temp);
306 +               return(FAIL);
307 +       }
308 +       
309 +       Debug(2, "lock made");
310 +       (void) unlink(temp);
311 +
312 +       Msg("Locking device");
313 +       add_lock_to_list(device);
314 +       return(SUCCESS);
315 +}
316 +   
317 +/*
318 + *     checklock() - test for presence of valid lock file
319 + *
320 + *     if lockfile found, return PID of process holding it, 0 otherwise
321 + */
322 +
323 +int checklock (char *device)
324 +{
325 +    int pid;
326 +    struct stat st;
327 +    char name[MAXLINE+1];
328 +    
329 +    if ( get_lock_name( name, device ) == NULL )
330 +    {
331 +       Error("cannot get lock name" );
332 +       return NO_LOCK;
333 +    }
334 +
335 +    if ((stat(name, &st) == FAIL) && errno == ENOENT)
336 +    {
337 +       Debug(2, "checklock: stat failed, no file");
338 +       return NO_LOCK;
339 +    }
340 +    
341 +    if ((pid = readlock(name)) == FAIL)
342 +    {
343 +       Msg("checklock: couldn't read lockfile");
344 +       return NO_LOCK;
345 +    }
346 +
347 +    if (pid == getpid())
348 +    {
349 +       Msg("huh? It's *our* lock file!" );
350 +       return NO_LOCK;
351 +    }
352 +               
353 +    if ((kill(pid, 0) == FAIL) && errno == ESRCH)
354 +    {
355 +       Debug(2, "checklock: no active process has lock, will remove");
356 +       (void) unlink(name);
357 +       return NO_LOCK;
358 +    }
359 +    
360 +    Debug(2, "lockfile found, pid=%d", pid );
361 +    
362 +    return pid;
363 +}
364 +
365 +/*
366 + *     readlock() - read contents of lockfile
367 + *
368 + *     Returns pid read or FAIL on error.
369 + *
370 + *      private function
371 + */
372 +
373 +int readlock (char *name)
374 +{
375 +       int fd, pid;
376 +       char apid[20];
377 +       int  length;
378 +
379 +       if ((fd = open(name, O_RDONLY)) == FAIL)
380 +               return(FAIL);
381 +
382 +       length = read(fd, apid, sizeof(apid)-1);
383 +       apid[length]=0;         /* make sscanf() happy */
384 +
385 +       pid = 0;
386 +       if ( length == sizeof( pid ) || sscanf(apid, "%d", &pid) != 1 ||
387 +            pid == 0 )
388 +       {
389 +           pid = * ( (int *) apid );
390 +#if LOCKS_BINARY == 0
391 +           Msg("compiled with ascii locks, found binary lock file (length=%d, pid=%d)!", length, pid );
392 +#endif
393 +       }
394 +#if LOCKS_BINARY == 1
395 +       else
396 +       {
397 +           Msg("compiled with binary locks, found ascii lock file (length=%d, pid=%d)!", length, pid );
398 +       }
399 +#endif
400 +
401 +       (void) close(fd);
402 +       return(pid);
403 +}
404 +
405 +/* lock_write_pid()
406 + *
407 + * write contents of lock file: my process ID in specified format
408 + *
409 + * private function
410 + */
411 +int lock_write_pid (int fd)
412 +{
413 +#if LOCKS_BINARY
414 +    int bpid;                  /* must be 4 bytes wide! */
415 +    bpid = getpid();
416 +    if ( write(fd, &bpid, sizeof(bpid) ) != sizeof(bpid) )
417 +#else
418 +    char apid[16];
419 +    sprintf( apid, "%10d\n", (int) getpid() );
420 +    if ( write(fd, apid, strlen(apid)) != strlen(apid) )
421 +#endif
422 +    {
423 +       Error("cannot write PID to (temp) lock file" );
424 +       close(fd);
425 +       return(FAIL);
426 +    }
427 +    close(fd);
428 +    return SUCCESS;
429 +}
430 +       
431 +/*
432 + *     rmlocks() - remove lockfile
433 + *     signal handler
434 + */
435 +
436 +void rmlocks(char *device)
437 +{
438 +    char lock[MAXLINE + 1];
439 +    Debug(2, "rmlocks: %s", device);
440 +    get_lock_name(lock, device);
441 +    Debug(2, "rmlocks: lock: %s", lock);
442 +    if (lock_is_on_list(device))
443 +    {
444 +       Msg("Removing lock file" );
445 +       if (unlink(lock) == -1 )
446 +           Error("error removing lock file (huh?!)" );
447 +       /* mark lock file as 'not set' */
448 +       remove_lock_from_list(device);
449 +    }
450 +}
451 +
452 +/* get_lock_name()
453 + *
454 + * determine full path + name of the lock file for a given device
455 + */
456 +
457 +#ifdef SVR4
458 +
459 +/*
460 + * get_lock_name() - create SVR4 lock file name (Bodo Bauer)
461 + */
462 +
463 +char *get_lock_name (char* lock, char* fax_tty)
464 +{
465 +  struct stat tbuf;
466 +  char ttyname[FILENAME_MAX];
467 +
468 +  Debug(2, "get_lock_name(%s) called", fax_tty);
469 +
470 +  if ( strncmp( fax_tty, "/dev/", 5 ) == 0 )
471 +      strcpy( ttyname, fax_tty );
472 +  else
473 +      sprintf(ttyname, "/dev/%s", fax_tty);
474 +  
475 +  Debug(2, "-> ttyname %s", ttyname);
476 +
477 +  if (stat(ttyname, &tbuf) < 0) {
478 +    if(errno == ENOENT) {
479 +      Debug(2, "device does not exist: %s", ttyname);
480 +      return(NULL);            
481 +    } else {
482 +      Debug(2, "could not access line: %s", ttyname);
483 +      return(NULL);            
484 +    }
485 +  }
486 +
487 +  sprintf(lock,"%s/LK.%03u.%03u.%03u",
488 +         LOCK_PATH,
489 +         major(tbuf.st_dev),
490 +         tbuf.st_rdev >> 18, 
491 +         minor(tbuf.st_rdev));
492 +
493 +  Debug(2, "lock file: %s", lock);
494 +  return(lock);
495 +}
496 +
497 +#else  /* not SVR4 */ 
498 +
499 +char * get_lock_name (char * lock_name, char * device)
500 +{
501 +#ifdef LOCKS_LOWERCASE
502 +    /* sco locking convention -> change all device names to lowercase */
503 +
504 +    char p[MAXLINE+1];
505 +    int i;
506 +    if ( ( i = strlen( device ) ) > sizeof(p) )
507 +    {
508 +       Error("get_lock_name: device name too long" );
509 +       exit(5);
510 +    }
511 +    
512 +#ifdef LOCKS_ALL_LOWERCASE
513 +    /* convert the full name */
514 +    while ( i >= 0 )
515 +    {
516 +       p[i] = tolower( device[i] ); i--;
517 +    }
518 +#else
519 +    /* convert only the last character */
520 +    strcpy( p, device );
521 +    i--;
522 +    p[i] = tolower( p[i] );
523 +#endif
524 +    
525 +    device = p;
526 +#endif /* LOCKS_LOWERCASE */
527 +
528 +    /* throw out all directory prefixes */
529 +    if ( strchr( device, '/' ) != NULL )
530 +        device = strrchr( device, '/' ) +1;
531 +    
532 +    sprintf( lock_name, LOCK, device);
533 +
534 +    return lock_name;
535 +}
536 +       
537 +#endif /* !SVR4 */
538 diff -Naur conserver-8.1.9/conserver/locks.h conserver-8.1.9-p/conserver/locks.h
539 --- conserver-8.1.9/conserver/locks.h   Thu Jan  1 01:00:00 1970
540 +++ conserver-8.1.9-p/conserver/locks.h Fri Aug  6 23:12:48 2004
541 @@ -0,0 +1,30 @@
542 +#ifndef        _locks_h
543 +#define LOCK   "/var/lock/LCK..%s"
544 +#define MAXLINE 1024
545 +#define FALSE (1==0)
546 +#define TRUE (1==1)
547 +#define SUCCESS 0
548 +#define FAIL -1
549 +#define NO_LOCK 0
550 +typedef void    RETSIGTYPE;
551 +
552 +int             makelock (char *device);
553 +// int             makelock_file ( char * lockname );
554 +int             checklock (char *device);
555 +RETSIGTYPE      rmlocks ();
556 +// int             steal_lock (char * device, int pid );
557 +
558 +
559 +typedef struct _s_ldev {
560 +       char *name;
561 +       struct _s_ldev *next;
562 +} s_ldev;
563 +
564 +static s_ldev *locked_devices = NULL;
565 +
566 +int readlock (char *name);
567 +int makelock (char *name);
568 +char *get_lock_name (char *lock_name, char *device);
569 +int lock_write_pid (int fd);
570 +
571 +#endif
572 diff -Naur conserver-8.1.9/conserver/main.c conserver-8.1.9-p/conserver/main.c
573 --- conserver-8.1.9/conserver/main.c    Wed Jul 14 07:28:42 2004
574 +++ conserver-8.1.9-p/conserver/main.c  Fri Aug  6 22:38:37 2004
575 @@ -952,6 +952,9 @@
576                        "DumpDataStructures():  motd=%s, idletimeout=%d, idlestring=%s",
577                        EMPTYSTR(pCE->motd), pCE->idletimeout,
578                        EMPTYSTR(pCE->idlestring)));
579 +           CONDDEBUG((1,
580 +                       "DumpDataStructures():  lock=%s",
581 +                       FLAGSTR(pCE->lock)));
582             if (pCE->ro) {
583                 CONSENTUSERS *u;
584                 for (u = pCE->ro; u != (CONSENTUSERS *)0; u = u->next) {
585 diff -Naur conserver-8.1.9/conserver/readcfg.c conserver-8.1.9-p/conserver/readcfg.c
586 --- conserver-8.1.9/conserver/readcfg.c Wed Jul 14 07:28:42 2004
587 +++ conserver-8.1.9-p/conserver/readcfg.c       Fri Aug  6 22:42:11 2004
588 @@ -1401,6 +1401,7 @@
589         c->reinitoncc = FLAGUNKNOWN;
590         c->autoreinit = FLAGUNKNOWN;
591         c->unloved = FLAGUNKNOWN;
592 +       c->lock = FLAGUNKNOWN;
593         return;
594      }
595  
596 @@ -1436,6 +1437,8 @@
597             c->autoreinit = negative ? FLAGFALSE : FLAGTRUE;
598         else if (strcasecmp("unloved", token) == 0)
599             c->unloved = negative ? FLAGFALSE : FLAGTRUE;
600 +       else if (strcasecmp("lock", token) == 0)
601 +           c->lock = negative ? FLAGFALSE : FLAGTRUE;
602         else if (isMaster)
603             Error("invalid option `%s' [%s:%d]", token, file, line);
604      }
605 @@ -2711,6 +2714,7 @@
606         pCEmatch->reinitoncc = c->reinitoncc;
607         pCEmatch->autoreinit = c->autoreinit;
608         pCEmatch->unloved = c->unloved;
609 +       pCEmatch->lock = c->lock;
610         while (pCEmatch->aliases != (NAMES *)0) {
611             NAMES *name;
612             name = pCEmatch->aliases->next;
613 @@ -2881,6 +2885,8 @@
614  #endif
615         if (c->ondemand == FLAGUNKNOWN)
616             c->ondemand = FLAGFALSE;
617 +       if (c->lock == FLAGUNKNOWN)
618 +           c->lock = FLAGFALSE;
619         if (c->reinitoncc == FLAGUNKNOWN)
620             c->reinitoncc = FLAGFALSE;
621         if (c->striphigh == FLAGUNKNOWN)
This page took 0.10896 seconds and 4 git commands to generate.