* Patch to fix test(1) expression parsing @ 2020-08-01 16:14 Alex Musolino 2020-08-01 16:38 ` [9front] " Alex Musolino 2020-08-02 4:35 ` ori 0 siblings, 2 replies; 7+ messages in thread From: Alex Musolino @ 2020-08-01 16:14 UTC (permalink / raw) To: 9front Hi all, The following patch fixes test(1) to correctly parse its own expression syntax whilst avoiding executing of filesystem operations when these tests ought not to be performed due to short-circuiting. Previously, short-circuiting of -a and -o logical operators would result in an incorrect parse. For example, the following invocations fail: term% test '(' 1 -gt 2 -a 1 -gt 1 ')' test: ) expected term% test '(' 2 -gt 1 -o 1 -gt 1 ')' test: ) expected Comments/feedback/testing welcome. diff -r 7510f0e7ba7f sys/src/cmd/test.c --- a/sys/src/cmd/test.c Thu Jul 30 15:59:04 2020 +0200 +++ b/sys/src/cmd/test.c Sun Aug 02 01:19:39 2020 +0930 @@ -30,7 +30,7 @@ int isnewerthan(char *, char *); int hasmode(char *, ulong); int tio(char *, int); -int e(void), e1(void), e2(void), e3(void); +int e(int), e0(int), e1(int), e2(int), e3(int); char *nxtarg(int); void @@ -47,7 +47,7 @@ argv[ac] = 0; if (ac<=1) exits("usage"); - r = e(); + r = e(1); /* * nice idea but short-circuit -o and -a operators may have * not consumed their right-hand sides. @@ -81,78 +81,116 @@ } int -e(void) +e(int eval) { + char *a; int p1; - p1 = e1(); - if (EQ(nxtarg(1), "-o")) - return(p1 || e()); + p1 = e0(eval); + a = nxtarg(0); + if (EQ(a, "-o")){ + if(p1){ + e0(0); + return 1; + } + return e0(eval); + } ap--; return(p1); } int -e1(void) +e0(int eval) { int p1; - p1 = e2(); - if (EQ(nxtarg(1), "-a")) - return (p1 && e1()); + if(EQ(nxtarg(0), "(")){ + p1 = e1(eval); + if(!EQ(nxtarg(0), ")")) + synbad(") expected",""); + return p1; + } + ap--; + return e1(eval); +} + +int +e1(int eval) +{ + int p1; + + p1 = e2(eval); + if (EQ(nxtarg(0), "-a")){ + if(p1) + return e(eval); + e(0); + return 0; + } ap--; return(p1); } int -e2(void) +e2(int eval) { if (EQ(nxtarg(0), "!")) - return(!e2()); + return(!e(eval)); ap--; - return(e3()); + return(e3(eval)); } int -e3(void) +e3(int eval) { - int p1, int1, int2; - char *a, *p2; + int int1, int2; + char *a, *b, *p2; a = nxtarg(0); - if(EQ(a, "(")) { - p1 = e(); - if(!EQ(nxtarg(0), ")")) - synbad(") expected",""); - return(p1); + + if(EQ(a, "-A")){ + b = nxtarg(0); + return(eval && hasmode(b, DMAPPEND)); } - if(EQ(a, "-A")) - return(hasmode(nxtarg(0), DMAPPEND)); + if(EQ(a, "-L")){ + b = nxtarg(0); + return(eval && hasmode(b, DMEXCL)); + } - if(EQ(a, "-L")) - return(hasmode(nxtarg(0), DMEXCL)); + if(EQ(a, "-T")){ + b = nxtarg(0); + return(eval && hasmode(b, DMTMP)); + } - if(EQ(a, "-T")) - return(hasmode(nxtarg(0), DMTMP)); + if(EQ(a, "-f")){ + b = nxtarg(0); + return(eval && isreg(b)); + } - if(EQ(a, "-f")) - return(isreg(nxtarg(0))); + if(EQ(a, "-d")){ + b = nxtarg(0); + return(eval && isdir(b)); + } - if(EQ(a, "-d")) - return(isdir(nxtarg(0))); + if(EQ(a, "-r")){ + b = nxtarg(0); + return(eval && tio(b, AREAD)); + } - if(EQ(a, "-r")) - return(tio(nxtarg(0), AREAD)); + if(EQ(a, "-w")){ + b = nxtarg(0); + return(eval && tio(b, AWRITE)); + } - if(EQ(a, "-w")) - return(tio(nxtarg(0), AWRITE)); + if(EQ(a, "-x")){ + b = nxtarg(0); + return(eval && tio(b, AEXEC)); + } - if(EQ(a, "-x")) - return(tio(nxtarg(0), AEXEC)); - - if(EQ(a, "-e")) - return(tio(nxtarg(0), AEXIST)); + if(EQ(a, "-e")){ + b = nxtarg(0); + return(eval && tio(b, AEXIST)); + } if(EQ(a, "-c")) return(0); @@ -166,14 +204,16 @@ if(EQ(a, "-g")) return(0); - if(EQ(a, "-s")) - return(fsizep(nxtarg(0))); + if(EQ(a, "-s")){ + b = nxtarg(0); + return(eval && fsizep(b)); + } if(EQ(a, "-t")) if(ap>=ac) - return(isatty(1)); + return(eval && isatty(1)); else if(nxtintarg(&int1)) - return(isatty(int1)); + return(eval && isatty(int1)); else synbad("not a valid file descriptor number ", ""); @@ -182,7 +222,8 @@ if(EQ(a, "-z")) return(EQ(nxtarg(0), "")); - p2 = nxtarg(1); + p2 = nxtarg(0); + if (p2==0) return(!EQ(a,"")); if(EQ(p2, "=")) @@ -191,14 +232,20 @@ if(EQ(p2, "!=")) return(!EQ(nxtarg(0), a)); - if(EQ(p2, "-older")) - return(isolder(nxtarg(0), a)); + if(EQ(p2, "-older")){ + b = nxtarg(0); + return(eval && isolder(b, a)); + } - if(EQ(p2, "-ot")) - return(isolderthan(nxtarg(0), a)); + if(EQ(p2, "-ot")){ + b = nxtarg(0); + return(eval && isolderthan(b, a)); + } - if(EQ(p2, "-nt")) - return(isnewerthan(nxtarg(0), a)); + if(EQ(p2, "-nt")){ + b = nxtarg(0); + return(eval && isnewerthan(b, a)); + } if(!isint(a, &int1)) synbad("unexpected operator/operand: ", p2); ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [9front] Patch to fix test(1) expression parsing 2020-08-01 16:14 Patch to fix test(1) expression parsing Alex Musolino @ 2020-08-01 16:38 ` Alex Musolino 2020-08-02 4:35 ` ori 1 sibling, 0 replies; 7+ messages in thread From: Alex Musolino @ 2020-08-01 16:38 UTC (permalink / raw) To: 9front I should have looked over the patch more closely before hitting send. Here's an improved patch. Please disregard the previous one. diff -r 7510f0e7ba7f sys/man/1/test --- a/sys/man/1/test Thu Jul 30 15:59:04 2020 +0200 +++ b/sys/man/1/test Sun Aug 02 02:07:18 2020 +0930 @@ -209,10 +209,3 @@ .B /sys/src/cmd/test.c .SH "SEE ALSO" .IR rc (1) -.SH BUGS -Won't complain about extraneous arguments -since there may be arguments left unprocessed by -short-circuit evaluation of -.B -a -or -.BR -o . diff -r 7510f0e7ba7f sys/src/cmd/test.c --- a/sys/src/cmd/test.c Thu Jul 30 15:59:04 2020 +0200 +++ b/sys/src/cmd/test.c Sun Aug 02 02:07:18 2020 +0930 @@ -30,7 +30,7 @@ int isnewerthan(char *, char *); int hasmode(char *, ulong); int tio(char *, int); -int e(void), e1(void), e2(void), e3(void); +int e(int), e0(int), e1(int), e2(int), e3(int); char *nxtarg(int); void @@ -47,12 +47,8 @@ argv[ac] = 0; if (ac<=1) exits("usage"); - r = e(); - /* - * nice idea but short-circuit -o and -a operators may have - * not consumed their right-hand sides. - */ - if(0 && (c = nxtarg(1)) != nil) + r = e(1); + if((c = nxtarg(1)) != nil) synbad("unexpected operator/operand: ", c); exits(r?0:"false"); } @@ -81,78 +77,122 @@ } int -e(void) +e(int eval) { + char *op; int p1; - p1 = e1(); - if (EQ(nxtarg(1), "-o")) - return(p1 || e()); - ap--; + p1 = e0(eval); + op = nxtarg(1); + if(op){ + if(EQ(op, "-o")){ + if(p1){ + e0(0); + return 1; + } + return e0(eval); + } + ap--; + } return(p1); } int -e1(void) +e0(int eval) { int p1; - p1 = e2(); - if (EQ(nxtarg(1), "-a")) - return (p1 && e1()); + if(EQ(nxtarg(0), "(")){ + p1 = e1(eval); + if(!EQ(nxtarg(0), ")")) + synbad(") expected",""); + return p1; + } ap--; + return e1(eval); +} + +int +e1(int eval) +{ + char *op; + int p1; + + p1 = e2(eval); + op = nxtarg(1); + if(op){ + if(EQ(op, "-a")){ + if(p1) + return e(eval); + e(0); + return 0; + } + ap--; + } return(p1); } int -e2(void) +e2(int eval) { if (EQ(nxtarg(0), "!")) - return(!e2()); + return(!e(eval)); ap--; - return(e3()); + return(e3(eval)); } int -e3(void) +e3(int eval) { - int p1, int1, int2; - char *a, *p2; + int int1, int2; + char *a, *b, *p2; a = nxtarg(0); - if(EQ(a, "(")) { - p1 = e(); - if(!EQ(nxtarg(0), ")")) - synbad(") expected",""); - return(p1); + + if(EQ(a, "-A")){ + b = nxtarg(0); + return(eval && hasmode(b, DMAPPEND)); } - if(EQ(a, "-A")) - return(hasmode(nxtarg(0), DMAPPEND)); + if(EQ(a, "-L")){ + b = nxtarg(0); + return(eval && hasmode(b, DMEXCL)); + } - if(EQ(a, "-L")) - return(hasmode(nxtarg(0), DMEXCL)); + if(EQ(a, "-T")){ + b = nxtarg(0); + return(eval && hasmode(b, DMTMP)); + } - if(EQ(a, "-T")) - return(hasmode(nxtarg(0), DMTMP)); + if(EQ(a, "-f")){ + b = nxtarg(0); + return(eval && isreg(b)); + } - if(EQ(a, "-f")) - return(isreg(nxtarg(0))); + if(EQ(a, "-d")){ + b = nxtarg(0); + return(eval && isdir(b)); + } - if(EQ(a, "-d")) - return(isdir(nxtarg(0))); + if(EQ(a, "-r")){ + b = nxtarg(0); + return(eval && tio(b, AREAD)); + } - if(EQ(a, "-r")) - return(tio(nxtarg(0), AREAD)); + if(EQ(a, "-w")){ + b = nxtarg(0); + return(eval && tio(b, AWRITE)); + } - if(EQ(a, "-w")) - return(tio(nxtarg(0), AWRITE)); + if(EQ(a, "-x")){ + b = nxtarg(0); + return(eval && tio(b, AEXEC)); + } - if(EQ(a, "-x")) - return(tio(nxtarg(0), AEXEC)); - - if(EQ(a, "-e")) - return(tio(nxtarg(0), AEXIST)); + if(EQ(a, "-e")){ + b = nxtarg(0); + return(eval && tio(b, AEXIST)); + } if(EQ(a, "-c")) return(0); @@ -166,14 +206,16 @@ if(EQ(a, "-g")) return(0); - if(EQ(a, "-s")) - return(fsizep(nxtarg(0))); + if(EQ(a, "-s")){ + b = nxtarg(0); + return(eval && fsizep(b)); + } if(EQ(a, "-t")) if(ap>=ac) - return(isatty(1)); + return(eval && isatty(1)); else if(nxtintarg(&int1)) - return(isatty(int1)); + return(eval && isatty(int1)); else synbad("not a valid file descriptor number ", ""); @@ -191,14 +233,20 @@ if(EQ(p2, "!=")) return(!EQ(nxtarg(0), a)); - if(EQ(p2, "-older")) - return(isolder(nxtarg(0), a)); + if(EQ(p2, "-older")){ + b = nxtarg(0); + return(eval && isolder(b, a)); + } - if(EQ(p2, "-ot")) - return(isolderthan(nxtarg(0), a)); + if(EQ(p2, "-ot")){ + b = nxtarg(0); + return(eval && isolderthan(b, a)); + } - if(EQ(p2, "-nt")) - return(isnewerthan(nxtarg(0), a)); + if(EQ(p2, "-nt")){ + b = nxtarg(0); + return(eval && isnewerthan(b, a)); + } if(!isint(a, &int1)) synbad("unexpected operator/operand: ", p2); ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [9front] Patch to fix test(1) expression parsing 2020-08-01 16:14 Patch to fix test(1) expression parsing Alex Musolino 2020-08-01 16:38 ` [9front] " Alex Musolino @ 2020-08-02 4:35 ` ori 2020-08-02 4:39 ` Alex Musolino 1 sibling, 1 reply; 7+ messages in thread From: ori @ 2020-08-02 4:35 UTC (permalink / raw) To: 9front > Hi all, > > The following patch fixes test(1) to correctly parse its own > expression syntax whilst avoiding executing of filesystem operations > when these tests ought not to be performed due to short-circuiting. > > Previously, short-circuiting of -a and -o logical operators would > result in an incorrect parse. For example, the following invocations Generally looks good, will test tomorrow -- just a minor comment: > fail: > > term% test '(' 1 -gt 2 -a 1 -gt 1 ')' > test: ) expected > term% test '(' 2 -gt 1 -o 1 -gt 1 ')' > test: ) expected > > - r = e(); > + r = e(1); > /* > * nice idea but short-circuit -o and -a operators may have > * not consumed their right-hand sides. This comment seems outdated -- we can use the nice idea and error out appropriately now. ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [9front] Patch to fix test(1) expression parsing 2020-08-02 4:35 ` ori @ 2020-08-02 4:39 ` Alex Musolino 2020-08-02 4:51 ` ori 2020-08-03 13:45 ` Alex Musolino 0 siblings, 2 replies; 7+ messages in thread From: Alex Musolino @ 2020-08-02 4:39 UTC (permalink / raw) To: 9front > This comment seems outdated -- we can use the nice idea and > error out appropriately now. Yeah, I did this in an updated patch I sent in a subsequent email. ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [9front] Patch to fix test(1) expression parsing 2020-08-02 4:39 ` Alex Musolino @ 2020-08-02 4:51 ` ori 2020-08-03 13:45 ` Alex Musolino 1 sibling, 0 replies; 7+ messages in thread From: ori @ 2020-08-02 4:51 UTC (permalink / raw) To: 9front >> This comment seems outdated -- we can use the nice idea and >> error out appropriately now. > > Yeah, I did this in an updated patch I sent in a subsequent email. Oops. Had the old email open. looking at it again, I'm wondering if we can split the unary and binary ops up, and then instead of adding if(EQ(a, '-?')) { b = nxtarg(0); return(eval && ...); } we could do: /* special case ops */ if(EQ(a, "special)) ... /* common case binary ops */ b = nxtarg(0); if(!eval) return 0; if(EQ(a, "-?") return(...) if(eq(a, "-!") return(...) It looks like there'd only be a few special cases that don't fit. ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [9front] Patch to fix test(1) expression parsing 2020-08-02 4:39 ` Alex Musolino 2020-08-02 4:51 ` ori @ 2020-08-03 13:45 ` Alex Musolino 2020-10-31 14:10 ` cinap_lenrek 1 sibling, 1 reply; 7+ messages in thread From: Alex Musolino @ 2020-08-03 13:45 UTC (permalink / raw) To: 9front > Yeah, I did this in an updated patch I sent in a subsequent email. It turns out that the second patch is rubbish too. This one should be better. I've further amended the man page to also drop the description of an unimplemented feature. I'm using a test suite [1] derived from something joe9 posted in #cat-v which, in turn, was based on something from OpenBSD. [1] http://musolino.id.au/up/2020/08/03/test.testsuite.rc diff -r aa3d84fc81f6 sys/man/1/test --- a/sys/man/1/test Sun Aug 02 18:30:01 2020 +0930 +++ b/sys/man/1/test Mon Aug 03 23:11:04 2020 +0930 @@ -100,11 +100,6 @@ .BR -le may be used in place of .BR -eq . -The (nonstandard) construct -.BI -l " string\f1, -meaning the length of -.IR string , -may be used in place of an integer. .TP .IB a " -nt " b True if file @@ -209,10 +204,3 @@ .B /sys/src/cmd/test.c .SH "SEE ALSO" .IR rc (1) -.SH BUGS -Won't complain about extraneous arguments -since there may be arguments left unprocessed by -short-circuit evaluation of -.B -a -or -.BR -o . diff -r aa3d84fc81f6 sys/src/cmd/test.c --- a/sys/src/cmd/test.c Sun Aug 02 18:30:01 2020 +0930 +++ b/sys/src/cmd/test.c Mon Aug 03 23:11:04 2020 +0930 @@ -30,7 +30,7 @@ int isnewerthan(char *, char *); int hasmode(char *, ulong); int tio(char *, int); -int e(void), e1(void), e2(void), e3(void); +int e(int), e1(int), e2(int), e3(int); char *nxtarg(int); void @@ -47,12 +47,8 @@ argv[ac] = 0; if (ac<=1) exits("usage"); - r = e(); - /* - * nice idea but short-circuit -o and -a operators may have - * not consumed their right-hand sides. - */ - if(0 && (c = nxtarg(1)) != nil) + r = e(1); + if((c = nxtarg(1)) != nil) synbad("unexpected operator/operand: ", c); exits(r?0:"false"); } @@ -81,78 +77,128 @@ } int -e(void) +e(int eval) { + char *op; int p1; - p1 = e1(); - if (EQ(nxtarg(1), "-o")) - return(p1 || e()); - ap--; - return(p1); + p1 = e1(eval); + for(;;){ + op = nxtarg(1); + if(op == nil) + break; + if(!EQ(op, "-o")){ + ap--; + return p1; + } + if(!p1 && eval) + p1 |= e1(1); + else + e1(0); + } + return p1; } int -e1(void) +e1(int eval) { + char *op; int p1; - p1 = e2(); - if (EQ(nxtarg(1), "-a")) - return (p1 && e1()); - ap--; - return(p1); + p1 = e2(eval); + for(;;){ + op = nxtarg(1); + if(op == nil) + break; + if(!EQ(op, "-a")){ + ap--; + return p1; + } + if(p1 && eval) + p1 &= e2(1); + else + e2(0); + } + return p1; } int -e2(void) +e2(int eval) { - if (EQ(nxtarg(0), "!")) - return(!e2()); + char *op; + int p1; + + p1 = 0; + for(;;){ + op = nxtarg(1); + if(op == nil) + return p1 ^ 1; + if(!EQ(op, "!")) + break; + p1 ^= 1; + } ap--; - return(e3()); + return(p1^e3(eval)); } int -e3(void) +e3(int eval) { int p1, int1, int2; - char *a, *p2; + char *a, *b, *p2; a = nxtarg(0); if(EQ(a, "(")) { - p1 = e(); + p1 = e(eval); if(!EQ(nxtarg(0), ")")) synbad(") expected",""); return(p1); } - if(EQ(a, "-A")) - return(hasmode(nxtarg(0), DMAPPEND)); + if(EQ(a, "-A")){ + b = nxtarg(0); + return(eval && hasmode(b, DMAPPEND)); + } - if(EQ(a, "-L")) - return(hasmode(nxtarg(0), DMEXCL)); + if(EQ(a, "-L")){ + b = nxtarg(0); + return(eval && hasmode(b, DMEXCL)); + } - if(EQ(a, "-T")) - return(hasmode(nxtarg(0), DMTMP)); + if(EQ(a, "-T")){ + b = nxtarg(0); + return(eval && hasmode(b, DMTMP)); + } - if(EQ(a, "-f")) - return(isreg(nxtarg(0))); + if(EQ(a, "-f")){ + b = nxtarg(0); + return(eval && isreg(b)); + } - if(EQ(a, "-d")) - return(isdir(nxtarg(0))); + if(EQ(a, "-d")){ + b = nxtarg(0); + return(eval && isdir(b)); + } - if(EQ(a, "-r")) - return(tio(nxtarg(0), AREAD)); + if(EQ(a, "-r")){ + b = nxtarg(0); + return(eval && tio(b, AREAD)); + } - if(EQ(a, "-w")) - return(tio(nxtarg(0), AWRITE)); + if(EQ(a, "-w")){ + b = nxtarg(0); + return(eval && tio(b, AWRITE)); + } - if(EQ(a, "-x")) - return(tio(nxtarg(0), AEXEC)); + if(EQ(a, "-x")){ + b = nxtarg(0); + return(eval && tio(b, AEXEC)); + } - if(EQ(a, "-e")) - return(tio(nxtarg(0), AEXIST)); + if(EQ(a, "-e")){ + b = nxtarg(0); + return(eval && tio(b, AEXIST)); + } if(EQ(a, "-c")) return(0); @@ -166,14 +212,16 @@ if(EQ(a, "-g")) return(0); - if(EQ(a, "-s")) - return(fsizep(nxtarg(0))); + if(EQ(a, "-s")){ + b = nxtarg(0); + return(eval && fsizep(b)); + } if(EQ(a, "-t")) if(ap>=ac) - return(isatty(1)); + return(eval && isatty(1)); else if(nxtintarg(&int1)) - return(isatty(int1)); + return(eval && isatty(int1)); else synbad("not a valid file descriptor number ", ""); @@ -191,35 +239,40 @@ if(EQ(p2, "!=")) return(!EQ(nxtarg(0), a)); - if(EQ(p2, "-older")) - return(isolder(nxtarg(0), a)); - - if(EQ(p2, "-ot")) - return(isolderthan(nxtarg(0), a)); - - if(EQ(p2, "-nt")) - return(isnewerthan(nxtarg(0), a)); - - if(!isint(a, &int1)) - synbad("unexpected operator/operand: ", p2); - - if(nxtintarg(&int2)){ - if(EQ(p2, "-eq")) - return(int1==int2); - if(EQ(p2, "-ne")) - return(int1!=int2); - if(EQ(p2, "-gt")) - return(int1>int2); - if(EQ(p2, "-lt")) - return(int1<int2); - if(EQ(p2, "-ge")) - return(int1>=int2); - if(EQ(p2, "-le")) - return(int1<=int2); + if(EQ(p2, "-older")){ + b = nxtarg(0); + return(eval && isolder(b, a)); } - synbad("unknown operator ",p2); - return 0; /* to shut ken up */ + if(EQ(p2, "-ot")){ + b = nxtarg(0); + return(eval && isolderthan(b, a)); + } + + if(EQ(p2, "-nt")){ + b = nxtarg(0); + return(eval && isnewerthan(b, a)); + } + + if(isint(a, &int1)){ + if(nxtintarg(&int2)){ + if(EQ(p2, "-eq")) + return(int1==int2); + if(EQ(p2, "-ne")) + return(int1!=int2); + if(EQ(p2, "-gt")) + return(int1>int2); + if(EQ(p2, "-lt")) + return(int1<int2); + if(EQ(p2, "-ge")) + return(int1>=int2); + if(EQ(p2, "-le")) + return(int1<=int2); + ap--; + } + } + ap--; + return !EQ(a, ""); } int ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [9front] Patch to fix test(1) expression parsing 2020-08-03 13:45 ` Alex Musolino @ 2020-10-31 14:10 ` cinap_lenrek 0 siblings, 0 replies; 7+ messages in thread From: cinap_lenrek @ 2020-10-31 14:10 UTC (permalink / raw) To: 9front ok, looks good to me. sorry for the delay. -- cinap ^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2020-10-31 14:10 UTC | newest] Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2020-08-01 16:14 Patch to fix test(1) expression parsing Alex Musolino 2020-08-01 16:38 ` [9front] " Alex Musolino 2020-08-02 4:35 ` ori 2020-08-02 4:39 ` Alex Musolino 2020-08-02 4:51 ` ori 2020-08-03 13:45 ` Alex Musolino 2020-10-31 14:10 ` cinap_lenrek
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).