From mboxrd@z Thu Jan 1 00:00:00 1970 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on inbox.vuxu.org X-Spam-Level: X-Spam-Status: No, score=-3.3 required=5.0 tests=HTML_MESSAGE, MAILING_LIST_MULTI,MIME_QP_LONG_LINE,RCVD_IN_DNSWL_MED, T_SCC_BODY_TEXT_LINE autolearn=ham autolearn_force=no version=3.4.4 Received: (qmail 9211 invoked from network); 2 Jul 2023 01:19:32 -0000 Received: from second.openwall.net (193.110.157.125) by inbox.vuxu.org with ESMTPUTF8; 2 Jul 2023 01:19:32 -0000 Received: (qmail 7787 invoked by uid 550); 2 Jul 2023 01:19:28 -0000 Mailing-List: contact musl-help@lists.openwall.com; run by ezmlm Precedence: bulk List-Post: List-Help: List-Unsubscribe: List-Subscribe: List-ID: Reply-To: musl@lists.openwall.com Received: (qmail 7764 invoked from network); 2 Jul 2023 01:19:27 -0000 Content-Type: multipart/alternative; boundary=Apple-Mail-B1D69962-A723-4727-AC61-6A10C115258F Content-Transfer-Encoding: 7bit From: "A. Wilcox" Mime-Version: 1.0 (1.0) Date: Sat, 1 Jul 2023 20:18:42 -0500 Message-Id: <9751E98E-025B-4E32-9EDF-D1984F998C10@wilcox-tech.com> References: <31b53a8d-7cf4-b3a3-371f-a5723963383e@cs.ucla.edu> Cc: libc-coord@lists.openwall.com, Jonathan Wakely , Rich Felker , linux-man@vger.kernel.org, libc-alpha@sourceware.org In-Reply-To: <31b53a8d-7cf4-b3a3-371f-a5723963383e@cs.ucla.edu> To: musl@lists.openwall.com X-Mailer: iPhone Mail (19D52) Subject: Re: [musl] Re: [libc-coord] Re: regression in man pages for interfaces using loff_t --Apple-Mail-B1D69962-A723-4727-AC61-6A10C115258F Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable On Jun 30, 2023, at 2:46 PM, Paul Eggert wrote: >=20 > =EF=BB=BFOn 2023-06-30 01:02, Jonathan Wakely wrote: >=20 >> For APIs like copy_file_range(2) and splice(2) the arguments are loff_t* s= o >> you can't just "pass arguments that fit in off_t" to them. >=20 > Sorry, I missed that detail. Still, the argument stands. On legacy 32-bit p= latforms without -D_FILE_OFFSET_BITS=3D64, calls will still work if they pas= s null pointers to copy_file_range, a common case in my experience. The call= s that don't, will get typecheck errors or warnings, and that's good enough t= o address the issue. >=20 >=20 >> And in C++ it won't even compile unless you get the pointer >> types exactly right (C compilers will typically allow the mismatch with >> just a warning). >=20 > That's good! People should be using -D_FILE_OFFSET_BITS=3D64 if they use t= hese functions, and the typecheck errors and/or warnings will remind them. T= he man pages don't need to (and shouldn't) document what happens if you call= these functions on legacy 32-bit platforms without first defining _FILE_OFF= SET_BITS to be 64. >=20 >=20 >> People miss footnotes. >=20 > OK, let's make the point more prominently, at the start of the man page. P= roposed patch attached. This patch should work for musl as well as for glibc= . The wording around "legacy" 32-bit is very concerning on my end. _FILE_OFFSET_BITS=3D32 is legacy, but Linux on 32-bit systems is not. In fa= ct, that is why we went through all the pain of time64 in Ad=C3=A9lie (with o= ur musl friends) - to ensure that 32-bit hardware is fully supported and cur= rent. New designs are still being made with 32-bit Arm chips; there are new ULP x8= 6 32-bit SoCs, etc. I think anyone casually browsing this documentation could get the wrong idea= that 32-bit platforms "in general" are legacy and this should be clarified.= Best, -A. -- A. Wilcox (Sent from my iPhone) Mac, iOS, Linux software engineer= --Apple-Mail-B1D69962-A723-4727-AC61-6A10C115258F Content-Type: multipart/mixed; boundary=Apple-Mail-85B531FA-4BDE-4FAF-8A47-41D1ACC0DCE0 Content-Transfer-Encoding: 7bit --Apple-Mail-85B531FA-4BDE-4FAF-8A47-41D1ACC0DCE0 Content-Type: text/html; charset=utf-8 Content-Transfer-Encoding: quoted-printable On Jun 30, 2023, at 2:46 PM, Paul Eggert &l= t;eggert@cs.ucla.edu> wrote:

=EF= =BB=BFOn 2023-06-30 01:02, Jonathan Wakely wrote:

For APIs like copy_file_range(2) and s= plice(2) the arguments are loff_t* so
you can't just "pass arguments that fit in off_t" to them.<= /span>

Sorry, I missed that detail. S= till, the argument stands. On legacy 32-bit platforms without -D_FILE_OFFSET= _BITS=3D64, calls will still work if they pass null pointers to copy_file_ra= nge, a common case in my experience. The calls that don't, will get typechec= k errors or warnings, and that's good enough to address the issue.

And in C+= + it won't even compile unless you get the pointer
types exactly right (C compilers will typicall= y allow the mismatch with
<= span>just a warning).

That's g= ood! People should be using -D_FILE_OFFSET_BITS=3D64 if they use these funct= ions, and the typecheck errors and/or warnings will remind them. The man pag= es don't need to (and shouldn't) document what happens if you call these fun= ctions on legacy 32-bit platforms without first defining _FILE_OFFSET_BITS t= o be 64.


People miss footnotes.

OK, let's make the point more prominently, at the start of the man page. P= roposed patch attached. This patch should work for musl as well as for glibc= .
= --Apple-Mail-85B531FA-4BDE-4FAF-8A47-41D1ACC0DCE0 Content-Type: application/octet-stream; name=0001-off64_t-prefer-off_t-for-splice-etc.patch; x-apple-part-url=FF3F037C-C54D-46D9-8E24-23DEB7752058 Content-Disposition: attachment; filename=0001-off64_t-prefer-off_t-for-splice-etc.patch Content-Transfer-Encoding: quoted-printable =46rom 38bfd1ecda2014955c701f7658a4ab55fa5c8b9d=20Mon=20Sep=2017=2000:00:00=20= 2001=0AFrom:=20Paul=20Eggert=20=0ADate:=20Fri,=2030=20Ju= n=202023=2012:25:53=20-0700=0ASubject:=20[PATCH]=20off64_t:=20prefer=20off_t= =20for=20splice,=20etc.=0A=0AFor=20the=20few=20functions=20that=20come=20onl= y=20in=2064-bit=20off_t=20flavors,=0Adocument=20their=20APIs=20as=20using=20= off_t=20instead=20of=20off64_t,=0Aand=20say=20also=20that=20code=20should=20= #define=20_FILE_OFFSET_BITS=2064.=0AThis=20documents=20what=20user=20code=20= is=20(and=20should=20be)=20doing=20anyway,=0Aif=20it=20needs=20to=20work=20o= n=20legacy=2032-bit=20Linux.=0A---=0A=20man2/copy_file_range.2=20=20=20=20=20= |=2017=20++++++++++++++---=0A=20man2/readahead.2=20=20=20=20=20=20=20=20=20=20= =20|=20=208=20+++++++-=0A=20man2/splice.2=20=20=20=20=20=20=20=20=20=20=20=20= =20=20|=2014=20++++++++++++--=0A=20man2/sync_file_range.2=20=20=20=20=20|=20= =209=20+++++++--=0A=20man3/fopencookie.3=20=20=20=20=20=20=20=20=20|=2014=20= +++++++++++---=0A=20man7/feature_test_macros.7=20|=2012=20++++++++----=0A=20= 6=20files=20changed,=2059=20insertions(+),=2015=20deletions(-)=0A=0Adiff=20-= -git=20a/man2/copy_file_range.2=20b/man2/copy_file_range.2=0Aindex=206f3aa49= 71..bb5aa2223=20100644=0A---=20a/man2/copy_file_range.2=0A+++=20b/man2/copy_= file_range.2=0A@@=20-11,10=20+11,11=20@@=20Standard=20C=20library=0A=20.SH=20= SYNOPSIS=0A=20.nf=0A=20.B=20#define=20_GNU_SOURCE=0A+.B=20#define=20_FILE_OFF= SET_BITS=2064=0A=20.B=20#include=20=0A=20.PP=0A-.BI=20"ssize_t=20c= opy_file_range(int=20"=20fd_in=20",=20off64_t=20*_Nullable=20"=20off_in=20,=0A= -.BI=20"=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20int=20"=20fd_out=20",=20off64_t=20*_Nullable=20"=20off_out=20,=0A+.BI=20"= ssize_t=20copy_file_range(int=20"=20fd_in=20",=20off_t=20*_Nullable=20"=20of= f_in=20,=0A+.BI=20"=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20int=20"=20fd_out=20",=20off_t=20*_Nullable=20"=20off_out=20,=0A= =20.BI=20"=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20size_t=20"=20len=20",=20unsigned=20int=20"=20flags=20);=0A=20.fi=0A=20= .SH=20DESCRIPTION=0A@@=20-224,6=20+225,15=20@@=20gives=20filesystems=20an=20= opportunity=20to=20implement=20"copy=20acceleration"=20techniques,=0A=20such= =20as=20the=20use=20of=20reflinks=20(i.e.,=20two=20or=20more=20inodes=20that= =20share=0A=20pointers=20to=20the=20same=20copy-on-write=20disk=20blocks)=0A= =20or=20server-side-copy=20(in=20the=20case=20of=20NFS).=0A+.PP=0A+.B=20_FIL= E_OFFSET_BITS=0A+should=20be=20defined=20to=20be=2064=20in=20code=20that=20u= ses=20non-null=0A+.I=20off_in=0A+or=0A+.I=20off_out=0A+or=20that=20takes=20t= he=20address=20of=0A+.BR=20copy_file_range=20,=0A+if=20the=20code=20is=20int= ended=20to=20be=20portable=20to=20legacy=2032-bit=20platforms.=0A=20.SH=20BU= GS=0A=20In=20Linux=205.3=20to=20Linux=205.18,=0A=20cross-filesystem=20copies= =20were=20implemented=20by=20the=20kernel,=0A@@=20-234,6=20+244,7=20@@=20the= =20call=20failed=20to=20copy,=20while=20still=20reporting=20success.=0A=20.\= "=20SRC=20BEGIN=20(copy_file_range.c)=0A=20.EX=0A=20#define=20_GNU_SOURCE=0A= +#define=20_FILE_OFFSET_BITS=2064=0A=20#include=20=0A=20#include=20= =0A=20#include=20=0A@@=20-244,7=20+255,7=20@@=20int=0A=20= main(int=20argc,=20char=20*argv[])=0A=20{=0A=20=20=20=20=20int=20=20=20=20=20= =20=20=20=20=20fd_in,=20fd_out;=0A-=20=20=20=20off64_t=20=20=20=20=20=20len,= =20ret;=0A+=20=20=20=20off_t=20=20=20=20=20=20=20=20len,=20ret;=0A=20=20=20=20= =20struct=20stat=20=20stat;=0A=20\&=0A=20=20=20=20=20if=20(argc=20!=3D=203)=20= {=0Adiff=20--git=20a/man2/readahead.2=20b/man2/readahead.2=0Aindex=20d697959= 79..62b9e6786=20100644=0A---=20a/man2/readahead.2=0A+++=20b/man2/readahead.2= =0A@@=20-14,9=20+14,10=20@@=20Standard=20C=20library=0A=20.SH=20SYNOPSIS=0A=20= .nf=0A=20.BR=20"#define=20_GNU_SOURCE"=20"=20=20=20=20=20=20=20=20=20=20=20=20= =20/*=20See=20feature_test_macros(7)=20*/"=0A+.B=20#define=20_FILE_OFFSET_BI= TS=2064=0A=20.B=20#include=20=0A=20.PP=0A-.BI=20"ssize_t=20readahea= d(int=20"=20fd=20",=20off64_t=20"=20offset=20",=20size_t=20"=20count=20);=0A= +.BI=20"ssize_t=20readahead(int=20"=20fd=20",=20off_t=20"=20offset=20",=20si= ze_t=20"=20count=20);=0A=20.fi=0A=20.SH=20DESCRIPTION=0A=20.BR=20readahead=20= ()=0A@@=20-73,6=20+74,11=20@@=20Linux.=0A=20.SH=20HISTORY=0A=20Linux=202.4.1= 3,=0A=20glibc=202.3.=0A+.SH=20NOTES=0A+.B=20_FILE_OFFSET_BITS=0A+should=20be= =20defined=20to=20be=2064=20in=20code=20that=20uses=20a=20pointer=20to=0A+.B= R=20readahead=20,=0A+if=20the=20code=20is=20intended=20to=20be=20portable=20= to=20legacy=2032-bit=20platforms.=0A=20.SH=20BUGS=0A=20.BR=20readahead=20()=0A= =20attempts=20to=20schedule=20the=20reads=20in=20the=20background=20and=20re= turn=20immediately.=0Adiff=20--git=20a/man2/splice.2=20b/man2/splice.2=0Aind= ex=20dd78e8cd4..829d2e336=20100644=0A---=20a/man2/splice.2=0A+++=20b/man2/sp= lice.2=0A@@=20-12,10=20+12,11=20@@=20Standard=20C=20library=0A=20.SH=20SYNOP= SIS=0A=20.nf=0A=20.BR=20"#define=20_GNU_SOURCE"=20"=20=20=20=20=20=20=20=20=20= /*=20See=20feature_test_macros(7)=20*/"=0A+.B=20"#define=20_FILE_OFFSET_BITS= =2064=0A=20.B=20#include=20=0A=20.PP=0A-.BI=20"ssize_t=20splice(int= =20"=20fd_in=20",=20off64_t=20*_Nullable=20"=20off_in=20,=0A-.BI=20"=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20int=20"=20fd_out=20",=20off64_t=20*_Null= able=20"=20off_out=20,=0A+.BI=20"ssize_t=20splice(int=20"=20fd_in=20",=20off= _t=20*_Nullable=20"=20off_in=20,=0A+.BI=20"=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20int=20"=20fd_out=20",=20off_t=20*_Nullable=20"=20off_out=20,=0A=20= .BI=20"=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20size_t=20"=20len=20",=20= unsigned=20int=20"=20flags=20);=0A=20.\"=20Return=20type=20was=20long=20befo= re=20glibc=202.7=0A=20.fi=0A@@=20-242,6=20+243,15=20@@=20only=20pointers=20a= re=20copied,=20not=20the=20pages=20of=20the=20buffer.=0A=20.\"=20the=20data=20= and=20choose=20to=20forward=20it=20to=20two=20or=20more=20different=0A=20.\"= =20users=20-=20for=20things=20like=20logging=20etc.).=0A=20.\"=0A+.PP=0A+.B=20= _FILE_OFFSET_BITS=0A+should=20be=20defined=20to=20be=2064=20in=20code=20that= =20uses=20non-null=0A+.I=20off_in=0A+or=0A+.I=20off_out=0A+or=20that=20takes= =20the=20address=20of=0A+.BR=20splice=20,=0A+if=20the=20code=20is=20intended= =20to=20be=20portable=20to=20legacy=2032-bit=20platforms.=0A=20.SH=20EXAMPLE= S=0A=20See=0A=20.BR=20tee=20(2).=0Adiff=20--git=20a/man2/sync_file_range.2=20= b/man2/sync_file_range.2=0Aindex=20d633b08ff..0bf17f824=20100644=0A---=20a/m= an2/sync_file_range.2=0A+++=20b/man2/sync_file_range.2=0A@@=20-16,9=20+16,10= =20@@=20Standard=20C=20library=0A=20.SH=20SYNOPSIS=0A=20.nf=0A=20.BR=20"#def= ine=20_GNU_SOURCE"=20"=20=20=20=20=20=20=20=20=20/*=20See=20feature_test_mac= ros(7)=20*/"=0A+.B=20#define=20_FILE_OFFSET_BITS=2064=0A=20.B=20#include=20<= fcntl.h>=0A=20.PP=0A-.BI=20"int=20sync_file_range(int=20"=20fd=20",=20off64_= t=20"=20offset=20",=20off64_t=20"=20nbytes=20,=0A+.BI=20"int=20sync_file_ran= ge(int=20"=20fd=20",=20off_t=20"=20offset=20",=20off_t=20"=20nbytes=20,=0A=20= .BI=20"=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20unsigned=20= int=20"=20flags=20);=0A=20.fi=0A=20.SH=20DESCRIPTION=0A@@=20-176,7=20+177,7=20= @@=20system=20call=20that=20orders=20the=20arguments=20suitably:=0A=20.in=20= +4n=0A=20.EX=0A=20.BI=20"int=20sync_file_range2(int=20"=20fd=20",=20unsigned= =20int=20"=20flags=20,=0A-.BI=20"=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20off64_t=20"=20offset=20",=20off64_t=20"=20nbytes=20);=0A+.= BI=20"=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20off_t=20= "=20offset=20",=20off_t=20"=20nbytes=20);=0A=20.EE=0A=20.in=0A=20.PP=0A@@=20= -198,6=20+199,10=20@@=20glibc=20transparently=20wraps=0A=20under=20the=20nam= e=0A=20.BR=20sync_file_range=20().=0A=20.SH=20NOTES=0A+.B=20_FILE_OFFSET_BIT= S=0A+should=20be=20defined=20to=20be=2064=20in=20code=20that=20takes=20the=20= address=20of=0A+.BR=20sync_file_range=20,=0A+if=20the=20code=20is=20intended= =20to=20be=20portable=20to=20legacy=2032-bit=20platforms.=0A=20.SH=20SEE=20A= LSO=0A=20.BR=20fdatasync=20(2),=0A=20.BR=20fsync=20(2),=0Adiff=20--git=20a/m= an3/fopencookie.3=20b/man3/fopencookie.3=0Aindex=20409a3c81a..08b190394=2010= 0644=0A---=20a/man3/fopencookie.3=0A+++=20b/man3/fopencookie.3=0A@@=20-13,6=20= +13,7=20@@=20Standard=20C=20library=0A=20.SH=20SYNOPSIS=0A=20.nf=0A=20.BR=20= "#define=20_GNU_SOURCE"=20"=20=20=20=20=20=20=20=20=20/*=20See=20feature_tes= t_macros(7)=20*/"=0A+.B=20#define=20_FILE_OFFSET_BITS=2064=0A=20.B=20#includ= e=20=0A=20.PP=0A=20.BI=20"FILE=20*fopencookie(void=20*restrict=20"=20= cookie=20",=20const=20char=20*restrict=20"=20mode=20,=0A@@=20-169,7=20+170,7= =20@@=20When=20called,=20it=20receives=20three=20arguments:=0A=20.IP=0A=20.i= n=20+4n=0A=20.EX=0A-int=20seek(void=20*cookie,=20off64_t=20*offset,=20int=20= whence);=0A+int=20seek(void=20*cookie,=20off_t=20*offset,=20int=20whence);=0A= =20.EE=0A=20.in=0A=20.IP=0A@@=20-351,9=20+352,9=20@@=20memfile_read(void=20*= c,=20char=20*buf,=20size_t=20size)=0A=20}=0A=20\&=0A=20int=0A-memfile_seek(v= oid=20*c,=20off64_t=20*offset,=20int=20whence)=0A+memfile_seek(void=20*c,=20= off_t=20*offset,=20int=20whence)=0A=20{=0A-=20=20=20=20off64_t=20new_offset;= =0A+=20=20=20=20off_t=20new_offset;=0A=20=20=20=20=20struct=20memfile_cookie= =20*cookie=20=3D=20c;=0A=20\&=0A=20=20=20=20=20if=20(whence=20=3D=3D=20SEEK_= SET)=0A@@=20-451,6=20+452,13=20@@=20main(int=20argc,=20char=20*argv[])=0A=20= }=0A=20.EE=0A=20.\"=20SRC=20END=0A+.SH=20NOTES=0A+.B=20_FILE_OFFSET_BITS=0A+= should=20be=20defined=20to=20be=2064=20in=20code=20that=20uses=20non-null=0A= +.I=20seek=0A+or=20that=20takes=20the=20address=20of=0A+.BR=20fopencookie=20= ,=0A+if=20the=20code=20is=20intended=20to=20be=20portable=20to=20legacy=2032= -bit=20platforms.=0A=20.SH=20SEE=20ALSO=0A=20.BR=20fclose=20(3),=0A=20.BR=20= fmemopen=20(3),=0Adiff=20--git=20a/man7/feature_test_macros.7=20b/man7/featu= re_test_macros.7=0Aindex=20f1620611c..462fd4abb=20100644=0A---=20a/man7/feat= ure_test_macros.7=0A+++=20b/man7/feature_test_macros.7=0A@@=20-113,15=20+113= ,16=20@@=20feature=20test=20macro=20requirements=20(this=20example=20from=0A= =20.RS=20+4=0A=20.EX=0A=20.B=20#define=20_GNU_SOURCE=0A+.B=20#define=20_FILE= _OFFSET_BITS=2064=0A=20.B=20#include=20=0A=20.PP=0A-.BI=20"ssize_t=20= readahead(int=20"=20fd=20",=20off64_t=20*"=20offset=20",=20size_t=20"=20coun= t=20);=0A+.BI=20"ssize_t=20readahead(int=20"=20fd=20",=20off_t=20*"=20offset= =20",=20size_t=20"=20count=20);=0A=20.EE=0A=20.RE=0A=20.PP=0A-This=20format=20= is=20employed=20in=20cases=20where=20only=20a=20single=0A-feature=20test=20m= acro=20can=20be=20used=20to=20expose=20the=20function=0A-declaration,=20and=20= that=20macro=20is=20not=20defined=20by=20default.=0A+This=20format=20is=20em= ployed=20in=20cases=20where=20feature=20macros=0A+expose=20the=20function=20= declaration=20with=20the=20correct=20type,=0A+and=20these=20macros=20are=20n= ot=20defined=20by=20default.=0A=20.SS=20Feature=20test=20macros=20understood= =20by=20glibc=0A=20The=20paragraphs=20below=20explain=20how=20feature=20test= =20macros=20are=20handled=0A=20in=20glibc=202.\fIx\fP,=0A@@=20-406,6=20+407,= 9=20@@=20related=20to=20file=20I/O=20and=20filesystem=20operations=20into=20= references=20to=0A=20their=2064-bit=20counterparts.=0A=20This=20is=20useful=20= for=20performing=20I/O=20on=20large=20files=20(>=202=20Gigabytes)=0A=20on=20= 32-bit=20systems.=0A+It=20is=20also=20useful=20when=20calling=20functions=20= like=0A+.BR=20copy_file_range=20(2)=0A+that=20were=20added=20more=20recently= =20and=20that=20come=20only=20in=2064-bit=20flavors.=0A=20(Defining=20this=20= macro=20permits=20correctly=20written=20programs=20to=20use=0A=20large=20fil= es=20with=20only=20a=20recompilation=20being=20required.)=0A=20.IP=0A--=20=0A= 2.41.0=0A=0A= --Apple-Mail-85B531FA-4BDE-4FAF-8A47-41D1ACC0DCE0 Content-Type: text/html; charset=utf-8 Content-Transfer-Encoding: quoted-printable


The wordi= ng around "legacy" 32-bit is very concerning on my end.

_= FILE_OFFSET_BITS=3D32 is legacy, but Linux on 32-bit systems is not.  I= n fact, that is why we went through all the pain of time64 in Ad=C3=A9lie (w= ith our musl friends) - to ensure that 32-bit hardware is fully supported an= d current.

New designs are still being made with 32= -bit Arm chips; there are new ULP x86 32-bit SoCs, etc.

=
I think anyone casually browsing this documentation could get the wrong= idea that 32-bit platforms "in general" are legacy and this should be clari= fied.

Best,
-A.

<= div style=3D"direction: inherit;">--
A. Wilcox (Sent from my iPhone)
Mac= , iOS, Linux software engineer
= --Apple-Mail-85B531FA-4BDE-4FAF-8A47-41D1ACC0DCE0-- --Apple-Mail-B1D69962-A723-4727-AC61-6A10C115258F--