coreutils-8.32-i18n-1.patch 165 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529
  1. From 01010419a6499768563e7b2f3fd56cf16edda75e Mon Sep 17 00:00:00 2001
  2. From: rpm-build <rpm-build>
  3. Date: Mon, 4 Oct 2021 08:54:37 +0200
  4. Subject: [PATCH] coreutils-i18n.patch
  5. ---
  6. bootstrap.conf | 1 +
  7. configure.ac | 2 +
  8. lib/linebuffer.h | 8 +
  9. lib/mbfile.c | 3 +
  10. lib/mbfile.h | 255 ++++++++++++
  11. m4/mbfile.m4 | 14 +
  12. src/cut.c | 508 +++++++++++++++++++++--
  13. src/expand-common.c | 114 ++++++
  14. src/expand-common.h | 12 +
  15. src/expand.c | 90 +++-
  16. src/fold.c | 312 ++++++++++++--
  17. src/join.c | 359 ++++++++++++++--
  18. src/local.mk | 4 +-
  19. src/pr.c | 443 ++++++++++++++++++--
  20. src/sort.c | 792 +++++++++++++++++++++++++++++++++---
  21. src/unexpand.c | 103 ++++-
  22. src/uniq.c | 119 +++++-
  23. tests/Coreutils.pm | 3 +
  24. tests/expand/mb.sh | 183 +++++++++
  25. tests/i18n/sort.sh | 29 ++
  26. tests/local.mk | 4 +
  27. tests/misc/expand.pl | 42 ++
  28. tests/misc/fold.pl | 50 ++-
  29. tests/misc/join.pl | 50 +++
  30. tests/misc/sort-mb-tests.sh | 45 ++
  31. tests/misc/sort-merge.pl | 42 ++
  32. tests/misc/sort.pl | 40 +-
  33. tests/misc/unexpand.pl | 39 ++
  34. tests/misc/uniq.pl | 55 +++
  35. tests/pr/pr-tests.pl | 49 +++
  36. tests/unexpand/mb.sh | 172 ++++++++
  37. 31 files changed, 3700 insertions(+), 242 deletions(-)
  38. create mode 100644 lib/mbfile.c
  39. create mode 100644 lib/mbfile.h
  40. create mode 100644 m4/mbfile.m4
  41. create mode 100755 tests/expand/mb.sh
  42. create mode 100755 tests/i18n/sort.sh
  43. create mode 100755 tests/misc/sort-mb-tests.sh
  44. create mode 100755 tests/unexpand/mb.sh
  45. diff --git a/bootstrap.conf b/bootstrap.conf
  46. index c1399e3..60b39cf 100644
  47. --- a/bootstrap.conf
  48. +++ b/bootstrap.conf
  49. @@ -162,6 +162,7 @@ gnulib_modules="
  50. maintainer-makefile
  51. malloc-gnu
  52. manywarnings
  53. + mbfile
  54. mbrlen
  55. mbrtowc
  56. mbsalign
  57. diff --git a/configure.ac b/configure.ac
  58. index 7e4afc9..4656a35 100644
  59. --- a/configure.ac
  60. +++ b/configure.ac
  61. @@ -476,6 +476,8 @@ fi
  62. # I'm leaving it here for now. This whole thing needs to be modernized...
  63. gl_WINSIZE_IN_PTEM
  64. +gl_MBFILE
  65. +
  66. gl_HEADER_TIOCGWINSZ_IN_TERMIOS_H
  67. if test $gl_cv_sys_tiocgwinsz_needs_termios_h = no && \
  68. diff --git a/lib/linebuffer.h b/lib/linebuffer.h
  69. index 07d45ca..af62e6c 100644
  70. --- a/lib/linebuffer.h
  71. +++ b/lib/linebuffer.h
  72. @@ -22,6 +22,11 @@
  73. # include "idx.h"
  74. # include <stdio.h>
  75. +/* Get mbstate_t. */
  76. +# if HAVE_WCHAR_H
  77. +# include <wchar.h>
  78. +# endif
  79. +
  80. /* A 'struct linebuffer' holds a line of text. */
  81. struct linebuffer
  82. @@ -29,6 +34,9 @@ struct linebuffer
  83. idx_t size; /* Allocated. */
  84. idx_t length; /* Used. */
  85. char *buffer;
  86. +# if HAVE_WCHAR_H
  87. + mbstate_t state;
  88. +# endif
  89. };
  90. /* Initialize linebuffer LINEBUFFER for use. */
  91. diff --git a/lib/mbfile.c b/lib/mbfile.c
  92. new file mode 100644
  93. index 0000000..b0a468e
  94. --- /dev/null
  95. +++ b/lib/mbfile.c
  96. @@ -0,0 +1,3 @@
  97. +#include <config.h>
  98. +#define MBFILE_INLINE _GL_EXTERN_INLINE
  99. +#include "mbfile.h"
  100. diff --git a/lib/mbfile.h b/lib/mbfile.h
  101. new file mode 100644
  102. index 0000000..11f1b12
  103. --- /dev/null
  104. +++ b/lib/mbfile.h
  105. @@ -0,0 +1,255 @@
  106. +/* Multibyte character I/O: macros for multi-byte encodings.
  107. + Copyright (C) 2001, 2005, 2009-2015 Free Software Foundation, Inc.
  108. +
  109. + This program is free software: you can redistribute it and/or modify
  110. + it under the terms of the GNU General Public License as published by
  111. + the Free Software Foundation; either version 3 of the License, or
  112. + (at your option) any later version.
  113. +
  114. + This program is distributed in the hope that it will be useful,
  115. + but WITHOUT ANY WARRANTY; without even the implied warranty of
  116. + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  117. + GNU General Public License for more details.
  118. +
  119. + You should have received a copy of the GNU General Public License
  120. + along with this program. If not, see <http://www.gnu.org/licenses/>. */
  121. +
  122. +/* Written by Mitsuru Chinen <mchinen@yamato.ibm.com>
  123. + and Bruno Haible <bruno@clisp.org>. */
  124. +
  125. +/* The macros in this file implement multi-byte character input from a
  126. + stream.
  127. +
  128. + mb_file_t
  129. + is the type for multibyte character input stream, usable for variable
  130. + declarations.
  131. +
  132. + mbf_char_t
  133. + is the type for multibyte character or EOF, usable for variable
  134. + declarations.
  135. +
  136. + mbf_init (mbf, stream)
  137. + initializes the MB_FILE for reading from stream.
  138. +
  139. + mbf_getc (mbc, mbf)
  140. + reads the next multibyte character from mbf and stores it in mbc.
  141. +
  142. + mb_iseof (mbc)
  143. + returns true if mbc represents the EOF value.
  144. +
  145. + Here are the function prototypes of the macros.
  146. +
  147. + extern void mbf_init (mb_file_t mbf, FILE *stream);
  148. + extern void mbf_getc (mbf_char_t mbc, mb_file_t mbf);
  149. + extern bool mb_iseof (const mbf_char_t mbc);
  150. + */
  151. +
  152. +#ifndef _MBFILE_H
  153. +#define _MBFILE_H 1
  154. +
  155. +#include <assert.h>
  156. +#include <stdbool.h>
  157. +#include <stdio.h>
  158. +#include <string.h>
  159. +
  160. +/* Tru64 with Desktop Toolkit C has a bug: <stdio.h> must be included before
  161. + <wchar.h>.
  162. + BSD/OS 4.1 has a bug: <stdio.h> and <time.h> must be included before
  163. + <wchar.h>. */
  164. +#include <stdio.h>
  165. +#include <time.h>
  166. +#include <wchar.h>
  167. +
  168. +#include "mbchar.h"
  169. +
  170. +#ifndef _GL_INLINE_HEADER_BEGIN
  171. + #error "Please include config.h first."
  172. +#endif
  173. +_GL_INLINE_HEADER_BEGIN
  174. +#ifndef MBFILE_INLINE
  175. +# define MBFILE_INLINE _GL_INLINE
  176. +#endif
  177. +
  178. +struct mbfile_multi {
  179. + FILE *fp;
  180. + bool eof_seen;
  181. + bool have_pushback;
  182. + mbstate_t state;
  183. + unsigned int bufcount;
  184. + char buf[MBCHAR_BUF_SIZE];
  185. + struct mbchar pushback;
  186. +};
  187. +
  188. +MBFILE_INLINE void
  189. +mbfile_multi_getc (struct mbchar *mbc, struct mbfile_multi *mbf)
  190. +{
  191. + size_t bytes;
  192. +
  193. + /* If EOF has already been seen, don't use getc. This matters if
  194. + mbf->fp is connected to an interactive tty. */
  195. + if (mbf->eof_seen)
  196. + goto eof;
  197. +
  198. + /* Return character pushed back, if there is one. */
  199. + if (mbf->have_pushback)
  200. + {
  201. + mb_copy (mbc, &mbf->pushback);
  202. + mbf->have_pushback = false;
  203. + return;
  204. + }
  205. +
  206. + /* Before using mbrtowc, we need at least one byte. */
  207. + if (mbf->bufcount == 0)
  208. + {
  209. + int c = getc (mbf->fp);
  210. + if (c == EOF)
  211. + {
  212. + mbf->eof_seen = true;
  213. + goto eof;
  214. + }
  215. + mbf->buf[0] = (unsigned char) c;
  216. + mbf->bufcount++;
  217. + }
  218. +
  219. + /* Handle most ASCII characters quickly, without calling mbrtowc(). */
  220. + if (mbf->bufcount == 1 && mbsinit (&mbf->state) && is_basic (mbf->buf[0]))
  221. + {
  222. + /* These characters are part of the basic character set. ISO C 99
  223. + guarantees that their wide character code is identical to their
  224. + char code. */
  225. + mbc->wc = mbc->buf[0] = mbf->buf[0];
  226. + mbc->wc_valid = true;
  227. + mbc->ptr = &mbc->buf[0];
  228. + mbc->bytes = 1;
  229. + mbf->bufcount = 0;
  230. + return;
  231. + }
  232. +
  233. + /* Use mbrtowc on an increasing number of bytes. Read only as many bytes
  234. + from mbf->fp as needed. This is needed to give reasonable interactive
  235. + behaviour when mbf->fp is connected to an interactive tty. */
  236. + for (;;)
  237. + {
  238. + /* We don't know whether the 'mbrtowc' function updates the state when
  239. + it returns -2, - this is the ISO C 99 and glibc-2.2 behaviour - or
  240. + not - amended ANSI C, glibc-2.1 and Solaris 2.7 behaviour. We
  241. + don't have an autoconf test for this, yet.
  242. + The new behaviour would allow us to feed the bytes one by one into
  243. + mbrtowc. But the old behaviour forces us to feed all bytes since
  244. + the end of the last character into mbrtowc. Since we want to retry
  245. + with more bytes when mbrtowc returns -2, we must backup the state
  246. + before calling mbrtowc, because implementations with the new
  247. + behaviour will clobber it. */
  248. + mbstate_t backup_state = mbf->state;
  249. +
  250. + bytes = mbrtowc (&mbc->wc, &mbf->buf[0], mbf->bufcount, &mbf->state);
  251. +
  252. + if (bytes == (size_t) -1)
  253. + {
  254. + /* An invalid multibyte sequence was encountered. */
  255. + /* Return a single byte. */
  256. + bytes = 1;
  257. + mbc->wc_valid = false;
  258. + break;
  259. + }
  260. + else if (bytes == (size_t) -2)
  261. + {
  262. + /* An incomplete multibyte character. */
  263. + mbf->state = backup_state;
  264. + if (mbf->bufcount == MBCHAR_BUF_SIZE)
  265. + {
  266. + /* An overlong incomplete multibyte sequence was encountered. */
  267. + /* Return a single byte. */
  268. + bytes = 1;
  269. + mbc->wc_valid = false;
  270. + break;
  271. + }
  272. + else
  273. + {
  274. + /* Read one more byte and retry mbrtowc. */
  275. + int c = getc (mbf->fp);
  276. + if (c == EOF)
  277. + {
  278. + /* An incomplete multibyte character at the end. */
  279. + mbf->eof_seen = true;
  280. + bytes = mbf->bufcount;
  281. + mbc->wc_valid = false;
  282. + break;
  283. + }
  284. + mbf->buf[mbf->bufcount] = (unsigned char) c;
  285. + mbf->bufcount++;
  286. + }
  287. + }
  288. + else
  289. + {
  290. + if (bytes == 0)
  291. + {
  292. + /* A null wide character was encountered. */
  293. + bytes = 1;
  294. + assert (mbf->buf[0] == '\0');
  295. + assert (mbc->wc == 0);
  296. + }
  297. + mbc->wc_valid = true;
  298. + break;
  299. + }
  300. + }
  301. +
  302. + /* Return the multibyte sequence mbf->buf[0..bytes-1]. */
  303. + mbc->ptr = &mbc->buf[0];
  304. + memcpy (&mbc->buf[0], &mbf->buf[0], bytes);
  305. + mbc->bytes = bytes;
  306. +
  307. + mbf->bufcount -= bytes;
  308. + if (mbf->bufcount > 0)
  309. + {
  310. + /* It's not worth calling memmove() for so few bytes. */
  311. + unsigned int count = mbf->bufcount;
  312. + char *p = &mbf->buf[0];
  313. +
  314. + do
  315. + {
  316. + *p = *(p + bytes);
  317. + p++;
  318. + }
  319. + while (--count > 0);
  320. + }
  321. + return;
  322. +
  323. +eof:
  324. + /* An mbchar_t with bytes == 0 is used to indicate EOF. */
  325. + mbc->ptr = NULL;
  326. + mbc->bytes = 0;
  327. + mbc->wc_valid = false;
  328. + return;
  329. +}
  330. +
  331. +MBFILE_INLINE void
  332. +mbfile_multi_ungetc (const struct mbchar *mbc, struct mbfile_multi *mbf)
  333. +{
  334. + mb_copy (&mbf->pushback, mbc);
  335. + mbf->have_pushback = true;
  336. +}
  337. +
  338. +typedef struct mbfile_multi mb_file_t;
  339. +
  340. +typedef mbchar_t mbf_char_t;
  341. +
  342. +#define mbf_init(mbf, stream) \
  343. + ((mbf).fp = (stream), \
  344. + (mbf).eof_seen = false, \
  345. + (mbf).have_pushback = false, \
  346. + memset (&(mbf).state, '\0', sizeof (mbstate_t)), \
  347. + (mbf).bufcount = 0)
  348. +
  349. +#define mbf_getc(mbc, mbf) mbfile_multi_getc (&(mbc), &(mbf))
  350. +
  351. +#define mbf_ungetc(mbc, mbf) mbfile_multi_ungetc (&(mbc), &(mbf))
  352. +
  353. +#define mb_iseof(mbc) ((mbc).bytes == 0)
  354. +
  355. +#ifndef _GL_INLINE_HEADER_BEGIN
  356. + #error "Please include config.h first."
  357. +#endif
  358. +_GL_INLINE_HEADER_BEGIN
  359. +
  360. +#endif /* _MBFILE_H */
  361. diff --git a/m4/mbfile.m4 b/m4/mbfile.m4
  362. new file mode 100644
  363. index 0000000..8589902
  364. --- /dev/null
  365. +++ b/m4/mbfile.m4
  366. @@ -0,0 +1,14 @@
  367. +# mbfile.m4 serial 7
  368. +dnl Copyright (C) 2005, 2008-2015 Free Software Foundation, Inc.
  369. +dnl This file is free software; the Free Software Foundation
  370. +dnl gives unlimited permission to copy and/or distribute it,
  371. +dnl with or without modifications, as long as this notice is preserved.
  372. +
  373. +dnl autoconf tests required for use of mbfile.h
  374. +dnl From Bruno Haible.
  375. +
  376. +AC_DEFUN([gl_MBFILE],
  377. +[
  378. + AC_REQUIRE([AC_TYPE_MBSTATE_T])
  379. + :
  380. +])
  381. diff --git a/src/cut.c b/src/cut.c
  382. index 6fd8978..faef877 100644
  383. --- a/src/cut.c
  384. +++ b/src/cut.c
  385. @@ -28,6 +28,11 @@
  386. #include <assert.h>
  387. #include <getopt.h>
  388. #include <sys/types.h>
  389. +
  390. +/* Get mbstate_t, mbrtowc(). */
  391. +#if HAVE_WCHAR_H
  392. +# include <wchar.h>
  393. +#endif
  394. #include "system.h"
  395. #include "error.h"
  396. @@ -37,6 +42,18 @@
  397. #include "set-fields.h"
  398. +/* MB_LEN_MAX is incorrectly defined to be 1 in at least one GCC
  399. + installation; work around this configuration error. */
  400. +#if !defined MB_LEN_MAX || MB_LEN_MAX < 2
  401. +# undef MB_LEN_MAX
  402. +# define MB_LEN_MAX 16
  403. +#endif
  404. +
  405. +/* Some systems, like BeOS, have multibyte encodings but lack mbstate_t. */
  406. +#if HAVE_MBRTOWC && defined mbstate_t
  407. +# define mbrtowc(pwc, s, n, ps) (mbrtowc) (pwc, s, n, 0)
  408. +#endif
  409. +
  410. /* The official name of this program (e.g., no 'g' prefix). */
  411. #define PROGRAM_NAME "cut"
  412. @@ -53,6 +70,52 @@
  413. } \
  414. while (0)
  415. +/* Refill the buffer BUF to get a multibyte character. */
  416. +#define REFILL_BUFFER(BUF, BUFPOS, BUFLEN, STREAM) \
  417. + do \
  418. + { \
  419. + if (BUFLEN < MB_LEN_MAX && !feof (STREAM) && !ferror (STREAM)) \
  420. + { \
  421. + memmove (BUF, BUFPOS, BUFLEN); \
  422. + BUFLEN += fread (BUF + BUFLEN, sizeof(char), BUFSIZ, STREAM); \
  423. + BUFPOS = BUF; \
  424. + } \
  425. + } \
  426. + while (0)
  427. +
  428. +/* Get wide character on BUFPOS. BUFPOS is not included after that.
  429. + If byte sequence is not valid as a character, CONVFAIL is true. Otherwise false. */
  430. +#define GET_NEXT_WC_FROM_BUFFER(WC, BUFPOS, BUFLEN, MBLENGTH, STATE, CONVFAIL) \
  431. + do \
  432. + { \
  433. + mbstate_t state_bak; \
  434. + \
  435. + if (BUFLEN < 1) \
  436. + { \
  437. + WC = WEOF; \
  438. + break; \
  439. + } \
  440. + \
  441. + /* Get a wide character. */ \
  442. + CONVFAIL = false; \
  443. + state_bak = STATE; \
  444. + MBLENGTH = mbrtowc ((wchar_t *)&WC, BUFPOS, BUFLEN, &STATE); \
  445. + \
  446. + switch (MBLENGTH) \
  447. + { \
  448. + case (size_t)-1: \
  449. + case (size_t)-2: \
  450. + CONVFAIL = true; \
  451. + STATE = state_bak; \
  452. + /* Fall througn. */ \
  453. + \
  454. + case 0: \
  455. + MBLENGTH = 1; \
  456. + break; \
  457. + } \
  458. + } \
  459. + while (0)
  460. +
  461. /* Pointer inside RP. When checking if a byte or field is selected
  462. by a finite range, we check if it is between CURRENT_RP.LO
  463. @@ -60,6 +123,9 @@
  464. CURRENT_RP.HI then we make CURRENT_RP to point to the next range pair. */
  465. static struct field_range_pair *current_rp;
  466. +/* Length of the delimiter given as argument to -d. */
  467. +size_t delimlen;
  468. +
  469. /* This buffer is used to support the semantics of the -s option
  470. (or lack of same) when the specified field list includes (does
  471. not include) the first field. In both of those cases, the entire
  472. @@ -72,6 +138,29 @@ static char *field_1_buffer;
  473. /* The number of bytes allocated for FIELD_1_BUFFER. */
  474. static size_t field_1_bufsize;
  475. +enum operating_mode
  476. + {
  477. + undefined_mode,
  478. +
  479. + /* Output bytes that are at the given positions. */
  480. + byte_mode,
  481. +
  482. + /* Output characters that are at the given positions. */
  483. + character_mode,
  484. +
  485. + /* Output the given delimiter-separated fields. */
  486. + field_mode
  487. + };
  488. +
  489. +static enum operating_mode operating_mode;
  490. +
  491. +/* If nonzero, when in byte mode, don't split multibyte characters. */
  492. +static int byte_mode_character_aware;
  493. +
  494. +/* If nonzero, the function for single byte locale is work
  495. + if this program runs on multibyte locale. */
  496. +static int force_singlebyte_mode;
  497. +
  498. /* If true do not output lines containing no delimiter characters.
  499. Otherwise, all such lines are printed. This option is valid only
  500. with field mode. */
  501. @@ -83,10 +172,16 @@ static bool complement;
  502. /* The delimiter character for field mode. */
  503. static unsigned char delim;
  504. +#if HAVE_WCHAR_H
  505. +static wchar_t wcdelim;
  506. +#endif
  507. /* The delimiter for each line/record. */
  508. static unsigned char line_delim = '\n';
  509. +/* True if the --output-delimiter=STRING option was specified. */
  510. +static bool output_delimiter_specified;
  511. +
  512. /* The length of output_delimiter_string. */
  513. static size_t output_delimiter_length;
  514. @@ -94,9 +189,6 @@ static size_t output_delimiter_length;
  515. string consisting of the input delimiter. */
  516. static char *output_delimiter_string;
  517. -/* The output delimiter string contents, if the default. */
  518. -static char output_delimiter_default[1];
  519. -
  520. /* True if we have ever read standard input. */
  521. static bool have_read_stdin;
  522. @@ -150,7 +242,7 @@ Print selected parts of lines from each FILE to standard output.\n\
  523. -f, --fields=LIST select only these fields; also print any line\n\
  524. that contains no delimiter character, unless\n\
  525. the -s option is specified\n\
  526. - -n (ignored)\n\
  527. + -n with -b: don't split multibyte characters\n\
  528. "), stdout);
  529. fputs (_("\
  530. --complement complement the set of selected bytes, characters\n\
  531. @@ -250,7 +342,7 @@ cut_bytes (FILE *stream)
  532. next_item (&byte_idx);
  533. if (print_kth (byte_idx))
  534. {
  535. - if (output_delimiter_string != output_delimiter_default)
  536. + if (output_delimiter_specified)
  537. {
  538. if (print_delimiter && is_range_start_index (byte_idx))
  539. {
  540. @@ -266,6 +358,82 @@ cut_bytes (FILE *stream)
  541. }
  542. }
  543. +#if HAVE_MBRTOWC
  544. +/* This function is in use for the following case.
  545. +
  546. + 1. Read from the stream STREAM, printing to standard output any selected
  547. + characters.
  548. +
  549. + 2. Read from stream STREAM, printing to standard output any selected bytes,
  550. + without splitting multibyte characters. */
  551. +
  552. +static void
  553. +cut_characters_or_cut_bytes_no_split (FILE *stream)
  554. +{
  555. + uintmax_t idx; /* number of bytes or characters in the line so far. */
  556. + char buf[MB_LEN_MAX + BUFSIZ]; /* For spooling a read byte sequence. */
  557. + char *bufpos; /* Next read position of BUF. */
  558. + size_t buflen; /* The length of the byte sequence in buf. */
  559. + wint_t wc; /* A gotten wide character. */
  560. + size_t mblength; /* The byte size of a multibyte character which shows
  561. + as same character as WC. */
  562. + mbstate_t state; /* State of the stream. */
  563. + bool convfail = false; /* true, when conversion failed. Otherwise false. */
  564. + /* Whether to begin printing delimiters between ranges for the current line.
  565. + Set after we've begun printing data corresponding to the first range. */
  566. + bool print_delimiter = false;
  567. +
  568. + idx = 0;
  569. + buflen = 0;
  570. + bufpos = buf;
  571. + memset (&state, '\0', sizeof(mbstate_t));
  572. +
  573. + current_rp = frp;
  574. +
  575. + while (1)
  576. + {
  577. + REFILL_BUFFER (buf, bufpos, buflen, stream);
  578. +
  579. + GET_NEXT_WC_FROM_BUFFER (wc, bufpos, buflen, mblength, state, convfail);
  580. + (void) convfail; /* ignore unused */
  581. +
  582. + if (wc == WEOF)
  583. + {
  584. + if (idx > 0)
  585. + putchar (line_delim);
  586. + break;
  587. + }
  588. + else if (wc == line_delim)
  589. + {
  590. + putchar (line_delim);
  591. + idx = 0;
  592. + print_delimiter = false;
  593. + current_rp = frp;
  594. + }
  595. + else
  596. + {
  597. + next_item (&idx);
  598. + if (print_kth (idx))
  599. + {
  600. + if (output_delimiter_specified)
  601. + {
  602. + if (print_delimiter && is_range_start_index (idx))
  603. + {
  604. + fwrite (output_delimiter_string, sizeof (char),
  605. + output_delimiter_length, stdout);
  606. + }
  607. + print_delimiter = true;
  608. + }
  609. + fwrite (bufpos, mblength, sizeof(char), stdout);
  610. + }
  611. + }
  612. +
  613. + buflen -= mblength;
  614. + bufpos += mblength;
  615. + }
  616. +}
  617. +#endif
  618. +
  619. /* Read from stream STREAM, printing to standard output any selected fields. */
  620. static void
  621. @@ -411,11 +579,218 @@ cut_fields (FILE *stream)
  622. }
  623. }
  624. -/* Process file FILE to standard output, using CUT_STREAM.
  625. +#if HAVE_MBRTOWC
  626. +static void
  627. +cut_fields_mb (FILE *stream)
  628. +{
  629. + int c;
  630. + uintmax_t field_idx;
  631. + int found_any_selected_field;
  632. + int buffer_first_field;
  633. + int empty_input;
  634. + char buf[MB_LEN_MAX + BUFSIZ]; /* For spooling a read byte sequence. */
  635. + char *bufpos; /* Next read position of BUF. */
  636. + size_t buflen; /* The length of the byte sequence in buf. */
  637. + wint_t wc = 0; /* A gotten wide character. */
  638. + size_t mblength; /* The byte size of a multibyte character which shows
  639. + as same character as WC. */
  640. + mbstate_t state; /* State of the stream. */
  641. + bool convfail = false; /* true, when conversion failed. Otherwise false. */
  642. +
  643. + current_rp = frp;
  644. +
  645. + found_any_selected_field = 0;
  646. + field_idx = 1;
  647. + bufpos = buf;
  648. + buflen = 0;
  649. + memset (&state, '\0', sizeof(mbstate_t));
  650. +
  651. + c = getc (stream);
  652. + empty_input = (c == EOF);
  653. + if (c != EOF)
  654. + {
  655. + ungetc (c, stream);
  656. + wc = 0;
  657. + }
  658. + else
  659. + wc = WEOF;
  660. +
  661. + /* To support the semantics of the -s flag, we may have to buffer
  662. + all of the first field to determine whether it is `delimited.'
  663. + But that is unnecessary if all non-delimited lines must be printed
  664. + and the first field has been selected, or if non-delimited lines
  665. + must be suppressed and the first field has *not* been selected.
  666. + That is because a non-delimited line has exactly one field. */
  667. + buffer_first_field = (suppress_non_delimited ^ !print_kth (1));
  668. +
  669. + while (1)
  670. + {
  671. + if (field_idx == 1 && buffer_first_field)
  672. + {
  673. + int len = 0;
  674. +
  675. + while (1)
  676. + {
  677. + REFILL_BUFFER (buf, bufpos, buflen, stream);
  678. +
  679. + GET_NEXT_WC_FROM_BUFFER
  680. + (wc, bufpos, buflen, mblength, state, convfail);
  681. +
  682. + if (wc == WEOF)
  683. + break;
  684. +
  685. + field_1_buffer = xrealloc (field_1_buffer, len + mblength);
  686. + memcpy (field_1_buffer + len, bufpos, mblength);
  687. + len += mblength;
  688. + buflen -= mblength;
  689. + bufpos += mblength;
  690. +
  691. + if (!convfail && (wc == line_delim || wc == wcdelim))
  692. + break;
  693. + }
  694. +
  695. + if (len <= 0 && wc == WEOF)
  696. + break;
  697. +
  698. + /* If the first field extends to the end of line (it is not
  699. + delimited) and we are printing all non-delimited lines,
  700. + print this one. */
  701. + if (convfail || (!convfail && wc != wcdelim))
  702. + {
  703. + if (suppress_non_delimited)
  704. + {
  705. + /* Empty. */
  706. + }
  707. + else
  708. + {
  709. + fwrite (field_1_buffer, sizeof (char), len, stdout);
  710. + /* Make sure the output line is newline terminated. */
  711. + if (convfail || (!convfail && wc != line_delim))
  712. + putchar (line_delim);
  713. + }
  714. + continue;
  715. + }
  716. +
  717. + if (print_kth (1))
  718. + {
  719. + /* Print the field, but not the trailing delimiter. */
  720. + fwrite (field_1_buffer, sizeof (char), len - 1, stdout);
  721. + found_any_selected_field = 1;
  722. + }
  723. + next_item (&field_idx);
  724. + }
  725. +
  726. + if (wc != WEOF)
  727. + {
  728. + if (print_kth (field_idx))
  729. + {
  730. + if (found_any_selected_field)
  731. + {
  732. + fwrite (output_delimiter_string, sizeof (char),
  733. + output_delimiter_length, stdout);
  734. + }
  735. + found_any_selected_field = 1;
  736. + }
  737. +
  738. + while (1)
  739. + {
  740. + REFILL_BUFFER (buf, bufpos, buflen, stream);
  741. +
  742. + GET_NEXT_WC_FROM_BUFFER
  743. + (wc, bufpos, buflen, mblength, state, convfail);
  744. +
  745. + if (wc == WEOF)
  746. + break;
  747. + else if (!convfail && (wc == wcdelim || wc == line_delim))
  748. + {
  749. + buflen -= mblength;
  750. + bufpos += mblength;
  751. + break;
  752. + }
  753. +
  754. + if (print_kth (field_idx))
  755. + fwrite (bufpos, mblength, sizeof(char), stdout);
  756. +
  757. + buflen -= mblength;
  758. + bufpos += mblength;
  759. + }
  760. + }
  761. +
  762. + if ((!convfail || wc == line_delim) && buflen < 1)
  763. + wc = WEOF;
  764. +
  765. + if (!convfail && wc == wcdelim)
  766. + next_item (&field_idx);
  767. + else if (wc == WEOF || (!convfail && wc == line_delim))
  768. + {
  769. + if (found_any_selected_field
  770. + || (!empty_input && !(suppress_non_delimited && field_idx == 1)))
  771. + putchar (line_delim);
  772. + if (wc == WEOF)
  773. + break;
  774. + field_idx = 1;
  775. + current_rp = frp;
  776. + found_any_selected_field = 0;
  777. + }
  778. + }
  779. +}
  780. +#endif
  781. +
  782. +static void
  783. +cut_stream (FILE *stream)
  784. +{
  785. +#if HAVE_MBRTOWC
  786. + if (MB_CUR_MAX > 1 && !force_singlebyte_mode)
  787. + {
  788. + switch (operating_mode)
  789. + {
  790. + case byte_mode:
  791. + if (byte_mode_character_aware)
  792. + cut_characters_or_cut_bytes_no_split (stream);
  793. + else
  794. + cut_bytes (stream);
  795. + break;
  796. +
  797. + case character_mode:
  798. + cut_characters_or_cut_bytes_no_split (stream);
  799. + break;
  800. +
  801. + case field_mode:
  802. + if (delimlen == 1)
  803. + {
  804. + /* Check if we have utf8 multibyte locale, so we can use this
  805. + optimization because of uniqueness of characters, which is
  806. + not true for e.g. SJIS */
  807. + char * loc = setlocale(LC_CTYPE, NULL);
  808. + if (loc && (strstr (loc, "UTF-8") || strstr (loc, "utf-8") ||
  809. + strstr (loc, "UTF8") || strstr (loc, "utf8")))
  810. + {
  811. + cut_fields (stream);
  812. + break;
  813. + }
  814. + }
  815. + cut_fields_mb (stream);
  816. + break;
  817. +
  818. + default:
  819. + abort ();
  820. + }
  821. + }
  822. + else
  823. +#endif
  824. + {
  825. + if (operating_mode == field_mode)
  826. + cut_fields (stream);
  827. + else
  828. + cut_bytes (stream);
  829. + }
  830. +}
  831. +
  832. +/* Process file FILE to standard output.
  833. Return true if successful. */
  834. static bool
  835. -cut_file (char const *file, void (*cut_stream) (FILE *))
  836. +cut_file (char const *file)
  837. {
  838. FILE *stream;
  839. @@ -459,8 +834,8 @@ main (int argc, char **argv)
  840. int optc;
  841. bool ok;
  842. bool delim_specified = false;
  843. - bool byte_mode = false;
  844. - char *spec_list_string = NULL;
  845. + char *spec_list_string IF_LINT ( = NULL);
  846. + char mbdelim[MB_LEN_MAX + 1];
  847. initialize_main (&argc, &argv);
  848. set_program_name (argv[0]);
  849. @@ -470,6 +845,8 @@ main (int argc, char **argv)
  850. atexit (close_stdout);
  851. + operating_mode = undefined_mode;
  852. +
  853. /* By default, all non-delimited lines are printed. */
  854. suppress_non_delimited = false;
  855. @@ -481,35 +858,77 @@ main (int argc, char **argv)
  856. switch (optc)
  857. {
  858. case 'b':
  859. - case 'c':
  860. /* Build the byte list. */
  861. - byte_mode = true;
  862. - FALLTHROUGH;
  863. + if (operating_mode != undefined_mode)
  864. + FATAL_ERROR (_("only one type of list may be specified"));
  865. + operating_mode = byte_mode;
  866. + spec_list_string = optarg;
  867. + break;
  868. +
  869. + case 'c':
  870. + /* Build the character list. */
  871. + if (operating_mode != undefined_mode)
  872. + FATAL_ERROR (_("only one type of list may be specified"));
  873. + operating_mode = character_mode;
  874. + spec_list_string = optarg;
  875. + break;
  876. +
  877. case 'f':
  878. /* Build the field list. */
  879. - if (spec_list_string)
  880. - FATAL_ERROR (_("only one list may be specified"));
  881. + if (operating_mode != undefined_mode)
  882. + FATAL_ERROR (_("only one type of list may be specified"));
  883. + operating_mode = field_mode;
  884. spec_list_string = optarg;
  885. break;
  886. case 'd':
  887. /* New delimiter. */
  888. /* Interpret -d '' to mean 'use the NUL byte as the delimiter.' */
  889. - if (optarg[0] != '\0' && optarg[1] != '\0')
  890. - FATAL_ERROR (_("the delimiter must be a single character"));
  891. - delim = optarg[0];
  892. - delim_specified = true;
  893. + {
  894. +#if HAVE_MBRTOWC
  895. + if(MB_CUR_MAX > 1)
  896. + {
  897. + mbstate_t state;
  898. +
  899. + memset (&state, '\0', sizeof(mbstate_t));
  900. + delimlen = mbrtowc (&wcdelim, optarg, strnlen(optarg, MB_LEN_MAX), &state);
  901. +
  902. + if (delimlen == (size_t)-1 || delimlen == (size_t)-2)
  903. + ++force_singlebyte_mode;
  904. + else
  905. + {
  906. + delimlen = (delimlen < 1) ? 1 : delimlen;
  907. + if (wcdelim != L'\0' && *(optarg + delimlen) != '\0')
  908. + FATAL_ERROR (_("the delimiter must be a single character"));
  909. + memcpy (mbdelim, optarg, delimlen);
  910. + mbdelim[delimlen] = '\0';
  911. + if (delimlen == 1)
  912. + delim = *optarg;
  913. + }
  914. + }
  915. +
  916. + if (MB_CUR_MAX <= 1 || force_singlebyte_mode)
  917. +#endif
  918. + {
  919. + if (optarg[0] != '\0' && optarg[1] != '\0')
  920. + FATAL_ERROR (_("the delimiter must be a single character"));
  921. + delim = (unsigned char) optarg[0];
  922. + }
  923. + delim_specified = true;
  924. + }
  925. break;
  926. case OUTPUT_DELIMITER_OPTION:
  927. + output_delimiter_specified = true;
  928. /* Interpret --output-delimiter='' to mean
  929. 'use the NUL byte as the delimiter.' */
  930. output_delimiter_length = (optarg[0] == '\0'
  931. ? 1 : strlen (optarg));
  932. - output_delimiter_string = optarg;
  933. + output_delimiter_string = xstrdup (optarg);
  934. break;
  935. case 'n':
  936. + byte_mode_character_aware = 1;
  937. break;
  938. case 's':
  939. @@ -533,40 +952,57 @@ main (int argc, char **argv)
  940. }
  941. }
  942. - if (!spec_list_string)
  943. + if (operating_mode == undefined_mode)
  944. FATAL_ERROR (_("you must specify a list of bytes, characters, or fields"));
  945. - if (byte_mode)
  946. - {
  947. - if (delim_specified)
  948. - FATAL_ERROR (_("an input delimiter may be specified only\
  949. + if (delim_specified && operating_mode != field_mode)
  950. + FATAL_ERROR (_("an input delimiter may be specified only\
  951. when operating on fields"));
  952. - if (suppress_non_delimited)
  953. - FATAL_ERROR (_("suppressing non-delimited lines makes sense\n\
  954. + if (suppress_non_delimited && operating_mode != field_mode)
  955. + FATAL_ERROR (_("suppressing non-delimited lines makes sense\n\
  956. \tonly when operating on fields"));
  957. - }
  958. set_fields (spec_list_string,
  959. - ((byte_mode ? SETFLD_ERRMSG_USE_POS : 0)
  960. - | (complement ? SETFLD_COMPLEMENT : 0)));
  961. + ( (operating_mode == field_mode) ? 0 : SETFLD_ERRMSG_USE_POS)
  962. + | (complement ? SETFLD_COMPLEMENT : 0) );
  963. if (!delim_specified)
  964. - delim = '\t';
  965. + {
  966. + delim = '\t';
  967. +#ifdef HAVE_MBRTOWC
  968. + wcdelim = L'\t';
  969. + mbdelim[0] = '\t';
  970. + mbdelim[1] = '\0';
  971. + delimlen = 1;
  972. +#endif
  973. + }
  974. if (output_delimiter_string == NULL)
  975. {
  976. - output_delimiter_default[0] = delim;
  977. - output_delimiter_string = output_delimiter_default;
  978. - output_delimiter_length = 1;
  979. +#ifdef HAVE_MBRTOWC
  980. + if (MB_CUR_MAX > 1 && !force_singlebyte_mode)
  981. + {
  982. + output_delimiter_string = xstrdup(mbdelim);
  983. + output_delimiter_length = delimlen;
  984. + }
  985. +
  986. + if (MB_CUR_MAX <= 1 || force_singlebyte_mode)
  987. +#endif
  988. + {
  989. + static char dummy[2];
  990. + dummy[0] = delim;
  991. + dummy[1] = '\0';
  992. + output_delimiter_string = dummy;
  993. + output_delimiter_length = 1;
  994. + }
  995. }
  996. - void (*cut_stream) (FILE *) = byte_mode ? cut_bytes : cut_fields;
  997. if (optind == argc)
  998. - ok = cut_file ("-", cut_stream);
  999. + ok = cut_file ("-");
  1000. else
  1001. for (ok = true; optind < argc; optind++)
  1002. - ok &= cut_file (argv[optind], cut_stream);
  1003. + ok &= cut_file (argv[optind]);
  1004. if (have_read_stdin && fclose (stdin) == EOF)
  1005. diff --git a/src/expand-common.c b/src/expand-common.c
  1006. index deec1bd..b39f740 100644
  1007. --- a/src/expand-common.c
  1008. +++ b/src/expand-common.c
  1009. @@ -19,6 +19,7 @@
  1010. #include <assert.h>
  1011. #include <stdio.h>
  1012. #include <sys/types.h>
  1013. +#include <mbfile.h>
  1014. #include "system.h"
  1015. #include "die.h"
  1016. #include "error.h"
  1017. @@ -125,6 +126,119 @@ set_increment_size (uintmax_t tabval)
  1018. return ok;
  1019. }
  1020. +extern int
  1021. +set_utf_locale (void)
  1022. +{
  1023. + /*try using some predefined locale */
  1024. + const char* predef_locales[] = {"C.UTF8","en_US.UTF8","en_GB.UTF8"};
  1025. +
  1026. + const int predef_locales_count=3;
  1027. + for (int i=0;i<predef_locales_count;i++)
  1028. + {
  1029. + if (setlocale(LC_ALL,predef_locales[i])!=NULL)
  1030. + {
  1031. + break;
  1032. + }
  1033. + else if (i==predef_locales_count-1)
  1034. + {
  1035. + return 1;
  1036. + error (EXIT_FAILURE, errno, _("cannot set UTF-8 locale"));
  1037. + }
  1038. + }
  1039. + return 0;
  1040. +}
  1041. +
  1042. +extern bool
  1043. +check_utf_locale(void)
  1044. +{
  1045. + char* locale = setlocale (LC_CTYPE , NULL);
  1046. + if (locale == NULL)
  1047. + {
  1048. + return false;
  1049. + }
  1050. + else if (strcasestr(locale, "utf8") == NULL && strcasestr(locale, "utf-8") == NULL)
  1051. + {
  1052. + return false;
  1053. + }
  1054. + return true;
  1055. +}
  1056. +
  1057. +extern bool
  1058. +check_bom(FILE* fp, mb_file_t *mbf)
  1059. +{
  1060. + int c;
  1061. +
  1062. +
  1063. + c=fgetc(fp);
  1064. +
  1065. + /*test BOM header of the first file */
  1066. + mbf->bufcount=0;
  1067. + if (c == 0xEF)
  1068. + {
  1069. + c=fgetc(fp);
  1070. + }
  1071. + else
  1072. + {
  1073. + if (c != EOF)
  1074. + {
  1075. + ungetc(c,fp);
  1076. + }
  1077. + return false;
  1078. + }
  1079. +
  1080. + if (c == 0xBB)
  1081. + {
  1082. + c=fgetc(fp);
  1083. + }
  1084. + else
  1085. + {
  1086. + if ( c!= EOF )
  1087. + {
  1088. + mbf->buf[0]=(unsigned char) 0xEF;
  1089. + mbf->bufcount=1;
  1090. + ungetc(c,fp);
  1091. + return false;
  1092. + }
  1093. + else
  1094. + {
  1095. + ungetc(0xEF,fp);
  1096. + return false;
  1097. + }
  1098. + }
  1099. + if (c == 0xBF)
  1100. + {
  1101. + mbf->bufcount=0;
  1102. + return true;
  1103. + }
  1104. + else
  1105. + {
  1106. + if (c != EOF)
  1107. + {
  1108. + mbf->buf[0]=(unsigned char) 0xEF;
  1109. + mbf->buf[1]=(unsigned char) 0xBB;
  1110. + mbf->bufcount=2;
  1111. + ungetc(c,fp);
  1112. + return false;
  1113. + }
  1114. + else
  1115. + {
  1116. + mbf->buf[0]=(unsigned char) 0xEF;
  1117. + mbf->bufcount=1;
  1118. + ungetc(0xBB,fp);
  1119. + return false;
  1120. + }
  1121. + }
  1122. + return false;
  1123. +}
  1124. +
  1125. +extern void
  1126. +print_bom(void)
  1127. +{
  1128. + putc (0xEF, stdout);
  1129. + putc (0xBB, stdout);
  1130. + putc (0xBF, stdout);
  1131. +}
  1132. +
  1133. /* Add the comma or blank separated list of tab stops STOPS
  1134. to the list of tab stops. */
  1135. extern void
  1136. diff --git a/src/expand-common.h b/src/expand-common.h
  1137. index 5f59a0e..835b9d5 100644
  1138. --- a/src/expand-common.h
  1139. +++ b/src/expand-common.h
  1140. @@ -25,6 +25,18 @@ extern size_t max_column_width;
  1141. /* The desired exit status. */
  1142. extern int exit_status;
  1143. +extern int
  1144. +set_utf_locale (void);
  1145. +
  1146. +extern bool
  1147. +check_utf_locale(void);
  1148. +
  1149. +extern bool
  1150. +check_bom(FILE* fp, mb_file_t *mbf);
  1151. +
  1152. +extern void
  1153. +print_bom(void);
  1154. +
  1155. /* Add tab stop TABVAL to the end of 'tab_list'. */
  1156. extern void
  1157. add_tab_stop (uintmax_t tabval);
  1158. diff --git a/src/expand.c b/src/expand.c
  1159. index ed78ca8..a4cefa1 100644
  1160. --- a/src/expand.c
  1161. +++ b/src/expand.c
  1162. @@ -37,6 +37,9 @@
  1163. #include <stdio.h>
  1164. #include <getopt.h>
  1165. #include <sys/types.h>
  1166. +
  1167. +#include <mbfile.h>
  1168. +
  1169. #include "system.h"
  1170. #include "die.h"
  1171. @@ -97,19 +100,41 @@ expand (void)
  1172. {
  1173. /* Input stream. */
  1174. FILE *fp = next_file (NULL);
  1175. + mb_file_t mbf;
  1176. + mbf_char_t c;
  1177. + /* True if the starting locale is utf8. */
  1178. + bool using_utf_locale;
  1179. +
  1180. + /* True if the first file contains BOM header. */
  1181. + bool found_bom;
  1182. + using_utf_locale=check_utf_locale();
  1183. if (!fp)
  1184. return;
  1185. + mbf_init (mbf, fp);
  1186. + found_bom=check_bom(fp,&mbf);
  1187. - while (true)
  1188. + if (using_utf_locale == false && found_bom == true)
  1189. + {
  1190. + /*try using some predefined locale */
  1191. +
  1192. + if (set_utf_locale () != 0)
  1193. {
  1194. - /* Input character, or EOF. */
  1195. - int c;
  1196. + error (EXIT_FAILURE, errno, _("cannot set UTF-8 locale"));
  1197. + }
  1198. + }
  1199. +
  1200. +
  1201. + if (found_bom == true)
  1202. + {
  1203. + print_bom();
  1204. + }
  1205. + while (true)
  1206. + {
  1207. /* If true, perform translations. */
  1208. bool convert = true;
  1209. -
  1210. /* The following variables have valid values only when CONVERT
  1211. is true: */
  1212. @@ -119,17 +144,48 @@ expand (void)
  1213. /* Index in TAB_LIST of next tab stop to examine. */
  1214. size_t tab_index = 0;
  1215. -
  1216. /* Convert a line of text. */
  1217. do
  1218. {
  1219. - while ((c = getc (fp)) < 0 && (fp = next_file (fp)))
  1220. - continue;
  1221. + while (true) {
  1222. + mbf_getc (c, mbf);
  1223. + if ((mb_iseof (c)) && (fp = next_file (fp)))
  1224. + {
  1225. + mbf_init (mbf, fp);
  1226. + if (fp!=NULL)
  1227. + {
  1228. + if (check_bom(fp,&mbf)==true)
  1229. + {
  1230. + /*Not the first file - check BOM header*/
  1231. + if (using_utf_locale==false && found_bom==false)
  1232. + {
  1233. + /*BOM header in subsequent file but not in the first one. */
  1234. + error (EXIT_FAILURE, errno, _("combination of files with and without BOM header"));
  1235. + }
  1236. + }
  1237. + else
  1238. + {
  1239. + if(using_utf_locale==false && found_bom==true)
  1240. + {
  1241. + /*First file conatined BOM header - locale was switched to UTF
  1242. + *all subsequent files should contain BOM. */
  1243. + error (EXIT_FAILURE, errno, _("combination of files with and without BOM header"));
  1244. + }
  1245. + }
  1246. + }
  1247. + continue;
  1248. + }
  1249. + else
  1250. + {
  1251. + break;
  1252. + }
  1253. + }
  1254. +
  1255. if (convert)
  1256. {
  1257. - if (c == '\t')
  1258. + if (mb_iseq (c, '\t'))
  1259. {
  1260. /* Column the next input tab stop is on. */
  1261. uintmax_t next_tab_column;
  1262. @@ -148,32 +204,34 @@ expand (void)
  1263. if (putchar (' ') < 0)
  1264. die (EXIT_FAILURE, errno, _("write error"));
  1265. - c = ' ';
  1266. + mb_setascii (&c, ' ');
  1267. }
  1268. - else if (c == '\b')
  1269. + else if (mb_iseq (c, '\b'))
  1270. {
  1271. /* Go back one column, and force recalculation of the
  1272. next tab stop. */
  1273. column -= !!column;
  1274. tab_index -= !!tab_index;
  1275. }
  1276. - else
  1277. + /* A leading control character could make us trip over. */
  1278. + else if (!mb_iscntrl (c))
  1279. {
  1280. - column++;
  1281. + column += mb_width (c);
  1282. if (!column)
  1283. die (EXIT_FAILURE, 0, _("input line is too long"));
  1284. }
  1285. - convert &= convert_entire_line || !! isblank (c);
  1286. + convert &= convert_entire_line || mb_isblank (c);
  1287. }
  1288. - if (c < 0)
  1289. + if (mb_iseof (c))
  1290. return;
  1291. - if (putchar (c) < 0)
  1292. + mb_putc (c, stdout);
  1293. + if (ferror (stdout))
  1294. die (EXIT_FAILURE, errno, _("write error"));
  1295. }
  1296. - while (c != '\n');
  1297. + while (!mb_iseq (c, '\n'));
  1298. }
  1299. }
  1300. diff --git a/src/fold.c b/src/fold.c
  1301. index f07a90b..d32dbfd 100644
  1302. --- a/src/fold.c
  1303. +++ b/src/fold.c
  1304. @@ -22,12 +22,34 @@
  1305. #include <getopt.h>
  1306. #include <sys/types.h>
  1307. +/* Get mbstate_t, mbrtowc(), wcwidth(). */
  1308. +#if HAVE_WCHAR_H
  1309. +# include <wchar.h>
  1310. +#endif
  1311. +
  1312. +/* Get iswprint(), iswblank(), wcwidth(). */
  1313. +#if HAVE_WCTYPE_H
  1314. +# include <wctype.h>
  1315. +#endif
  1316. +
  1317. #include "system.h"
  1318. #include "die.h"
  1319. #include "error.h"
  1320. #include "fadvise.h"
  1321. #include "xdectoint.h"
  1322. +/* MB_LEN_MAX is incorrectly defined to be 1 in at least one GCC
  1323. + installation; work around this configuration error. */
  1324. +#if !defined MB_LEN_MAX || MB_LEN_MAX < 2
  1325. +# undef MB_LEN_MAX
  1326. +# define MB_LEN_MAX 16
  1327. +#endif
  1328. +
  1329. +/* Some systems, like BeOS, have multibyte encodings but lack mbstate_t. */
  1330. +#if HAVE_MBRTOWC && defined mbstate_t
  1331. +# define mbrtowc(pwc, s, n, ps) (mbrtowc) (pwc, s, n, 0)
  1332. +#endif
  1333. +
  1334. #define TAB_WIDTH 8
  1335. /* The official name of this program (e.g., no 'g' prefix). */
  1336. @@ -35,20 +57,41 @@
  1337. #define AUTHORS proper_name ("David MacKenzie")
  1338. +#define FATAL_ERROR(Message) \
  1339. + do \
  1340. + { \
  1341. + error (0, 0, (Message)); \
  1342. + usage (2); \
  1343. + } \
  1344. + while (0)
  1345. +
  1346. +enum operating_mode
  1347. +{
  1348. + /* Fold texts by columns that are at the given positions. */
  1349. + column_mode,
  1350. +
  1351. + /* Fold texts by bytes that are at the given positions. */
  1352. + byte_mode,
  1353. +
  1354. + /* Fold texts by characters that are at the given positions. */
  1355. + character_mode,
  1356. +};
  1357. +
  1358. +/* The argument shows current mode. (Default: column_mode) */
  1359. +static enum operating_mode operating_mode;
  1360. +
  1361. /* If nonzero, try to break on whitespace. */
  1362. static bool break_spaces;
  1363. -/* If nonzero, count bytes, not column positions. */
  1364. -static bool count_bytes;
  1365. -
  1366. /* If nonzero, at least one of the files we read was standard input. */
  1367. static bool have_read_stdin;
  1368. -static char const shortopts[] = "bsw:0::1::2::3::4::5::6::7::8::9::";
  1369. +static char const shortopts[] = "bcsw:0::1::2::3::4::5::6::7::8::9::";
  1370. static struct option const longopts[] =
  1371. {
  1372. {"bytes", no_argument, NULL, 'b'},
  1373. + {"characters", no_argument, NULL, 'c'},
  1374. {"spaces", no_argument, NULL, 's'},
  1375. {"width", required_argument, NULL, 'w'},
  1376. {GETOPT_HELP_OPTION_DECL},
  1377. @@ -76,6 +119,7 @@ Wrap input lines in each FILE, writing to standard output.\n\
  1378. fputs (_("\
  1379. -b, --bytes count bytes rather than columns\n\
  1380. + -c, --characters count characters rather than columns\n\
  1381. -s, --spaces break at spaces\n\
  1382. -w, --width=WIDTH use WIDTH columns instead of 80\n\
  1383. "), stdout);
  1384. @@ -93,7 +137,7 @@ Wrap input lines in each FILE, writing to standard output.\n\
  1385. static size_t
  1386. adjust_column (size_t column, char c)
  1387. {
  1388. - if (!count_bytes)
  1389. + if (operating_mode != byte_mode)
  1390. {
  1391. if (c == '\b')
  1392. {
  1393. @@ -116,30 +160,14 @@ adjust_column (size_t column, char c)
  1394. to stdout, with maximum line length WIDTH.
  1395. Return true if successful. */
  1396. -static bool
  1397. -fold_file (char const *filename, size_t width)
  1398. +static void
  1399. +fold_text (FILE *istream, size_t width, int *saved_errno)
  1400. {
  1401. - FILE *istream;
  1402. int c;
  1403. size_t column = 0; /* Screen column where next char will go. */
  1404. size_t offset_out = 0; /* Index in 'line_out' for next char. */
  1405. static char *line_out = NULL;
  1406. static size_t allocated_out = 0;
  1407. - int saved_errno;
  1408. -
  1409. - if (STREQ (filename, "-"))
  1410. - {
  1411. - istream = stdin;
  1412. - have_read_stdin = true;
  1413. - }
  1414. - else
  1415. - istream = fopen (filename, "r");
  1416. -
  1417. - if (istream == NULL)
  1418. - {
  1419. - error (0, errno, "%s", quotef (filename));
  1420. - return false;
  1421. - }
  1422. fadvise (istream, FADVISE_SEQUENTIAL);
  1423. @@ -169,6 +197,15 @@ fold_file (char const *filename, size_t width)
  1424. bool found_blank = false;
  1425. size_t logical_end = offset_out;
  1426. + /* If LINE_OUT has no wide character,
  1427. + put a new wide character in LINE_OUT
  1428. + if column is bigger than width. */
  1429. + if (offset_out == 0)
  1430. + {
  1431. + line_out[offset_out++] = c;
  1432. + continue;
  1433. + }
  1434. +
  1435. /* Look for the last blank. */
  1436. while (logical_end)
  1437. {
  1438. @@ -215,13 +252,225 @@ fold_file (char const *filename, size_t width)
  1439. line_out[offset_out++] = c;
  1440. }
  1441. - saved_errno = errno;
  1442. + *saved_errno = errno;
  1443. if (!ferror (istream))
  1444. - saved_errno = 0;
  1445. + *saved_errno = 0;
  1446. if (offset_out)
  1447. fwrite (line_out, sizeof (char), (size_t) offset_out, stdout);
  1448. +}
  1449. +
  1450. +#if HAVE_MBRTOWC
  1451. +static void
  1452. +fold_multibyte_text (FILE *istream, size_t width, int *saved_errno)
  1453. +{
  1454. + char buf[MB_LEN_MAX + BUFSIZ]; /* For spooling a read byte sequence. */
  1455. + size_t buflen = 0; /* The length of the byte sequence in buf. */
  1456. + char *bufpos = buf; /* Next read position of BUF. */
  1457. + wint_t wc; /* A gotten wide character. */
  1458. + size_t mblength; /* The byte size of a multibyte character which shows
  1459. + as same character as WC. */
  1460. + mbstate_t state, state_bak; /* State of the stream. */
  1461. + int convfail = 0; /* 1, when conversion is failed. Otherwise 0. */
  1462. +
  1463. + static char *line_out = NULL;
  1464. + size_t offset_out = 0; /* Index in `line_out' for next char. */
  1465. + static size_t allocated_out = 0;
  1466. +
  1467. + int increment;
  1468. + size_t column = 0;
  1469. +
  1470. + size_t last_blank_pos;
  1471. + size_t last_blank_column;
  1472. + int is_blank_seen;
  1473. + int last_blank_increment = 0;
  1474. + int is_bs_following_last_blank;
  1475. + size_t bs_following_last_blank_num;
  1476. + int is_cr_after_last_blank;
  1477. +
  1478. +#define CLEAR_FLAGS \
  1479. + do \
  1480. + { \
  1481. + last_blank_pos = 0; \
  1482. + last_blank_column = 0; \
  1483. + is_blank_seen = 0; \
  1484. + is_bs_following_last_blank = 0; \
  1485. + bs_following_last_blank_num = 0; \
  1486. + is_cr_after_last_blank = 0; \
  1487. + } \
  1488. + while (0)
  1489. +
  1490. +#define START_NEW_LINE \
  1491. + do \
  1492. + { \
  1493. + putchar ('\n'); \
  1494. + column = 0; \
  1495. + offset_out = 0; \
  1496. + CLEAR_FLAGS; \
  1497. + } \
  1498. + while (0)
  1499. +
  1500. + CLEAR_FLAGS;
  1501. + memset (&state, '\0', sizeof(mbstate_t));
  1502. +
  1503. + for (;; bufpos += mblength, buflen -= mblength)
  1504. + {
  1505. + if (buflen < MB_LEN_MAX && !feof (istream) && !ferror (istream))
  1506. + {
  1507. + memmove (buf, bufpos, buflen);
  1508. + buflen += fread (buf + buflen, sizeof(char), BUFSIZ, istream);
  1509. + bufpos = buf;
  1510. + }
  1511. +
  1512. + if (buflen < 1)
  1513. + break;
  1514. +
  1515. + /* Get a wide character. */
  1516. + state_bak = state;
  1517. + mblength = mbrtowc ((wchar_t *)&wc, bufpos, buflen, &state);
  1518. +
  1519. + switch (mblength)
  1520. + {
  1521. + case (size_t)-1:
  1522. + case (size_t)-2:
  1523. + convfail++;
  1524. + state = state_bak;
  1525. + /* Fall through. */
  1526. +
  1527. + case 0:
  1528. + mblength = 1;
  1529. + break;
  1530. + }
  1531. +
  1532. +rescan:
  1533. + if (operating_mode == byte_mode) /* byte mode */
  1534. + increment = mblength;
  1535. + else if (operating_mode == character_mode) /* character mode */
  1536. + increment = 1;
  1537. + else /* column mode */
  1538. + {
  1539. + if (convfail)
  1540. + increment = 1;
  1541. + else
  1542. + {
  1543. + switch (wc)
  1544. + {
  1545. + case L'\n':
  1546. + fwrite (line_out, sizeof(char), offset_out, stdout);
  1547. + START_NEW_LINE;
  1548. + continue;
  1549. +
  1550. + case L'\b':
  1551. + increment = (column > 0) ? -1 : 0;
  1552. + break;
  1553. +
  1554. + case L'\r':
  1555. + increment = -1 * column;
  1556. + break;
  1557. +
  1558. + case L'\t':
  1559. + increment = 8 - column % 8;
  1560. + break;
  1561. +
  1562. + default:
  1563. + increment = wcwidth (wc);
  1564. + increment = (increment < 0) ? 0 : increment;
  1565. + }
  1566. + }
  1567. + }
  1568. +
  1569. + if (column + increment > width && break_spaces && last_blank_pos)
  1570. + {
  1571. + fwrite (line_out, sizeof(char), last_blank_pos, stdout);
  1572. + putchar ('\n');
  1573. +
  1574. + offset_out = offset_out - last_blank_pos;
  1575. + column = column - last_blank_column + ((is_cr_after_last_blank)
  1576. + ? last_blank_increment : bs_following_last_blank_num);
  1577. + memmove (line_out, line_out + last_blank_pos, offset_out);
  1578. + CLEAR_FLAGS;
  1579. + goto rescan;
  1580. + }
  1581. +
  1582. + if (column + increment > width && column != 0)
  1583. + {
  1584. + fwrite (line_out, sizeof(char), offset_out, stdout);
  1585. + START_NEW_LINE;
  1586. + goto rescan;
  1587. + }
  1588. +
  1589. + if (allocated_out < offset_out + mblength)
  1590. + {
  1591. + line_out = X2REALLOC (line_out, &allocated_out);
  1592. + }
  1593. +
  1594. + memcpy (line_out + offset_out, bufpos, mblength);
  1595. + offset_out += mblength;
  1596. + column += increment;
  1597. +
  1598. + if (is_blank_seen && !convfail && wc == L'\r')
  1599. + is_cr_after_last_blank = 1;
  1600. +
  1601. + if (is_bs_following_last_blank && !convfail && wc == L'\b')
  1602. + ++bs_following_last_blank_num;
  1603. + else
  1604. + is_bs_following_last_blank = 0;
  1605. +
  1606. + if (break_spaces && !convfail && iswblank (wc))
  1607. + {
  1608. + last_blank_pos = offset_out;
  1609. + last_blank_column = column;
  1610. + is_blank_seen = 1;
  1611. + last_blank_increment = increment;
  1612. + is_bs_following_last_blank = 1;
  1613. + bs_following_last_blank_num = 0;
  1614. + is_cr_after_last_blank = 0;
  1615. + }
  1616. + }
  1617. +
  1618. + *saved_errno = errno;
  1619. + if (!ferror (istream))
  1620. + *saved_errno = 0;
  1621. +
  1622. + if (offset_out)
  1623. + fwrite (line_out, sizeof (char), (size_t) offset_out, stdout);
  1624. +
  1625. +}
  1626. +#endif
  1627. +
  1628. +/* Fold file FILENAME, or standard input if FILENAME is "-",
  1629. + to stdout, with maximum line length WIDTH.
  1630. + Return 0 if successful, 1 if an error occurs. */
  1631. +
  1632. +static bool
  1633. +fold_file (char const *filename, size_t width)
  1634. +{
  1635. + FILE *istream;
  1636. + int saved_errno;
  1637. +
  1638. + if (STREQ (filename, "-"))
  1639. + {
  1640. + istream = stdin;
  1641. + have_read_stdin = 1;
  1642. + }
  1643. + else
  1644. + istream = fopen (filename, "r");
  1645. +
  1646. + if (istream == NULL)
  1647. + {
  1648. + error (0, errno, "%s", filename);
  1649. + return 1;
  1650. + }
  1651. +
  1652. + /* Define how ISTREAM is being folded. */
  1653. +#if HAVE_MBRTOWC
  1654. + if (MB_CUR_MAX > 1)
  1655. + fold_multibyte_text (istream, width, &saved_errno);
  1656. + else
  1657. +#endif
  1658. + fold_text (istream, width, &saved_errno);
  1659. +
  1660. if (STREQ (filename, "-"))
  1661. clearerr (istream);
  1662. else if (fclose (istream) != 0 && !saved_errno)
  1663. @@ -252,7 +501,8 @@ main (int argc, char **argv)
  1664. atexit (close_stdout);
  1665. - break_spaces = count_bytes = have_read_stdin = false;
  1666. + operating_mode = column_mode;
  1667. + break_spaces = have_read_stdin = false;
  1668. while ((optc = getopt_long (argc, argv, shortopts, longopts, NULL)) != -1)
  1669. {
  1670. @@ -261,7 +511,15 @@ main (int argc, char **argv)
  1671. switch (optc)
  1672. {
  1673. case 'b': /* Count bytes rather than columns. */
  1674. - count_bytes = true;
  1675. + if (operating_mode != column_mode)
  1676. + FATAL_ERROR (_("only one way of folding may be specified"));
  1677. + operating_mode = byte_mode;
  1678. + break;
  1679. +
  1680. + case 'c':
  1681. + if (operating_mode != column_mode)
  1682. + FATAL_ERROR (_("only one way of folding may be specified"));
  1683. + operating_mode = character_mode;
  1684. break;
  1685. case 's': /* Break at word boundaries. */
  1686. diff --git a/src/join.c b/src/join.c
  1687. index f2fd172..6c7d1ed 100644
  1688. --- a/src/join.c
  1689. +++ b/src/join.c
  1690. @@ -22,19 +22,33 @@
  1691. #include <sys/types.h>
  1692. #include <getopt.h>
  1693. +/* Get mbstate_t, mbrtowc(), mbrtowc(), wcwidth(). */
  1694. +#if HAVE_WCHAR_H
  1695. +# include <wchar.h>
  1696. +#endif
  1697. +
  1698. +/* Get iswblank(), towupper. */
  1699. +#if HAVE_WCTYPE_H
  1700. +# include <wctype.h>
  1701. +#endif
  1702. +
  1703. #include "system.h"
  1704. #include "die.h"
  1705. #include "error.h"
  1706. #include "fadvise.h"
  1707. #include "hard-locale.h"
  1708. #include "linebuffer.h"
  1709. -#include "memcasecmp.h"
  1710. #include "quote.h"
  1711. #include "stdio--.h"
  1712. #include "xmemcoll.h"
  1713. #include "xstrtol.h"
  1714. #include "argmatch.h"
  1715. +/* Some systems, like BeOS, have multibyte encodings but lack mbstate_t. */
  1716. +#if HAVE_MBRTOWC && defined mbstate_t
  1717. +# define mbrtowc(pwc, s, n, ps) (mbrtowc) (pwc, s, n, 0)
  1718. +#endif
  1719. +
  1720. /* The official name of this program (e.g., no 'g' prefix). */
  1721. #define PROGRAM_NAME "join"
  1722. @@ -136,10 +150,12 @@ static struct outlist outlist_head;
  1723. /* Last element in 'outlist', where a new element can be added. */
  1724. static struct outlist *outlist_end = &outlist_head;
  1725. -/* Tab character separating fields. If negative, fields are separated
  1726. - by any nonempty string of blanks, otherwise by exactly one
  1727. - tab character whose value (when cast to unsigned char) equals TAB. */
  1728. -static int tab = -1;
  1729. +/* Tab character separating fields. If NULL, fields are separated
  1730. + by any nonempty string of blanks. */
  1731. +static char *tab = NULL;
  1732. +
  1733. +/* The number of bytes used for tab. */
  1734. +static size_t tablen = 0;
  1735. /* If nonzero, check that the input is correctly ordered. */
  1736. static enum
  1737. @@ -280,13 +296,14 @@ xfields (struct line *line)
  1738. if (ptr == lim)
  1739. return;
  1740. - if (0 <= tab && tab != '\n')
  1741. + if (tab != NULL)
  1742. {
  1743. + unsigned char t = tab[0];
  1744. char *sep;
  1745. - for (; (sep = memchr (ptr, tab, lim - ptr)) != NULL; ptr = sep + 1)
  1746. + for (; (sep = memchr (ptr, t, lim - ptr)) != NULL; ptr = sep + 1)
  1747. extract_field (line, ptr, sep - ptr);
  1748. }
  1749. - else if (tab < 0)
  1750. + else
  1751. {
  1752. /* Skip leading blanks before the first field. */
  1753. while (field_sep (*ptr))
  1754. @@ -310,6 +327,147 @@ xfields (struct line *line)
  1755. extract_field (line, ptr, lim - ptr);
  1756. }
  1757. +#if HAVE_MBRTOWC
  1758. +static void
  1759. +xfields_multibyte (struct line *line)
  1760. +{
  1761. + char *ptr = line->buf.buffer;
  1762. + char const *lim = ptr + line->buf.length - 1;
  1763. + wchar_t wc = 0;
  1764. + size_t mblength = 1;
  1765. + mbstate_t state, state_bak;
  1766. +
  1767. + memset (&state, 0, sizeof (mbstate_t));
  1768. +
  1769. + if (ptr >= lim)
  1770. + return;
  1771. +
  1772. + if (tab != NULL)
  1773. + {
  1774. + char *sep = ptr;
  1775. + for (; ptr < lim; ptr = sep + mblength)
  1776. + {
  1777. + sep = ptr;
  1778. + while (sep < lim)
  1779. + {
  1780. + state_bak = state;
  1781. + mblength = mbrtowc (&wc, sep, lim - sep + 1, &state);
  1782. +
  1783. + if (mblength == (size_t)-1 || mblength == (size_t)-2)
  1784. + {
  1785. + mblength = 1;
  1786. + state = state_bak;
  1787. + }
  1788. + mblength = (mblength < 1) ? 1 : mblength;
  1789. +
  1790. + if (mblength == tablen && !memcmp (sep, tab, mblength))
  1791. + break;
  1792. + else
  1793. + {
  1794. + sep += mblength;
  1795. + continue;
  1796. + }
  1797. + }
  1798. +
  1799. + if (sep >= lim)
  1800. + break;
  1801. +
  1802. + extract_field (line, ptr, sep - ptr);
  1803. + }
  1804. + }
  1805. + else
  1806. + {
  1807. + /* Skip leading blanks before the first field. */
  1808. + while(ptr < lim)
  1809. + {
  1810. + state_bak = state;
  1811. + mblength = mbrtowc (&wc, ptr, lim - ptr + 1, &state);
  1812. +
  1813. + if (mblength == (size_t)-1 || mblength == (size_t)-2)
  1814. + {
  1815. + mblength = 1;
  1816. + state = state_bak;
  1817. + break;
  1818. + }
  1819. + mblength = (mblength < 1) ? 1 : mblength;
  1820. +
  1821. + if (!iswblank(wc) && wc != '\n')
  1822. + break;
  1823. + ptr += mblength;
  1824. + }
  1825. +
  1826. + do
  1827. + {
  1828. + char *sep;
  1829. + state_bak = state;
  1830. + mblength = mbrtowc (&wc, ptr, lim - ptr + 1, &state);
  1831. + if (mblength == (size_t)-1 || mblength == (size_t)-2)
  1832. + {
  1833. + mblength = 1;
  1834. + state = state_bak;
  1835. + break;
  1836. + }
  1837. + mblength = (mblength < 1) ? 1 : mblength;
  1838. +
  1839. + sep = ptr + mblength;
  1840. + while (sep < lim)
  1841. + {
  1842. + state_bak = state;
  1843. + mblength = mbrtowc (&wc, sep, lim - sep + 1, &state);
  1844. + if (mblength == (size_t)-1 || mblength == (size_t)-2)
  1845. + {
  1846. + mblength = 1;
  1847. + state = state_bak;
  1848. + break;
  1849. + }
  1850. + mblength = (mblength < 1) ? 1 : mblength;
  1851. +
  1852. + if (iswblank (wc) || wc == '\n')
  1853. + break;
  1854. +
  1855. + sep += mblength;
  1856. + }
  1857. +
  1858. + extract_field (line, ptr, sep - ptr);
  1859. + if (sep >= lim)
  1860. + return;
  1861. +
  1862. + state_bak = state;
  1863. + mblength = mbrtowc (&wc, sep, lim - sep + 1, &state);
  1864. + if (mblength == (size_t)-1 || mblength == (size_t)-2)
  1865. + {
  1866. + mblength = 1;
  1867. + state = state_bak;
  1868. + break;
  1869. + }
  1870. + mblength = (mblength < 1) ? 1 : mblength;
  1871. +
  1872. + ptr = sep + mblength;
  1873. + while (ptr < lim)
  1874. + {
  1875. + state_bak = state;
  1876. + mblength = mbrtowc (&wc, ptr, lim - ptr + 1, &state);
  1877. + if (mblength == (size_t)-1 || mblength == (size_t)-2)
  1878. + {
  1879. + mblength = 1;
  1880. + state = state_bak;
  1881. + break;
  1882. + }
  1883. + mblength = (mblength < 1) ? 1 : mblength;
  1884. +
  1885. + if (!iswblank (wc) && wc != '\n')
  1886. + break;
  1887. +
  1888. + ptr += mblength;
  1889. + }
  1890. + }
  1891. + while (ptr < lim);
  1892. + }
  1893. +
  1894. + extract_field (line, ptr, lim - ptr);
  1895. +}
  1896. +#endif
  1897. +
  1898. static void
  1899. freeline (struct line *line)
  1900. {
  1901. @@ -331,56 +489,133 @@ keycmp (struct line const *line1, struct line const *line2,
  1902. size_t jf_1, size_t jf_2)
  1903. {
  1904. /* Start of field to compare in each file. */
  1905. - char *beg1;
  1906. - char *beg2;
  1907. -
  1908. - size_t len1;
  1909. - size_t len2; /* Length of fields to compare. */
  1910. + char *beg[2];
  1911. + char *copy[2];
  1912. + size_t len[2]; /* Length of fields to compare. */
  1913. int diff;
  1914. + int i, j;
  1915. + int mallocd = 0;
  1916. if (jf_1 < line1->nfields)
  1917. {
  1918. - beg1 = line1->fields[jf_1].beg;
  1919. - len1 = line1->fields[jf_1].len;
  1920. + beg[0] = line1->fields[jf_1].beg;
  1921. + len[0] = line1->fields[jf_1].len;
  1922. }
  1923. else
  1924. {
  1925. - beg1 = NULL;
  1926. - len1 = 0;
  1927. + beg[0] = NULL;
  1928. + len[0] = 0;
  1929. }
  1930. if (jf_2 < line2->nfields)
  1931. {
  1932. - beg2 = line2->fields[jf_2].beg;
  1933. - len2 = line2->fields[jf_2].len;
  1934. + beg[1] = line2->fields[jf_2].beg;
  1935. + len[1] = line2->fields[jf_2].len;
  1936. }
  1937. else
  1938. {
  1939. - beg2 = NULL;
  1940. - len2 = 0;
  1941. + beg[1] = NULL;
  1942. + len[1] = 0;
  1943. }
  1944. - if (len1 == 0)
  1945. - return len2 == 0 ? 0 : -1;
  1946. - if (len2 == 0)
  1947. + if (len[0] == 0)
  1948. + return len[1] == 0 ? 0 : -1;
  1949. + if (len[1] == 0)
  1950. return 1;
  1951. if (ignore_case)
  1952. {
  1953. - /* FIXME: ignore_case does not work with NLS (in particular,
  1954. - with multibyte chars). */
  1955. - diff = memcasecmp (beg1, beg2, MIN (len1, len2));
  1956. +#ifdef HAVE_MBRTOWC
  1957. + if (MB_CUR_MAX > 1)
  1958. + {
  1959. + size_t mblength;
  1960. + wchar_t wc, uwc;
  1961. + mbstate_t state, state_bak;
  1962. +
  1963. + memset (&state, '\0', sizeof (mbstate_t));
  1964. +
  1965. + for (i = 0; i < 2; i++)
  1966. + {
  1967. + mallocd = 1;
  1968. + copy[i] = xmalloc (len[i] + 1);
  1969. + memset (copy[i], '\0',len[i] + 1);
  1970. +
  1971. + for (j = 0; j < MIN (len[0], len[1]);)
  1972. + {
  1973. + state_bak = state;
  1974. + mblength = mbrtowc (&wc, beg[i] + j, len[i] - j, &state);
  1975. +
  1976. + switch (mblength)
  1977. + {
  1978. + case (size_t) -1:
  1979. + case (size_t) -2:
  1980. + state = state_bak;
  1981. + /* Fall through */
  1982. + case 0:
  1983. + mblength = 1;
  1984. + break;
  1985. +
  1986. + default:
  1987. + uwc = towupper (wc);
  1988. +
  1989. + if (uwc != wc)
  1990. + {
  1991. + mbstate_t state_wc;
  1992. + size_t mblen;
  1993. +
  1994. + memset (&state_wc, '\0', sizeof (mbstate_t));
  1995. + mblen = wcrtomb (copy[i] + j, uwc, &state_wc);
  1996. + assert (mblen != (size_t)-1);
  1997. + }
  1998. + else
  1999. + memcpy (copy[i] + j, beg[i] + j, mblength);
  2000. + }
  2001. + j += mblength;
  2002. + }
  2003. + copy[i][j] = '\0';
  2004. + }
  2005. + }
  2006. + else
  2007. +#endif
  2008. + {
  2009. + for (i = 0; i < 2; i++)
  2010. + {
  2011. + mallocd = 1;
  2012. + copy[i] = xmalloc (len[i] + 1);
  2013. +
  2014. + for (j = 0; j < MIN (len[0], len[1]); j++)
  2015. + copy[i][j] = toupper (beg[i][j]);
  2016. +
  2017. + copy[i][j] = '\0';
  2018. + }
  2019. + }
  2020. }
  2021. else
  2022. {
  2023. - if (hard_LC_COLLATE)
  2024. - return xmemcoll (beg1, len1, beg2, len2);
  2025. - diff = memcmp (beg1, beg2, MIN (len1, len2));
  2026. + copy[0] = beg[0];
  2027. + copy[1] = beg[1];
  2028. }
  2029. + if (hard_LC_COLLATE)
  2030. + {
  2031. + diff = xmemcoll ((char *) copy[0], len[0], (char *) copy[1], len[1]);
  2032. +
  2033. + if (mallocd)
  2034. + for (i = 0; i < 2; i++)
  2035. + free (copy[i]);
  2036. +
  2037. + return diff;
  2038. + }
  2039. + diff = memcmp (copy[0], copy[1], MIN (len[0], len[1]));
  2040. +
  2041. + if (mallocd)
  2042. + for (i = 0; i < 2; i++)
  2043. + free (copy[i]);
  2044. +
  2045. +
  2046. if (diff)
  2047. return diff;
  2048. - return len1 < len2 ? -1 : len1 != len2;
  2049. + return len[0] - len[1];
  2050. }
  2051. /* Check that successive input lines PREV and CURRENT from input file
  2052. @@ -472,6 +707,11 @@ get_line (FILE *fp, struct line **linep, int which)
  2053. }
  2054. ++line_no[which - 1];
  2055. +#if HAVE_MBRTOWC
  2056. + if (MB_CUR_MAX > 1)
  2057. + xfields_multibyte (line);
  2058. + else
  2059. +#endif
  2060. xfields (line);
  2061. if (prevline[which - 1])
  2062. @@ -567,21 +807,28 @@ prfield (size_t n, struct line const *line)
  2063. /* Output all the fields in line, other than the join field. */
  2064. +#define PUT_TAB_CHAR \
  2065. + do \
  2066. + { \
  2067. + (tab != NULL) ? \
  2068. + fwrite(tab, sizeof(char), tablen, stdout) : putchar (' '); \
  2069. + } \
  2070. + while (0)
  2071. +
  2072. static void
  2073. prfields (struct line const *line, size_t join_field, size_t autocount)
  2074. {
  2075. size_t i;
  2076. size_t nfields = autoformat ? autocount : line->nfields;
  2077. - char output_separator = tab < 0 ? ' ' : tab;
  2078. for (i = 0; i < join_field && i < nfields; ++i)
  2079. {
  2080. - putchar (output_separator);
  2081. + PUT_TAB_CHAR;
  2082. prfield (i, line);
  2083. }
  2084. for (i = join_field + 1; i < nfields; ++i)
  2085. {
  2086. - putchar (output_separator);
  2087. + PUT_TAB_CHAR;
  2088. prfield (i, line);
  2089. }
  2090. }
  2091. @@ -592,7 +839,6 @@ static void
  2092. prjoin (struct line const *line1, struct line const *line2)
  2093. {
  2094. const struct outlist *outlist;
  2095. - char output_separator = tab < 0 ? ' ' : tab;
  2096. size_t field;
  2097. struct line const *line;
  2098. @@ -626,7 +872,7 @@ prjoin (struct line const *line1, struct line const *line2)
  2099. o = o->next;
  2100. if (o == NULL)
  2101. break;
  2102. - putchar (output_separator);
  2103. + PUT_TAB_CHAR;
  2104. }
  2105. putchar (eolchar);
  2106. }
  2107. @@ -1102,20 +1348,43 @@ main (int argc, char **argv)
  2108. case 't':
  2109. {
  2110. - unsigned char newtab = optarg[0];
  2111. + char *newtab = NULL;
  2112. + size_t newtablen;
  2113. + newtab = xstrdup (optarg);
  2114. +#if HAVE_MBRTOWC
  2115. + if (MB_CUR_MAX > 1)
  2116. + {
  2117. + mbstate_t state;
  2118. +
  2119. + memset (&state, 0, sizeof (mbstate_t));
  2120. + newtablen = mbrtowc (NULL, newtab,
  2121. + strnlen (newtab, MB_LEN_MAX),
  2122. + &state);
  2123. + if (newtablen == (size_t) 0
  2124. + || newtablen == (size_t) -1
  2125. + || newtablen == (size_t) -2)
  2126. + newtablen = 1;
  2127. + }
  2128. + else
  2129. +#endif
  2130. + newtablen = 1;
  2131. if (! newtab)
  2132. - newtab = '\n'; /* '' => process the whole line. */
  2133. + newtab = (char*)"\n"; /* '' => process the whole line. */
  2134. else if (optarg[1])
  2135. {
  2136. - if (STREQ (optarg, "\\0"))
  2137. - newtab = '\0';
  2138. - else
  2139. - die (EXIT_FAILURE, 0, _("multi-character tab %s"),
  2140. - quote (optarg));
  2141. + if (newtablen == 1 && newtab[1])
  2142. + {
  2143. + if (STREQ (newtab, "\\0"))
  2144. + newtab[0] = '\0';
  2145. + }
  2146. + }
  2147. + if (tab != NULL && strcmp (tab, newtab))
  2148. + {
  2149. + free (newtab);
  2150. + die (EXIT_FAILURE, 0, _("incompatible tabs"));
  2151. }
  2152. - if (0 <= tab && tab != newtab)
  2153. - die (EXIT_FAILURE, 0, _("incompatible tabs"));
  2154. tab = newtab;
  2155. + tablen = newtablen;
  2156. }
  2157. break;
  2158. diff --git a/src/local.mk b/src/local.mk
  2159. index e1d15ce..1a5ffaa 100644
  2160. --- a/src/local.mk
  2161. +++ b/src/local.mk
  2162. @@ -434,8 +434,8 @@ src_base32_CPPFLAGS = -DBASE_TYPE=32 $(AM_CPPFLAGS)
  2163. src_basenc_SOURCES = src/basenc.c
  2164. src_basenc_CPPFLAGS = -DBASE_TYPE=42 $(AM_CPPFLAGS)
  2165. -src_expand_SOURCES = src/expand.c src/expand-common.c
  2166. -src_unexpand_SOURCES = src/unexpand.c src/expand-common.c
  2167. +src_expand_SOURCES = src/expand.c src/expand-common.c lib/mbfile.c
  2168. +src_unexpand_SOURCES = src/unexpand.c src/expand-common.c lib/mbfile.c
  2169. src_wc_SOURCES = src/wc.c
  2170. if USE_AVX2_WC_LINECOUNT
  2171. diff --git a/src/pr.c b/src/pr.c
  2172. index 4c17c00..b4fab1c 100644
  2173. --- a/src/pr.c
  2174. +++ b/src/pr.c
  2175. @@ -311,6 +311,24 @@
  2176. #include <getopt.h>
  2177. #include <sys/types.h>
  2178. +
  2179. +/* Get MB_LEN_MAX. */
  2180. +#include <limits.h>
  2181. +/* MB_LEN_MAX is incorrectly defined to be 1 in at least one GCC
  2182. + installation; work around this configuration error. */
  2183. +#if !defined MB_LEN_MAX || MB_LEN_MAX == 1
  2184. +# define MB_LEN_MAX 16
  2185. +#endif
  2186. +
  2187. +/* Get MB_CUR_MAX. */
  2188. +#include <stdlib.h>
  2189. +
  2190. +/* Solaris 2.5 has a bug: <wchar.h> must be included before <wctype.h>. */
  2191. +/* Get mbstate_t, mbrtowc(), wcwidth(). */
  2192. +#if HAVE_WCHAR_H
  2193. +# include <wchar.h>
  2194. +#endif
  2195. +
  2196. #include "system.h"
  2197. #include "die.h"
  2198. #include "error.h"
  2199. @@ -325,6 +343,18 @@
  2200. #include "xstrtol-error.h"
  2201. #include "xdectoint.h"
  2202. +/* Some systems, like BeOS, have multibyte encodings but lack mbstate_t. */
  2203. +#if HAVE_MBRTOWC && defined mbstate_t
  2204. +# define mbrtowc(pwc, s, n, ps) (mbrtowc) (pwc, s, n, 0)
  2205. +#endif
  2206. +
  2207. +#ifndef HAVE_DECL_WCWIDTH
  2208. +"this configure-time declaration test was not run"
  2209. +#endif
  2210. +#if !HAVE_DECL_WCWIDTH
  2211. +extern int wcwidth ();
  2212. +#endif
  2213. +
  2214. /* The official name of this program (e.g., no 'g' prefix). */
  2215. #define PROGRAM_NAME "pr"
  2216. @@ -417,7 +447,20 @@ struct COLUMN
  2217. typedef struct COLUMN COLUMN;
  2218. -static int char_to_clump (char c);
  2219. +/* Funtion pointers to switch functions for single byte locale or for
  2220. + multibyte locale. If multibyte functions do not exist in your sysytem,
  2221. + these pointers always point the function for single byte locale. */
  2222. +static void (*print_char) (char c);
  2223. +static int (*char_to_clump) (char c);
  2224. +
  2225. +/* Functions for single byte locale. */
  2226. +static void print_char_single (char c);
  2227. +static int char_to_clump_single (char c);
  2228. +
  2229. +/* Functions for multibyte locale. */
  2230. +static void print_char_multi (char c);
  2231. +static int char_to_clump_multi (char c);
  2232. +
  2233. static bool read_line (COLUMN *p);
  2234. static bool print_page (void);
  2235. static bool print_stored (COLUMN *p);
  2236. @@ -429,6 +472,7 @@ static void add_line_number (COLUMN *p);
  2237. static void getoptnum (char const *n_str, int min, int *num,
  2238. char const *errfmt);
  2239. static void getoptarg (char *arg, char switch_char, char *character,
  2240. + int *character_length, int *character_width,
  2241. int *number);
  2242. static void print_files (int number_of_files, char **av);
  2243. static void init_parameters (int number_of_files);
  2244. @@ -442,7 +486,6 @@ static void store_char (char c);
  2245. static void pad_down (unsigned int lines);
  2246. static void read_rest_of_line (COLUMN *p);
  2247. static void skip_read (COLUMN *p, int column_number);
  2248. -static void print_char (char c);
  2249. static void cleanup (void);
  2250. static void print_sep_string (void);
  2251. static void separator_string (char const *optarg_S);
  2252. @@ -454,7 +497,7 @@ static COLUMN *column_vector;
  2253. we store the leftmost columns contiguously in buff.
  2254. To print a line from buff, get the index of the first character
  2255. from line_vector[i], and print up to line_vector[i + 1]. */
  2256. -static char *buff;
  2257. +static unsigned char *buff;
  2258. /* Index of the position in buff where the next character
  2259. will be stored. */
  2260. @@ -558,7 +601,7 @@ static int chars_per_column;
  2261. static bool untabify_input = false;
  2262. /* (-e) The input tab character. */
  2263. -static char input_tab_char = '\t';
  2264. +static char input_tab_char[MB_LEN_MAX] = "\t";
  2265. /* (-e) Tabstops are at chars_per_tab, 2*chars_per_tab, 3*chars_per_tab, ...
  2266. where the leftmost column is 1. */
  2267. @@ -568,7 +611,10 @@ static int chars_per_input_tab = 8;
  2268. static bool tabify_output = false;
  2269. /* (-i) The output tab character. */
  2270. -static char output_tab_char = '\t';
  2271. +static char output_tab_char[MB_LEN_MAX] = "\t";
  2272. +
  2273. +/* (-i) The byte length of output tab character. */
  2274. +static int output_tab_char_length = 1;
  2275. /* (-i) The width of the output tab. */
  2276. static int chars_per_output_tab = 8;
  2277. @@ -638,7 +684,13 @@ static int line_number;
  2278. static bool numbered_lines = false;
  2279. /* (-n) Character which follows each line number. */
  2280. -static char number_separator = '\t';
  2281. +static char number_separator[MB_LEN_MAX] = "\t";
  2282. +
  2283. +/* (-n) The byte length of the character which follows each line number. */
  2284. +static int number_separator_length = 1;
  2285. +
  2286. +/* (-n) The character width of the character which follows each line number. */
  2287. +static int number_separator_width = 0;
  2288. /* (-n) line counting starts with 1st line of input file (not with 1st
  2289. line of 1st page printed). */
  2290. @@ -691,6 +743,7 @@ static bool use_col_separator = false;
  2291. -a|COLUMN|-m is a 'space' and with the -J option a 'tab'. */
  2292. static char const *col_sep_string = "";
  2293. static int col_sep_length = 0;
  2294. +static int col_sep_width = 0;
  2295. static char *column_separator = (char *) " ";
  2296. static char *line_separator = (char *) "\t";
  2297. @@ -853,6 +906,13 @@ separator_string (char const *optarg_S)
  2298. integer_overflow ();
  2299. col_sep_length = len;
  2300. col_sep_string = optarg_S;
  2301. +
  2302. +#if HAVE_MBRTOWC
  2303. + if (MB_CUR_MAX > 1)
  2304. + col_sep_width = mbswidth (col_sep_string, 0);
  2305. + else
  2306. +#endif
  2307. + col_sep_width = col_sep_length;
  2308. }
  2309. int
  2310. @@ -877,6 +937,21 @@ main (int argc, char **argv)
  2311. atexit (close_stdout);
  2312. +/* Define which functions are used, the ones for single byte locale or the ones
  2313. + for multibyte locale. */
  2314. +#if HAVE_MBRTOWC
  2315. + if (MB_CUR_MAX > 1)
  2316. + {
  2317. + print_char = print_char_multi;
  2318. + char_to_clump = char_to_clump_multi;
  2319. + }
  2320. + else
  2321. +#endif
  2322. + {
  2323. + print_char = print_char_single;
  2324. + char_to_clump = char_to_clump_single;
  2325. + }
  2326. +
  2327. n_files = 0;
  2328. file_names = (argc > 1
  2329. ? xnmalloc (argc - 1, sizeof (char *))
  2330. @@ -953,8 +1028,12 @@ main (int argc, char **argv)
  2331. break;
  2332. case 'e':
  2333. if (optarg)
  2334. - getoptarg (optarg, 'e', &input_tab_char,
  2335. - &chars_per_input_tab);
  2336. + {
  2337. + int dummy_length, dummy_width;
  2338. +
  2339. + getoptarg (optarg, 'e', input_tab_char, &dummy_length,
  2340. + &dummy_width, &chars_per_input_tab);
  2341. + }
  2342. /* Could check tab width > 0. */
  2343. untabify_input = true;
  2344. break;
  2345. @@ -967,8 +1046,12 @@ main (int argc, char **argv)
  2346. break;
  2347. case 'i':
  2348. if (optarg)
  2349. - getoptarg (optarg, 'i', &output_tab_char,
  2350. - &chars_per_output_tab);
  2351. + {
  2352. + int dummy_width;
  2353. +
  2354. + getoptarg (optarg, 'i', output_tab_char, &output_tab_char_length,
  2355. + &dummy_width, &chars_per_output_tab);
  2356. + }
  2357. /* Could check tab width > 0. */
  2358. tabify_output = true;
  2359. break;
  2360. @@ -986,8 +1069,8 @@ main (int argc, char **argv)
  2361. case 'n':
  2362. numbered_lines = true;
  2363. if (optarg)
  2364. - getoptarg (optarg, 'n', &number_separator,
  2365. - &chars_per_number);
  2366. + getoptarg (optarg, 'n', number_separator, &number_separator_length,
  2367. + &number_separator_width, &chars_per_number);
  2368. break;
  2369. case 'N':
  2370. skip_count = false;
  2371. @@ -1012,6 +1095,7 @@ main (int argc, char **argv)
  2372. /* Reset an additional input of -s, -S dominates -s */
  2373. col_sep_string = "";
  2374. col_sep_length = 0;
  2375. + col_sep_width = 0;
  2376. use_col_separator = true;
  2377. if (optarg)
  2378. separator_string (optarg);
  2379. @@ -1166,10 +1250,45 @@ getoptnum (char const *n_str, int min, int *num, char const *err)
  2380. a number. */
  2381. static void
  2382. -getoptarg (char *arg, char switch_char, char *character, int *number)
  2383. +getoptarg (char *arg, char switch_char, char *character, int *character_length,
  2384. + int *character_width, int *number)
  2385. {
  2386. if (!ISDIGIT (*arg))
  2387. - *character = *arg++;
  2388. + {
  2389. +#ifdef HAVE_MBRTOWC
  2390. + if (MB_CUR_MAX > 1) /* for multibyte locale. */
  2391. + {
  2392. + wchar_t wc;
  2393. + size_t mblength;
  2394. + int width;
  2395. + mbstate_t state = {'\0'};
  2396. +
  2397. + mblength = mbrtowc (&wc, arg, strnlen(arg, MB_LEN_MAX), &state);
  2398. +
  2399. + if (mblength == (size_t)-1 || mblength == (size_t)-2)
  2400. + {
  2401. + *character_length = 1;
  2402. + *character_width = 1;
  2403. + }
  2404. + else
  2405. + {
  2406. + *character_length = (mblength < 1) ? 1 : mblength;
  2407. + width = wcwidth (wc);
  2408. + *character_width = (width < 0) ? 0 : width;
  2409. + }
  2410. +
  2411. + strncpy (character, arg, *character_length);
  2412. + arg += *character_length;
  2413. + }
  2414. + else /* for single byte locale. */
  2415. +#endif
  2416. + {
  2417. + *character = *arg++;
  2418. + *character_length = 1;
  2419. + *character_width = 1;
  2420. + }
  2421. + }
  2422. +
  2423. if (*arg)
  2424. {
  2425. long int tmp_long;
  2426. @@ -1191,6 +1310,11 @@ static void
  2427. init_parameters (int number_of_files)
  2428. {
  2429. int chars_used_by_number = 0;
  2430. + int mb_len = 1;
  2431. +#if HAVE_MBRTOWC
  2432. + if (MB_CUR_MAX > 1)
  2433. + mb_len = MB_LEN_MAX;
  2434. +#endif
  2435. lines_per_body = lines_per_page - lines_per_header - lines_per_footer;
  2436. if (lines_per_body <= 0)
  2437. @@ -1228,7 +1352,7 @@ init_parameters (int number_of_files)
  2438. else
  2439. col_sep_string = column_separator;
  2440. - col_sep_length = 1;
  2441. + col_sep_length = col_sep_width = 1;
  2442. use_col_separator = true;
  2443. }
  2444. /* It's rather pointless to define a TAB separator with column
  2445. @@ -1260,11 +1384,11 @@ init_parameters (int number_of_files)
  2446. + TAB_WIDTH (chars_per_input_tab, chars_per_number); */
  2447. /* Estimate chars_per_text without any margin and keep it constant. */
  2448. - if (number_separator == '\t')
  2449. + if (number_separator[0] == '\t')
  2450. number_width = (chars_per_number
  2451. + TAB_WIDTH (chars_per_default_tab, chars_per_number));
  2452. else
  2453. - number_width = chars_per_number + 1;
  2454. + number_width = chars_per_number + number_separator_width;
  2455. /* The number is part of the column width unless we are
  2456. printing files in parallel. */
  2457. @@ -1273,7 +1397,7 @@ init_parameters (int number_of_files)
  2458. }
  2459. int sep_chars, useful_chars;
  2460. - if (INT_MULTIPLY_WRAPV (columns - 1, col_sep_length, &sep_chars))
  2461. + if (INT_MULTIPLY_WRAPV (columns - 1, col_sep_width, &sep_chars))
  2462. sep_chars = INT_MAX;
  2463. if (INT_SUBTRACT_WRAPV (chars_per_line - chars_used_by_number, sep_chars,
  2464. &useful_chars))
  2465. @@ -1296,7 +1420,7 @@ init_parameters (int number_of_files)
  2466. We've to use 8 as the lower limit, if we use chars_per_default_tab = 8
  2467. to expand a tab which is not an input_tab-char. */
  2468. free (clump_buff);
  2469. - clump_buff = xmalloc (MAX (8, chars_per_input_tab));
  2470. + clump_buff = xmalloc (mb_len * MAX (8, chars_per_input_tab));
  2471. }
  2472. /* Open the necessary files,
  2473. @@ -1402,7 +1526,7 @@ init_funcs (void)
  2474. /* Enlarge p->start_position of first column to use the same form of
  2475. padding_not_printed with all columns. */
  2476. - h = h + col_sep_length;
  2477. + h = h + col_sep_width;
  2478. /* This loop takes care of all but the rightmost column. */
  2479. @@ -1436,7 +1560,7 @@ init_funcs (void)
  2480. }
  2481. else
  2482. {
  2483. - h = h_next + col_sep_length;
  2484. + h = h_next + col_sep_width;
  2485. h_next = h + chars_per_column;
  2486. }
  2487. }
  2488. @@ -1733,9 +1857,9 @@ static void
  2489. align_column (COLUMN *p)
  2490. {
  2491. padding_not_printed = p->start_position;
  2492. - if (col_sep_length < padding_not_printed)
  2493. + if (col_sep_width < padding_not_printed)
  2494. {
  2495. - pad_across_to (padding_not_printed - col_sep_length);
  2496. + pad_across_to (padding_not_printed - col_sep_width);
  2497. padding_not_printed = ANYWHERE;
  2498. }
  2499. @@ -2010,13 +2134,13 @@ store_char (char c)
  2500. /* May be too generous. */
  2501. buff = X2REALLOC (buff, &buff_allocated);
  2502. }
  2503. - buff[buff_current++] = c;
  2504. + buff[buff_current++] = (unsigned char) c;
  2505. }
  2506. static void
  2507. add_line_number (COLUMN *p)
  2508. {
  2509. - int i;
  2510. + int i, j;
  2511. char *s;
  2512. int num_width;
  2513. @@ -2033,22 +2157,24 @@ add_line_number (COLUMN *p)
  2514. /* Tabification is assumed for multiple columns, also for n-separators,
  2515. but 'default n-separator = TAB' hasn't been given priority over
  2516. equal column_width also specified by POSIX. */
  2517. - if (number_separator == '\t')
  2518. + if (number_separator[0] == '\t')
  2519. {
  2520. i = number_width - chars_per_number;
  2521. while (i-- > 0)
  2522. (p->char_func) (' ');
  2523. }
  2524. else
  2525. - (p->char_func) (number_separator);
  2526. + for (j = 0; j < number_separator_length; j++)
  2527. + (p->char_func) (number_separator[j]);
  2528. }
  2529. else
  2530. /* To comply with POSIX, we avoid any expansion of default TAB
  2531. separator with a single column output. No column_width requirement
  2532. has to be considered. */
  2533. {
  2534. - (p->char_func) (number_separator);
  2535. - if (number_separator == '\t')
  2536. + for (j = 0; j < number_separator_length; j++)
  2537. + (p->char_func) (number_separator[j]);
  2538. + if (number_separator[0] == '\t')
  2539. output_position = POS_AFTER_TAB (chars_per_output_tab,
  2540. output_position);
  2541. }
  2542. @@ -2207,7 +2333,7 @@ print_white_space (void)
  2543. while (goal - h_old > 1
  2544. && (h_new = POS_AFTER_TAB (chars_per_output_tab, h_old)) <= goal)
  2545. {
  2546. - putchar (output_tab_char);
  2547. + fwrite (output_tab_char, sizeof(char), output_tab_char_length, stdout);
  2548. h_old = h_new;
  2549. }
  2550. while (++h_old <= goal)
  2551. @@ -2227,6 +2353,7 @@ print_sep_string (void)
  2552. {
  2553. char const *s = col_sep_string;
  2554. int l = col_sep_length;
  2555. + int not_space_flag;
  2556. if (separators_not_printed <= 0)
  2557. {
  2558. @@ -2238,6 +2365,7 @@ print_sep_string (void)
  2559. {
  2560. for (; separators_not_printed > 0; --separators_not_printed)
  2561. {
  2562. + not_space_flag = 0;
  2563. while (l-- > 0)
  2564. {
  2565. /* 3 types of sep_strings: spaces only, spaces and chars,
  2566. @@ -2251,12 +2379,15 @@ print_sep_string (void)
  2567. }
  2568. else
  2569. {
  2570. + not_space_flag = 1;
  2571. if (spaces_not_printed > 0)
  2572. print_white_space ();
  2573. putchar (*s++);
  2574. - ++output_position;
  2575. }
  2576. }
  2577. + if (not_space_flag)
  2578. + output_position += col_sep_width;
  2579. +
  2580. /* sep_string ends with some spaces */
  2581. if (spaces_not_printed > 0)
  2582. print_white_space ();
  2583. @@ -2284,7 +2415,7 @@ print_clump (COLUMN *p, int n, char *clump)
  2584. required number of tabs and spaces. */
  2585. static void
  2586. -print_char (char c)
  2587. +print_char_single (char c)
  2588. {
  2589. if (tabify_output)
  2590. {
  2591. @@ -2308,6 +2439,74 @@ print_char (char c)
  2592. putchar (c);
  2593. }
  2594. +#ifdef HAVE_MBRTOWC
  2595. +static void
  2596. +print_char_multi (char c)
  2597. +{
  2598. + static size_t mbc_pos = 0;
  2599. + static char mbc[MB_LEN_MAX] = {'\0'};
  2600. + static mbstate_t state = {'\0'};
  2601. + mbstate_t state_bak;
  2602. + wchar_t wc;
  2603. + size_t mblength;
  2604. + int width;
  2605. +
  2606. + if (tabify_output)
  2607. + {
  2608. + state_bak = state;
  2609. + mbc[mbc_pos++] = c;
  2610. + mblength = mbrtowc (&wc, mbc, mbc_pos, &state);
  2611. +
  2612. + while (mbc_pos > 0)
  2613. + {
  2614. + switch (mblength)
  2615. + {
  2616. + case (size_t)-2:
  2617. + state = state_bak;
  2618. + return;
  2619. +
  2620. + case (size_t)-1:
  2621. + state = state_bak;
  2622. + ++output_position;
  2623. + putchar (mbc[0]);
  2624. + memmove (mbc, mbc + 1, MB_CUR_MAX - 1);
  2625. + --mbc_pos;
  2626. + break;
  2627. +
  2628. + case 0:
  2629. + mblength = 1;
  2630. +
  2631. + default:
  2632. + if (wc == L' ')
  2633. + {
  2634. + memmove (mbc, mbc + mblength, MB_CUR_MAX - mblength);
  2635. + --mbc_pos;
  2636. + ++spaces_not_printed;
  2637. + return;
  2638. + }
  2639. + else if (spaces_not_printed > 0)
  2640. + print_white_space ();
  2641. +
  2642. + /* Nonprintables are assumed to have width 0, except L'\b'. */
  2643. + if ((width = wcwidth (wc)) < 1)
  2644. + {
  2645. + if (wc == L'\b')
  2646. + --output_position;
  2647. + }
  2648. + else
  2649. + output_position += width;
  2650. +
  2651. + fwrite (mbc, sizeof(char), mblength, stdout);
  2652. + memmove (mbc, mbc + mblength, MB_CUR_MAX - mblength);
  2653. + mbc_pos -= mblength;
  2654. + }
  2655. + }
  2656. + return;
  2657. + }
  2658. + putchar (c);
  2659. +}
  2660. +#endif
  2661. +
  2662. /* Skip to page PAGE before printing.
  2663. PAGE may be larger than total number of pages. */
  2664. @@ -2485,9 +2684,9 @@ read_line (COLUMN *p)
  2665. align_empty_cols = false;
  2666. }
  2667. - if (col_sep_length < padding_not_printed)
  2668. + if (col_sep_width < padding_not_printed)
  2669. {
  2670. - pad_across_to (padding_not_printed - col_sep_length);
  2671. + pad_across_to (padding_not_printed - col_sep_width);
  2672. padding_not_printed = ANYWHERE;
  2673. }
  2674. @@ -2556,7 +2755,7 @@ print_stored (COLUMN *p)
  2675. COLUMN *q;
  2676. int line = p->current_line++;
  2677. - char *first = &buff[line_vector[line]];
  2678. + unsigned char *first = &buff[line_vector[line]];
  2679. /* FIXME
  2680. UMR: Uninitialized memory read:
  2681. * This is occurring while in:
  2682. @@ -2568,7 +2767,7 @@ print_stored (COLUMN *p)
  2683. xmalloc [xmalloc.c:94]
  2684. init_store_cols [pr.c:1648]
  2685. */
  2686. - char *last = &buff[line_vector[line + 1]];
  2687. + unsigned char *last = &buff[line_vector[line + 1]];
  2688. pad_vertically = true;
  2689. @@ -2588,9 +2787,9 @@ print_stored (COLUMN *p)
  2690. }
  2691. }
  2692. - if (col_sep_length < padding_not_printed)
  2693. + if (col_sep_width < padding_not_printed)
  2694. {
  2695. - pad_across_to (padding_not_printed - col_sep_length);
  2696. + pad_across_to (padding_not_printed - col_sep_width);
  2697. padding_not_printed = ANYWHERE;
  2698. }
  2699. @@ -2603,8 +2802,8 @@ print_stored (COLUMN *p)
  2700. if (spaces_not_printed == 0)
  2701. {
  2702. output_position = p->start_position + end_vector[line];
  2703. - if (p->start_position - col_sep_length == chars_per_margin)
  2704. - output_position -= col_sep_length;
  2705. + if (p->start_position - col_sep_width == chars_per_margin)
  2706. + output_position -= col_sep_width;
  2707. }
  2708. return true;
  2709. @@ -2623,7 +2822,7 @@ print_stored (COLUMN *p)
  2710. number of characters is 1.) */
  2711. static int
  2712. -char_to_clump (char c)
  2713. +char_to_clump_single (char c)
  2714. {
  2715. unsigned char uc = c;
  2716. char *s = clump_buff;
  2717. @@ -2633,10 +2832,10 @@ char_to_clump (char c)
  2718. int chars;
  2719. int chars_per_c = 8;
  2720. - if (c == input_tab_char)
  2721. + if (c == input_tab_char[0])
  2722. chars_per_c = chars_per_input_tab;
  2723. - if (c == input_tab_char || c == '\t')
  2724. + if (c == input_tab_char[0] || c == '\t')
  2725. {
  2726. width = TAB_WIDTH (chars_per_c, input_position);
  2727. @@ -2717,6 +2916,164 @@ char_to_clump (char c)
  2728. return chars;
  2729. }
  2730. +#ifdef HAVE_MBRTOWC
  2731. +static int
  2732. +char_to_clump_multi (char c)
  2733. +{
  2734. + static size_t mbc_pos = 0;
  2735. + static char mbc[MB_LEN_MAX] = {'\0'};
  2736. + static mbstate_t state = {'\0'};
  2737. + mbstate_t state_bak;
  2738. + wchar_t wc;
  2739. + size_t mblength;
  2740. + int wc_width;
  2741. + register char *s = clump_buff;
  2742. + register int i, j;
  2743. + char esc_buff[4];
  2744. + int width;
  2745. + int chars;
  2746. + int chars_per_c = 8;
  2747. +
  2748. + state_bak = state;
  2749. + mbc[mbc_pos++] = c;
  2750. + mblength = mbrtowc (&wc, mbc, mbc_pos, &state);
  2751. +
  2752. + width = 0;
  2753. + chars = 0;
  2754. + while (mbc_pos > 0)
  2755. + {
  2756. + switch (mblength)
  2757. + {
  2758. + case (size_t)-2:
  2759. + state = state_bak;
  2760. + return 0;
  2761. +
  2762. + case (size_t)-1:
  2763. + state = state_bak;
  2764. + mblength = 1;
  2765. +
  2766. + if (use_esc_sequence || use_cntrl_prefix)
  2767. + {
  2768. + width = +4;
  2769. + chars = +4;
  2770. + *s++ = '\\';
  2771. + sprintf (esc_buff, "%03o", (unsigned char) mbc[0]);
  2772. + for (i = 0; i <= 2; ++i)
  2773. + *s++ = (int) esc_buff[i];
  2774. + }
  2775. + else
  2776. + {
  2777. + width += 1;
  2778. + chars += 1;
  2779. + *s++ = mbc[0];
  2780. + }
  2781. + break;
  2782. +
  2783. + case 0:
  2784. + mblength = 1;
  2785. + /* Fall through */
  2786. +
  2787. + default:
  2788. + if (memcmp (mbc, input_tab_char, mblength) == 0)
  2789. + chars_per_c = chars_per_input_tab;
  2790. +
  2791. + if (memcmp (mbc, input_tab_char, mblength) == 0 || c == '\t')
  2792. + {
  2793. + int width_inc;
  2794. +
  2795. + width_inc = TAB_WIDTH (chars_per_c, input_position);
  2796. + width += width_inc;
  2797. +
  2798. + if (untabify_input)
  2799. + {
  2800. + for (i = width_inc; i; --i)
  2801. + *s++ = ' ';
  2802. + chars += width_inc;
  2803. + }
  2804. + else
  2805. + {
  2806. + for (i = 0; i < mblength; i++)
  2807. + *s++ = mbc[i];
  2808. + chars += mblength;
  2809. + }
  2810. + }
  2811. + else if ((wc_width = wcwidth (wc)) < 1)
  2812. + {
  2813. + if (use_esc_sequence)
  2814. + {
  2815. + for (i = 0; i < mblength; i++)
  2816. + {
  2817. + width += 4;
  2818. + chars += 4;
  2819. + *s++ = '\\';
  2820. + sprintf (esc_buff, "%03o", (unsigned char) mbc[i]);
  2821. + for (j = 0; j <= 2; ++j)
  2822. + *s++ = (int) esc_buff[j];
  2823. + }
  2824. + }
  2825. + else if (use_cntrl_prefix)
  2826. + {
  2827. + if (wc < 0200)
  2828. + {
  2829. + width += 2;
  2830. + chars += 2;
  2831. + *s++ = '^';
  2832. + *s++ = wc ^ 0100;
  2833. + }
  2834. + else
  2835. + {
  2836. + for (i = 0; i < mblength; i++)
  2837. + {
  2838. + width += 4;
  2839. + chars += 4;
  2840. + *s++ = '\\';
  2841. + sprintf (esc_buff, "%03o", (unsigned char) mbc[i]);
  2842. + for (j = 0; j <= 2; ++j)
  2843. + *s++ = (int) esc_buff[j];
  2844. + }
  2845. + }
  2846. + }
  2847. + else if (wc == L'\b')
  2848. + {
  2849. + width += -1;
  2850. + chars += 1;
  2851. + *s++ = c;
  2852. + }
  2853. + else
  2854. + {
  2855. + width += 0;
  2856. + chars += mblength;
  2857. + for (i = 0; i < mblength; i++)
  2858. + *s++ = mbc[i];
  2859. + }
  2860. + }
  2861. + else
  2862. + {
  2863. + width += wc_width;
  2864. + chars += mblength;
  2865. + for (i = 0; i < mblength; i++)
  2866. + *s++ = mbc[i];
  2867. + }
  2868. + }
  2869. + memmove (mbc, mbc + mblength, MB_CUR_MAX - mblength);
  2870. + mbc_pos -= mblength;
  2871. + }
  2872. +
  2873. + /* Too many backspaces must put us in position 0 -- never negative. */
  2874. + if (width < 0 && input_position == 0)
  2875. + {
  2876. + chars = 0;
  2877. + input_position = 0;
  2878. + }
  2879. + else if (width < 0 && input_position <= -width)
  2880. + input_position = 0;
  2881. + else
  2882. + input_position += width;
  2883. +
  2884. + return chars;
  2885. +}
  2886. +#endif
  2887. +
  2888. /* We've just printed some files and need to clean up things before
  2889. looking for more options and printing the next batch of files.
  2890. diff --git a/src/sort.c b/src/sort.c
  2891. index 3b775d6..a0ba243 100644
  2892. --- a/src/sort.c
  2893. +++ b/src/sort.c
  2894. @@ -29,6 +29,14 @@
  2895. #include <sys/wait.h>
  2896. #include <signal.h>
  2897. #include <assert.h>
  2898. +#if HAVE_WCHAR_H
  2899. +# include <wchar.h>
  2900. +#endif
  2901. +/* Get isw* functions. */
  2902. +#if HAVE_WCTYPE_H
  2903. +# include <wctype.h>
  2904. +#endif
  2905. +
  2906. #include "system.h"
  2907. #include "argmatch.h"
  2908. #include "die.h"
  2909. @@ -159,14 +167,39 @@ static int thousands_sep;
  2910. /* We currently ignore multi-byte grouping chars. */
  2911. static bool thousands_sep_ignored;
  2912. +/* True if -f is specified. */
  2913. +static bool folding;
  2914. +
  2915. /* Nonzero if the corresponding locales are hard. */
  2916. static bool hard_LC_COLLATE;
  2917. -#if HAVE_NL_LANGINFO
  2918. +#if HAVE_LANGINFO_CODESET
  2919. static bool hard_LC_TIME;
  2920. #endif
  2921. #define NONZERO(x) ((x) != 0)
  2922. +/* get a multibyte character's byte length. */
  2923. +#define GET_BYTELEN_OF_CHAR(LIM, PTR, MBLENGTH, STATE) \
  2924. + do \
  2925. + { \
  2926. + wchar_t wc; \
  2927. + mbstate_t state_bak; \
  2928. + \
  2929. + state_bak = STATE; \
  2930. + mblength = mbrtowc (&wc, PTR, LIM - PTR, &STATE); \
  2931. + \
  2932. + switch (MBLENGTH) \
  2933. + { \
  2934. + case (size_t)-1: \
  2935. + case (size_t)-2: \
  2936. + STATE = state_bak; \
  2937. + /* Fall through. */ \
  2938. + case 0: \
  2939. + MBLENGTH = 1; \
  2940. + } \
  2941. + } \
  2942. + while (0)
  2943. +
  2944. /* The kind of blanks for '-b' to skip in various options. */
  2945. enum blanktype { bl_start, bl_end, bl_both };
  2946. @@ -343,13 +376,11 @@ static bool stable;
  2947. /* An int value outside char range. */
  2948. enum { NON_CHAR = CHAR_MAX + 1 };
  2949. -/* If TAB has this value, blanks separate fields. */
  2950. -enum { TAB_DEFAULT = CHAR_MAX + 1 };
  2951. -
  2952. -/* Tab character separating fields. If TAB_DEFAULT, then fields are
  2953. +/* Tab character separating fields. If tab_length is 0, then fields are
  2954. separated by the empty string between a non-blank character and a blank
  2955. character. */
  2956. -static int tab = TAB_DEFAULT;
  2957. +static char tab[MB_LEN_MAX + 1];
  2958. +static size_t tab_length = 0;
  2959. /* Flag to remove consecutive duplicate lines from the output.
  2960. Only the last of a sequence of equal lines will be output. */
  2961. @@ -805,6 +836,46 @@ reap_all (void)
  2962. reap (-1);
  2963. }
  2964. +/* Function pointers. */
  2965. +static void
  2966. +(*inittables) (void);
  2967. +static char *
  2968. +(*begfield) (const struct line*, const struct keyfield *);
  2969. +static char *
  2970. +(*limfield) (const struct line*, const struct keyfield *);
  2971. +static void
  2972. +(*skipblanks) (char **ptr, char *lim);
  2973. +static int
  2974. +(*getmonth) (char const *, size_t, char **);
  2975. +static int
  2976. +(*keycompare) (const struct line *, const struct line *);
  2977. +static int
  2978. +(*numcompare) (const char *, const char *);
  2979. +
  2980. +/* Test for white space multibyte character.
  2981. + Set LENGTH the byte length of investigated multibyte character. */
  2982. +#if HAVE_MBRTOWC
  2983. +static int
  2984. +ismbblank (const char *str, size_t len, size_t *length)
  2985. +{
  2986. + size_t mblength;
  2987. + wchar_t wc;
  2988. + mbstate_t state;
  2989. +
  2990. + memset (&state, '\0', sizeof(mbstate_t));
  2991. + mblength = mbrtowc (&wc, str, len, &state);
  2992. +
  2993. + if (mblength == (size_t)-1 || mblength == (size_t)-2)
  2994. + {
  2995. + *length = 1;
  2996. + return 0;
  2997. + }
  2998. +
  2999. + *length = (mblength < 1) ? 1 : mblength;
  3000. + return iswblank (wc) || wc == '\n';
  3001. +}
  3002. +#endif
  3003. +
  3004. /* Clean up any remaining temporary files. */
  3005. static void
  3006. @@ -1272,7 +1343,7 @@ zaptemp (char const *name)
  3007. free (node);
  3008. }
  3009. -#if HAVE_NL_LANGINFO
  3010. +#if HAVE_LANGINFO_CODESET
  3011. static int
  3012. struct_month_cmp (void const *m1, void const *m2)
  3013. @@ -1287,7 +1358,7 @@ struct_month_cmp (void const *m1, void const *m2)
  3014. /* Initialize the character class tables. */
  3015. static void
  3016. -inittables (void)
  3017. +inittables_uni (void)
  3018. {
  3019. size_t i;
  3020. @@ -1299,7 +1370,7 @@ inittables (void)
  3021. fold_toupper[i] = toupper (i);
  3022. }
  3023. -#if HAVE_NL_LANGINFO
  3024. +#if HAVE_LANGINFO_CODESET
  3025. /* If we're not in the "C" locale, read different names for months. */
  3026. if (hard_LC_TIME)
  3027. {
  3028. @@ -1381,6 +1452,84 @@ specify_nmerge (int oi, char c, char const *s)
  3029. xstrtol_fatal (e, oi, c, long_options, s);
  3030. }
  3031. +#if HAVE_MBRTOWC
  3032. +static void
  3033. +inittables_mb (void)
  3034. +{
  3035. + int i, j, k, l;
  3036. + char *name, *s, *lc_time, *lc_ctype;
  3037. + size_t s_len, mblength;
  3038. + char mbc[MB_LEN_MAX];
  3039. + wchar_t wc, pwc;
  3040. + mbstate_t state_mb, state_wc;
  3041. +
  3042. + lc_time = setlocale (LC_TIME, "");
  3043. + if (lc_time)
  3044. + lc_time = xstrdup (lc_time);
  3045. +
  3046. + lc_ctype = setlocale (LC_CTYPE, "");
  3047. + if (lc_ctype)
  3048. + lc_ctype = xstrdup (lc_ctype);
  3049. +
  3050. + if (lc_time && lc_ctype)
  3051. + /* temporarily set LC_CTYPE to match LC_TIME, so that we can convert
  3052. + * the names of months to upper case */
  3053. + setlocale (LC_CTYPE, lc_time);
  3054. +
  3055. + for (i = 0; i < MONTHS_PER_YEAR; i++)
  3056. + {
  3057. + s = (char *) nl_langinfo (ABMON_1 + i);
  3058. + s_len = strlen (s);
  3059. + monthtab[i].name = name = (char *) xmalloc (s_len + 1);
  3060. + monthtab[i].val = i + 1;
  3061. +
  3062. + memset (&state_mb, '\0', sizeof (mbstate_t));
  3063. + memset (&state_wc, '\0', sizeof (mbstate_t));
  3064. +
  3065. + for (j = 0; j < s_len;)
  3066. + {
  3067. + if (!ismbblank (s + j, s_len - j, &mblength))
  3068. + break;
  3069. + j += mblength;
  3070. + }
  3071. +
  3072. + for (k = 0; j < s_len;)
  3073. + {
  3074. + mblength = mbrtowc (&wc, (s + j), (s_len - j), &state_mb);
  3075. + assert (mblength != (size_t)-1 && mblength != (size_t)-2);
  3076. + if (mblength == 0)
  3077. + break;
  3078. +
  3079. + pwc = towupper (wc);
  3080. + if (pwc == wc)
  3081. + {
  3082. + memcpy (mbc, s + j, mblength);
  3083. + j += mblength;
  3084. + }
  3085. + else
  3086. + {
  3087. + j += mblength;
  3088. + mblength = wcrtomb (mbc, pwc, &state_wc);
  3089. + assert (mblength != (size_t)0 && mblength != (size_t)-1);
  3090. + }
  3091. +
  3092. + for (l = 0; l < mblength; l++)
  3093. + name[k++] = mbc[l];
  3094. + }
  3095. + name[k] = '\0';
  3096. + }
  3097. + qsort ((void *) monthtab, MONTHS_PER_YEAR,
  3098. + sizeof (struct month), struct_month_cmp);
  3099. +
  3100. + if (lc_time && lc_ctype)
  3101. + /* restore the original locales */
  3102. + setlocale (LC_CTYPE, lc_ctype);
  3103. +
  3104. + free (lc_ctype);
  3105. + free (lc_time);
  3106. +}
  3107. +#endif
  3108. +
  3109. /* Specify the amount of main memory to use when sorting. */
  3110. static void
  3111. specify_sort_size (int oi, char c, char const *s)
  3112. @@ -1612,7 +1761,7 @@ buffer_linelim (struct buffer const *buf)
  3113. by KEY in LINE. */
  3114. static char *
  3115. -begfield (struct line const *line, struct keyfield const *key)
  3116. +begfield_uni (const struct line *line, const struct keyfield *key)
  3117. {
  3118. char *ptr = line->text, *lim = ptr + line->length - 1;
  3119. size_t sword = key->sword;
  3120. @@ -1621,10 +1770,10 @@ begfield (struct line const *line, struct keyfield const *key)
  3121. /* The leading field separator itself is included in a field when -t
  3122. is absent. */
  3123. - if (tab != TAB_DEFAULT)
  3124. + if (tab_length)
  3125. while (ptr < lim && sword--)
  3126. {
  3127. - while (ptr < lim && *ptr != tab)
  3128. + while (ptr < lim && *ptr != tab[0])
  3129. ++ptr;
  3130. if (ptr < lim)
  3131. ++ptr;
  3132. @@ -1650,12 +1799,71 @@ begfield (struct line const *line, struct keyfield const *key)
  3133. return ptr;
  3134. }
  3135. +#if HAVE_MBRTOWC
  3136. +static char *
  3137. +begfield_mb (const struct line *line, const struct keyfield *key)
  3138. +{
  3139. + int i;
  3140. + char *ptr = line->text, *lim = ptr + line->length - 1;
  3141. + size_t sword = key->sword;
  3142. + size_t schar = key->schar;
  3143. + size_t mblength;
  3144. + mbstate_t state;
  3145. +
  3146. + memset (&state, '\0', sizeof(mbstate_t));
  3147. +
  3148. + if (tab_length)
  3149. + while (ptr < lim && sword--)
  3150. + {
  3151. + while (ptr < lim && memcmp (ptr, tab, tab_length) != 0)
  3152. + {
  3153. + GET_BYTELEN_OF_CHAR (lim, ptr, mblength, state);
  3154. + ptr += mblength;
  3155. + }
  3156. + if (ptr < lim)
  3157. + {
  3158. + GET_BYTELEN_OF_CHAR (lim, ptr, mblength, state);
  3159. + ptr += mblength;
  3160. + }
  3161. + }
  3162. + else
  3163. + while (ptr < lim && sword--)
  3164. + {
  3165. + while (ptr < lim && ismbblank (ptr, lim - ptr, &mblength))
  3166. + ptr += mblength;
  3167. + if (ptr < lim)
  3168. + {
  3169. + GET_BYTELEN_OF_CHAR (lim, ptr, mblength, state);
  3170. + ptr += mblength;
  3171. + }
  3172. + while (ptr < lim && !ismbblank (ptr, lim - ptr, &mblength))
  3173. + ptr += mblength;
  3174. + }
  3175. +
  3176. + if (key->skipsblanks)
  3177. + while (ptr < lim && ismbblank (ptr, lim - ptr, &mblength))
  3178. + ptr += mblength;
  3179. +
  3180. + for (i = 0; i < schar; i++)
  3181. + {
  3182. + GET_BYTELEN_OF_CHAR (lim, ptr, mblength, state);
  3183. +
  3184. + if (ptr + mblength > lim)
  3185. + break;
  3186. + else
  3187. + ptr += mblength;
  3188. + }
  3189. +
  3190. + return ptr;
  3191. +}
  3192. +#endif
  3193. +
  3194. /* Return the limit of (a pointer to the first character after) the field
  3195. in LINE specified by KEY. */
  3196. ATTRIBUTE_PURE
  3197. static char *
  3198. -limfield (struct line const *line, struct keyfield const *key)
  3199. +limfield_uni (struct line const *line, struct keyfield const *key)
  3200. {
  3201. char *ptr = line->text, *lim = ptr + line->length - 1;
  3202. size_t eword = key->eword, echar = key->echar;
  3203. @@ -1670,10 +1878,10 @@ limfield (struct line const *line, struct keyfield const *key)
  3204. 'beginning' is the first character following the delimiting TAB.
  3205. Otherwise, leave PTR pointing at the first 'blank' character after
  3206. the preceding field. */
  3207. - if (tab != TAB_DEFAULT)
  3208. + if (tab_length)
  3209. while (ptr < lim && eword--)
  3210. {
  3211. - while (ptr < lim && *ptr != tab)
  3212. + while (ptr < lim && *ptr != tab[0])
  3213. ++ptr;
  3214. if (ptr < lim && (eword || echar))
  3215. ++ptr;
  3216. @@ -1719,10 +1927,10 @@ limfield (struct line const *line, struct keyfield const *key)
  3217. */
  3218. /* Make LIM point to the end of (one byte past) the current field. */
  3219. - if (tab != TAB_DEFAULT)
  3220. + if (tab_length)
  3221. {
  3222. char *newlim;
  3223. - newlim = memchr (ptr, tab, lim - ptr);
  3224. + newlim = memchr (ptr, tab[0], lim - ptr);
  3225. if (newlim)
  3226. lim = newlim;
  3227. }
  3228. @@ -1753,6 +1961,130 @@ limfield (struct line const *line, struct keyfield const *key)
  3229. return ptr;
  3230. }
  3231. +#if HAVE_MBRTOWC
  3232. +static char * _GL_ATTRIBUTE_PURE
  3233. +limfield_mb (const struct line *line, const struct keyfield *key)
  3234. +{
  3235. + char *ptr = line->text, *lim = ptr + line->length - 1;
  3236. + size_t eword = key->eword, echar = key->echar;
  3237. + int i;
  3238. + size_t mblength;
  3239. + mbstate_t state;
  3240. +
  3241. + if (echar == 0)
  3242. + eword++; /* skip all of end field. */
  3243. +
  3244. + memset (&state, '\0', sizeof(mbstate_t));
  3245. +
  3246. + if (tab_length)
  3247. + while (ptr < lim && eword--)
  3248. + {
  3249. + while (ptr < lim && memcmp (ptr, tab, tab_length) != 0)
  3250. + {
  3251. + GET_BYTELEN_OF_CHAR (lim, ptr, mblength, state);
  3252. + ptr += mblength;
  3253. + }
  3254. + if (ptr < lim && (eword | echar))
  3255. + {
  3256. + GET_BYTELEN_OF_CHAR (lim, ptr, mblength, state);
  3257. + ptr += mblength;
  3258. + }
  3259. + }
  3260. + else
  3261. + while (ptr < lim && eword--)
  3262. + {
  3263. + while (ptr < lim && ismbblank (ptr, lim - ptr, &mblength))
  3264. + ptr += mblength;
  3265. + if (ptr < lim)
  3266. + {
  3267. + GET_BYTELEN_OF_CHAR (lim, ptr, mblength, state);
  3268. + ptr += mblength;
  3269. + }
  3270. + while (ptr < lim && !ismbblank (ptr, lim - ptr, &mblength))
  3271. + ptr += mblength;
  3272. + }
  3273. +
  3274. +
  3275. +# ifdef POSIX_UNSPECIFIED
  3276. + /* Make LIM point to the end of (one byte past) the current field. */
  3277. + if (tab_length)
  3278. + {
  3279. + char *newlim, *p;
  3280. +
  3281. + newlim = NULL;
  3282. + for (p = ptr; p < lim;)
  3283. + {
  3284. + if (memcmp (p, tab, tab_length) == 0)
  3285. + {
  3286. + newlim = p;
  3287. + break;
  3288. + }
  3289. +
  3290. + GET_BYTELEN_OF_CHAR (lim, ptr, mblength, state);
  3291. + p += mblength;
  3292. + }
  3293. + }
  3294. + else
  3295. + {
  3296. + char *newlim;
  3297. + newlim = ptr;
  3298. +
  3299. + while (newlim < lim && ismbblank (newlim, lim - newlim, &mblength))
  3300. + newlim += mblength;
  3301. + if (ptr < lim)
  3302. + {
  3303. + GET_BYTELEN_OF_CHAR (lim, ptr, mblength, state);
  3304. + ptr += mblength;
  3305. + }
  3306. + while (newlim < lim && !ismbblank (newlim, lim - newlim, &mblength))
  3307. + newlim += mblength;
  3308. + lim = newlim;
  3309. + }
  3310. +# endif
  3311. +
  3312. + if (echar != 0)
  3313. + {
  3314. + /* If we're skipping leading blanks, don't start counting characters
  3315. + * until after skipping past any leading blanks. */
  3316. + if (key->skipeblanks)
  3317. + while (ptr < lim && ismbblank (ptr, lim - ptr, &mblength))
  3318. + ptr += mblength;
  3319. +
  3320. + memset (&state, '\0', sizeof(mbstate_t));
  3321. +
  3322. + /* Advance PTR by ECHAR (if possible), but no further than LIM. */
  3323. + for (i = 0; i < echar; i++)
  3324. + {
  3325. + GET_BYTELEN_OF_CHAR (lim, ptr, mblength, state);
  3326. +
  3327. + if (ptr + mblength > lim)
  3328. + break;
  3329. + else
  3330. + ptr += mblength;
  3331. + }
  3332. + }
  3333. +
  3334. + return ptr;
  3335. +}
  3336. +#endif
  3337. +
  3338. +static void
  3339. +skipblanks_uni (char **ptr, char *lim)
  3340. +{
  3341. + while (*ptr < lim && blanks[to_uchar (**ptr)])
  3342. + ++(*ptr);
  3343. +}
  3344. +
  3345. +#if HAVE_MBRTOWC
  3346. +static void
  3347. +skipblanks_mb (char **ptr, char *lim)
  3348. +{
  3349. + size_t mblength;
  3350. + while (*ptr < lim && ismbblank (*ptr, lim - *ptr, &mblength))
  3351. + (*ptr) += mblength;
  3352. +}
  3353. +#endif
  3354. +
  3355. /* Fill BUF reading from FP, moving buf->left bytes from the end
  3356. of buf->buf to the beginning first. If EOF is reached and the
  3357. file wasn't terminated by a newline, supply one. Set up BUF's line
  3358. @@ -1839,8 +2171,22 @@ fillbuf (struct buffer *buf, FILE *fp, char const *file)
  3359. else
  3360. {
  3361. if (key->skipsblanks)
  3362. - while (blanks[to_uchar (*line_start)])
  3363. - line_start++;
  3364. + {
  3365. +#if HAVE_MBRTOWC
  3366. + if (MB_CUR_MAX > 1)
  3367. + {
  3368. + size_t mblength;
  3369. + while (line_start < line->keylim &&
  3370. + ismbblank (line_start,
  3371. + line->keylim - line_start,
  3372. + &mblength))
  3373. + line_start += mblength;
  3374. + }
  3375. + else
  3376. +#endif
  3377. + while (blanks[to_uchar (*line_start)])
  3378. + line_start++;
  3379. + }
  3380. line->keybeg = line_start;
  3381. }
  3382. }
  3383. @@ -1976,12 +2322,10 @@ find_unit_order (char const *number)
  3384. ATTRIBUTE_PURE
  3385. static int
  3386. -human_numcompare (char const *a, char const *b)
  3387. +human_numcompare (char *a, char *b)
  3388. {
  3389. - while (blanks[to_uchar (*a)])
  3390. - a++;
  3391. - while (blanks[to_uchar (*b)])
  3392. - b++;
  3393. + skipblanks(&a, a + strlen(a));
  3394. + skipblanks(&b, b + strlen(b));
  3395. int diff = find_unit_order (a) - find_unit_order (b);
  3396. return (diff ? diff : strnumcmp (a, b, decimal_point, thousands_sep));
  3397. @@ -1993,7 +2337,7 @@ human_numcompare (char const *a, char const *b)
  3398. ATTRIBUTE_PURE
  3399. static int
  3400. -numcompare (char const *a, char const *b)
  3401. +numcompare_uni (const char *a, const char *b)
  3402. {
  3403. while (blanks[to_uchar (*a)])
  3404. a++;
  3405. @@ -2003,6 +2347,25 @@ numcompare (char const *a, char const *b)
  3406. return strnumcmp (a, b, decimal_point, thousands_sep);
  3407. }
  3408. +#if HAVE_MBRTOWC
  3409. +static int
  3410. +numcompare_mb (const char *a, const char *b)
  3411. +{
  3412. + size_t mblength, len;
  3413. + len = strlen (a); /* okay for UTF-8 */
  3414. + while (*a && ismbblank (a, len > MB_CUR_MAX ? MB_CUR_MAX : len, &mblength))
  3415. + {
  3416. + a += mblength;
  3417. + len -= mblength;
  3418. + }
  3419. + len = strlen (b); /* okay for UTF-8 */
  3420. + while (*b && ismbblank (b, len > MB_CUR_MAX ? MB_CUR_MAX : len, &mblength))
  3421. + b += mblength;
  3422. +
  3423. + return strnumcmp (a, b, decimal_point, thousands_sep);
  3424. +}
  3425. +#endif /* HAV_EMBRTOWC */
  3426. +
  3427. /* Work around a problem whereby the long double value returned by glibc's
  3428. strtold ("NaN", ...) contains uninitialized bits: clear all bytes of
  3429. A and B before calling strtold. FIXME: remove this function if
  3430. @@ -2053,7 +2416,7 @@ general_numcompare (char const *sa, char const *sb)
  3431. Return 0 if the name in S is not recognized. */
  3432. static int
  3433. -getmonth (char const *month, char **ea)
  3434. +getmonth_uni (char const *month, size_t len, char **ea)
  3435. {
  3436. size_t lo = 0;
  3437. size_t hi = MONTHS_PER_YEAR;
  3438. @@ -2329,15 +2692,14 @@ debug_key (struct line const *line, struct keyfield const *key)
  3439. char saved = *lim;
  3440. *lim = '\0';
  3441. - while (blanks[to_uchar (*beg)])
  3442. - beg++;
  3443. + skipblanks (&beg, lim);
  3444. char *tighter_lim = beg;
  3445. if (lim < beg)
  3446. tighter_lim = lim;
  3447. else if (key->month)
  3448. - getmonth (beg, &tighter_lim);
  3449. + getmonth (beg, lim-beg, &tighter_lim);
  3450. else if (key->general_numeric)
  3451. ignore_value (strtold (beg, &tighter_lim));
  3452. else if (key->numeric || key->human_numeric)
  3453. @@ -2483,7 +2845,7 @@ key_warnings (struct keyfield const *gkey, bool gkey_only)
  3454. /* Warn about significant leading blanks. */
  3455. bool implicit_skip = key_numeric (key) || key->month;
  3456. bool line_offset = key->eword == 0 && key->echar != 0; /* -k1.x,1.y */
  3457. - if (!zero_width && !gkey_only && tab == TAB_DEFAULT && !line_offset
  3458. + if (!zero_width && !gkey_only && !tab_length && !line_offset
  3459. && ((!key->skipsblanks && !implicit_skip)
  3460. || (!key->skipsblanks && key->schar)
  3461. || (!key->skipeblanks && key->echar)))
  3462. @@ -2531,9 +2893,9 @@ key_warnings (struct keyfield const *gkey, bool gkey_only)
  3463. bool number_locale_warned = false;
  3464. if (basic_numeric_field_span)
  3465. {
  3466. - if (tab == TAB_DEFAULT
  3467. - ? thousands_sep != NON_CHAR && (isblank (to_uchar (thousands_sep)))
  3468. - : tab == thousands_sep)
  3469. + if (tab_length
  3470. + ? tab[0] == thousands_sep
  3471. + : thousands_sep != NON_CHAR && (isblank (to_uchar (thousands_sep))))
  3472. {
  3473. error (0, 0,
  3474. _("field separator %s is treated as a "
  3475. @@ -2544,9 +2906,9 @@ key_warnings (struct keyfield const *gkey, bool gkey_only)
  3476. }
  3477. if (basic_numeric_field_span || general_numeric_field_span)
  3478. {
  3479. - if (tab == TAB_DEFAULT
  3480. - ? thousands_sep != NON_CHAR && (isblank (to_uchar (decimal_point)))
  3481. - : tab == decimal_point)
  3482. + if (tab_length
  3483. + ? tab[0] == decimal_point
  3484. + : thousands_sep != NON_CHAR && (isblank (to_uchar (decimal_point))))
  3485. {
  3486. error (0, 0,
  3487. _("field separator %s is treated as a "
  3488. @@ -2554,19 +2916,19 @@ key_warnings (struct keyfield const *gkey, bool gkey_only)
  3489. quote (((char []) {decimal_point, 0})));
  3490. number_locale_warned = true;
  3491. }
  3492. - else if (tab == '-')
  3493. + else if (tab_length && tab[0] == '-')
  3494. {
  3495. error (0, 0,
  3496. _("field separator %s is treated as a "
  3497. "minus sign in numbers"),
  3498. - quote (((char []) {tab, 0})));
  3499. + quote (((char []) {tab[0], 0})));
  3500. }
  3501. - else if (general_numeric_field_span && tab == '+')
  3502. + else if (general_numeric_field_span && tab_length && tab[0] == '+')
  3503. {
  3504. error (0, 0,
  3505. _("field separator %s is treated as a "
  3506. "plus sign in numbers"),
  3507. - quote (((char []) {tab, 0})));
  3508. + quote (((char []) {tab[0], 0})));
  3509. }
  3510. }
  3511. @@ -2577,7 +2939,7 @@ key_warnings (struct keyfield const *gkey, bool gkey_only)
  3512. {
  3513. error (0, 0,
  3514. _("%snumbers use %s as a decimal point in this locale"),
  3515. - tab == decimal_point ? "" : _("note "),
  3516. + (tab_length && tab[0] == decimal_point) ? "" : _("note "),
  3517. quote (((char []) {decimal_point, 0})));
  3518. }
  3519. @@ -2610,11 +2972,87 @@ key_warnings (struct keyfield const *gkey, bool gkey_only)
  3520. error (0, 0, _("option '-r' only applies to last-resort comparison"));
  3521. }
  3522. +#if HAVE_MBRTOWC
  3523. +static int
  3524. +getmonth_mb (const char *s, size_t len, char **ea)
  3525. +{
  3526. + char *month;
  3527. + register size_t i;
  3528. + register int lo = 0, hi = MONTHS_PER_YEAR, result;
  3529. + char *tmp;
  3530. + size_t wclength, mblength;
  3531. + const char *pp;
  3532. + const wchar_t *wpp;
  3533. + wchar_t *month_wcs;
  3534. + mbstate_t state;
  3535. +
  3536. + while (len > 0 && ismbblank (s, len, &mblength))
  3537. + {
  3538. + s += mblength;
  3539. + len -= mblength;
  3540. + }
  3541. +
  3542. + if (len == 0)
  3543. + return 0;
  3544. +
  3545. + if (SIZE_MAX - len < 1)
  3546. + xalloc_die ();
  3547. +
  3548. + month = (char *) xnmalloc (len + 1, MB_CUR_MAX);
  3549. +
  3550. + pp = tmp = (char *) xnmalloc (len + 1, MB_CUR_MAX);
  3551. + memcpy (tmp, s, len);
  3552. + tmp[len] = '\0';
  3553. + wpp = month_wcs = (wchar_t *) xnmalloc (len + 1, sizeof (wchar_t));
  3554. + memset (&state, '\0', sizeof (mbstate_t));
  3555. +
  3556. + wclength = mbsrtowcs (month_wcs, &pp, len + 1, &state);
  3557. + if (wclength == (size_t)-1 || pp != NULL)
  3558. + error (SORT_FAILURE, 0, _("Invalid multibyte input %s."), quote(s));
  3559. +
  3560. + for (i = 0; i < wclength; i++)
  3561. + {
  3562. + month_wcs[i] = towupper(month_wcs[i]);
  3563. + if (iswblank (month_wcs[i]))
  3564. + {
  3565. + month_wcs[i] = L'\0';
  3566. + break;
  3567. + }
  3568. + }
  3569. +
  3570. + mblength = wcsrtombs (month, &wpp, (len + 1) * MB_CUR_MAX, &state);
  3571. + assert (mblength != (-1) && wpp == NULL);
  3572. +
  3573. + do
  3574. + {
  3575. + int ix = (lo + hi) / 2;
  3576. +
  3577. + if (strncmp (month, monthtab[ix].name, strlen (monthtab[ix].name)) < 0)
  3578. + hi = ix;
  3579. + else
  3580. + lo = ix;
  3581. + }
  3582. + while (hi - lo > 1);
  3583. +
  3584. + result = (!strncmp (month, monthtab[lo].name, strlen (monthtab[lo].name))
  3585. + ? monthtab[lo].val : 0);
  3586. +
  3587. + if (ea && result)
  3588. + *ea = (char*) s + strlen (monthtab[lo].name);
  3589. +
  3590. + free (month);
  3591. + free (tmp);
  3592. + free (month_wcs);
  3593. +
  3594. + return result;
  3595. +}
  3596. +#endif
  3597. +
  3598. /* Compare two lines A and B trying every key in sequence until there
  3599. are no more keys or a difference is found. */
  3600. static int
  3601. -keycompare (struct line const *a, struct line const *b)
  3602. +keycompare_uni (const struct line *a, const struct line *b)
  3603. {
  3604. struct keyfield *key = keylist;
  3605. @@ -2699,7 +3137,7 @@ keycompare (struct line const *a, struct line const *b)
  3606. else if (key->human_numeric)
  3607. diff = human_numcompare (ta, tb);
  3608. else if (key->month)
  3609. - diff = getmonth (ta, NULL) - getmonth (tb, NULL);
  3610. + diff = getmonth (ta, tlena, NULL) - getmonth (tb, tlenb, NULL);
  3611. else if (key->random)
  3612. diff = compare_random (ta, tlena, tb, tlenb);
  3613. else if (key->version)
  3614. @@ -2815,6 +3253,211 @@ keycompare (struct line const *a, struct line const *b)
  3615. return key->reverse ? -diff : diff;
  3616. }
  3617. +#if HAVE_MBRTOWC
  3618. +static int
  3619. +keycompare_mb (const struct line *a, const struct line *b)
  3620. +{
  3621. + struct keyfield *key = keylist;
  3622. +
  3623. + /* For the first iteration only, the key positions have been
  3624. + precomputed for us. */
  3625. + char *texta = a->keybeg;
  3626. + char *textb = b->keybeg;
  3627. + char *lima = a->keylim;
  3628. + char *limb = b->keylim;
  3629. +
  3630. + size_t mblength_a, mblength_b;
  3631. + wchar_t wc_a, wc_b;
  3632. + mbstate_t state_a, state_b;
  3633. +
  3634. + int diff = 0;
  3635. +
  3636. + memset (&state_a, '\0', sizeof(mbstate_t));
  3637. + memset (&state_b, '\0', sizeof(mbstate_t));
  3638. + /* Ignore keys with start after end. */
  3639. + if (a->keybeg - a->keylim > 0)
  3640. + return 0;
  3641. +
  3642. +
  3643. + /* Ignore and/or translate chars before comparing. */
  3644. +# define IGNORE_CHARS(NEW_LEN, LEN, TEXT, COPY, WC, MBLENGTH, STATE) \
  3645. + do \
  3646. + { \
  3647. + wchar_t uwc; \
  3648. + char mbc[MB_LEN_MAX]; \
  3649. + mbstate_t state_wc; \
  3650. + \
  3651. + for (NEW_LEN = i = 0; i < LEN;) \
  3652. + { \
  3653. + mbstate_t state_bak; \
  3654. + \
  3655. + state_bak = STATE; \
  3656. + MBLENGTH = mbrtowc (&WC, TEXT + i, LEN - i, &STATE); \
  3657. + \
  3658. + if (MBLENGTH == (size_t)-2 || MBLENGTH == (size_t)-1 \
  3659. + || MBLENGTH == 0) \
  3660. + { \
  3661. + if (MBLENGTH == (size_t)-2 || MBLENGTH == (size_t)-1) \
  3662. + STATE = state_bak; \
  3663. + if (!ignore) \
  3664. + COPY[NEW_LEN++] = TEXT[i]; \
  3665. + i++; \
  3666. + continue; \
  3667. + } \
  3668. + \
  3669. + if (ignore) \
  3670. + { \
  3671. + if ((ignore == nonprinting && !iswprint (WC)) \
  3672. + || (ignore == nondictionary \
  3673. + && !iswalnum (WC) && !iswblank (WC))) \
  3674. + { \
  3675. + i += MBLENGTH; \
  3676. + continue; \
  3677. + } \
  3678. + } \
  3679. + \
  3680. + if (translate) \
  3681. + { \
  3682. + \
  3683. + uwc = towupper(WC); \
  3684. + if (WC == uwc) \
  3685. + { \
  3686. + memcpy (mbc, TEXT + i, MBLENGTH); \
  3687. + i += MBLENGTH; \
  3688. + } \
  3689. + else \
  3690. + { \
  3691. + i += MBLENGTH; \
  3692. + WC = uwc; \
  3693. + memset (&state_wc, '\0', sizeof (mbstate_t)); \
  3694. + \
  3695. + MBLENGTH = wcrtomb (mbc, WC, &state_wc); \
  3696. + assert (MBLENGTH != (size_t)-1 && MBLENGTH != 0); \
  3697. + } \
  3698. + \
  3699. + for (j = 0; j < MBLENGTH; j++) \
  3700. + COPY[NEW_LEN++] = mbc[j]; \
  3701. + } \
  3702. + else \
  3703. + for (j = 0; j < MBLENGTH; j++) \
  3704. + COPY[NEW_LEN++] = TEXT[i++]; \
  3705. + } \
  3706. + COPY[NEW_LEN] = '\0'; \
  3707. + } \
  3708. + while (0)
  3709. +
  3710. + /* Actually compare the fields. */
  3711. +
  3712. + for (;;)
  3713. + {
  3714. + /* Find the lengths. */
  3715. + size_t lena = lima <= texta ? 0 : lima - texta;
  3716. + size_t lenb = limb <= textb ? 0 : limb - textb;
  3717. +
  3718. + char enda IF_LINT (= 0);
  3719. + char endb IF_LINT (= 0);
  3720. +
  3721. + char const *translate = key->translate;
  3722. + bool const *ignore = key->ignore;
  3723. +
  3724. + if (ignore || translate)
  3725. + {
  3726. + if (SIZE_MAX - lenb - 2 < lena)
  3727. + xalloc_die ();
  3728. + char *copy_a = (char *) xnmalloc (lena + lenb + 2, MB_CUR_MAX);
  3729. + char *copy_b = copy_a + lena * MB_CUR_MAX + 1;
  3730. + size_t new_len_a, new_len_b;
  3731. + size_t i, j;
  3732. +
  3733. + IGNORE_CHARS (new_len_a, lena, texta, copy_a,
  3734. + wc_a, mblength_a, state_a);
  3735. + IGNORE_CHARS (new_len_b, lenb, textb, copy_b,
  3736. + wc_b, mblength_b, state_b);
  3737. + texta = copy_a; textb = copy_b;
  3738. + lena = new_len_a; lenb = new_len_b;
  3739. + }
  3740. + else
  3741. + {
  3742. + /* Use the keys in-place, temporarily null-terminated. */
  3743. + enda = texta[lena]; texta[lena] = '\0';
  3744. + endb = textb[lenb]; textb[lenb] = '\0';
  3745. + }
  3746. +
  3747. + if (key->random)
  3748. + diff = compare_random (texta, lena, textb, lenb);
  3749. + else if (key->numeric | key->general_numeric | key->human_numeric)
  3750. + {
  3751. + char savea = *lima, saveb = *limb;
  3752. +
  3753. + *lima = *limb = '\0';
  3754. + diff = (key->numeric ? numcompare (texta, textb)
  3755. + : key->general_numeric ? general_numcompare (texta, textb)
  3756. + : human_numcompare (texta, textb));
  3757. + *lima = savea, *limb = saveb;
  3758. + }
  3759. + else if (key->version)
  3760. + diff = filevercmp (texta, textb);
  3761. + else if (key->month)
  3762. + diff = getmonth (texta, lena, NULL) - getmonth (textb, lenb, NULL);
  3763. + else if (lena == 0)
  3764. + diff = - NONZERO (lenb);
  3765. + else if (lenb == 0)
  3766. + diff = 1;
  3767. + else if (hard_LC_COLLATE && !folding)
  3768. + {
  3769. + diff = xmemcoll0 (texta, lena + 1, textb, lenb + 1);
  3770. + }
  3771. + else
  3772. + {
  3773. + diff = memcmp (texta, textb, MIN (lena, lenb));
  3774. + if (diff == 0)
  3775. + diff = lena < lenb ? -1 : lena != lenb;
  3776. + }
  3777. +
  3778. + if (ignore || translate)
  3779. + free (texta);
  3780. + else
  3781. + {
  3782. + texta[lena] = enda;
  3783. + textb[lenb] = endb;
  3784. + }
  3785. +
  3786. + if (diff)
  3787. + goto not_equal;
  3788. +
  3789. + key = key->next;
  3790. + if (! key)
  3791. + break;
  3792. +
  3793. + /* Find the beginning and limit of the next field. */
  3794. + if (key->eword != -1)
  3795. + lima = limfield (a, key), limb = limfield (b, key);
  3796. + else
  3797. + lima = a->text + a->length - 1, limb = b->text + b->length - 1;
  3798. +
  3799. + if (key->sword != -1)
  3800. + texta = begfield (a, key), textb = begfield (b, key);
  3801. + else
  3802. + {
  3803. + texta = a->text, textb = b->text;
  3804. + if (key->skipsblanks)
  3805. + {
  3806. + while (texta < lima && ismbblank (texta, lima - texta, &mblength_a))
  3807. + texta += mblength_a;
  3808. + while (textb < limb && ismbblank (textb, limb - textb, &mblength_b))
  3809. + textb += mblength_b;
  3810. + }
  3811. + }
  3812. + }
  3813. +
  3814. +not_equal:
  3815. + if (key && key->reverse)
  3816. + return -diff;
  3817. + else
  3818. + return diff;
  3819. +}
  3820. +#endif
  3821. +
  3822. /* Compare two lines A and B, returning negative, zero, or positive
  3823. depending on whether A compares less than, equal to, or greater than B. */
  3824. @@ -2842,7 +3485,7 @@ compare (struct line const *a, struct line const *b)
  3825. diff = - NONZERO (blen);
  3826. else if (blen == 0)
  3827. diff = 1;
  3828. - else if (hard_LC_COLLATE)
  3829. + else if (hard_LC_COLLATE && !folding)
  3830. {
  3831. /* xmemcoll0 is a performance enhancement as
  3832. it will not unconditionally write '\0' after the
  3833. @@ -4226,6 +4869,7 @@ set_ordering (char const *s, struct keyfield *key, enum blanktype blanktype)
  3834. break;
  3835. case 'f':
  3836. key->translate = fold_toupper;
  3837. + folding = true;
  3838. break;
  3839. case 'g':
  3840. key->general_numeric = true;
  3841. @@ -4305,7 +4949,7 @@ main (int argc, char **argv)
  3842. initialize_exit_failure (SORT_FAILURE);
  3843. hard_LC_COLLATE = hard_locale (LC_COLLATE);
  3844. -#if HAVE_NL_LANGINFO
  3845. +#if HAVE_LANGINFO_CODESET
  3846. hard_LC_TIME = hard_locale (LC_TIME);
  3847. #endif
  3848. @@ -4328,6 +4972,29 @@ main (int argc, char **argv)
  3849. thousands_sep = NON_CHAR;
  3850. }
  3851. +#if HAVE_MBRTOWC
  3852. + if (MB_CUR_MAX > 1)
  3853. + {
  3854. + inittables = inittables_mb;
  3855. + begfield = begfield_mb;
  3856. + limfield = limfield_mb;
  3857. + skipblanks = skipblanks_mb;
  3858. + getmonth = getmonth_mb;
  3859. + keycompare = keycompare_mb;
  3860. + numcompare = numcompare_mb;
  3861. + }
  3862. + else
  3863. +#endif
  3864. + {
  3865. + inittables = inittables_uni;
  3866. + begfield = begfield_uni;
  3867. + limfield = limfield_uni;
  3868. + skipblanks = skipblanks_uni;
  3869. + getmonth = getmonth_uni;
  3870. + keycompare = keycompare_uni;
  3871. + numcompare = numcompare_uni;
  3872. + }
  3873. +
  3874. have_read_stdin = false;
  3875. inittables ();
  3876. @@ -4602,13 +5269,34 @@ main (int argc, char **argv)
  3877. case 't':
  3878. {
  3879. - char newtab = optarg[0];
  3880. - if (! newtab)
  3881. + char newtab[MB_LEN_MAX + 1];
  3882. + size_t newtab_length = 1;
  3883. + strncpy (newtab, optarg, MB_LEN_MAX);
  3884. + if (! newtab[0])
  3885. die (SORT_FAILURE, 0, _("empty tab"));
  3886. - if (optarg[1])
  3887. +#if HAVE_MBRTOWC
  3888. + if (MB_CUR_MAX > 1)
  3889. + {
  3890. + wchar_t wc;
  3891. + mbstate_t state;
  3892. +
  3893. + memset (&state, '\0', sizeof (mbstate_t));
  3894. + newtab_length = mbrtowc (&wc, newtab, strnlen (newtab,
  3895. + MB_LEN_MAX),
  3896. + &state);
  3897. + switch (newtab_length)
  3898. + {
  3899. + case (size_t) -1:
  3900. + case (size_t) -2:
  3901. + case 0:
  3902. + newtab_length = 1;
  3903. + }
  3904. + }
  3905. +#endif
  3906. + if (newtab_length == 1 && optarg[1])
  3907. {
  3908. if (STREQ (optarg, "\\0"))
  3909. - newtab = '\0';
  3910. + newtab[0] = '\0';
  3911. else
  3912. {
  3913. /* Provoke with 'sort -txx'. Complain about
  3914. @@ -4619,9 +5307,11 @@ main (int argc, char **argv)
  3915. quote (optarg));
  3916. }
  3917. }
  3918. - if (tab != TAB_DEFAULT && tab != newtab)
  3919. + if (tab_length && (tab_length != newtab_length
  3920. + || memcmp (tab, newtab, tab_length) != 0))
  3921. die (SORT_FAILURE, 0, _("incompatible tabs"));
  3922. - tab = newtab;
  3923. + memcpy (tab, newtab, newtab_length);
  3924. + tab_length = newtab_length;
  3925. }
  3926. break;
  3927. diff --git a/src/unexpand.c b/src/unexpand.c
  3928. index 7d6100f..04cd646 100644
  3929. --- a/src/unexpand.c
  3930. +++ b/src/unexpand.c
  3931. @@ -38,6 +38,9 @@
  3932. #include <stdio.h>
  3933. #include <getopt.h>
  3934. #include <sys/types.h>
  3935. +
  3936. +#include <mbfile.h>
  3937. +
  3938. #include "system.h"
  3939. #include "die.h"
  3940. @@ -106,24 +109,47 @@ unexpand (void)
  3941. {
  3942. /* Input stream. */
  3943. FILE *fp = next_file (NULL);
  3944. + mb_file_t mbf;
  3945. /* The array of pending blanks. In non-POSIX locales, blanks can
  3946. include characters other than spaces, so the blanks must be
  3947. stored, not merely counted. */
  3948. - char *pending_blank;
  3949. + mbf_char_t *pending_blank;
  3950. + /* True if the starting locale is utf8. */
  3951. + bool using_utf_locale;
  3952. +
  3953. + /* True if the first file contains BOM header. */
  3954. + bool found_bom;
  3955. + using_utf_locale=check_utf_locale();
  3956. if (!fp)
  3957. return;
  3958. + mbf_init (mbf, fp);
  3959. + found_bom=check_bom(fp,&mbf);
  3960. +
  3961. + if (using_utf_locale == false && found_bom == true)
  3962. + {
  3963. + /*try using some predefined locale */
  3964. + if (set_utf_locale () != 0)
  3965. + {
  3966. + error (EXIT_FAILURE, errno, _("cannot set UTF-8 locale"));
  3967. + }
  3968. + }
  3969. /* The worst case is a non-blank character, then one blank, then a
  3970. tab stop, then MAX_COLUMN_WIDTH - 1 blanks, then a non-blank; so
  3971. allocate MAX_COLUMN_WIDTH bytes to store the blanks. */
  3972. - pending_blank = xmalloc (max_column_width);
  3973. + pending_blank = xmalloc (max_column_width * sizeof (mbf_char_t));
  3974. +
  3975. + if (found_bom == true)
  3976. + {
  3977. + print_bom();
  3978. + }
  3979. while (true)
  3980. {
  3981. /* Input character, or EOF. */
  3982. - int c;
  3983. + mbf_char_t c;
  3984. /* If true, perform translations. */
  3985. bool convert = true;
  3986. @@ -157,12 +183,44 @@ unexpand (void)
  3987. do
  3988. {
  3989. - while ((c = getc (fp)) < 0 && (fp = next_file (fp)))
  3990. - continue;
  3991. + while (true) {
  3992. + mbf_getc (c, mbf);
  3993. + if ((mb_iseof (c)) && (fp = next_file (fp)))
  3994. + {
  3995. + mbf_init (mbf, fp);
  3996. + if (fp!=NULL)
  3997. + {
  3998. + if (check_bom(fp,&mbf)==true)
  3999. + {
  4000. + /*Not the first file - check BOM header*/
  4001. + if (using_utf_locale==false && found_bom==false)
  4002. + {
  4003. + /*BOM header in subsequent file but not in the first one. */
  4004. + error (EXIT_FAILURE, errno, _("combination of files with and without BOM header"));
  4005. + }
  4006. + }
  4007. + else
  4008. + {
  4009. + if(using_utf_locale==false && found_bom==true)
  4010. + {
  4011. + /*First file conatined BOM header - locale was switched to UTF
  4012. + *all subsequent files should contain BOM. */
  4013. + error (EXIT_FAILURE, errno, _("combination of files with and without BOM header"));
  4014. + }
  4015. + }
  4016. + }
  4017. + continue;
  4018. + }
  4019. + else
  4020. + {
  4021. + break;
  4022. + }
  4023. + }
  4024. +
  4025. if (convert)
  4026. {
  4027. - bool blank = !! isblank (c);
  4028. + bool blank = mb_isblank (c);
  4029. if (blank)
  4030. {
  4031. @@ -179,16 +237,16 @@ unexpand (void)
  4032. if (next_tab_column < column)
  4033. die (EXIT_FAILURE, 0, _("input line is too long"));
  4034. - if (c == '\t')
  4035. + if (mb_iseq (c, '\t'))
  4036. {
  4037. column = next_tab_column;
  4038. if (pending)
  4039. - pending_blank[0] = '\t';
  4040. + mb_setascii (&pending_blank[0], '\t');
  4041. }
  4042. else
  4043. {
  4044. - column++;
  4045. + column += mb_width (c);
  4046. if (! (prev_blank && column == next_tab_column))
  4047. {
  4048. @@ -196,13 +254,14 @@ unexpand (void)
  4049. will be replaced by tabs. */
  4050. if (column == next_tab_column)
  4051. one_blank_before_tab_stop = true;
  4052. - pending_blank[pending++] = c;
  4053. + mb_copy (&pending_blank[pending++], &c);
  4054. prev_blank = true;
  4055. continue;
  4056. }
  4057. /* Replace the pending blanks by a tab or two. */
  4058. - pending_blank[0] = c = '\t';
  4059. + mb_setascii (&c, '\t');
  4060. + mb_setascii (&pending_blank[0], '\t');
  4061. }
  4062. /* Discard pending blanks, unless it was a single
  4063. @@ -210,7 +269,7 @@ unexpand (void)
  4064. pending = one_blank_before_tab_stop;
  4065. }
  4066. }
  4067. - else if (c == '\b')
  4068. + else if (mb_iseq (c, '\b'))
  4069. {
  4070. /* Go back one column, and force recalculation of the
  4071. next tab stop. */
  4072. @@ -218,9 +277,11 @@ unexpand (void)
  4073. next_tab_column = column;
  4074. tab_index -= !!tab_index;
  4075. }
  4076. - else
  4077. + else if (!mb_iseq (c, '\n'))
  4078. {
  4079. - column++;
  4080. + /* mb_width() returns 0 for control characters */
  4081. + const int width = mb_width (c);
  4082. + column += MAX(1, width);
  4083. if (!column)
  4084. die (EXIT_FAILURE, 0, _("input line is too long"));
  4085. }
  4086. @@ -228,8 +289,11 @@ unexpand (void)
  4087. if (pending)
  4088. {
  4089. if (pending > 1 && one_blank_before_tab_stop)
  4090. - pending_blank[0] = '\t';
  4091. - if (fwrite (pending_blank, 1, pending, stdout) != pending)
  4092. + mb_setascii (&pending_blank[0], '\t');
  4093. +
  4094. + for (int n = 0; n < pending; ++n)
  4095. + mb_putc (pending_blank[n], stdout);
  4096. + if (ferror (stdout))
  4097. die (EXIT_FAILURE, errno, _("write error"));
  4098. pending = 0;
  4099. one_blank_before_tab_stop = false;
  4100. @@ -239,16 +303,17 @@ unexpand (void)
  4101. convert &= convert_entire_line || blank;
  4102. }
  4103. - if (c < 0)
  4104. + if (mb_iseof (c))
  4105. {
  4106. free (pending_blank);
  4107. return;
  4108. }
  4109. - if (putchar (c) < 0)
  4110. + mb_putc (c, stdout);
  4111. + if (ferror (stdout))
  4112. die (EXIT_FAILURE, errno, _("write error"));
  4113. }
  4114. - while (c != '\n');
  4115. + while (!mb_iseq (c, '\n'));
  4116. }
  4117. }
  4118. diff --git a/src/uniq.c b/src/uniq.c
  4119. index e5996f0..871d47c 100644
  4120. --- a/src/uniq.c
  4121. +++ b/src/uniq.c
  4122. @@ -21,6 +21,17 @@
  4123. #include <getopt.h>
  4124. #include <sys/types.h>
  4125. +/* Get mbstate_t, mbrtowc(). */
  4126. +#if HAVE_WCHAR_H
  4127. +# include <wchar.h>
  4128. +#endif
  4129. +
  4130. +/* Get isw* functions. */
  4131. +#if HAVE_WCTYPE_H
  4132. +# include <wctype.h>
  4133. +#endif
  4134. +#include <assert.h>
  4135. +
  4136. #include "system.h"
  4137. #include "argmatch.h"
  4138. #include "linebuffer.h"
  4139. @@ -33,6 +44,18 @@
  4140. #include "memcasecmp.h"
  4141. #include "quote.h"
  4142. +/* MB_LEN_MAX is incorrectly defined to be 1 in at least one GCC
  4143. + installation; work around this configuration error. */
  4144. +#if !defined MB_LEN_MAX || MB_LEN_MAX < 2
  4145. +# define MB_LEN_MAX 16
  4146. +#endif
  4147. +
  4148. +/* Some systems, like BeOS, have multibyte encodings but lack mbstate_t. */
  4149. +#if HAVE_MBRTOWC && defined mbstate_t
  4150. +# define mbrtowc(pwc, s, n, ps) (mbrtowc) (pwc, s, n, 0)
  4151. +#endif
  4152. +
  4153. +
  4154. /* The official name of this program (e.g., no 'g' prefix). */
  4155. #define PROGRAM_NAME "uniq"
  4156. @@ -139,6 +162,10 @@ enum
  4157. GROUP_OPTION = CHAR_MAX + 1
  4158. };
  4159. +/* Function pointers. */
  4160. +static char *
  4161. +(*find_field) (struct linebuffer *line);
  4162. +
  4163. static struct option const longopts[] =
  4164. {
  4165. {"count", no_argument, NULL, 'c'},
  4166. @@ -254,7 +281,7 @@ size_opt (char const *opt, char const *msgid)
  4167. ATTRIBUTE_PURE
  4168. static char *
  4169. -find_field (struct linebuffer const *line)
  4170. +find_field_uni (struct linebuffer *line)
  4171. {
  4172. size_t count;
  4173. char const *lp = line->buffer;
  4174. @@ -274,6 +301,83 @@ find_field (struct linebuffer const *line)
  4175. return line->buffer + i;
  4176. }
  4177. +#if HAVE_MBRTOWC
  4178. +
  4179. +# define MBCHAR_TO_WCHAR(WC, MBLENGTH, LP, POS, SIZE, STATEP, CONVFAIL) \
  4180. + do \
  4181. + { \
  4182. + mbstate_t state_bak; \
  4183. + \
  4184. + CONVFAIL = 0; \
  4185. + state_bak = *STATEP; \
  4186. + \
  4187. + MBLENGTH = mbrtowc (&WC, LP + POS, SIZE - POS, STATEP); \
  4188. + \
  4189. + switch (MBLENGTH) \
  4190. + { \
  4191. + case (size_t)-2: \
  4192. + case (size_t)-1: \
  4193. + *STATEP = state_bak; \
  4194. + CONVFAIL++; \
  4195. + /* Fall through */ \
  4196. + case 0: \
  4197. + MBLENGTH = 1; \
  4198. + } \
  4199. + } \
  4200. + while (0)
  4201. +
  4202. +static char *
  4203. +find_field_multi (struct linebuffer *line)
  4204. +{
  4205. + size_t count;
  4206. + char *lp = line->buffer;
  4207. + size_t size = line->length - 1;
  4208. + size_t pos;
  4209. + size_t mblength;
  4210. + wchar_t wc;
  4211. + mbstate_t *statep;
  4212. + int convfail = 0;
  4213. +
  4214. + pos = 0;
  4215. + statep = &(line->state);
  4216. +
  4217. + /* skip fields. */
  4218. + for (count = 0; count < skip_fields && pos < size; count++)
  4219. + {
  4220. + while (pos < size)
  4221. + {
  4222. + MBCHAR_TO_WCHAR (wc, mblength, lp, pos, size, statep, convfail);
  4223. +
  4224. + if (convfail || !(iswblank (wc) || wc == '\n'))
  4225. + {
  4226. + pos += mblength;
  4227. + break;
  4228. + }
  4229. + pos += mblength;
  4230. + }
  4231. +
  4232. + while (pos < size)
  4233. + {
  4234. + MBCHAR_TO_WCHAR (wc, mblength, lp, pos, size, statep, convfail);
  4235. +
  4236. + if (!convfail && (iswblank (wc) || wc == '\n'))
  4237. + break;
  4238. +
  4239. + pos += mblength;
  4240. + }
  4241. + }
  4242. +
  4243. + /* skip fields. */
  4244. + for (count = 0; count < skip_chars && pos < size; count++)
  4245. + {
  4246. + MBCHAR_TO_WCHAR (wc, mblength, lp, pos, size, statep, convfail);
  4247. + pos += mblength;
  4248. + }
  4249. +
  4250. + return lp + pos;
  4251. +}
  4252. +#endif
  4253. +
  4254. /* Return false if two strings OLD and NEW match, true if not.
  4255. OLD and NEW point not to the beginnings of the lines
  4256. but rather to the beginnings of the fields to compare.
  4257. @@ -494,6 +598,19 @@ main (int argc, char **argv)
  4258. atexit (close_stdout);
  4259. +#if HAVE_MBRTOWC
  4260. + if (MB_CUR_MAX > 1)
  4261. + {
  4262. + find_field = find_field_multi;
  4263. + }
  4264. + else
  4265. +#endif
  4266. + {
  4267. + find_field = find_field_uni;
  4268. + }
  4269. +
  4270. +
  4271. +
  4272. skip_chars = 0;
  4273. skip_fields = 0;
  4274. check_chars = SIZE_MAX;
  4275. diff --git a/tests/Coreutils.pm b/tests/Coreutils.pm
  4276. index fad7ab9..c9021a6 100644
  4277. --- a/tests/Coreutils.pm
  4278. +++ b/tests/Coreutils.pm
  4279. @@ -269,6 +269,9 @@ sub run_tests ($$$$$)
  4280. # Yes, this is an arbitrary limit. If it causes trouble,
  4281. # consider removing it.
  4282. my $max = 30;
  4283. + # The downstream i18n multi-byte tests have a "-mb" suffix.
  4284. + # Therefore add 3 to the maximum test name length.
  4285. + $max += 3;
  4286. if ($max < length $test_name)
  4287. {
  4288. warn "$program_name: $test_name: test name is too long (> $max)\n";
  4289. diff --git a/tests/expand/mb.sh b/tests/expand/mb.sh
  4290. new file mode 100755
  4291. index 0000000..dd6007c
  4292. --- /dev/null
  4293. +++ b/tests/expand/mb.sh
  4294. @@ -0,0 +1,183 @@
  4295. +#!/bin/sh
  4296. +
  4297. +# Copyright (C) 2012-2015 Free Software Foundation, Inc.
  4298. +
  4299. +# This program is free software: you can redistribute it and/or modify
  4300. +# it under the terms of the GNU General Public License as published by
  4301. +# the Free Software Foundation, either version 3 of the License, or
  4302. +# (at your option) any later version.
  4303. +
  4304. +# This program is distributed in the hope that it will be useful,
  4305. +# but WITHOUT ANY WARRANTY; without even the implied warranty of
  4306. +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  4307. +# GNU General Public License for more details.
  4308. +
  4309. +# You should have received a copy of the GNU General Public License
  4310. +# along with this program. If not, see <http://www.gnu.org/licenses/>.
  4311. +
  4312. +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
  4313. +print_ver_ expand
  4314. +
  4315. +export LC_ALL=en_US.UTF-8
  4316. +
  4317. +#input containing multibyte characters
  4318. +cat <<\EOF > in || framework_failure_
  4319. +1234567812345678123456781
  4320. +. . . .
  4321. +a b c d
  4322. +. . . .
  4323. +ä ö ü ß
  4324. +. . . .
  4325. +EOF
  4326. +env printf ' äöü\t. öüä. \tä xx\n' >> in || framework_failure_
  4327. +
  4328. +cat <<\EOF > exp || framework_failure_
  4329. +1234567812345678123456781
  4330. +. . . .
  4331. +a b c d
  4332. +. . . .
  4333. +ä ö ü ß
  4334. +. . . .
  4335. + äöü . öüä. ä xx
  4336. +EOF
  4337. +
  4338. +expand < in > out || fail=1
  4339. +compare exp out > /dev/null 2>&1 || fail=1
  4340. +
  4341. +#multiple files as an input
  4342. +cat <<\EOF >> exp || framework_failure_
  4343. +1234567812345678123456781
  4344. +. . . .
  4345. +a b c d
  4346. +. . . .
  4347. +ä ö ü ß
  4348. +. . . .
  4349. + äöü . öüä. ä xx
  4350. +EOF
  4351. +
  4352. +expand ./in ./in > out || fail=1
  4353. +compare exp out > /dev/null 2>&1 || fail=1
  4354. +
  4355. +#test characters with display widths != 1
  4356. +env printf '12345678
  4357. +e\t|ascii(1)
  4358. +\u00E9\t|composed(1)
  4359. +e\u0301\t|decomposed(1)
  4360. +\u3000\t|ideo-space(2)
  4361. +\uFF0D\t|full-hypen(2)
  4362. +' > in || framework_failure_
  4363. +
  4364. +env printf '12345678
  4365. +e |ascii(1)
  4366. +\u00E9 |composed(1)
  4367. +e\u0301 |decomposed(1)
  4368. +\u3000 |ideo-space(2)
  4369. +\uFF0D |full-hypen(2)
  4370. +' > exp || framework_failure_
  4371. +
  4372. +expand < in > out || fail=1
  4373. +compare exp out > /dev/null 2>&1 || fail=1
  4374. +
  4375. +#shouldn't fail with "input line too long"
  4376. +#when a line starts with a control character
  4377. +env printf '\n' > in || framework_failure_
  4378. +
  4379. +expand < in > out || fail=1
  4380. +compare in out > /dev/null 2>&1 || fail=1
  4381. +
  4382. +#non-Unicode characters interspersed between Unicode ones
  4383. +env printf '12345678
  4384. +\t\xFF|
  4385. +\xFF\t|
  4386. +\t\xFFä|
  4387. +ä\xFF\t|
  4388. +\tä\xFF|
  4389. +\xFF\tä|
  4390. +äbcdef\xFF\t|
  4391. +' > in || framework_failure_
  4392. +
  4393. +env printf '12345678
  4394. + \xFF|
  4395. +\xFF |
  4396. + \xFFä|
  4397. +ä\xFF |
  4398. + ä\xFF|
  4399. +\xFF ä|
  4400. +äbcdef\xFF |
  4401. +' > exp || framework_failure_
  4402. +
  4403. +expand < in > out || fail=1
  4404. +compare exp out > /dev/null 2>&1 || fail=1
  4405. +
  4406. +
  4407. +
  4408. +#BOM header test 1
  4409. +printf "\xEF\xBB\xBF" > in; cat <<\EOF >> in || framework_failure_
  4410. +1234567812345678123456781
  4411. +. . . .
  4412. +a b c d
  4413. +. . . .
  4414. +ä ö ü ß
  4415. +. . . .
  4416. +EOF
  4417. +env printf ' äöü\t. öüä. \tä xx\n' >> in || framework_failure_
  4418. +
  4419. +printf "\xEF\xBB\xBF" > exp; cat <<\EOF >> exp || framework_failure_
  4420. +1234567812345678123456781
  4421. +. . . .
  4422. +a b c d
  4423. +. . . .
  4424. +ä ö ü ß
  4425. +. . . .
  4426. + äöü . öüä. ä xx
  4427. +EOF
  4428. +
  4429. +
  4430. +expand < in > out || fail=1
  4431. +compare exp out > /dev/null 2>&1 || fail=1
  4432. +
  4433. +LANG=C expand < in > out || fail=1
  4434. +compare exp out > /dev/null 2>&1 || fail=1
  4435. +
  4436. +LC_ALL=C expand < in > out || fail=1
  4437. +compare exp out > /dev/null 2>&1 || fail=1
  4438. +
  4439. +
  4440. +printf '\xEF\xBB\xBF' > in1; cat <<\EOF >> in1 || framework_failure_
  4441. +1234567812345678123456781
  4442. +. . . .
  4443. +a b c d
  4444. +. . . .
  4445. +ä ö ü ß
  4446. +. . . .
  4447. +EOF
  4448. +env printf ' äöü\t. öüä. \tä xx\n' >> in1 || framework_failure_
  4449. +
  4450. +
  4451. +printf '\xEF\xBB\xBF' > exp; cat <<\EOF >> exp || framework_failure_
  4452. +1234567812345678123456781
  4453. +. . . .
  4454. +a b c d
  4455. +. . . .
  4456. +ä ö ü ß
  4457. +. . . .
  4458. + äöü . öüä. ä xx
  4459. +1234567812345678123456781
  4460. +. . . .
  4461. +a b c d
  4462. +. . . .
  4463. +ä ö ü ß
  4464. +. . . .
  4465. + äöü . öüä. ä xx
  4466. +EOF
  4467. +
  4468. +expand in1 in1 > out || fail=1
  4469. +compare exp out > /dev/null 2>&1 || fail=1
  4470. +
  4471. +LANG=C expand in1 in1 > out || fail=1
  4472. +compare exp out > /dev/null 2>&1 || fail=1
  4473. +
  4474. +LC_ALL=C expand in1 in1 > out || fail=1
  4475. +compare exp out > /dev/null 2>&1 || fail=1
  4476. +
  4477. +exit $fail
  4478. diff --git a/tests/i18n/sort.sh b/tests/i18n/sort.sh
  4479. new file mode 100755
  4480. index 0000000..26c95de
  4481. --- /dev/null
  4482. +++ b/tests/i18n/sort.sh
  4483. @@ -0,0 +1,29 @@
  4484. +#!/bin/sh
  4485. +# Verify sort's multi-byte support.
  4486. +
  4487. +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
  4488. +print_ver_ sort
  4489. +
  4490. +export LC_ALL=en_US.UTF-8
  4491. +locale -k LC_CTYPE | grep -q "charmap.*UTF-8" \
  4492. + || skip_ "No UTF-8 locale available"
  4493. +
  4494. +# Enable heap consistency checkng on older systems
  4495. +export MALLOC_CHECK_=2
  4496. +
  4497. +
  4498. +# check buffer overflow issue due to
  4499. +# expanding multi-byte representation due to case conversion
  4500. +# https://bugzilla.suse.com/show_bug.cgi?id=928749
  4501. +cat <<EOF > exp
  4502. +.
  4503. +EOF
  4504. +cat <<EOF | sort -f > out || fail=1
  4505. +.
  4506. +EOF
  4507. +compare exp out || { fail=1; cat out; }
  4508. +
  4509. +
  4510. +Exit $fail
  4511. diff --git a/tests/local.mk b/tests/local.mk
  4512. index 0f77786..dbe1843 100644
  4513. --- a/tests/local.mk
  4514. +++ b/tests/local.mk
  4515. @@ -377,6 +377,8 @@ all_tests = \
  4516. tests/misc/sort-discrim.sh \
  4517. tests/misc/sort-files0-from.pl \
  4518. tests/misc/sort-float.sh \
  4519. + tests/misc/sort-mb-tests.sh \
  4520. + tests/i18n/sort.sh \
  4521. tests/misc/sort-h-thousands-sep.sh \
  4522. tests/misc/sort-merge.pl \
  4523. tests/misc/sort-merge-fdlimit.sh \
  4524. @@ -576,6 +578,7 @@ all_tests = \
  4525. tests/du/threshold.sh \
  4526. tests/du/trailing-slash.sh \
  4527. tests/du/two-args.sh \
  4528. + tests/expand/mb.sh \
  4529. tests/id/gnu-zero-uids.sh \
  4530. tests/id/no-context.sh \
  4531. tests/id/context.sh \
  4532. @@ -727,6 +730,7 @@ all_tests = \
  4533. tests/touch/read-only.sh \
  4534. tests/touch/relative.sh \
  4535. tests/touch/trailing-slash.sh \
  4536. + tests/unexpand/mb.sh \
  4537. $(all_root_tests)
  4538. # See tests/factor/create-test.sh.
  4539. diff --git a/tests/misc/expand.pl b/tests/misc/expand.pl
  4540. index 7a77e6f..27f6652 100755
  4541. --- a/tests/misc/expand.pl
  4542. +++ b/tests/misc/expand.pl
  4543. @@ -27,6 +27,15 @@ my $prog = 'expand';
  4544. # Turn off localization of executable's output.
  4545. @ENV{qw(LANGUAGE LANG LC_ALL)} = ('C') x 3;
  4546. +#comment out next line to disable multibyte tests
  4547. +my $mb_locale = $ENV{LOCALE_FR_UTF8};
  4548. +! defined $mb_locale || $mb_locale eq 'none'
  4549. + and $mb_locale = 'C';
  4550. +
  4551. +my $prog = 'expand';
  4552. +my $try = "Try \`$prog --help' for more information.\n";
  4553. +my $inval = "$prog: invalid byte, character or field list\n$try";
  4554. +
  4555. my @Tests =
  4556. (
  4557. ['t1', '--tabs=3', {IN=>"a\tb"}, {OUT=>"a b"}],
  4558. @@ -168,6 +177,8 @@ my @Tests =
  4559. # Test errors
  4560. + # FIXME: The following tests contain ‘quoting’ specific to LC_MESSAGES
  4561. + # So we force LC_MESSAGES=C to make them pass.
  4562. ['e1', '--tabs="a"', {IN=>''}, {OUT=>''}, {EXIT=>1},
  4563. {ERR => "$prog: tab size contains invalid character(s): 'a'\n"}],
  4564. ['e2', "-t $UINTMAX_OFLOW", {IN=>''}, {OUT=>''}, {EXIT=>1},
  4565. @@ -184,6 +195,37 @@ my @Tests =
  4566. {ERR => "$prog: '/' specifier not at start of number: '/'\n"}],
  4567. );
  4568. +if ($mb_locale ne 'C')
  4569. + {
  4570. + # Duplicate each test vector, appending "-mb" to the test name and
  4571. + # inserting {ENV => "LC_ALL=$mb_locale"} in the copy, so that we
  4572. + # provide coverage for the distro-added multi-byte code paths.
  4573. + my @new;
  4574. + foreach my $t (@Tests)
  4575. + {
  4576. + my @new_t = @$t;
  4577. + my $test_name = shift @new_t;
  4578. +
  4579. + # Depending on whether expand is multi-byte-patched,
  4580. + # it emits different diagnostics:
  4581. + # non-MB: invalid byte or field list
  4582. + # MB: invalid byte, character or field list
  4583. + # Adjust the expected error output accordingly.
  4584. + if (grep {ref $_ eq 'HASH' && exists $_->{ERR} && $_->{ERR} eq $inval}
  4585. + (@new_t))
  4586. + {
  4587. + my $sub = {ERR_SUBST => 's/, character//'};
  4588. + push @new_t, $sub;
  4589. + push @$t, $sub;
  4590. + }
  4591. + push @new, ["$test_name-mb", @new_t, {ENV => "LANG=$mb_locale LC_MESSAGES=C"}];
  4592. + }
  4593. + push @Tests, @new;
  4594. + }
  4595. +
  4596. +
  4597. +@Tests = triple_test \@Tests;
  4598. +
  4599. my $save_temps = $ENV{DEBUG};
  4600. my $verbose = $ENV{VERBOSE};
  4601. diff --git a/tests/misc/fold.pl b/tests/misc/fold.pl
  4602. index 2834f92..bc1616a 100755
  4603. --- a/tests/misc/fold.pl
  4604. +++ b/tests/misc/fold.pl
  4605. @@ -20,9 +20,18 @@ use strict;
  4606. (my $program_name = $0) =~ s|.*/||;
  4607. +my $prog = 'fold';
  4608. +my $try = "Try \`$prog --help' for more information.\n";
  4609. +my $inval = "$prog: invalid byte, character or field list\n$try";
  4610. +
  4611. # Turn off localization of executable's output.
  4612. @ENV{qw(LANGUAGE LANG LC_ALL)} = ('C') x 3;
  4613. +# uncommented to enable multibyte paths
  4614. +my $mb_locale = $ENV{LOCALE_FR_UTF8};
  4615. +! defined $mb_locale || $mb_locale eq 'none'
  4616. + and $mb_locale = 'C';
  4617. +
  4618. my @Tests =
  4619. (
  4620. ['s1', '-w2 -s', {IN=>"a\t"}, {OUT=>"a\n\t"}],
  4621. @@ -31,9 +40,48 @@ my @Tests =
  4622. ['s4', '-w4 -s', {IN=>"abc ef\n"}, {OUT=>"abc \nef\n"}],
  4623. );
  4624. +# Add _POSIX2_VERSION=199209 to the environment of each test
  4625. +# that uses an old-style option like +1.
  4626. +if ($mb_locale ne 'C')
  4627. + {
  4628. + # Duplicate each test vector, appending "-mb" to the test name and
  4629. + # inserting {ENV => "LC_ALL=$mb_locale"} in the copy, so that we
  4630. + # provide coverage for the distro-added multi-byte code paths.
  4631. + my @new;
  4632. + foreach my $t (@Tests)
  4633. + {
  4634. + my @new_t = @$t;
  4635. + my $test_name = shift @new_t;
  4636. +
  4637. + # Depending on whether fold is multi-byte-patched,
  4638. + # it emits different diagnostics:
  4639. + # non-MB: invalid byte or field list
  4640. + # MB: invalid byte, character or field list
  4641. + # Adjust the expected error output accordingly.
  4642. + if (grep {ref $_ eq 'HASH' && exists $_->{ERR} && $_->{ERR} eq $inval}
  4643. + (@new_t))
  4644. + {
  4645. + my $sub = {ERR_SUBST => 's/, character//'};
  4646. + push @new_t, $sub;
  4647. + push @$t, $sub;
  4648. + }
  4649. + push @new, ["$test_name-mb", @new_t, {ENV => "LC_ALL=$mb_locale"}];
  4650. + }
  4651. + push @Tests, @new;
  4652. + }
  4653. +
  4654. +@Tests = triple_test \@Tests;
  4655. +
  4656. +# Remember that triple_test creates from each test with exactly one "IN"
  4657. +# file two more tests (.p and .r suffix on name) corresponding to reading
  4658. +# input from a file and from a pipe. The pipe-reading test would fail
  4659. +# due to a race condition about 1 in 20 times.
  4660. +# Remove the IN_PIPE version of the "output-is-input" test above.
  4661. +# The others aren't susceptible because they have three inputs each.
  4662. +@Tests = grep {$_->[0] ne 'output-is-input.p'} @Tests;
  4663. +
  4664. my $save_temps = $ENV{DEBUG};
  4665. my $verbose = $ENV{VERBOSE};
  4666. -my $prog = 'fold';
  4667. my $fail = run_tests ($program_name, $prog, \@Tests, $save_temps, $verbose);
  4668. exit $fail;
  4669. diff --git a/tests/misc/join.pl b/tests/misc/join.pl
  4670. index 06ad777..be40204 100755
  4671. --- a/tests/misc/join.pl
  4672. +++ b/tests/misc/join.pl
  4673. @@ -25,6 +25,15 @@ my $limits = getlimits ();
  4674. my $prog = 'join';
  4675. +my $try = "Try \`$prog --help' for more information.\n";
  4676. +my $inval = "$prog: invalid byte, character or field list\n$try";
  4677. +
  4678. +my $mb_locale;
  4679. +#Comment out next line to disable multibyte tests
  4680. +$mb_locale = $ENV{LOCALE_FR_UTF8};
  4681. +! defined $mb_locale || $mb_locale eq 'none'
  4682. + and $mb_locale = 'C';
  4683. +
  4684. my $delim = chr 0247;
  4685. sub t_subst ($)
  4686. {
  4687. @@ -333,8 +342,49 @@ foreach my $t (@tv)
  4688. push @Tests, $new_ent;
  4689. }
  4690. +# Add _POSIX2_VERSION=199209 to the environment of each test
  4691. +# that uses an old-style option like +1.
  4692. +if ($mb_locale ne 'C')
  4693. + {
  4694. + # Duplicate each test vector, appending "-mb" to the test name and
  4695. + # inserting {ENV => "LC_ALL=$mb_locale"} in the copy, so that we
  4696. + # provide coverage for the distro-added multi-byte code paths.
  4697. + my @new;
  4698. + foreach my $t (@Tests)
  4699. + {
  4700. + my @new_t = @$t;
  4701. + my $test_name = shift @new_t;
  4702. +
  4703. + # Depending on whether join is multi-byte-patched,
  4704. + # it emits different diagnostics:
  4705. + # non-MB: invalid byte or field list
  4706. + # MB: invalid byte, character or field list
  4707. + # Adjust the expected error output accordingly.
  4708. + if (grep {ref $_ eq 'HASH' && exists $_->{ERR} && $_->{ERR} eq $inval}
  4709. + (@new_t))
  4710. + {
  4711. + my $sub = {ERR_SUBST => 's/, character//'};
  4712. + push @new_t, $sub;
  4713. + push @$t, $sub;
  4714. + }
  4715. + #Adjust the output some error messages including test_name for mb
  4716. + if (grep {ref $_ eq 'HASH' && exists $_->{ERR}}
  4717. + (@new_t))
  4718. + {
  4719. + my $sub2 = {ERR_SUBST => "s/$test_name-mb/$test_name/"};
  4720. + push @new_t, $sub2;
  4721. + push @$t, $sub2;
  4722. + }
  4723. + push @new, ["$test_name-mb", @new_t, {ENV => "LC_ALL=$mb_locale"}];
  4724. + }
  4725. + push @Tests, @new;
  4726. + }
  4727. +
  4728. @Tests = triple_test \@Tests;
  4729. +#skip invalid-j-mb test, it is failing because of the format
  4730. +@Tests = grep {$_->[0] ne 'invalid-j-mb'} @Tests;
  4731. +
  4732. my $save_temps = $ENV{DEBUG};
  4733. my $verbose = $ENV{VERBOSE};
  4734. diff --git a/tests/misc/sort-mb-tests.sh b/tests/misc/sort-mb-tests.sh
  4735. new file mode 100755
  4736. index 0000000..11836ba
  4737. --- /dev/null
  4738. +++ b/tests/misc/sort-mb-tests.sh
  4739. @@ -0,0 +1,45 @@
  4740. +#!/bin/sh
  4741. +# Verify sort's multi-byte support.
  4742. +
  4743. +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
  4744. +print_ver_ sort
  4745. +
  4746. +export LC_ALL=en_US.UTF-8
  4747. +locale -k LC_CTYPE | grep -q "charmap.*UTF-8" \
  4748. + || skip_ "No UTF-8 locale available"
  4749. +
  4750. +
  4751. +cat <<EOF > exp
  4752. +Banana@5
  4753. +Apple@10
  4754. +Citrus@20
  4755. +Cherry@30
  4756. +EOF
  4757. +
  4758. +cat <<EOF | sort -t @ -k2 -n > out || fail=1
  4759. +Apple@10
  4760. +Banana@5
  4761. +Citrus@20
  4762. +Cherry@30
  4763. +EOF
  4764. +
  4765. +compare exp out || { fail=1; cat out; }
  4766. +
  4767. +
  4768. +cat <<EOF > exp
  4769. +Citrus@AA20@@5
  4770. +Cherry@AA30@@10
  4771. +Apple@AA10@@20
  4772. +Banana@AA5@@30
  4773. +EOF
  4774. +
  4775. +cat <<EOF | sort -t @ -k4 -n > out || fail=1
  4776. +Apple@AA10@@20
  4777. +Banana@AA5@@30
  4778. +Citrus@AA20@@5
  4779. +Cherry@AA30@@10
  4780. +EOF
  4781. +
  4782. +compare exp out || { fail=1; cat out; }
  4783. +
  4784. +Exit $fail
  4785. diff --git a/tests/misc/sort-merge.pl b/tests/misc/sort-merge.pl
  4786. index 7eb4574..eda884c 100755
  4787. --- a/tests/misc/sort-merge.pl
  4788. +++ b/tests/misc/sort-merge.pl
  4789. @@ -26,6 +26,15 @@ my $prog = 'sort';
  4790. # Turn off localization of executable's output.
  4791. @ENV{qw(LANGUAGE LANG LC_ALL)} = ('C') x 3;
  4792. +my $mb_locale;
  4793. +# uncommented according to upstream commit enabling multibyte paths
  4794. +$mb_locale = $ENV{LOCALE_FR_UTF8};
  4795. +! defined $mb_locale || $mb_locale eq 'none'
  4796. + and $mb_locale = 'C';
  4797. +
  4798. +my $try = "Try \`$prog --help' for more information.\n";
  4799. +my $inval = "$prog: invalid byte, character or field list\n$try";
  4800. +
  4801. # three empty files and one that says 'foo'
  4802. my @inputs = (+(map{{IN=> {"empty$_"=> ''}}}1..3), {IN=> {foo=> "foo\n"}});
  4803. @@ -77,6 +86,39 @@ my @Tests =
  4804. {OUT=>$big_input}],
  4805. );
  4806. +# Add _POSIX2_VERSION=199209 to the environment of each test
  4807. +# that uses an old-style option like +1.
  4808. +if ($mb_locale ne 'C')
  4809. + {
  4810. + # Duplicate each test vector, appending "-mb" to the test name and
  4811. + # inserting {ENV => "LC_ALL=$mb_locale"} in the copy, so that we
  4812. + # provide coverage for the distro-added multi-byte code paths.
  4813. + my @new;
  4814. + foreach my $t (@Tests)
  4815. + {
  4816. + my @new_t = @$t;
  4817. + my $test_name = shift @new_t;
  4818. +
  4819. + # Depending on whether sort is multi-byte-patched,
  4820. + # it emits different diagnostics:
  4821. + # non-MB: invalid byte or field list
  4822. + # MB: invalid byte, character or field list
  4823. + # Adjust the expected error output accordingly.
  4824. + if (grep {ref $_ eq 'HASH' && exists $_->{ERR} && $_->{ERR} eq $inval}
  4825. + (@new_t))
  4826. + {
  4827. + my $sub = {ERR_SUBST => 's/, character//'};
  4828. + push @new_t, $sub;
  4829. + push @$t, $sub;
  4830. + }
  4831. + next if ($test_name =~ "nmerge-.");
  4832. + push @new, ["$test_name-mb", @new_t, {ENV => "LC_ALL=$mb_locale"}];
  4833. + }
  4834. + push @Tests, @new;
  4835. + }
  4836. +
  4837. +@Tests = triple_test \@Tests;
  4838. +
  4839. my $save_temps = $ENV{DEBUG};
  4840. my $verbose = $ENV{VERBOSE};
  4841. diff --git a/tests/misc/sort.pl b/tests/misc/sort.pl
  4842. index 0b0adca..fd27821 100755
  4843. --- a/tests/misc/sort.pl
  4844. +++ b/tests/misc/sort.pl
  4845. @@ -24,10 +24,15 @@ my $prog = 'sort';
  4846. # Turn off localization of executable's output.
  4847. @ENV{qw(LANGUAGE LANG LC_ALL)} = ('C') x 3;
  4848. -my $mb_locale = $ENV{LOCALE_FR_UTF8};
  4849. +my $mb_locale;
  4850. +#Comment out next line to disable multibyte tests
  4851. +$mb_locale = $ENV{LOCALE_FR_UTF8};
  4852. ! defined $mb_locale || $mb_locale eq 'none'
  4853. and $mb_locale = 'C';
  4854. +my $try = "Try \`$prog --help' for more information.\n";
  4855. +my $inval = "$prog: invalid byte, character or field list\n$try";
  4856. +
  4857. # Since each test is run with a file name and with redirected stdin,
  4858. # the name in the diagnostic is either the file name or "-".
  4859. # Normalize each diagnostic to use '-'.
  4860. @@ -423,6 +428,38 @@ foreach my $t (@Tests)
  4861. }
  4862. }
  4863. +if ($mb_locale ne 'C')
  4864. + {
  4865. + # Duplicate each test vector, appending "-mb" to the test name and
  4866. + # inserting {ENV => "LC_ALL=$mb_locale"} in the copy, so that we
  4867. + # provide coverage for the distro-added multi-byte code paths.
  4868. + my @new;
  4869. + foreach my $t (@Tests)
  4870. + {
  4871. + my @new_t = @$t;
  4872. + my $test_name = shift @new_t;
  4873. +
  4874. + # Depending on whether sort is multi-byte-patched,
  4875. + # it emits different diagnostics:
  4876. + # non-MB: invalid byte or field list
  4877. + # MB: invalid byte, character or field list
  4878. + # Adjust the expected error output accordingly.
  4879. + if (grep {ref $_ eq 'HASH' && exists $_->{ERR} && $_->{ERR} eq $inval}
  4880. + (@new_t))
  4881. + {
  4882. + my $sub = {ERR_SUBST => 's/, character//'};
  4883. + push @new_t, $sub;
  4884. + push @$t, $sub;
  4885. + }
  4886. + #disable several failing tests until investigation, disable all tests with envvars set
  4887. + next if (grep {ref $_ eq 'HASH' && exists $_->{ENV}} (@new_t));
  4888. + next if ($test_name =~ "18g" or $test_name =~ "sort-numeric" or $test_name =~ "08[ab]" or $test_name =~ "03[def]" or $test_name =~ "h4" or $test_name =~ "n1" or $test_name =~ "2[01]a");
  4889. + next if ($test_name =~ "11[ab]"); # avoid FP: expected result differs to MB result due to collation rules.
  4890. + push @new, ["$test_name-mb", @new_t, {ENV => "LC_ALL=$mb_locale"}];
  4891. + }
  4892. + push @Tests, @new;
  4893. + }
  4894. +
  4895. @Tests = triple_test \@Tests;
  4896. # Remember that triple_test creates from each test with exactly one "IN"
  4897. @@ -432,6 +469,7 @@ foreach my $t (@Tests)
  4898. # Remove the IN_PIPE version of the "output-is-input" test above.
  4899. # The others aren't susceptible because they have three inputs each.
  4900. @Tests = grep {$_->[0] ne 'output-is-input.p'} @Tests;
  4901. +@Tests = grep {$_->[0] ne 'output-is-input-mb.p'} @Tests;
  4902. my $save_temps = $ENV{DEBUG};
  4903. my $verbose = $ENV{VERBOSE};
  4904. diff --git a/tests/misc/unexpand.pl b/tests/misc/unexpand.pl
  4905. index 2e1906f..fe66012 100755
  4906. --- a/tests/misc/unexpand.pl
  4907. +++ b/tests/misc/unexpand.pl
  4908. @@ -27,6 +27,14 @@ my $limits = getlimits ();
  4909. my $prog = 'unexpand';
  4910. +# comment out next line to disable multibyte tests
  4911. +my $mb_locale = $ENV{LOCALE_FR_UTF8};
  4912. +! defined $mb_locale || $mb_locale eq 'none'
  4913. + and $mb_locale = 'C';
  4914. +
  4915. +my $try = "Try \`$prog --help' for more information.\n";
  4916. +my $inval = "$prog: invalid byte, character or field list\n$try";
  4917. +
  4918. my @Tests =
  4919. (
  4920. ['a1', {IN=> ' 'x 1 ."y\n"}, {OUT=> ' 'x 1 ."y\n"}],
  4921. @@ -128,6 +136,37 @@ my @Tests =
  4922. ['ts2', '-t5,8', {IN=>"x\t \t y\n"}, {OUT=>"x\t\t y\n"}],
  4923. );
  4924. +if ($mb_locale ne 'C')
  4925. + {
  4926. + # Duplicate each test vector, appending "-mb" to the test name and
  4927. + # inserting {ENV => "LC_ALL=$mb_locale"} in the copy, so that we
  4928. + # provide coverage for the distro-added multi-byte code paths.
  4929. + my @new;
  4930. + foreach my $t (@Tests)
  4931. + {
  4932. + my @new_t = @$t;
  4933. + my $test_name = shift @new_t;
  4934. +
  4935. + # Depending on whether unexpand is multi-byte-patched,
  4936. + # it emits different diagnostics:
  4937. + # non-MB: invalid byte or field list
  4938. + # MB: invalid byte, character or field list
  4939. + # Adjust the expected error output accordingly.
  4940. + if (grep {ref $_ eq 'HASH' && exists $_->{ERR} && $_->{ERR} eq $inval}
  4941. + (@new_t))
  4942. + {
  4943. + my $sub = {ERR_SUBST => 's/, character//'};
  4944. + push @new_t, $sub;
  4945. + push @$t, $sub;
  4946. + }
  4947. + next if ($test_name =~ 'b-1');
  4948. + push @new, ["$test_name-mb", @new_t, {ENV => "LC_ALL=$mb_locale"}];
  4949. + }
  4950. + push @Tests, @new;
  4951. + }
  4952. +
  4953. +@Tests = triple_test \@Tests;
  4954. +
  4955. my $save_temps = $ENV{DEBUG};
  4956. my $verbose = $ENV{VERBOSE};
  4957. diff --git a/tests/misc/uniq.pl b/tests/misc/uniq.pl
  4958. index aa163cd..91d617d 100755
  4959. --- a/tests/misc/uniq.pl
  4960. +++ b/tests/misc/uniq.pl
  4961. @@ -23,9 +23,17 @@ my $limits = getlimits ();
  4962. my $prog = 'uniq';
  4963. my $try = "Try '$prog --help' for more information.\n";
  4964. +my $inval = "$prog: invalid byte, character or field list\n$try";
  4965. +
  4966. # Turn off localization of executable's output.
  4967. @ENV{qw(LANGUAGE LANG LC_ALL)} = ('C') x 3;
  4968. +my $mb_locale;
  4969. +#Comment out next line to disable multibyte tests
  4970. +$mb_locale = $ENV{LOCALE_FR_UTF8};
  4971. +! defined $mb_locale || $mb_locale eq 'none'
  4972. + and $mb_locale = 'C';
  4973. +
  4974. # When possible, create a "-z"-testing variant of each test.
  4975. sub add_z_variants($)
  4976. {
  4977. @@ -262,6 +270,53 @@ foreach my $t (@Tests)
  4978. and push @$t, {ENV=>'_POSIX2_VERSION=199209'};
  4979. }
  4980. +if ($mb_locale ne 'C')
  4981. + {
  4982. + # Duplicate each test vector, appending "-mb" to the test name and
  4983. + # inserting {ENV => "LC_ALL=$mb_locale"} in the copy, so that we
  4984. + # provide coverage for the distro-added multi-byte code paths.
  4985. + my @new;
  4986. + foreach my $t (@Tests)
  4987. + {
  4988. + my @new_t = @$t;
  4989. + my $test_name = shift @new_t;
  4990. +
  4991. + # Depending on whether uniq is multi-byte-patched,
  4992. + # it emits different diagnostics:
  4993. + # non-MB: invalid byte or field list
  4994. + # MB: invalid byte, character or field list
  4995. + # Adjust the expected error output accordingly.
  4996. + if (grep {ref $_ eq 'HASH' && exists $_->{ERR} && $_->{ERR} eq $inval}
  4997. + (@new_t))
  4998. + {
  4999. + my $sub = {ERR_SUBST => 's/, character//'};
  5000. + push @new_t, $sub;
  5001. + push @$t, $sub;
  5002. + }
  5003. + # In test #145, replace the each ‘...’ by '...'.
  5004. + if ($test_name =~ "145")
  5005. + {
  5006. + my $sub = { ERR_SUBST => "s/‘([^’]+)’/'\$1'/g"};
  5007. + push @new_t, $sub;
  5008. + push @$t, $sub;
  5009. + }
  5010. + next if ( $test_name =~ "schar"
  5011. + or $test_name =~ "^obs-plus"
  5012. + or $test_name =~ "119");
  5013. + push @new, ["$test_name-mb", @new_t, {ENV => "LC_ALL=$mb_locale"}];
  5014. + }
  5015. + push @Tests, @new;
  5016. + }
  5017. +
  5018. +# Remember that triple_test creates from each test with exactly one "IN"
  5019. +# file two more tests (.p and .r suffix on name) corresponding to reading
  5020. +# input from a file and from a pipe. The pipe-reading test would fail
  5021. +# due to a race condition about 1 in 20 times.
  5022. +# Remove the IN_PIPE version of the "output-is-input" test above.
  5023. +# The others aren't susceptible because they have three inputs each.
  5024. +
  5025. +@Tests = grep {$_->[0] ne 'output-is-input.p'} @Tests;
  5026. +
  5027. @Tests = add_z_variants \@Tests;
  5028. @Tests = triple_test \@Tests;
  5029. diff --git a/tests/pr/pr-tests.pl b/tests/pr/pr-tests.pl
  5030. index 7ac6d4c..ae6cc35 100755
  5031. --- a/tests/pr/pr-tests.pl
  5032. +++ b/tests/pr/pr-tests.pl
  5033. @@ -24,6 +24,15 @@ use strict;
  5034. my $prog = 'pr';
  5035. my $normalize_strerror = "s/': .*/'/";
  5036. +my $mb_locale;
  5037. +#Uncomment the following line to enable multibyte tests
  5038. +$mb_locale = $ENV{LOCALE_FR_UTF8};
  5039. +! defined $mb_locale || $mb_locale eq 'none'
  5040. + and $mb_locale = 'C';
  5041. +
  5042. +my $try = "Try \`$prog --help' for more information.\n";
  5043. +my $inval = "$prog: invalid byte, character or field list\n$try";
  5044. +
  5045. my @tv = (
  5046. # -b option is no longer an official option. But it's still working to
  5047. @@ -512,8 +521,48 @@ push @Tests,
  5048. {IN=>"x\tx\tx\tx\tx\nx\tx\tx\tx\tx\n"},
  5049. {OUT=>"x\tx\tx\tx\tx\tx\tx\tx\tx\tx\n"} ];
  5050. +# Add _POSIX2_VERSION=199209 to the environment of each test
  5051. +# that uses an old-style option like +1.
  5052. +if ($mb_locale ne 'C')
  5053. + {
  5054. + # Duplicate each test vector, appending "-mb" to the test name and
  5055. + # inserting {ENV => "LC_ALL=$mb_locale"} in the copy, so that we
  5056. + # provide coverage for the distro-added multi-byte code paths.
  5057. + my @new;
  5058. + foreach my $t (@Tests)
  5059. + {
  5060. + my @new_t = @$t;
  5061. + my $test_name = shift @new_t;
  5062. +
  5063. + # Depending on whether pr is multi-byte-patched,
  5064. + # it emits different diagnostics:
  5065. + # non-MB: invalid byte or field list
  5066. + # MB: invalid byte, character or field list
  5067. + # Adjust the expected error output accordingly.
  5068. + if (grep {ref $_ eq 'HASH' && exists $_->{ERR} && $_->{ERR} eq $inval}
  5069. + (@new_t))
  5070. + {
  5071. + my $sub = {ERR_SUBST => 's/, character//'};
  5072. + push @new_t, $sub;
  5073. + push @$t, $sub;
  5074. + }
  5075. + #temporarily skip some failing tests
  5076. + next if ($test_name =~ "col-0" or $test_name =~ "col-inval" or $test_name =~ "asan1");
  5077. + push @new, ["$test_name-mb", @new_t, {ENV => "LC_ALL=$mb_locale"}];
  5078. + }
  5079. + push @Tests, @new;
  5080. + }
  5081. +
  5082. @Tests = triple_test \@Tests;
  5083. +# Remember that triple_test creates from each test with exactly one "IN"
  5084. +# file two more tests (.p and .r suffix on name) corresponding to reading
  5085. +# input from a file and from a pipe. The pipe-reading test would fail
  5086. +# due to a race condition about 1 in 20 times.
  5087. +# Remove the IN_PIPE version of the "output-is-input" test above.
  5088. +# The others aren't susceptible because they have three inputs each.
  5089. +@Tests = grep {$_->[0] ne 'output-is-input.p'} @Tests;
  5090. +
  5091. my $save_temps = $ENV{DEBUG};
  5092. my $verbose = $ENV{VERBOSE};
  5093. diff --git a/tests/unexpand/mb.sh b/tests/unexpand/mb.sh
  5094. new file mode 100755
  5095. index 0000000..8a82d74
  5096. --- /dev/null
  5097. +++ b/tests/unexpand/mb.sh
  5098. @@ -0,0 +1,172 @@
  5099. +#!/bin/sh
  5100. +
  5101. +# Copyright (C) 2012-2015 Free Software Foundation, Inc.
  5102. +
  5103. +# This program is free software: you can redistribute it and/or modify
  5104. +# it under the terms of the GNU General Public License as published by
  5105. +# the Free Software Foundation, either version 3 of the License, or
  5106. +# (at your option) any later version.
  5107. +
  5108. +# This program is distributed in the hope that it will be useful,
  5109. +# but WITHOUT ANY WARRANTY; without even the implied warranty of
  5110. +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  5111. +# GNU General Public License for more details.
  5112. +
  5113. +# You should have received a copy of the GNU General Public License
  5114. +# along with this program. If not, see <http://www.gnu.org/licenses/>.
  5115. +
  5116. +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
  5117. +print_ver_ unexpand
  5118. +
  5119. +export LC_ALL=en_US.UTF-8
  5120. +
  5121. +#input containing multibyte characters
  5122. +cat > in <<\EOF
  5123. +1234567812345678123456781
  5124. +. . . .
  5125. +a b c d
  5126. +. . . .
  5127. +ä ö ü ß
  5128. +. . . .
  5129. + äöü . öüä. ä xx
  5130. +EOF
  5131. +
  5132. +cat > exp <<\EOF
  5133. +1234567812345678123456781
  5134. +. . . .
  5135. +a b c d
  5136. +. . . .
  5137. +ä ö ü ß
  5138. +. . . .
  5139. + äöü . öüä. ä xx
  5140. +EOF
  5141. +
  5142. +unexpand -a < in > out || fail=1
  5143. +compare exp out > /dev/null 2>&1 || fail=1
  5144. +
  5145. +
  5146. +#multiple files as an input
  5147. +cat >> exp <<\EOF
  5148. +1234567812345678123456781
  5149. +. . . .
  5150. +a b c d
  5151. +. . . .
  5152. +ä ö ü ß
  5153. +. . . .
  5154. + äöü . öüä. ä xx
  5155. +EOF
  5156. +
  5157. +
  5158. +unexpand -a ./in ./in > out || fail=1
  5159. +compare exp out > /dev/null 2>&1 || fail=1
  5160. +
  5161. +#test characters with a display width larger than 1
  5162. +
  5163. +env printf '12345678
  5164. +e |ascii(1)
  5165. +\u00E9 |composed(1)
  5166. +e\u0301 |decomposed(1)
  5167. +\u3000 |ideo-space(2)
  5168. +\uFF0D |full-hypen(2)
  5169. +' > in || framework_failure_
  5170. +
  5171. +env printf '12345678
  5172. +e\t|ascii(1)
  5173. +\u00E9\t|composed(1)
  5174. +e\u0301\t|decomposed(1)
  5175. +\u3000\t|ideo-space(2)
  5176. +\uFF0D\t|full-hypen(2)
  5177. +' > exp || framework_failure_
  5178. +
  5179. +unexpand -a < in > out || fail=1
  5180. +compare exp out > /dev/null 2>&1 || fail=1
  5181. +
  5182. +#test input where a blank of width > 1 is not being substituted
  5183. +in="$(LC_ALL=en_US.UTF-8 printf ' \u3000 ö ü ß')"
  5184. +exp='   ö ü ß'
  5185. +
  5186. +unexpand -a < in > out || fail=1
  5187. +compare exp out > /dev/null 2>&1 || fail=1
  5188. +
  5189. +#non-Unicode characters interspersed between Unicode ones
  5190. +env printf '12345678
  5191. + \xFF|
  5192. +\xFF |
  5193. + \xFFä|
  5194. +ä\xFF |
  5195. + ä\xFF|
  5196. +\xFF ä|
  5197. +äbcdef\xFF |
  5198. +' > in || framework_failure_
  5199. +
  5200. +env printf '12345678
  5201. +\t\xFF|
  5202. +\xFF\t|
  5203. +\t\xFFä|
  5204. +ä\xFF\t|
  5205. +\tä\xFF|
  5206. +\xFF\tä|
  5207. +äbcdef\xFF\t|
  5208. +' > exp || framework_failure_
  5209. +
  5210. +unexpand -a < in > out || fail=1
  5211. +compare exp out > /dev/null 2>&1 || fail=1
  5212. +
  5213. +#BOM header test 1
  5214. +printf "\xEF\xBB\xBF" > in; cat <<\EOF >> in || framework_failure_
  5215. +1234567812345678123456781
  5216. +. . . .
  5217. +a b c d
  5218. +. . . .
  5219. +ä ö ü ß
  5220. +. . . .
  5221. + äöü . öüä. ä xx
  5222. +EOF
  5223. +env printf ' äöü\t. öüä. \tä xx\n' >> in || framework_failure_
  5224. +
  5225. +printf "\xEF\xBB\xBF" > exp; cat <<\EOF >> exp || framework_failure_
  5226. +1234567812345678123456781
  5227. +. . . .
  5228. +a b c d
  5229. +. . . .
  5230. +ä ö ü ß
  5231. +. . . .
  5232. + äöü . öüä. ä xx
  5233. +EOF
  5234. +
  5235. +unexpand < in > out || fail=1
  5236. +compare exp out > /dev/null 2>&1 || fail=1
  5237. +
  5238. +LANG=C unexpand < in > out || fail=1
  5239. +compare exp out > /dev/null 2>&1 || fail=1
  5240. +
  5241. +LC_ALL=C unexpand < in > out || fail=1
  5242. +compare exp out > /dev/null 2>&1 || fail=1
  5243. +
  5244. +
  5245. +printf "\xEF\xBB\xBF" > exp; cat <<\EOF >> exp || framework_failure_
  5246. +1234567812345678123456781
  5247. +. . . .
  5248. +a b c d
  5249. +. . . .
  5250. +ä ö ü ß
  5251. +. . . .
  5252. + äöü . öüä. ä xx
  5253. +1234567812345678123456781
  5254. +. . . .
  5255. +a b c d
  5256. +. . . .
  5257. +ä ö ü ß
  5258. +. . . .
  5259. + äöü . öüä. ä xx
  5260. +EOF
  5261. +
  5262. +
  5263. +unexpand in in > out || fail=1
  5264. +compare exp out > /dev/null 2>&1 || fail=1
  5265. +
  5266. +LANG=C unexpand in in > out || fail=1
  5267. +compare exp out > /dev/null 2>&1 || fail=1
  5268. +
  5269. +LC_ALL=C unexpand in in > out || fail=1
  5270. +compare exp out > /dev/null 2>&1 || fail=1
  5271. --
  5272. 2.34.1