1/1/ʎ1Ҽ111ڸv&x&z11sL 1 &x&z1 1@s1ڸk&x&z1Bi1irsi;rպ ؎..6i1ӡiH168t)NiJ8t0)N1҈΀0P 1&uôCS0[ *PTRead error. Automatic reboot. Booting MINIX 1.3. Copyright 1988 Prentice-Hall, Inc. ll`Ȏء؎Љ .rvv6v4a?XP;QKP.Q>t[5PPa3PPJ3PP33Dxp{hs`kXcP[HS@K8C 0; (3 + # Pr`XkkkrGG Wg܌ێ6kUWVRQ6kPfvfvk>t5&rX[YZ^_ko&.fvo.k]g Wwww kUVW[FF`kkNFFFFtФvyF~ }F^F }A `ËuFF^^FDF-؉^\2 DDD2 `ËuD|u |VX5[DD };D$FD"D*kNL(D0kNkL.kkL,VZËFËkD$ uvvD"ËkD*ËkD"D(ËkD,ËkD(D.D"DD(DD(DD(DVG~&s DV,Z5ZPPW[[[%ǁu uPvPPΣP.FV1PPvv1PPX >t[1}vkW:G}v=PW Gv"P P vPPv<P PvRP PvEP PvhPPv_PP>t5vPvPFNT1RR1RTRQPW vP PY>tv<PqPA1PtPP0pΣ&2>t)PePf[[PePe[[<P!Pe[[1P!Pe[[PPe[[eU~u,kP>_[P6d,[[V&ÁtS2[ v4lP_[[rG*PwblP^9eU帄lP^[rG*PwlP^lP^[mP^[eUVv<t%V:mP^[[~t vLmP^[[PmP^[RmP^[YdU VFFFFF11ɋkVȹVF1NV‰NVF1QQRPvvU RdUMdUGdUVGOFPtP`[[fÁvcFPc[֍FPV[[ʍFPVl [[뾍FPVC [[벍FPV [[릍FPV [[1PPPPPvvDPP }0qX{`U>~ N tcv[cUVWUF^w$FFFFNFN ~ FG^wvU[~u3c$FN GFGFFPv{[[fÁ^^샿~^싇%"F~t^샿~v[F^슇FF1PPPPvvvCP* _bU VWFFfÁރ~uF=;|F=D FPH[Kb|@b%"ǀuFP[F~ubFF t F%F tt tFPY9u2u+VO[=tPV[[ PV[[PV[[aFPY9u+u$V[ uPV[[ PV[[vau7~\uƄFPV[[VaFPY9ujF@[ƄFPY9tGFPY9t8FPY9t)Ã\@9u~ u tF FPY9tFPY9u(FPY9uPPFvV_ [[`FPY9uƄo`uƄV[X`~ u @~t~G}~Q~>"t> t|>uuÃ9u@EPV [[Ã[9u@[PV[[F-GÊ$qFÃ1ɊNQX9u@FPV[[a_UVWF%NJFt1PPF>t> u y!>"u> t mPjmP^>t} mƃ>t+F~&Vs!>"u> t jmPmP^rrM>tA| Z a|z >t > t΀>t~uV-PnN"lN cNZNQ~t>t +`A+F,~t>t +;A+FDqXZPX]UVvu]F=u"t'-  FPVR4[[ FPVnG[[V/7[]UPVvWuP69t SP_= t= uPH1PX8]UPPVvW~~1PPPPPu5DP]EEEV'[FF1PPPPvv5DP]\U VvW"t1PPFt ~tuPVÁt^FFvvPv$FVF FuP<P$PPtP#FVFFF9}F~}vPF1F$FN ~aÃÊF卄9u^FFG~ tF=u~tHF=uFF~t~tHPWFFRPvvvvL FFVFVFF))t ~uDŽvXZUVvWDŽpDŽuZ- DPCPFFF1PPPPvvvv.ƄZU VvW~~1PPPPPu5DP]ZEEEƄDŽVÁt^FFvvPv! u DŽDŽV[}t*FF1PPPPPvvDPFYU&VvW~FFFFFuKE U %1ɋE U %E}tE- -PEE U %E U %E U %1ɋE U %EU%J%FV%FVFV F VFVRPY[ ӉN^%FV%FVꊄ%FV抄%FV⊄%F܉V޹FVRV^Y QV^Y Q1ɋV^Y ىFNF܋VFVFVqXTvvvvvu5DP WUVvW~E1PPPPPu5DP{VUPVvW~u uVEFFtDŽDŽDŽƄFt DŽƄ1PPPPPu5DPkVUFFFFF FF NFNFNFNFPvR[[.VUVvƄPV[[DŽDŽrVA[t v[[UUVWJzPtP5R[[>Jz} 6JzqP[[>JzPzN6LzJzP:[JzP.[ qXPRLzDNNz6PzJzPWQ[[UVW~EF~|~|PfÁnzދFDEu u 1PPOS tPu u 1PPRFVvv1P` PT |PDbzbz㋇q1vvQP^RDbz㋇qzqQvvRPR[ÊD bz㋇q1vvQP>SbzqD EDEDED|tPF~~FF uqbzAșbzTbz㋇qFF1vvQPQDFzqQvvRPR[ÊD F1vvQPR~D fzbz㋇qvvRPS |Y>fztVi[V[V[F~t4Vt[F~u ~tjz PP[[~u |~dz~uPPX-SUVW~=uFPJPFEFuVeVvPtSFV1ɋFV%FFV%FFV%F-1%F-%FF Fu vqPo[[1NFPQPYXZ%FF9Ft vrP0[[mDFv PIR[[v P=R[[vP1R[[vP%R[[vPR[[vP R[[vPR[[vD[P PQ[[QUVWCF^GƉ Gjzlzhzthz jz#lz6jzPQ[[jzlz6hzvC[ tPQbzPq[[JzPPM[[-QUjzlz9t6jzP7Q[[jzlzQUPVvW|uVX[ tPD9Du1PP[D DP[bz㋇qdP[>fztPJzPPL[[P[V[NJD%= tDbzPqdY9t tV[ tP6 utPD u!>tPP[[JzPPuL[[WXPUVvW|uPhDlzuPN>tbzqPO[[<uPPFv[D DP[t[t [t [nqP[bzq[bzqy[Pq[>fztPJzPPK[[V[F~tvDuDtDDtt>rPH[[PlDtP^DPDY tPHD+DbzqNJD+D bzqNJD+D Ǹ9DtP1PXNU VvW1|FF~d}/F~ }FFPPN[[FtFF˃~uP?Fu1P4F@uP(FPP=N[[F%\GxfzPXMUPPV>fztMdN ~*FPPM[[f~tvPM[[MfzMUPVvWV[P[t[>fztPoJzPPI[[Pu[V[D uD%= u|tfzDP(D>tPP[[JzPPfI[[1PXLU VWfz(?Flzjz1PPL[[ PPL[[v>[JzPPI[[nzDP[V[FD0FP[P[P[1}ƇzGYLULzNzFTzVzNXzJzPPH[[*LUJzPP|H[[LUVW*~PtPfH[[>~}6~trPE[[݋>~~N6~~P.[ jrXH~DN~6~~PWH[[U VW~FEF~|~ |P@}tP2fÁ~޹FD~9L|PEu u 1PPaI tPu u 1PP$HFV1FNQPttJ ~PFNDLFNd 1vvQPeHDvv1PPqIDd 1vvQP[IRP1PP0HD EDEDED~,F~|P->hrtV#[Vq[F~u΃~uPPX4JUVW~=uGPKPFEFuVeVvPtSFVF%FFV%FFV%F-%F-%FF Fu vrP[[1NFPQPYXZ%FF9Ft vrPH[[;Fv PaI[[v PUI[[vPII[[vP=I[[vP1I[[vP%I[[vPI[[v,;[HUVv<uP P~D D ~D% D~D%~~~P[ tPCP PH[[~PPD[[V)[ u1PD%?=uhrPX;HUPPVvWFP PaH[[1P#PEH[[Fu1Pi~Dࣼ~1PI[ tPG1}/P[ tP.FP PH[[F%\G̊D?tP1PXGU>hrtGP[ u v PG[[xGUPPVFv!PG[[1'}FP!PG[[FuFFtrP@[P hrPXGUPPVW~ ~1PX[ tPu79ǹzRG[zQ:[6z2[zR$[zQ[zR [zQ[6z[W8[  t hrP>~~ 1P[ tP8ǹzR[zQ[6z[zR[zQv[zRh[zQ[[6zS[W38[j t hrPV196~~K~ࣼ~~P[ tP&~PPB[[^ t hrPF1PX~EUPP[ uFP PE[[FtP1PNEXJEUP~ 1P[ u?P>[ u3FP PXE[[P%[ uFP P?E[[FthrDUPPVW~1FP!PE[[!~F='}~t'| hrP1PXDUVW1v#PD[[v"PD[[1,}FP!PD[[FtF,u hrPr61}^FP!P{D[[FtFuhrWj6[P8F%= thrWN6[P~ PD[[FW/6[1PXCUVWFP"P D[[F%FF%F>w6w11ȉVF1P@PPzPP P4 RPvv4 ^㸰zPúzS^[[^㸦zPúzSH[[1PPPzPP P RP1PuP4 z0~F~sfËz~F~~zRPz@RP1@~~F~ sfËz~F塨zRPz@RP1@>~~ L t~F~9NsVf~~~~~z~~~P{[=t vrP [[fP7[FAUVvW~E0DEDEDE0DAU VW1|FFFÁ~^ljzzv[XYGO^ww1PP? tG^GOFNww1PPS>ҋ^GW^GO+FNFNËzz+FN^GOF6F@Á~S[@UPPVvWF~|1|G Gu2@G Gt@SSi[[X@wwV[ww_@ ~0@G Gt@SS[[GYFH@UVvW~=^ ?=>^=>?UPVPtP(<[[6~P[xP_[lPq[`P[TP[HP[<Pa[0Ph[$P,[PN[ &sX<6P6;[[SUVW^GFGFGF~|~} ~|~|PVfÁt޸VfÁt߸VfÁt^FVFN tG1P^XFLdDTFD4D6D8D:D<D>D@DBDD1PXE>UVW^F^GFG F~|~|PVfÁt޸VfÁtF1NNFFFD FvvPWFVF Fu PBsP[[vvPxPRFVF Fu P`sP}[[vvvvvv. D"DD(DD(DD(DDFd~t |uV [1PX'=UVW^G F ||P=VÁtދFDDDFDHduV [vW4[[1PXL@D6L8E>M@E>M@DBLDD:L<EBMDEBMDDFDH|uV[ [1Pv:4[[DtTtsK}Ju>9uJuDLEJ6EJF^GLF~t9vu^wL^XGLFFՃVDtD1PX;UPPVW^ ||PVÁtދL1PXq;UPPVW~EF~|~|PDVfÁtދD6L8EMD:LL@E MDBLDEM1PX;UP~sP[[:UVW^GFGFGF~|~|PVfÁtF DFvV P,F)FWvPxP:FVWvPV%FVF Fu PsPP[[1PWvvvv+ FDFD1PX$:UPPVW^w||P WV[[1PX9UVvW|DFDFDFD FDFDLFNuD L FN$VvvvtSGFV~uDLFN%VfvvvtS FVF FtF FuPvvvvvv* 1PX9UPPVWVfÁtDu|uV[LFH DTDT$Et}Ptt88UPV$scDtX@-tV-LTP1PP t PsPe[[DTduV[O8VG8U VvW~~ w1PPF F-F tD*D&9FsPP_FP\ øw\ XY9r1PPh\ 1ONFFVFV1NNF\ ù1ɋV^)ˉV^FNFNQPXZe7UVW~>tu P<Po7[[ PPe7[[>tu PPL7[[؉FvWP4 t*bNu'F b؉FPF#bbF~tF>Ft0PPPF~u#bbF뽃>jt>}>u]6UPPVWVfÁtރ~| ~| ~tt,6~t ~|6Ft$v vv5ǃ~t t< t5Ftv vvXlj<5UVW~|~t ~tP)VfÁt߸VfÁtDtPE*FFFFFF-FF9Fr F+E&9FwPDt#EJF~t9vuP^GLFDt7Du1|PttF9DPu#tNt(vu(v ' duYV[R~uPIFEN}uW[MDJF~u|J^Lt ^GLF^LEL1PX4UVWVfÁtߋuJEug tc-tV-F~ttF9Fu~~u ~tu1PX3UPV>jt1>ltヿjt4㋇j-tV-㋏jr>|rprΣrpB3UVvWv%F-tV-F~}1P~}PP_ヿju ㉷j ㋟dwR㉷dDRv%[2UVW~%F-tV-F~}1P~}PPF^㋷j u v$[u29u^DRj9>ru839|RttR uv$[D2\RGRDR|RttR^㉷dvh$[2UPVX$ƃ>nuVO$[2hnORnhnORnhGRV$[1UPVP>PtP&.[[6@V->PG[*"%>PF[o6@sP[[sXO.@t>P6>-[[U VW^G O FNGFVÁtދDH DFu1PPDFLH+QP1P<P$.RPHJF Fu1PPFNQPDFDH }؉F,XZts0DH DFt#tHtF6Z6X0 }LFTHXZV0U@661P<P-\^HJ\0U^w w 661P<PP-Y[)Ӊ\^-0UVW>@)6Z6X66/ ~XZtrDH DFu~tHtF66/ >-tV-F~|Pvz[[F؉㋇,DFDHDH DFt#tHtF6Z6Xc/ }LFTHXZVh[>t >usu>V9puspV>t>~ 9u.UPV>|pG6O8G6O8BpG:O<G:OtD:L<D:L<{.UVWM%ǹ%F6PCP|.[[W@Pr.[[v@Pf.[[5.UVW*1QZxzkkQX1ɣhjdf PtPL*[[>} 6sP[[>N6)PF[$P:[P[ sX[*DN6PW)[[UVvW| ||Pu|u1Pt| }Pw㋇prD L FNvvb`- | uP71P1DFFFVRPb`, ~㋇`b+FNFVdÁt^vtPvCFVF FuPrpP1Pc, };|uFRPvvvv yFRPvvvv _Ft PtP [[FF|uvvvvv Fvvvvv F~tFvX+UPVvW| tP9D L prDRP1)D T ㉇`b1PXa+U VW>tFPhP+[[6FP`Pq+[[FPaPd+[[F PaPD+[[vaP8+[[> u,>t 9NuN 1~u@F- ~+t`*t[6tV8tQ:tLEtG PP*[[*z ϊN u%>t~u~u PP*[[f*>t>t ~Su 9~;ËF Ƈ   PP[[ PP,*[[)U VvW%F%lj~F~*u#Wv[[FFPVJ[[GHV([+FFFFu VU[[M)UVvv ~FPV|[[0)FP'%)V[3P [)HPV)@PV(@PV(HPV(t PVh[[u 1PV[[ @Vz(1PViv(% = u PV[[uV(S(V[ƄvF(P|<(p@uV[~N0 ȋpBp㍌ˉ@(0tX$UVvWV[F>te~u1P1PP6erFPQP61PFPPP6WPPr61P>u~uAr@9>}!rQ1QP6DŽrrrFFr- }*IrRQP6r r-rrPPW61PrP P[[]~uP`PFrF#r~urF#rPPW61PyrP P[[]&UVvpuM&pt6P>pttPPB[[DŽp&UVvW~V[~|~P} ||%FPrNtQP[[%UVvvPƄwx|DŽzzxFP%FwƄvFPV[[}Ƅvu\tXK"F=0|+F=9"|9|sR| 'PF-0Y:~;u||9|s%|FPV[[ƄvhtX!$UVvWV}[wutF% ~S~J>tF% ~3F%~&>tF% ~~>t$F%NPQXY ȉ~FpuF% p~F% ~~|H~%BF-<ù tN ȉ~F-<ù tt ȣtf~(|H~/BF-Pù tN ȉ~F-Pù tt ȣtt~ztXtX2Ƅv9UPVW~~ vF9N sv Q^VvvW  t߉FFF )F UVW~ >t >uP1PFvT~t 8Fv[9vWQ^Vvvv"FFFF)`UVW~ >t >uP1PFvX~t Fvk[9vWQ^Vvvv+F؉F+F؉F)UvP[[F%PQ[[F@PQ[[F%PQ[[UV>ttvƸPCP[[F%PBP[[F%PBPl[[FPaPo[[F PaPO[[tV^[FFFFFUNFPP^[[UPPV'ƍFPaP[[F%PaP[[tV[U VW>u >u ʉV>t hFF`PV[[FPV[[19~~GvVx[[FPV~[[=UPVPPM[[PP@[[sHDŽ DŽGAƄDŽ~ƄƄ@ƄƄƄƄƄƬ벃>t?p>t ?@P P[[1P P}[[P1PP6>t 6) ( p  tXUPV1is31|oPXm1oPXjm1oPXyF 5WUPV1is#1nPXm1>nPXjmF}UPV1is#1pPXm1PpPXjmFKUFPP[[8U~;uw~t tP[tP[~Cu>t PP[[U VvW F-ÁúߋNQP[[F%=uvF%FvaVn[V[ȸ-ËNQP[[vHuP[[럸-ËNQPY[[vfuP[[u"uXvF [UVWf-Ë1NQP[[ 9~?ËF ËF  > } PP[[ PP[[~U> umށ ܁PP[[QUVW> uu u PP^[[-f-ÁúދNQPK[[F u PP+[[|~V[ PP[[fÁDDD~W[ PP[[u+Ɓց NȁƅāPP[[ PP[[qUPVv|~\FP4[[DDHDFUPV v [Vw [-UVvWt-Ff-ÁúߍF%F%FFF򃼤~Z]]9^vOvv [[FF􋄤HF= }FPWV뼋]]FE@E@럋F+FFFF}u EW[$UVW~YF-Ff-Áúދ\\9uFPVW|u DVV[v[UVvW~F]]^FP-DŽ~Fuu^ FE@EDŽ]ډVFFF% = u%FN t ^ FFEFFH6uX^FE@E@UVWǾPsKu>FF1PPPPvvCP7ƄW[PƬW[1PXyUPPVWF-Ǹ-ÁúDDDDAUVWPsBDŽ DŽSƄƄ@ƄƄƄƄƄƄƬs[-@FEEEEPPPPPv  QP[[@QP[[U VWF%dƃdunF%dǃdunFF@tFFtFnuPPFF%FvvvWVv UVW1f-ËQP[[~2}F~2}FFQQRPFF%1PQ[[F%APQ[[dFdPFY PfÏ|~ tF ƃ~ t~ u F H ƃ~|~ F- ƉVP2[[U@FPtPT[[vFP[FP[ڍFP5[лuXUVvW1>t|VdÁt^ttPv9كFVF Fu tF LLLFVFN%F~}UAFPQB[[F%=u-F%=uF~}Fv[Fv[F%=uWt4DPUPVW~}u6P^>tV66CPQuuj[LUVv>u<Pt4DPUFFF FFFFPvS [[UVv tuP [uuP [uuPu [UPV>txPPQP[[1d}F QP[[jUVW9t PP{[[>uC>AFPQh[[F%=uY66[[FF0W6,[[QP[[QP [[1d}FF%=u>u1PvPP/[[U1PZvXK }UPSQRVWF~NjFvދ~vV NuutV N1) u u _^ZY[X]ÉV NF^F^uVWw G& _^ÜXUv]USV^vFDGDGDG^[][UWV);&fvvfvrG26jvPaf^_]$0<0t1ót1UQRWF~m_ZY]UQRV^vo^ZY]UVWSQRv~#>N +~)ىhvt utFt.dv򥝃~^ F~t6hvvZY[_^]átR tXZUVWQv~N FY_^]UVWQv~N FY_^]UF^&0]UVQF.N_F.P_F.V_F .X_N .T_.L_.<_Y^]Ri55f  Z1&w&w x@ظ4ruءPPˡe! 輦81@ظ4ruءPPˡe!v1򥡤w&w&UVWwP][PPPxPJ҃FVtrnDt_D"FD0D.F1VVFvv1PPJF1VVFvv1PPF-tVP[vvt= 16. pc = 0x%x text+data+bss = 0x%x This may be due to accidentally including a non-MINIX library routine that is trying to make a system call. Kernel panic: %s %d Type space to reboot 1234567890-= qwertyuiop[] asdfghjkl;'`\zxcvbnm,./* 0!@#$%^&*()_+ QWERTYUIOP{} ASDFGHJKL:"~|ZXCVBNM<>?* 7894561230.1234567890-^ qwertyuiop@[ asdfghjkl;:]\zxcvbnm,./* 0.  /!"#$%&'()_=~ QWERTYUIOP`{ ASDFGHJKL+*}|ZXCVBNM<>?* 789456123  /1234567890/ qwertyuiop* asdfghjkl+'@ZXCVBNM;:=* 7894561230,[/{}\|`1234567890-= qwertyuiop[] asdfghjkl;'\zxcvbnm,./* 0!@#$%^&*()_+ QWERTYUIOP{} ASDFGHJKL:"~|ZXCVBNM<>?* 7894561230.HAVSDGCTYBUpd**4@LXb    6 t tt>t5 ***##  ` ---disk task got message from FS gave floppy disk driver bad addrTrying to DMA across 64K boundaryDiskette in drive %d is write protected. xllwinchester task got message from %d FS gave winchester disk driver bad addrTrying to DMA across 64K boundaryHard disk won't reset Can't read partition table of winchester , +++++++++,+bad call to sys_newmap (src)bad call to sys_newmap (dst)do_sig can't signal; SP badcan't inform MM99999clock task got bad message====mem task got message from RAM disk got odd byte count B BBjB B ~B .B BB BVBBEME[EHFEELMwFMKJMKMKJJMKJL @IAFBFCGD;GH`GJGKHLKHMHPbImJCP P P1P &PSoftware scrolling enabled. Hardware scrolling enabled. Q RQQQQQQAVUUUAVAVURS 232 line status event %x RS 232 modem status event %x JYfYJYpYJY\YPrinter is out of paper Printer is not on line Printer error TTY AYPRINTR-WINCHEaFLOPPY =RAMDSKp9CLOCK q+SYS IDLE MM FS INIT \\\Kernel stack overrun, task = proc -pid- -pc- -sp- flag user -sys- base limit recv command %4d %4x %4x %4x %6D %7D %3dK %3dK /bin/sh%s PROC -----TEXT----- -----DATA----- ----STACK----- BASE SIZE %4x %4x %4x %4x %4x %4x %4x %4x %4x %3dK %3dK ANY %s%4d g DQfOfUfXfcfdTfefffgfofsfufx f(null)kyy&yy=y=yTyError: Division by 0 Illegal EM instruct'n Err in EM case instr Variable out of range Err in EM set instr Floating pt not impl. Heap overflow EM trap 0000000 octal $$&"&UPVk(&>;Á6+t;1r;:;><;|><;F|<;㋇$&Љƃ>r;t봃><;;u u66;68;V6>;9UX;PtP![[ t P$P9[[X;>;Z;<;#UPV(fÁ6+D&tD&t#NB;ND;N R;@;Pvk![[ t P$P[[V#Ur!(6(/[\+++p;+#UVW>>;tP\;N^;N`;N>b;FFFFFF+FFvR[1P6(P@PX!FF 1QP1P@PA!F1PvP@P0!FPP$Pv$P[[v$Pz[[v%Pn[[F+F+FP%P[[[F+F+F= }P.%P@[[+|FDFDFD FDFD ~tL& 1PX"UVW>t;>p;uP>p;| }tPEFE +EFFE& t FEF1VVFv[F~uP1VVF1UVFvvvv1PPvv1PP?F~} vR%P[[6+-s D&u(-6+(Fp;~vF(FN t^F1P^XF>;LFDDFDE+EDDDDF>P%0u}P%AQPP%6+-sP%9Mt P%9MuF(܋P%L~u>>;uDDtv6>;tv6>;PVv4[[1PPPv_6P%X U6\;6t; [[r;1UPPVvW-6+(NjFD(d\+tV[L&D&t 1PW[[Wt[[1PPWPDFD +DFFD& t FDFvt$[[WUPPVW16+-s+D&t >;9LuGD&tV1[r;1P!( ~t;O&r;1PPXU VvW-6+(F(dÁ6+^D0FD F1Pvttd&d&^g&d&p;+tP1PF6+-s(F9EuE~tE&t W][F(IU VW6t;b;N1NPQPYXZ9|v 6;P9+DǍFP6>;z[[vWV!F~u6b;P6;vXUVW~EFE F11ɋTȉVFFF1Pvvv |P-11ɋ+VȉVFvv1PP_ ~v4F^FFF9FsP^GFG9Ft^FGNvv1PP  ~-1 +NF 1L+NFL1LNFLNE&% Fu uuu uvp F~u~t-6+(WP[[1PCFt ^FGFt(1 NF 1LNFL1L+NFLPXBUVWF-1ƹF-1ǹF -1F~ u F=~P&F=~PFF 9FsP1PXUVW(fÁ6+މ-6+(NQP[[vt V umD" PV [[[UB VW6t;^;vP>\;~>\;~Pub;\;11RPQP1PSP6>; tW21PP6>; PPPPF1PP1P P~}v-艆PPPPPFPvF~}v2[Pd;111PQP1PRP6>; tv[PGPP t v[WD D+P[[111PQP6>;RP1Pe t Pf%P[[1PvPvv[tDtt6>;PtDDPDP6>;.PD"d&F D&6>;[[1PXU.Vv W~ PFPv= tPI#F#N=utP+1 #F#N t P1P^FډFމF^ ^FNO^FNO^G uP^? t -F^ -F^O-QZFF9FrPi^? u1PvFF+FFF^vvvvv7% FF֋N%F~ ~F- PFPv[vX0U$VWF-FFF-FF -FF N-QZFF+F+FF~}PFFPz Y9vP6t;DFD +DFFD& t FDFvtA [[FFP [F~u P~%P [[FDFDFD FFDFDDFFDDDFFD V6>;([[~^^9vGFFF1QZFVFF11ɋTTȉVFFVFVF1ɉF܉NދF܋NFNFN+FNމFNvv1PPw FvvRPa }vvFRPFF1vvvv1QQPvP1P%  t P%P [[FNFNFN+FNFNm1PXUVWFvF=})^9r<t <~<F@FUVWF>; FFft;ǃ~t,|9vsvVWvk9tߋF)F.UPPVWt; >\;~>\;~P>\; u1P|\;Iƃ>f;ut; w t;#G"G"E>f;ut;#G G t;#G"G" t;#G G t; w"t;f;O$tP1PX~Ut;w6^;6\;eUVW>>;tPr;\;N(fÁ6+D&tD&t1PzDF^;Nt;6+t;DG~uvH[F~}>tu1PvF uFNFt 1PWvFG1PXUVW~~~~PFFHF+-rD&uD9Ft~t1~~ D9Ft1D&t1~ut;D9Gt1~u ~u~uD&u= td& u.FFD t!vVM[[-6+(P[~~(Ot;G&t t;G&tr;~~1PPXUVvWD&uFHDž|"tT#D"D"-6+(NQP [[F-Fvt Vw u-6+(t$vP FH &VFD~tV*[1PV [[UPPVW>\;W6>; [[ƉUPPVW~(>(<f1ɣ ((~t(Á\+^^(Á\+^^'(PPw[[ t P%P[[6 (QUt;O&r;1<UPVW~(Á6+D&tD&ud&1PPPW[D&tD&ud&1PPPW81PPWAP U\VvW-6+(1PP P D9Dt1PP1P P t;6t;PFP&PǸPFP%P|t; |W# [| [|u1} }tP&P [[1PP1P P  }D DPVW  }W [1dž|311W1PPP 1PP7 } 1PPP1PPW |}W[ +51PP1P Pn WT[g WL[_ UPPVW6t;6<;|L8;DNJD8;(&>;ËJ+(dËJ+8;\;9Lt |tPo\;L\;L\;QQ6>;P 1HDP\;Y9t |tP/\;L\;L\;PP6>;.P 1%X WX UVW6( t9F9Dr*FFD+FD|tvVW[[v t1PX8 UVW( u P%P[[FFEM(6( t9Fwu>(W^[ t9Fvvt^GE^v2[ UVvW~96(u L(DE(L6( UPVvW| u| D9uDEDWV[[| uU D9uDEDWV[[9 UPPVW6(1 t 9|v|t UPV(+s DD"(+((($((N ( UVW1Pv[[ } ;QvVn[[ } P%P[[^G%F~u~t Vd[Pxt;u~u ^GItV\t;w^GY9ut;G^9G u1t;u ~tV^GFtV V[PX UF Fu1PRN"+N$+NV (+*+N#+N &+NV,+.+NV0+2++P[6 +X U Uv%P{[[~t v&Ph[[&P_[1PPP$PN K U;;ˊF;>;du ~ u! U>;u x;|;z;;;;~;v;PP[[;UPPVW^ ދF1v׃09~NF1vF~uՋ^) ߉F1P^XFO uF|UPPVW^ ދF vvRP=ǃ09~NF vvRPFVvv1PPs u^) ߉F1P^XFO uFU&VWFF^F u%tW[FƍFFF^?-uFFF ^?0uFF0F^Fǃ0| 90*u^F? fF~FF.u9^Fǃ0| 90*u^F? fF~FFltLuFދ^?t ^FWF޿F޿ F޿~t^FWw7VL^FW7VFރ~t<^FOFډN܃~}-F^^܃^ PvvV^FF~}-F^ PvVecF ?FXF ^FFEF ^FF~uF&~uFv<tN|FF&XB+FFF+FF~}F~u^~}+^?-u~0u^FPH[Nv>[FuN|^FP&[~uqv[NUvvPP?UPV1PPvPPvPP>ƉU1PPPPPvPPUvvPP=UPV1PPvPvvPPƉU1PPvPvvPPU1PPPPvvPPwU1PPPPPvPPz'^PUN'N 'N'PP[[(U1PPPvvvPP'U1PPvPPvPPU1PPvPPvPPUVvDVP[[ t P&P[[UVv1PPVPPvPP' ' T ''LT''LT ''L TBU1PPPPPP PPH'UN'N ' PP[[U1PPPv vvvPUN'N  'N  'N'N'N'vvQ[[UPPVWv [lj>'N 'N  ''O t ^ F Fvv[[\UPVN''PvP[[ tV>'}'ى;P6'XUPVvW1F?tG@ UF^ ]Ìø<W@)&1&u=u_[YZXV\^9wr9w t1øËOI|;u_uPVW\D u1ҋL D _^[1ۋD T 9wr 9Tv+T@VW\D u1ҋL D 1ۉЉ_^[1ۋD T 9wr 9Tv+T@YF'0]'(t' ''''1ۇ'tPXûSRP;܉ƒs'Ӌt'0K'U;^;^;^<;PP; ]_^]^_[YZXV\^9|9w t1øMM receive errorMM can't reply%c[H%c[JMemory size = %3dK MINIX = %3dK RAM disk = %3dK Available = %dK Not enough memory to run MINIX do_fork can't copydo_exec stack copy errMM hole list is inconsistentnew_mem can't zeroalarm er.Aa$./1Hole table fullallowed: fstat failedMemory manager panic: %s %d core6;qp( C H DOUX|cBde7f7g7osUux(null)sys_copy can't send''F'']']'t'Error: Division by 0 Illegal EM instruct'n Err in EM case instr Variable out of range Err in EM set instr Floating pt not impl. Heap overflow EM trap 0000000 octal <<&\MUPV&jD&XÁYY0uP1PXX>X|>XF|X㋇^MЉƃ>XtV6X[[>XtUPV>XtcYB]sM|=uB-YDXD6%XD6%XL8XL:XD<D=XGD뭸PHPE=[[XPtPD[[ t PHP'=[[XXXX]GUNXXPvC[[EGUPPVWp1}`DÁYYPP*[[ƋYwVQ-[YwYG.YG0YG2YG3YFU VW__`_dվ`ps"DŽDŽ   dd`prC1ɉFN-F1NF%1ҋ^^VPQRSB t@`u_dh,du_XX  d`ps __EUVWHNHNHN11ɋVVVȉVFFVFVF1PPv+Ƹ2PVPP9FP^tLPV,[[F1PPvz+Ƹ2PVPP9FP^t PxIP:[[^wO XF~@~ vIP:[[FFPVb,[[~~FFFXBNXNXFFFFXNXXP1PA[[ t PIP:[[XXNVXXNXXPP@[[ t PIP9[[~u vIPV;[[JPM;[FF9Fr1PvvA*ƸPvP.*ǸPVWs8ƅ PV]+[[PWS+[[1P1vARP1PP@FVvv1PPA u1PvvLJP:F_~u ZJP:[JP:[XPƋ> tV'&[W$YG^ËF^G^wvXAUPV>XuPNP6X6X6 t6XPP%[6X|>X~PP6X6X5 t6XP[ u6UV6XM)[ u6U1P[&>U$VW>Xu0Xt(XVXV&X? XNF>Xt>X}P6X5([ u6Xu1P^EMFN~}PEuDLFNFF%F~ uP1PFށ~`uP1PF~tF Fu FFO~tH6X6X6XvvtvDF~}FFFFVFVF~uW~uQvvt [áXwO [)SQ< ~PXvvvvy< ~1PvvVV|'t#vv6XvV F~v >Xuvv1PPU:F+F9Xs6X+FPF~} +FF~t ~tE~u?FN+FNFNvv1PP; yFRPvv; ~FFvv6XvvvvvVZF~t5>O},^XXX+NXFFFVFV~u@~t~t{vvvv; ~ FNDL(/DT D&M|'tGvvtt: |4DDFFPVf%[[F܃~t ^GGFNEM~u:|)u4vv1PP8 u ~t~@u6XNVXX~uD)>OtON>OuFF~uvvX":UVW~%1=`uAN~tvv1PP/7FEFvvW8FE F~u>~u8~u1PPP V[nvvW u[63FV~|PNFF~tP1Pvt )ǃ~tW[^^^?u6tt ,[[^F tƅ ^?uBPW&[[6X~PPV1Pv~PjPV[[ uY-YD QP+[[P@F V RP1PP"0 ~ 1P%[1PF F uPPV_PX/U~uXYG<X XYW6YXO8YXO:F؋YG>X/UPVW~YB]sT|<uI|=uCD6%9Fu8D6DË9u -YD1QP[[XNu4/D,/UPVW~ || WdKP$[[DÁYހ|<t|=u.|>u D=XD<FD:vWw[[.U VW>X~P>X || WpKPY$[[DÁYހ|<u1PD>؉F~uxD6%F~|~| P~KP$[[^DËF^_GF1ɋF%O>O^OOOPv[[PW[[1PX-Uvs[NOO㋇MOQ6O[[O-UvC[O㋇MOQ6O[[-Uv[~uPPO1ɋF%ONV OONONON OO㋇MOQ6O[[>Ou6OB[O&-UPPVW6X[ u6Ou6O[OPXPk,6OX{,UF%O1ɋF%O>Ot "N9O| 6OKP![[O㋏MO-,UVvv([[ƃu*FPv([[ t PKP![[vv[[ t PKP![[+UPPVvWY4uDVP[[+YG4%F^㋿MY1ɋG4%DVWO[[u+U^Gi+UPPVW~EYw@GBY9tF+Y4t9+YB]sE9D4u%+DYEG4E%F1ɋE%EEXMXM^㋇MWv[[*UPPVWY4YB]s9|4uD4DW,[O1ɉ%OXOOO㋇MOQ6O[[1h*UVWFPv,[[ u1P~uVFPV[[V[WX**UVW^?/u YwYw^VS[vvB[[ u VA[1P,=uV$vV[[FV#[~u1P~vX)UVW~vF~/uF^À9s%~/t~t^9sFGFFЃ~/u^À9s FF^9sG^À9r [[v [2 u1P/}(u(PPs9|*uW [Pt& [[׃2WX(UVW~%=@tP~ uPPF1PvWzF~tvuu1PP$FFFFFvvuu' |vvWF1Pvu Fv^9rFF9Fv~ tvFn~ tP<tKPvDP t6~ u^Ƈ 4EU ^APv[[1P~ u <uFh~tAPvl[[FV ~ tP~u+FuPyuuW|F~u6XuPP6X6X2 t6XP[F~u6XP[ u D&6XuPP6X6X* t6XP[F~u6XP<[ u6Xu t V[WP6X6X t V[6XP[[F~u>XPR[[ u6XuF~uPFPFPWF~u D HD D&VK[WF[vX UVvW%F~ t~`u D FV[FF1FV|'u DDFFvvtt }:vvVF~tNFFvvh [[FNFNtvO [[DF~tQNFF1PvvkF~^9s5v [[BPv[[vv[[V0[UVWv[ƋD_D=rP|w|v PKPB[[19|v 1QPvLˉGۋDF19|v F1QPvLˉGۋDF^DF^DD__1PXUPPVWv[ƋDD+_أ_19|vDøP7y[[G19|vDøP7[[[G1UVWF9F rF F FF+F ؉FFF~uvF@PFFN u^^F^^ދ^9utF~tdF~}YNFuH+FF V‰VF9FrEN ^Ƈ v%F롃FF9FuFF@1PXU VW Flj+F؉FFFFV^7 uO^Nu vKP[[^^N#Ƅ UPVPPsF9D&uV2vLP[[XUPPVW~EF~uPPPsF9D&uP21PXUPV^w [ƋD UPPVvW~u,D&F1PPt&Ǹ2PWV FD&!PPt&Ǹ2PVWƅ D1PW[[UPPVW1PLPW[vvhLPG1POFFvP[[ uvEP=[[(FD YG0DYG3D FD V[VXUPVvWDDDT D&1 }DGdUPVvH[vDP[[HUVvWt %[FD"- 1^GGF1Pvt iNjD"- 1ӹ^~u PvV PVv}ƅ @PWg[[D&UVvD$@D$UPPVWF%㋷_~t5 t1F9u"F9u u_ @ V˃>_u PLP[[_6_  ~ t t  ~ PLP[[%㋿_9u%㋄_t9u 〼 ut PV[[FF @ %㋇_%㉷_~t~u 1PV[[VXTUPPVvW uC H   ~-_F~t ^>_ t FN_Ft)DŽ_>_u6__6_'_DŽ>_u6__6_F@t ut PV[[~uDŽsU VWvT[ƋD-+F؉FD+D@vtPDP uGFt(1ɋF%VPRLP DŽ>OƄ $UPV`psF9uDŽ UPPVW^1}YSXÃ?u^?G݋^?}P1B]_s$|uFDD^71P ָPXUVvXu\D&XÁYދYwR[>Xt YwtYXGYw([>Xt1Pt0YXG01PY6X6XS7PXUPV>XuPY6X6XS VXtUPPVWPvv  t6XP[ u6XPl[ u6XP2[ u6XuV[ tV[W%X ȉD&V[1PXAUPPVW>XuPSP6X6X_ t6XP[ u6XP$[ u6XP[ u6XuV[ t uXXLT D&VV[WXU!XX1UPV>XuP6OXXOOOPPO [[ t VMP_[[1PXUFP6X [[NVXXNVXXNVXXNVXX1RU VWXϋ6YW[ u6XuFPXP1PF~t0v_>X|>X|PK9>Xu6X?XXKωD7XDÏXDË^@6XXUVWP[ tE,U.}0uE1FP~XtPD&XÁY߸D&XÁY^FDFN tG1P^XFD&XÁYF~}+^DÃ?t^DË^@FϋXL@>XuXLBtG[t@[1PX UPPVW>XtP~D&XÁYY>Xw@GBY9u Y4tY<uY>uX>X݋YG<1} 6XFYw[Yw[1PXS UPV>XtPXuXL.XL0>X.uXL3XL21PX U>X~P6X6X[[X1PX UPPVWOOPPU[[ t W.MPe[[P[ tOOL,T.|0uD1OO} UVvW~GFPY9t1P NuPXN UVvW~~7 F uC u7 u+FF~v^F7^FNu%FF~v^F1P^XFNu UVW~u~>XXGFNu1P<~~ XPvv6XPmӃ Pt? Pv>MP{[[~t vVMPh[[ZMP_[kU U~u* ىˊFك>du ~ u U>u rvtوًىxٸpPPn[[ UPPVW^ ދF1v׃09~NF1vF~uՋ^) ߉F1P^XFO uF. UPPVW^ ދF vvRPǃ09~NF vvRPFVvv1PP u^) ߉F1P^XFO uFU&VWFF^F u%tW[FƍFFF^?-uFFF ^?0uFF0F^Fǃ0| 90*u^F? fF~FF.u9^Fǃ0| 90*u^F? fF~FFltLuFދ^?t ^FWF޿F޿ F޿~t^FWw7VL^FW7VFރ~t<^FOFډN܃~}-F^^܃^ PvvV^FF~}-F^ PvVecF ?FXF ^FFEF ^FF~uF\N~uFv<tN|FF$NX+FFF+FF~}F~u^~}+^?-u~0u^FP<[Nv2[FuN|^FP[~uqv[NU1PPPPvvPPU1PPPPPvPPzN^UN|NN~NNNPP[[U1PPPvvvPP'zU1PPvPPvPPZU1PPvPPvPP:UVvDVP[[ t PdNP[[UVv1PPVPPvPP|N~N TNNLTNNLT NNL TU1PPPPPP PPHUN|NN~N PP[[zU1PPPv vvvPYUN|NN ~NN NNNNNNNvvQ[[UPPVWv [lj>|NN~NN NNO t ^ F Fvv[[UPVNzNxNPvP[[ tV>zN}zNىٸP6zNXUPVvW1F?tG@t UF^ ]ÌøW@)&1&u=u_[YZXV\^9wr9w t1øË+G;Gw Ë_u ӅuPOI|;u_uPVW\D9u6!}t.1ҋL D !}!}؃_^[R1!}\ߋD T !} T9wr 9Tv+T@_VW\D u1ҋL D _^[1ۋD T 9wr 9Tv+T@ʉRgZPgZ‰ȉg[VW\D9u:!}t21ҋL D !}1ۃ| }ڃЉ_^[1!}\ߋD T !}ڹ9wr 9Tv+T@VW\D u1ҋL D 1ۉЉ_^[1ۋD T 9wr 9Tv+T@YN0N(N NO,OCO1ۇpOtPXûSRP;ƒs⻐NӋtaO0KSOU^ً^ً^ٸPPٹ ]襷wƉ[_^]^_[YZXV\^9|9w t1øeget_work couldn't revive anyonefs receive errorZONE_NUM_SIZE != 2SUPER_SIZE > BLOCK_SIZEBLOCK_SIZE % INODE_SIZE != 0NR_FDS > 127NR_BUFS < 6inode size != 32Invalid root file systemRAM disk is too big. # blocks = FS Can't report to MMCan't report size to MEMRAM disk of %d blocks is in extended memory Loading RAM disk. Loaded: 0K %4DK %c RAM disk loaded. Please remove root diskette. RAM disk loaded. Root file system corrupted. Possibly wrong diskette.init: can't load root bit maps @}`b @`   revive errunpause err 1unpause err 2bad major devrw_dev: can't receiverw_dev: can't senddo_umounttoo many map blocksfreeing unused block or inode--check file syscan't find superblock for device (in decimal)Out of i-nodes on root device (RAM disk) Out of i-nodes on device %d/%d All buffers in useNo free bufferNo space on root device (RAM disk) No space on device %d/%d Unrecoverable disk error on device %d/%d, block %d do_stime errorclock_time errFile system panic: %s %d ;:[ j9>%&9>g385K69>4 9>v"~$9<9>89>9>49>89>9>69>9>99>9>9>9><989>9>9<9>9>9>9>9>9>9>h9>9>9>9>9>639>9>9>9><9>9>aaa%aaA D0AO@U@X@cAd3AeAfAgAo@sAu@x@(null)sys_copy can't sendNONCONNNError: Division by 0 Illegal EM instruct'n Err in EM case instr Variable out of range Err in EM set instr Floating pt not impl. Heap overflow EM trap 0000000 octal &P UVW I t FPW [B1PR P[[ }P[PZ P[[Pd P[[n P[P[x Pv P$[[1Pz P\[[F~tPFP1PZF~tF-0FF-aNJF-0F~~~~븀~0u |~뢃~|~ ~딋^㉿Wv[[뀸P P[[FP Pv vF[ PV[[FFP$ [F1 }F9u#0N PK P[[V[[FUPPVvW t^㉿1P[F0N PF P[[ tP%[PF P[[=tP [PF P[[=tP[tTPtP1PoF㋏ ㋏ ㋇  P tP1P/F P[ P[ P[ P[( % UVWP8 P1[[ } P1PPW1} BBFPvFPPvFPFP[PFPWPFPWPFPWWI[ U6FPvDp UPVFƉ?t4FPvG U6vv2 UVWdždžFF?t싞?t싆ÉދF9r HP'Njdž9}V)^F?t!FF9rHPFdž9}U)^F?t FF9rHPPF)Aș1PPv1Pvu[P;P1PPXUVv1P"PV1PPVG[P;P1Pt\U1PPPPPPP1PYBU1PPPPPvPP;$U*VvWN N v1L0NF1L0NFFV F V  DDPY ȣ 6PP<[[Wm1 0NF1L0NF1L0NF1L0NF1L0NމF1L0NډFܹFVRV^Y QV^Y Q1ɋV^Y ٣  1ɋFڋVRVދ^Y ٣6PPR[[W6PP?[[ǹ  T1ɋ  TLV։^؋F֋N%F֋V%DW6PP[[ǹ    T  T1ɋ  TTTWHW X?X)UPVN NV  N  PP[[ tRP6 6 XZUvvPPUPV1PPvPvvPPƉUPPVvWF-Ë(F-É(N t uV P0P1P[[F~u~}vWX6PSQRVWU_Kۋ(6 PX ]_^ZY[X UPVW~v~~%GF tNt~t NtFFU1PPPPPP$PPUVW~1PPPPPP PPƃ> | t ىHPP#  NV t FNMvvXZBUPVW~1PPPPPPP1P: | t U1PPvPvvPPUN N  N  N N NvvQ[[UPPVWv [lj> N N  O t ^ F Fvv[[fUPVN PvP[[ tV> } ىHP6 X'UPVvW1F?tG@  UF^ ]ËOI|;u_uPY40K(b y1ۇtPXûSRP;ƒs Ӌt0KUL^N^P^TJPPJ ]L_^]^_ 0` 0`@@ @@0@`@/usr/adm/wtmp/dev/tty?/etc/rc/dev/tty0/dev/tty0/bin/sh~/etc/ttys/dev/tty0Init can't open /etc/ttys /usr/bin/login/bin/login/bin/sh/usr/bin/sht tt3ty4KKbError: Division by 0 Illegal EM instruct'n Err in EM case instr Variable out of range Err in EM set instr Floating pt not impl. Heap overflow EM trap 0000000 octal bb1@l)pl҉rl`'`ڎŽ`pl1FU]011y/~ >/F16rlɈʼn16plvl^F :pl~F fF /u/0/6/;6/t FF [UWV)f^_]U~ u P=[v6[.UPV=%ƃ u V[.U&VWF܉ƃ~ t~}^^^N F 1F~ }2~}GFNщFN9~+FǃFVFȉD1FN F FuF N ~ v6[~ t-FF9v NP[-UVv~t- =^w\t V[P\P[Vj0P[gbP[]nP[SrP[ItP[?fP[5\P[+%1ɺ0RR1RRQP P .0X+PX6-U VWv<uOP= PG[P?[1F<-uF<0u0P PF1-0= w ljF-0߀`u b1P&[1Pfvd1P[[>`tj1P[PEljqtQuP1[V[ untNuP1PX1 uAQX)UPVW~1=0|=8}G-0܉R)UPVW~vp1Po[>`tFr1P`[O t"uP[ t19vs@PFPP[ uP1PX(UPPVW>2`F-1f2`2`962`v (UPVW~=u$- =^wP?Px1P[[GN up(UVv|tt[|1P[S[H(U4`u ~1Pc[64`[v&4`71PD[[4`71P1[[0X%~t1P['UVWFplrl^P6LlPvXtN utDpl rl"P6LlPvtN ut1P [U'UVW~1P1v%FV u 8`9Nu&'N8` FVFFF`1ɉFNvv1PP& ~1P [PK[F`Q6LlPW`u&C1Pv u2P2PV1P F`Q6LlPWuj&Nu1VPv u62P<2P6\l2Pu B2P[7&U1PvvPP$P[[v v6Llvv1PP$[S%U>`uD2P[~ t1PvvPP#Ps[[v 6Llvv1PPY$[Sv;Pvv1PP#P5[[6`%U~uvv vva%UP6^l^2P~[[PPFP[[ t FP]"[^l6`lr2PU[[PPFP[[ t FP4"[`l6bl2P,[[PPFP[[ t FP "[bl6dl2P[[PPFPt[[ t FP![dl6fl2P[[PPFPK[[ t FP![fl6hl2P[[PPFP"[[ t FP![hl6ll6jl2PPPFP[[ tFPb![jlll2P[ tP^lP1PP$2P[ t>`t1Pk[#UVFF^l  1dlbl΋`lPQY[[L`PL`PvvL`FVN uًfl0bldl1 R0RP1Q!RP^hlfl1QL`QPR1!RP8hlfl1 Q0QPR1!RP#UP 3P%[/`l6/63P[[PPFP[[ t FP[`lhl6hlN3P[[PPFPS[[ t FP[hl/^l6^lj3P[[PPFP"[[ t FP[^l^l Q Xbl`l Q Xdlhl^l  P1bldlX-hlfljllljlllhlRQPYXZRP1PPRPPP! |hljlllnl3P[`&`3Pm[ uP^lP1PPfiU!U>`tDP^lP1PP>`t>nlt3P[>^l3P[>`lw3P[>bl3P[>dl3P[>fl3P|[>hl}3Pm[6ll6jl1PP 4PR[ UV^l Q XƁ>nlt4P'[96bl}:4P[96blt r4Pp4PVN4P6blt4Ps[[`l Q X96dl}4P[96dlt 4P4PV4P6dl4P+[[`l9flr4P[>hlr4P[>hl~ 6hl5P[[hl^l  P1bldlX-hl96fl}$5P0[96fltV>5P[[6flj5P[[FFFN-QPhlXZRP1PP)RPPP* |hlFVFN9jlu9llt"vvz5P6ll6jl5PUPVPv[[Ɓ UPPVWv19~~%1QVP1vRPOGF֋^oUPPVWv19~~%1QVP1vRPPGF6UVWFt(F^^F% FҹF^޸f^9v U6bl[Vl6dl[Rl6bl[Tl6dl[Pl6bl[NlUVvW~ uF 9Fr1tB^@^ u+^?t#>`t>`u 5P+[ t ^B^?t:tuvv 5P7utvv 5PljFLUVWv~FF>`tv v v7v5P[[v v v9tFPFPvvv54FF9Fr̓>`t>`t~u5Pg[~~ ~ v6PP[[~t6P[ tv v v~~$6P [UPPV>`t&6P[%Vltbldl1RVRP1Q1RPSQPYXZY[SQ4~t]V<6P[[V6PF[ tGbldl1 RL`RP1Q1RPSQPYXZY[SQF96^lr*^6P$[U^lAPQ[[J`U"Vv>Xlt`6P[Xlbldl1 RVRP1Q#1RPSQPYXZY[SQ>J`^ދ^ފPFYJ`PFPVt6Pr6P-[ t_J`F u6P[bldl1 RVRP1Q1RPSQPYXZY[SQUPV96^lrJ`?tV[F>Xlu6P[UVvW~t6P6P[t6P6P[vtv 6P[[t6P6Pi[5UVv>`t`6PJ[v6P?[[%P26P.[/6P$[%6P[6P[6P[0XsPPP4sPPP4 tPP1P4D P6P[[%P61ɋD%TPR6Ptt6Pz0XP1P[[0UVvWTl^% 7P[ t@J`^^H:`vFFN tG1P^XFP1PXUPVv W~ 9[[4DP7PW27P[[FTl^F% Tl^% Tl^% B7P[ uJ`^^H<J`^^@1Pvv|tP1PXY[9u9uvvDPL7P1PP=[[4DPj7PFTl^F% Tl^% Tl^% PXUVW^ހ<uF7P[1PP[[FTl^F% v[ t1PyO tp<tkF?/u7P[1PP9[[FTl^F% 7P[^S[7Pp[v [ t1PPX'UVv W~<r ^l9 w}7P2[1PP[[47P[[7P[DP1[8P[8P[ t3F:`vFFN t^F1P^XF1PMPFJ`=u>48P[[.8P[DP[D8P[P1P [[V[ t1PJ`^^@T8PDP[[ u!4`G GWVvvWT PV8PDP[[ u24`G GuW 4`_7VvvW PsVW[[ u1Pc%Nlt>Z8P[1PP1[[|8P[DP[48P[[V([1 uAQV[PXBU VWhlNjhlF 1QP1PPdžƍF9r^GO+FN QP1PP }b8P[1PPg[[8P[ t<FTlF% ^FN GOdž1P<tVv vv% udžFV ^v vww/ |0tPPO t^v vww }PXUv8P[[1PPj[[v8P[[v(8P[%8P[8P[9P[1X$v v 9P^UPPVW~+fl@Ƌ^`^@9>flw9>`lwv v vW9PQ 1P%Rlt;Pl^% v v vW,9P 1Pa$`%PltVv v vWv69PRl^% PXSUVW~hlF 1QP1PPF HPPPWvv u1P)N t^u5ww |PXUPPVvhlFV t1vvFVN uFVUVvW~~ u?%=@uv t4Wv u1P,hlTTPv v VWv PXUPPVW19v ^w7^ww |^ ?u!^w7v[Y[S^XGh^w7vv V[X7vE u#^w7v[Y[S^XG1!^ v7vvv u1FDMU VWFF^1PPSFPvv F }1^WPSXSFPvv uFFGʋF UPPVW~FNl^F% uu1PP ~<9P[PP<[[Wv-[[Ƌ4`GuV9P[PP[[14`Gud9Pp[PP[[1+ UVv%P\.`Vv[[PS,`Vv)[[PC*`P9(`P/&`t9P [1PP[[49P[[1P1X X UPVvW~u'%=@t9P[4W9P9P/[| u9P[1PP[[1P0`Vl^% D =vZ9PW[1PP[[D P9P>[[J`^^-Tl^% J`^^PD Y)VW[[PX U2VW^7FF֋4`N؍N։4`%TltV:P[[P1P[[%Vl# t >`ufbldl1 RVRP1Q 1RPSQPYXZY[SQ>`t FPV[[ tFPV[[ tTl^Ή% :P[ uJ`^΋^ΊPFHYVl^Ή%#bldl1 RL`RP1Q1RPSQPYXZY[SQF:`FFFFN t^F1P^XF4`O4`1P4`O4`PX U>`u^l0``l+flA$`FFFP[ u:P&[*:P[k UP,:P[[hlPD:Px[[V:Po[r:Pp:P6.`X:P:P:P6,`t:P:P:P6*`:P:P:P6(`:Pj>&`t:P:P6&`:PM;P;P60`:P7;P;P6$`;P!U>`u>`t`NZl#^lA6blQ6Vl)`l+flA6dlQ6RlQblfl ;P6`l6dlQR6Pl6RlF@^lA&;PQ6blP1P6Tl6Vl >6`t,;P [U VW11F>/tpl//>pl |>rl}X;P[LlLh\lt;z;P[;P[;P[;P[tl}PPvlrlDpl/Pv=P [[F }J`/P=P[[`/P=P[[```b=P[`>pluJ//<F%PVF%PHF%P:=Pg[$1XvWV=P`````~XUP=P%[ֈFFPH>P[[~ uL>P[>tl~2>tl~&F=1|F=9~5u F-0tltlU&VW1G~N>P[PsP6Ll1PPփFFu1|Ll10NFLl10NFLl10NމFLl10NډFܹFދVRVڋ^Y QV^Y F NVӉOFw71PPw ~x>P[P[F>tl| tlQ6tl^FP=[-^Ë``1 t`1`1PXUPPVvWF~|1|G u.@G t@SSc[[R@w7VP[Xw7 ~,@G t@SS[[GeFT=UPPVvW~MFNLMFNL UVvW~PY9t PY)Q<u1PFGXUVvWFF1Ê>tF<-uFF-0F~ s) 1vvFV1NVN‰NV tFN؃QPvvXZK+G;Gw Ë_u ӅuP]OI|;u_uP>VW\D9u6!}t.1ҋL D !}!}؃_^[R1!}\ߋD T !} T9wr 9Tv+T@_륉RgZPgZ‰ȉg[VW\D9u:!}t21ҋL D !}1ۃ| }ڃЉ_^[1!}\ߋD T !}ڹ9wr 9Tv+T@YV?0m?(? ????1ۇ @tPXûSRP;Љƒs,?Ӌt?0K?Uzl^|l^~l^lxlPPxl ]z_^]^_[YZXV\^9|9w t1ø h_Answer questions with y or n. Then hit RETURNT 6 " @ ,\JB]C!DOSU+Xnb;c!dosu xM %...A  @`e A`A<(2" "@"`""~)=T)dp)f(h(l)m.)ub)0123456789ABCDEF%s fatal %s? yes --> %c// (ino = %u, (ino = %u) can't determine diskette-typeFsck cannot read beyond sector 65535 error 0x%x %s block %D, retry readingwriting%s: can't %s block %D (error = 0x%x) readwriteinternal error (devwrite)ninodes = %unzones = %uimap_blocks = %uzmap_blocks = %ufirstdatazone = %ulog_zone_size = %umaxsize = %Uok nowDo you want to try againHit RETURN key to select default values # zones (default: %d) log zonesize (default: %d) #inodes (default: %u) is this okbad magic number in super blockno inodesno zonesno imapno zmapfirst data zone too smallzone size < block sizemax. file size <= 0bad magic number in super blocktoo few imap blockswarning: expected %d imap_block%ss instead of %d too few zmap blockswarning: expected %d zmap_block%ss instead of %d first data zone too largelog_zone_size too largewarning: large log_zone_size (%d) first data zone too smallwarning: expected first data zone to be %d instead of %u warning: expected max size to be %D instead of %D stop this listing%s %u is missing %s %u is not free Checking %s map etc. %d errors found. install a new map Checking inode list mode inode %u not cleared. clear INODE NLINK COUNT %5u %5u %5u adjustinternal error (counterror) r-w-%cx- inode permission link size name %6u -dcb? %3u %2x,%2x %7D . remove entrybad %s in %s is linked to %u instead of %u). repairwarning: %s has offset %D in %s is linked to %u) null name found in found a '/' in entry of directory entry = '')bad inode found in directory ino found = %u, name = ''). remove entrytoo many links to ino %u discovered at entry '' in directory ...link to directory discovered in name = '', dir ino = %u)bad format in directory . truncate%s zone in zno = %u, type = DATASINGLE INDIRECTDOUBLE INDIRECTVERY INDIRECT, pos = %D) out-of-rangeduplicatefoundwarning: huge directory: . missing in .. missing in bad mode of mode = %o)root inode is not a directory (ino = %u, mode = %o) link count zero of link count too big in cnt = %u) found inode %u: removebad root inode blocksize = %5d zonesize = %5d %6u Regular file%s s%6u Director%s yies%6u Block special file%s s%6u Character special file%s s%6u Bad inode%s s%6u Free inode%s s%6u Free zone%s szoneinode----- FILE SYSTEM HAS BEEN MODIFIED ----- Bootblok gave bad tracksiz fsck Hit key as follows: = start MINIX (root file system in drive 0) u start MINIX on PS/2 Model 30, U.S. keyboard (root file sys in drive 0) d start MINIX on PS/2 Model 30, Dutch keyboard (root file sys in drive 0) f check the file system (first insert any file system diskette) l check and list file system (first insert any file system diskette) m make an (empty) file system (first insert blank, formatted diskette) h check hard disk file system # %c Checking hard disk. %s Checking diskette. %s Checking diskette. %s Making empty file system Illegal command disk(ette) Please enter partition number. Drive 0: 1-4, drive 1: 6-9, then hit RETURN: %c Disk errors. Can't read partition table Fsck can't handle partitions above sector 65535  AAAAAABBBBBB ??V??m?m??Error: Division by 0 Illegal EM instruct'n Err in EM case instr Variable out of range Err in EM set instr Floating pt not impl. Heap overflow EM trap 0000000 octal llmain() { exit(0); } char *envp[3] = {"spring", "summer", 0}; extern errno; int errct; main() { int i; printf("Test 11 "); for (i = 0; i<9; i++) { test110(); test111(); } if (errct == 0) printf("ok\n"); else printf(" %d errors\n", errct); exit(0); } e(n) int n; { printf("\nError %d. errno = %d ",n, errno); perror(""); errct++; } test110() { /* Test exec */ int n, fd, fd1, i; char aa[4]; if (fork()) { wait(&n); if (n != 25600) e(1); unlink("t1"); unlink("t2"); } else { if (chown("t11a", 10, 20) < 0) e(2); chmod("t11a", 0666); /* The following call should fail because the mode has no X bits on. * If a bug lets it unexpectedly succeed, the child will print an * error message since the arguments are wrong. */ execl("t11a", 0, envp); /* should fail -- no X bits on */ /* Control should come here after the failed execl(). */ chmod("t11a", 06555); if ( (fd = creat("t1", 0600)) != 3) e(3); if (close(fd) < 0) e(4); if (open("t1", 2) != 3) e(5); if (chown("t1", 10, 99) < 0) e(6); if ( (fd = creat("t2", 0060)) != 4) e(7); if (close(fd) < 0) e(8); if (open("t2", 2) != 4) e(9); if (chown("t2", 99, 20) < 0) e(10); if (setgid(6) < 0) e(11); if (setuid(5) < 0) e(12); if (getuid() != 5) e(13); if (geteuid() != 5) e(14); if (getgid() != 6) e(15); if (getegid() != 6) e(16); aa[0] = 3; aa[1] = 5; aa[2] = 7; aa[3] = 9; if (write(3, aa, 4) != 4) e(17); lseek(3, 2L, 0); execle("t11a", "t11a", "arg0", "arg1", "arg2", 0, envp); e(18); printf("Can't exec t11a\n"); exit(3); } } test111() { int n; char *argv[5]; if (fork()) { wait(&n); if (n != (75<<8)) e(20); } else { /* Child tests execv. */ argv[0] = "t11b"; argv[1] = "abc"; argv[2] = "defghi"; argv[3] = "j"; argv[4] = 0; execv("t11b", argv); e(19); } } main(argc, argv, envp) int argc; char *argv[], *envp[]; { /* See if arguments passed ok. */ char aa[4]; if (diff(argv[0], "t11a")) e(21); if (diff(argv[1], "arg0")) e(22); if (diff(argv[2], "arg1")) e(23); if (diff(argv[3], "arg2")) e(24); if (diff(envp[0], "spring")) e(25); if (diff(envp[1], "summer")) e(26); if (argc != 4) e(27); /* Now see if the files are ok. */ if (read(3, aa, 1000) != 2) e(28); if (aa[0] != 7 || aa[1] != 9) e(29); if (getuid() == 10) e(30); if (geteuid() != 10) e(31); if (getgid() == 20) e(32); if (getegid() != 20) e(33); if (open("t1", 0) < 0) e(34); if (open("t2", 0) < 0) e(35); exit(100); } diff(s1, s2) char *s1, *s2; { while (1) { if (*s1 == 0 && *s2 == 0) return(0); if (*s1 != *s2) return(1); s1++; s2++; } } e(n) int n; { printf("Error %d\n", n); } main(argc, argv) int argc; char *argv[]; { /* See if arguments passed ok. */ if (diff(argv[0], "t11b")) e(31); if (diff(argv[1], "abc")) e(32); if (diff(argv[2], "defghi")) e(33); if (diff(argv[3], "j")) e(34); if (argv[4] != 0) e(35); if (argc != 4) e(36); exit(75); } diff(s1, s2) char *s1, *s2; { while (1) { if (*s1 == 0 && *s2 == 0) return(0); if (*s1 != *s2) return(1); s1++; s2++; } } e(n) int n; { printf("Error %d\n", n); } test0 test1 test2 test3 test4 test5 test6 test7 test8 test9 test10 test11 echo All tests completed. *. ..+makefile# To make 'test0', type: make f=test0 # To make 'test1', type: make f=test1 # To make 'test2', type: make f=test2 # Get the idea? l=/usr/lib CFLAGS = -I/usr/include -F file: $l/libc.a $f.s @asld -o $f $l/crtso.s $f.s $l/libc.a $l/end.s ,...mined.hmined1.cmined2.csh.hsh1.csh2.csh3.csh4.csh5.cx 0x܋@ãPSQP+-+P[++PPPPPvP+P;5FF F FFFvveKv >FF Ot^ FF vvn=FPvF~tF>} أg'G?tFDC[UWV+;&rPPf^_] U싆 ]Stack overflow 0܋@ãPSQP+-+P[++PPPPPvP+P;5FF F FFFvveKv >FF Ot^ FF vvn=FPvF~tF>} أg'G?tFDC[UWV+;&rPPf^_] U싆 ]Stack overflow 0 ܋@ãPSQP+-+P[++PPPPPvP+P;5FF F FFFvveKv >FF Ot^ FF vvn=FPvF~tF>} أg'G?tFDC[UWV+;&rPPf^_] U싆 ]Stack overflow 0` ܋@ãPSQP+-+P[++PPPPPvP+P;5FF F FFFvveKv >FF Ot^ FF vvn=FPvF~tF>} أg'G?tFDC[UWV+;&rPPf^_] U싆 ]Stack overflow 0܋@ãPSQP+-+P[++PPPPPvP+P;5FF F FFFvveKv >FF Ot^ FF vvn=FPvF~tF>} أg'G?tFDC[UWV+;&rPPf^_] U싆 ]Stack overflow 0܋@ãPSQP+-+P[++PPPPPvP+P;5FF F FFFvveKv >FF Ot^ FF vvn=FPvF~tF>} أg'G?tFDC[UWV+;&rPPf^_] U싆 ]Stack overflow 0܋@ãPSQP+-+P[++PPPPPvP+P;5FF F FFFvveKv >FF Ot^ FF vvn=FPvF~tF>} أg'G?tFDC[UWV+;&rPPf^_] U싆 ]Stack overflow 0܋@ãPSQP+-+P[++PPPPPvP+P;5FF F FFFvveKv >FF Ot^ FF vvn=FPvF~tF>} أg'G?tFDC[UWV+;&rPPf^_] U싆 ]Stack overflow lways a linefeed) can never be deleted. * The bottomline (as indicated by YMAX + 1) is used as a status line during * editing. This line is usually blank or contains information mined needs * during editing. This information (or rather questions) is displayed in * reverse video. * * The terminal modes are changed completely. All signals like start/stop, * interrupt etc. are unset. The only signal that remains is the quit signal. * The quit signal (^\) is the general abort signal for mined. Typing a ^\ * during searching or when mined is asking for filenames, etc. will abort * the function and mined will return to the main loop. Sending a quit * signal during the main loop will abort the session (after confirmation) * and the file is not (!) saved. * The session will also be aborted when an unrecoverable error occurs. E.g * when there is no more memory available. If the file has been modified, * mined will ask if the file has to be saved or not. * If there is no more space left on the disk, mined will just give an error * message and continue. * * The number of system calls are minized. This is done to keep the editor * as fast as possible. I/O is done in SCREEN_SIZE reads/writes. Accumulated * output is also flushed at the end of each character typed. * * 2. Regular expressions * * Mined has a build in regular expression matcher, which is used for * searching and replace routines. A regular expression consists of a * sequence of: * * 1. A normal character matching that character. * 2. A . matching any character. * 3. A ^ matching the begin of a line. * 4. A $ (as last character of the pattern) mathing the end of a line. * 5. A \ matching . * 6. A number of characters enclosed in [] pairs matching any of these * characters. A list of characters can be indicated by a '-'. So * [a-z] matches any letter of the alphabet. If the first character * after the '[' is a '^' then the set is negated (matching none of * the characters). * A ']', '^' or '-' can be escaped by putting a '\' in front of it. * Of course this means that a \ must be represented by \\. * 7. If one of the expressions as described in 1-6 is followed by a * '*' than that expressions matches a sequence of 0 or more of * that expression. * * Parsing of regular expression is done in two phases. In the first phase * the expression is compiled into a more comprehensible form. In the second * phase the actual matching is done. For more details see 3.6. * * * 3. Implementation of mined. * * 3.1 Data structures. * * The main data structures are as follows. The whole file is kept in a * double linked list of lines. The LINE structure looks like this: * * typedef struct Line { * struct Line *next; * struct Line *prev; * char *text; * unsigned char shift_count; * } LINE; * * Each line entry contains a pointer to the next line, a pointer to the * previous line and a pointer to the text of that line. A special field * shift_count contains the number of shifts (in units of SHIFT_SIZE) * that is performed on that line. The total size of the structure is 7 * bytes so a file consisting of 1000 empty lines will waste a lot of * memory. A LINE structure is allocated for each line in the file. After * that the number of characters of the line is counted and sufficient * space is allocated to store them (including a linefeed and a '\0'). * The resulting address is assigned to the text field in the structure. * * A special structure is allocated and its address is assigned to the * variable header as well as the variable tail. The text field of this * structure is set to NIL_PTR. The tail->prev of this structure points * to the last LINE of the file and the header->next to the first LINE. * Other LINE *variables are top_line and bot_line which point to the * first line resp. the last line on the screen. * Two other variables are important as well. First the LINE *cur_line, * which points to the LINE currently in use and the char *cur_text, * which points to the character at which the cursor stands. * Whenever an ASCII character is typed, a new line is build with this * character inserted. Then the old data space (pointed to by * cur_line->text) is freed, data space for the new line is allocated and * assigned to cur_line->text. * * Two global variables called x and y represent the x and y coordinates * from the cursor. The global variable nlines contains the number of * lines in the file. Last_y indicates the maximum y coordinate of the * screen (which is usually SCREENMAX). * * A few strings must be initialized by hand before compiling mined. * These string are enter_string, which is printed upon entering mined, * rev_video (turn on reverse video), normal_video, rev_scroll (perform a * reverse scroll) and pos_string. The last string should hold the * absolute position string to be printed for cursor motion. The #define * X_PLUS and Y_PLUS should contain the characters to be added to the * coordinates x and y (both starting at 0) to finish cursor positioning. * * 3.2 Starting up. * * Mined can be called with or without argument and the function * load_file () is called with these arguments. load_file () checks * if the file exists if it can be read and if it is writable and * sets the writable flag accordingly. If the file can be read, * load_file () reads a line from the file and stores this line into * a structure by calling install_line () and line_insert () which * installs the line into the double linked list, until the end of the * file is reached. * Lines are read by the function get_line (), which buffers the * reading in blocks of SCREEN_SIZE. Load_file () also initializes the * LINE *variables described above. * * 3.3 Moving around. * * Several commands are implemented for moving through the file. * Moving up (UP), down (DN) left (LF) and right (RT) are done by the * arrow keys. Moving one line below the screen scrolls the screen one * line up. Moving one line above the screen scrolls the screen one line * down. The functions forward_scroll () and reverse_scroll () take care * of that. * Several other move functions exist: move to begin of line (BL), end of * line (EL) top of screen (HIGH), bottom of screen (LOW), top of file * (HO), end of file (EF), scroll one page down (PD), scroll one page up * (PU), scroll one line down (SD), scroll one line up (SU) and move to a * certain line number (GOTO). * Two functions called MN () and MP () each move one word further or * backwards. A word is a number of non-blanks seperated by a space, a * tab or a linefeed. * * 3.4 Modifying text. * * The modifying commands can be separated into two modes. The first * being inserting text, and the other deleting text. Two functions are * created for these purposes: insert () and delete (). Both are capable * of deleting or inserting large amounts of text as well as one * character. Insert () must be given the line and location at which * the text must be inserted. Is doesn't make any difference whether this * text contains linefeeds or not. Delete () must be given a pointer to * the start line, a pointer from where deleting should start on that * line and the same information about the end position. The last * character of the file will never be deleted. Delete () will make the * necessary changes to the screen after deleting, but insert () won't. * The functions for modifying text are: insert one char (S), insert a * file (file_insert (fd)), insert a linefeed and put cursor back to * end of line (LIB), delete character under the cursor (DCC), delete * before cursor (even linefeed) (DPC), delete next word (DNW), delete * previous word (DPC) and delete to end of line (if the cursor is at * a linefeed delete line) (DLN). * * 3.5 Yanking. * * A few utilities are provided for yanking pieces of text. The function * MA () marks the current position in the file. This is done by setting * LINE *mark_line and char *mark_text to the current position. Yanking * of text can be done in two modes. The first mode just copies the text * from the mark to the current position (or visa versa) into a buffer * (YA) and the second also deletes the text (DT). Both functions call * the function set_up () with the delete flag on or off. Set_up () * checks if the marked position is still a valid one (by using * check_mark () and legal ()), and then calls the function yank () with * a start and end position in the file. This function copies the text * into a scratch_file as indicated by the variable yank_file. This * scratch_file is made uniq by the function scratch_file (). At the end * of copying yank will (if necessary) delete the text. A global flag * called yank_status keeps track of the buffer (or file) status. It is * initialized on NOT_VALID and set to EMPTY (by set_up ()) or VALID (by * yank ()). Several things can be done with the buffer. It can be * inserted somewhere else in the file (PT) or it can be copied into * another file (WB), which will be prompted for. * * 3.6 Search and replace routines. * * Searching for strings and replacing strings are done by regular * expressions. For any expression the function compile () is called * with as argument the expression to compile. Compile () returns a * pointer to a structure which looks like this: * * typedef struct regex { * union { * char *err_mess; * int *expression; * } result; * char status; * char *start_ptr; * char *end_ptr; * } REGEX; * * If something went wrong during compiling (e.g. an illegal expression * was given), the function reg_error () is called, which sets the status * field to REG_ERROR and the err_mess field to the error message. If the * match must be anchored at the beginning of the line (end of line), the * status field is set to BEGIN_LINE (END_LINE). If none of these special * cases are true, the field is set to 0 and the function finished () is * called. Finished () allocates space to hold the compiled expression * and copies this expression into the expression field of the union * (bcopy ()). Matching is done by the routines match() and line_check(). * Match () takes as argument the REGEX *program, a pointer to the * startposition on the current line, and a flag indicating FORWARD or * REVERSE search. Match () checks out the whole file until a match is * found. If match is found it returns a pointer to the line in which the * match was found else it returns a NIL_LINE. Line_check () takes the * same arguments, but return either MATCH or NO_MATCH. * During checking, the start_ptr and end_ptr fields of the REGEX * structure are assigned to the start and end of the match. * Both functions try to find a match by walking through the line * character by character. For each possibility, the function * check_string () is called with as arguments the REGEX *program and the * string to search in. It starts walking through the expression until * the end of the expression or the end of the string is reached. * Whenever a * is encountered, this position of the string is marked, * the maximum number of matches are performed and the function star () * is called in order to try to find the longest match possible. Star () * takes as arguments the REGEX program, the current position of the * string, the marked position and the current position of the expression * Star () walks from the current position of the string back to the * marked position, and calls string_check () in order to find a match. * It returns MATCH or NO_MATCH, just as string_check () does. * Searching is now easy. Both search routines (forward (SF) and * backwards search (SR)) call search () with an apropiate message and a * flag indicating FORWARD or REVERSE search. Search () will get an * expression from the user by calling get_expression(). Get_expression() * returns a pointer to a REGEX structure or NIL_REG upon errors and * prompts for the expression. If no expression if given, the previous is * used instead. After that search will call match (), and if a match is * found, we can move to that place in the file by the functions find_x() * and find_y () which will find display the match on the screen. * Replacing can be done in two ways. A global replace (GR) or a line * replace (LR). Both functions call change () with a message an a flag * indicating global or line replacement. Change () will prompt for the * expression and for the replacement. Every & in the replacement pattern * means substitute the match instead. An & can be escaped by a \. When * a match is found, the function substitute () will perform the * substitution. * * 3.6 Miscellaneous commands. * * A few commands haven't be discussed yet. These are redraw the screen * (RD) fork a shell (SH), print file status (FS), write file to disc * (WT), insert a file at current position (IF), leave editor (XT) and * visit another file (VI). The last two functions will check if the file * has been modified. If it has, they will ask if you want to save the * file by calling ask_save (). * The function ESC () will repeat a command n times. It will prompt for * the number. Aborting the loop can be done by sending the ^\ signal. * * 3.7 Utility functions. * * Several functions exists for internal use. First allocation routines: * alloc (bytes) and newline () will return a pointer to free data space * if the given size. If there is no more memory available, the function * panic () is called. * Signal handling: The only signal that can be send to mined is the * SIGQUIT signal. This signal, functions as a general abort command. * Mined will abort if the signal is given during the main loop. The * function abort_mined () takes care of that. * Panic () is a function with as argument a error message. It will print * the message and the error number set by the kernel (errno) and will * ask if the file must be saved or not. It resets the terminal * (raw_mode ()) and exits. * String handling routines like copy_string(to, from), length_of(string) * and build_string (buffer, format, arg1, arg2, ...). The latter takes * a description of the string out out the format field and puts the * result in the buffer. (It works like printf (3), but then into a * string). The functions status_line (string1, string2), error (string1, * string2), clear_status () and bottom_line () all print information on * the status line. * Get_string (message, buffer) reads a string and getchar () reads one * character from the terminal. * Num_out ((long) number) prints the number into a 11 digit field * without leading zero's. It returns a pointer to the resulting string. * File_status () prints all file information on the status line. * Set_cursor (x, y) prints the string to put the cursor at coordinates * x and y. * Output is done by four functions: writeline(fd,string), clear_buffer() * write_char (fd, c) and flush_buffer (fd). Three defines are provided * to write on filedescriptor STD_OUT (terminal) which is used normally: * string_print (string), putchar (c) and flush (). All these functions * use the global I/O buffer screen and the global index for this array * called out_count. In this way I/O can be buffered, so that reads or * writes can be done in blocks of SCREEN_SIZE size. * The following functions all handle internal line maintenance. The * function proceed (start_line, count) returns the count'th line after * start_line. If count is negative, the count'th line before the * start_line is returned. If header or tail is encountered then that * will be returned. Display (x, y, start_line, count) displays count * lines starting at coordinates [x, y] and beginning at start_line. If * the header or tail is encountered, empty lines are displayed instead. * The function reset (head_line, ny) reset top_line, last_y, bot_line, * cur_line and y-coordinate. This is not a neat way to do the * maintenance, but it sure saves a lot of code. It is usually used in * combination with display (). * Put_line(line, offset, clear_line), prints a line (skipping characters * according to the line->shift_size field) until XBREAK - offset * characters are printed or a '\n' is encountered. If clear_line is * TRUE, spaces are printed until XBREAK - offset characters. * Line_print (line) is a #define from put_line (line, 0, TRUE). * Moving is done by the functions move_to (x, y), move_addres (address) * and move (x, adress, y). This function is the most important one in * mined. New_y must be between 0 and last_y, new_x can be about * anything, address must be a pointer to an character on the current * line (or y). Move_to () first adjust the y coordinate together with * cur_line. If an address is given, it finds the corresponding * x-coordinate. If an new x-coordinate was given, it will try to locate * the corresponding character. After that it sets the shift_count field * of cur_line to an apropiate number according to new_x. The only thing * left to do now is to assign the new values to cur_line, cur_text, x * and y. * * 4. Summary of commands. * * CURSOR MOTION * up-arrow Move cursor 1 line up. At top of screen, reverse scroll * down-arrow Move cursor 1 line down. At bottom, scroll forward. * left-arrow Move cursor 1 character left or to end of previous line * right-arrow Move cursor 1 character right or to start of next line * CTRL-A Move cursor to start of current line * CTRL-Z Move cursor to end of current line * CTRL-^ Move cursor to top of screen * CTRL-_ Move cursor to bottom of screen * CTRL-F Forward to start of next word (even to next line) * CTRL-B Backward to first character of previous word * * SCREEN MOTION * Home key Move cursor to first character of file * End key Move cursor to last character of file * PgUp Scroll backward 1 page. Bottom line becomes top line * PgD Scroll backward 1 page. Top line becomes bottom line * CTRL-D Scroll screen down one line (reverse scroll) * CTRL-U Scroll screen up one line (forward scroll) * * MODIFYING TEXT * ASCII char Self insert character at cursor * tab Insert tab at cursor * backspace Delete the previous char (left of cursor), even line feed * Del Delete the character under the cursor * CTRL-N Delete next word * CTRL-P Delete previous word * CTRL-O Insert line feed at cursor and back up 1 character * CTRL-T Delete tail of line (cursor to end); if empty, delete line * CTRL-@ Set the mark (remember the current location) * CTRL-K Delete text from the mark to current position save on file * CTRL-C Save the text from the mark to the current position * CTRL-Y Insert the contents of the save file at current position * CTRL-Q Insert the contents of the save file into a new file * CTRL-G Insert a file at the current position * * MISCELLANEOUS * CTRL-E Erase and redraw the screen * CTRL-V Visit file (read a new file); complain if old one changed * CTRL-W Write the current file back to the disk * numeric + Search forward (prompt for regular expression) * numeric - Search backward (prompt for regular expression) * numeric 5 Print the current status of the file * CTRL-R (Global) Replace str1 by str2 (prompts for each string) * CTRL-L (Line) Replace string1 by string2 * CTRL-S Fork off a shell and wait for it to finish * CTRL-X EXIT (prompt if file modified) * CTRL-] Go to a line. Prompts for linenumber * CTRL-\ Abort whatever editor was doing and start again * escape key Repeat a command count times; (prompts for count) */ /* ======================================================================== * * Utilities * * ======================================================================== */ #include "mined.h" #include "signal.h" #include "sgtty.h" #ifdef UNIX #include #else #include "errno.h" #endif UNIX extern int errno; /* * Print file status. */ FS() { fstatus(file_name[0] ? "" : "[buffer]", -1L); } /* * Visit (edit) another file. If the file has been modified, ask the user if * he wants to save it. */ VI() { char new_file[LINE_LEN]; /* Buffer to hold new file name */ if (modified == TRUE && ask_save() == ERRORS) return; /* Get new file name */ if (get_file("Visit file:", new_file) == ERRORS) return; /* Free old linked list, initialize global variables and load new file */ initialize(); #ifdef UNIX tputs(CL, 0, _putchar); #else string_print (enter_string); #endif UNIX load_file(new_file[0] == '\0' ? NIL_PTR : new_file); } /* * Write file in core to disc. */ WT() { register LINE *line; register long count = 0L; /* Nr of chars written */ char file[LINE_LEN]; /* Buffer for new file name */ int fd; /* Filedescriptor of file */ if (modified == FALSE) { error ("Write not necessary.", NIL_PTR); return FINE; } /* Check if file_name is valid and if file can be written */ if (file_name[0] == '\0' || writable == FALSE) { if (get_file("Enter file name:", file) != FINE) return ERRORS; copy_string(file_name, file); /* Save file name */ } if ((fd = creat(file_name, 0644)) < 0) { /* Empty file */ error("Cannot create ", file_name); writable = FALSE; return ERRORS; } else writable = TRUE; clear_buffer(); status_line("Writing ", file_name); for (line = header->next; line != tail; line = line->next) { if (line->shift_count & DUMMY) { if (line->next == tail && line->text[0] == '\n') continue; } if (writeline(fd, line->text) == ERRORS) { count = -1L; break; } count += (long) length_of(line->text); } if (count > 0L && flush_buffer(fd) == ERRORS) count = -1L; (void) close(fd); if (count == -1L) return ERRORS; modified = FALSE; rpipe = FALSE; /* File name is now assigned */ /* Display how many chars (and lines) were written */ fstatus("Wrote", count); return FINE; } /* * Call an interactive shell. */ SH() { register int w; int pid, status; switch (pid = fork()) { case -1: /* Error */ error("Cannot fork.", NIL_PTR); return; case 0: /* This is the child */ set_cursor(0, YMAX); putchar('\n'); flush(); raw_mode(OFF); if (rpipe) { /* Fix stdin */ close (0); if (open("/dev/tty", 0) < 0) exit (126); } execl("/bin/sh", "sh", "-i", 0); exit(127); /* Exit with 127 */ default : /* This is the parent */ signal(SIGINT, SIG_IGN); signal(SIGQUIT, SIG_IGN); do { w = wait(&status); } while (w != -1 && w != pid); } raw_mode(ON); RD(); if ((status >> 8) == 127) /* Child died with 127 */ error("Cannot exec /bin/sh (possibly not enough memory)", NIL_PTR); else if ((status >> 8) == 126) error("Cannot open /dev/tty as fd #0", NIL_PTR); } /* * Proceed returns the count'th line after `line'. When count is negative * it returns the count'th line before `line'. When the next (previous) * line is the tail (header) indicating EOF (tof) it stops. */ LINE *proceed(line, count) register LINE *line; register int count; { if (count < 0) while (count++ < 0 && line != header) line = line->prev; else while (count-- > 0 && line != tail) line = line->next; return line; } /* * Show concatenation of s1 and s2 on the status line (bottom of screen) * If revfl is TRUE, turn on reverse video on both strings. Set stat_visible * only if bottom_line is visible. */ bottom_line(revfl, s1, s2, inbuf, statfl) FLAG revfl; char *s1, *s2; char *inbuf; FLAG statfl; { int ret = FINE; if (revfl == ON && stat_visible == TRUE) clear_status (); set_cursor(0, YMAX); if (revfl == ON) { /* Print rev. start sequence */ #ifdef UNIX tputs(SO, 0, _putchar); #else string_print(rev_video); #endif UNIX stat_visible = TRUE; } else /* Used as clear_status() */ stat_visible = FALSE; putchar(' '); if (s1 != NIL_PTR) string_print(s1); if (s2 != NIL_PTR) string_print(s2); putchar(' '); if (inbuf != NIL_PTR) ret = input(inbuf, statfl); /* Print normal video */ #ifdef UNIX tputs(SE, 0, _putchar); tputs(CE, 0, _putchar); #else string_print(normal_video); string_print(blank_line); /* Clear the rest of the line */ #endif UNIX if (inbuf != NIL_PTR) set_cursor(0, YMAX); else set_cursor(x, y); /* Set cursor back to old position */ flush(); /* Perform the actual write */ if (ret != FINE) clear_status(); return ret; } /* * Count_chars() count the number of chars that the line would occupy on the * screen. Counting starts at the real x-coordinate of the line. */ count_chars(line) LINE *line; { register int cnt = get_shift(line->shift_count) * -SHIFT_SIZE; register char *textp = line->text; /* Find begin of line on screen */ while (cnt < 0) { if (is_tab(*textp++)) cnt = tab(cnt); else cnt++; } /* Count number of chars left */ cnt = 0; while (*textp != '\n') { if (is_tab(*textp++)) cnt = tab(cnt); else cnt++; } return cnt; } /* * Move to coordinates nx, ny at screen. The caller must check that scrolling * is not needed. * If new_x is lower than 0 or higher than XBREAK, move_to() will check if * the line can be shifted. If it can it sets(or resets) the shift_count field * of the current line accordingly. * Move also sets cur_text to the right char. * If we're moving to the same x coordinate, try to move the the x-coordinate * used on the other previous call. */ move(new_x, new_address, new_y) register int new_x; int new_y; char *new_address; { register LINE *line = cur_line; /* For building new cur_line */ int shift = 0; /* How many shifts to make */ static int rel_x = 0; /* Remember relative x position */ int tx = x; char *find_address(); /* Check for illegal values */ if (new_y < 0 || new_y > last_y) return; /* Adjust y-coordinate and cur_line */ if (new_y < y) while (y != new_y) { y--; line = line->prev; } else while (y != new_y) { y++; line = line->next; } /* Set or unset relative x-coordinate */ if (new_address == NIL_PTR) { new_address = find_address(line, (new_x == x) ? rel_x : new_x , &tx); if (new_x != x) rel_x = tx; new_x = tx; } else rel_x = new_x = find_x(line, new_address); /* Adjust shift_count if new_x lower than 0 or higher than XBREAK */ if (new_x < 0 || new_x >= XBREAK) { if (new_x > XBREAK || (new_x == XBREAK && *new_address != '\n')) shift = (new_x - XBREAK) / SHIFT_SIZE + 1; else { shift = new_x / SHIFT_SIZE; if (new_x % SHIFT_SIZE) shift--; } if (shift != 0) { line->shift_count += shift; new_x = find_x(line, new_address); set_cursor(0, y); line_print(line); rel_x = new_x; } } /* Assign and position cursor */ x = new_x; cur_text = new_address; cur_line = line; set_cursor(x, y); } /* * Find_x() returns the x coordinate belonging to address. * (Tabs are expanded). */ find_x(line, address) LINE *line; char *address; { register char *textp = line->text; register int nx = get_shift(line->shift_count) * -SHIFT_SIZE; while (textp != address && *textp != '\0') { if (is_tab(*textp++)) /* Expand tabs */ nx = tab(nx); else nx++; } return nx; } /* * Find_address() returns the pointer in the line with offset x_coord. * (Tabs are expanded). */ char *find_address(line, x_coord, old_x) LINE *line; int x_coord; int *old_x; { register char *textp = line->text; register int tx = get_shift(line->shift_count) * -SHIFT_SIZE; while (tx < x_coord && *textp != '\n') { if (is_tab(*textp)) { if (*old_x - x_coord == 1 && tab(tx) > x_coord) break; /* Moving left over tab */ else tx = tab(tx); } else tx++; textp++; } *old_x = tx; return textp; } /* * Length_of() returns the number of characters int the string `string' * excluding the '\0'. */ length_of(string) register char *string; { register int count = 0; if (string != NIL_PTR) { while (*string++ != '\0') count++; } return count; } /* * Copy_string() copies the string `from' into the string `to'. `To' must be * long enough to hold `from'. */ copy_string(to, from) register char *to; register char *from; { while (*to++ = *from++) ; } /* * Reset assigns bot_line, top_line and cur_line according to `head_line' * which must be the first line of the screen, and an y-coordinate, * which will be the current y-coordinate (if it isn't larger than last_y) */ reset(head_line, screen_y) LINE *head_line; int screen_y; { register LINE *line; top_line = line = head_line; /* Search for bot_line (might be last line in file) */ for (last_y = 0; last_y < nlines - 1 && last_y < SCREENMAX && line->next != tail; last_y++) line = line->next; bot_line = line; y = (screen_y > last_y) ? last_y : screen_y; /* Set cur_line according to the new y value */ cur_line = proceed(top_line, y); } /* * Set cursor at coordinates x, y. */ set_cursor(nx, ny) int nx, ny; { #ifdef UNIX extern char *tgoto(); tputs(tgoto(CM, nx, ny), 0, _putchar); #else string_print(pos_string); putchar(X_PLUS + nx); putchar(Y_PLUS + YMAX - ny);/* Driver has (0,0) at lower left corner */ #endif UNIX } /* * Routine to open terminal when mined is used in a pipeline. */ open_device() { if ((input_fd = open("/dev/tty", 0)) < 0) panic("Cannot open /dev/tty for read"); } /* * Getchar() reads one character from the terminal. The character must be * masked with 0377 to avoid sign extension. */ getchar() { #ifdef UNIX return (_getchar() & 0177); #else char c; if (read(input_fd, &c, 1) != 1 && quit == FALSE) panic("Can't read one char from fd #0"); return c & 0377; #endif UNIX } /* * Display() shows count lines on the terminal starting at the given * coordinates. When the tail of the list is encountered it will fill the * rest of the screen with blank_line's. * When count is negative, a backwards print from `line' will be done. */ display(x_coord, y_coord, line, count) int x_coord, y_coord; register LINE *line; register int count; { set_cursor(x_coord, y_coord); /* Find new startline if count is negative */ if (count < 0) { line = proceed(line, count); count = -count; } /* Print the lines */ while (line != tail && count-- >= 0) { line_print(line); line = line->next; } /* Print the blank lines (if any) */ if (loading == FALSE) { while (count-- >= 0) { #ifdef UNIX tputs(CE, 0, _putchar); #else string_print(blank_line); #endif UNIX putchar('\n'); } } } /* * Write_char does a buffered output. */ write_char(fd, c) int fd; char c; { screen [out_count++] = c; if (out_count == SCREEN_SIZE) /* Flush on SCREEN_SIZE chars */ return flush_buffer(fd); return FINE; } /* * Writeline writes the given string on the given filedescriptor. */ writeline(fd, text) register int fd; register char *text; { while(*text) if (write_char(fd, *text++) == ERRORS) return ERRORS; return FINE; } /* * Put_line print the given line on the standard output. If offset is not zero * printing will start at that x-coordinate. If the FLAG clear_line is TRUE, * then (screen) line will be cleared when the end of the line has been * reached. */ put_line(line, offset, clear_line) LINE *line; /* Line to print */ int offset; /* Offset to start */ FLAG clear_line; /* Clear to eoln if TRUE */ { register char *textp = line->text; register int count = get_shift(line->shift_count) * -SHIFT_SIZE; int tab_count; /* Used in tab expansion */ /* Skip all chars as indicated by the offset and the shift_count field */ while (count < offset) { if (is_tab(*textp++)) count = tab(count); else count++; } while (*textp != '\n' && count < XBREAK) { if (is_tab(*textp)) { /* Expand tabs to spaces */ tab_count = tab(count); while (count < XBREAK && count < tab_count) { count++; putchar(' '); } textp++; } else { if (*textp >= '\01' && *textp <= '\037') { #ifdef UNIX tputs(SO, 0, _putchar); #else string_print (rev_video); #endif UNIX putchar(*textp++ + '\100'); #ifdef UNIX tputs(SE, 0, _putchar); #else string_print (normal_video); #endif UNIX } else putchar(*textp++); count++; } } /* If line is longer than XBREAK chars, print the shift_mark */ if (count == XBREAK && *textp != '\n') putchar(SHIFT_MARK); /* Clear the rest of the line is clear_line is TRUE */ if (clear_line == TRUE) { #ifdef UNIX tputs(CE, 0, _putchar); #else while (count++ <= XBREAK) putchar(' '); #endif UNIX putchar('\n'); } } /* * Flush the I/O buffer on filedescriptor fd. */ flush_buffer(fd) int fd; { if (out_count <= 0) /* There is nothing to flush */ return FINE; #ifdef UNIX if (fd == STD_OUT) { printf("%.*s", out_count, screen); _flush(); } else #endif UNIX if (write(fd, screen, out_count) != out_count) { bad_write(fd); return ERRORS; } clear_buffer(); /* Empty buffer */ return FINE; } /* * Bad_write() is called when a write failed. Notify the user. */ bad_write(fd) int fd; { if (fd == STD_OUT) /* Cannot write to terminal? */ exit(1); clear_buffer(); build_string(text_buffer, "Command aborted: %s (File incomplete)", (errno == ENOSPC || errno == -ENOSPC) ? "No space on device" : "Write error"); error(text_buffer, NIL_PTR); } /* * Catch the SIGQUIT signal (^\) send to mined. It turns on the quitflag. */ catch() { /* Reset the signal */ signal(SIGQUIT, catch); quit = TRUE; } /* * Abort_mined() will leave mined. Confirmation is asked first. */ abort_mined() { quit = FALSE; /* Ask for confirmation */ status_line("Really abort? ", NIL_PTR); if (getchar() != 'y') { clear_status(); return; } /* Reset terminal */ raw_mode(OFF); set_cursor(0, YMAX); putchar('\n'); flush(); #ifdef UNIX abort(); #else exit(1); #endif UNIX } #define UNDEF -1 /* * Set and reset tty into CBREAK or old mode according to argument `state'. It * also sets all signal characters (except for ^\) to UNDEF. ^\ is caught. */ raw_mode(state) FLAG state; { static struct sgttyb old_tty; static struct sgttyb new_tty; static struct tchars old_tchars; static struct tchars new_tchars = {UNDEF, '\034', UNDEF, UNDEF, UNDEF, UNDEF}; #ifdef UNIX int ldisc; #endif UNIX if (state == OFF) { ioctl(input_fd, TIOCSETP, &old_tty); ioctl(input_fd, TIOCSETC, &old_tchars); #ifdef UNIX ldisc = NTTYDISC; ioctl(input_fd, TIOCSETD, &ldisc); #endif UNIX return; } /* Save old tty settings */ ioctl(input_fd, TIOCGETC, &old_tchars); ioctl(input_fd, TIOCGETP, &old_tty); #ifdef UNIX ldisc = OTTYDISC; ioctl(input_fd, TIOCSETD, &ldisc); #endif UNIX /* Set tty to CBREAK mode */ ioctl(input_fd, TIOCGETP, &new_tty); new_tty.sg_flags |= CBREAK; new_tty.sg_flags &= ~ECHO; ioctl(input_fd, TIOCSETP, &new_tty); /* Unset signal chars */ ioctl(input_fd, TIOCSETC, &new_tchars); /* Only leaves you ^\ */ signal(SIGQUIT, catch); /* Which is caught */ } /* * Panic() is called with an error number and a message. It is called when * something unrecoverable has happened. * It writes the message to the terminal, resets the tty and exits. * Ask the user if he wants to save his file. */ panic(message) register char *message; { extern char yank_file[]; #ifdef UNIX tputs(CL, 0, _putchar); build_string(text_buffer, "%s\nError code %d\n", message, errno); #else build_string(text_buffer, "%s%s\nError code %d\n", enter_string, message, errno); #endif UNIX (void) write(STD_OUT, text_buffer, length_of(text_buffer)); if (loading == FALSE) XT(); /* Check if file can be saved */ else (void) unlink(yank_file); raw_mode(OFF); #ifdef UNIX abort(); #else exit(1); #endif UNIX } #ifndef lint typedef unsigned vir_bytes; #define POINTER_SIZE (sizeof(char *)) #define cast(x) ((vir_bytes) (x)) #define align(x, a) (((x) + (a - 1)) & ~(a - 1)) #define BUSY 1 #define succ(p) (* (char **) (p)) #define is_busy(p) (cast(p) & BUSY) #define set_busy(p) ((char *) (cast(p) | BUSY)) char *free_list; /* * Init_alloc() sets up the free list. The free list initially consists of * MEMORY_SIZE bytes. */ init_alloc() { register char *ptr, *top; extern char *sbrk(); /* Get data space for free list */ free_list = sbrk(POINTER_SIZE); if ((ptr = sbrk(MEMORY_SIZE)) < 0) panic("Bad memory allocation in startup"); top = sbrk(POINTER_SIZE); /* Set up list */ succ(free_list) = ptr; succ(ptr) = top; succ(top) = NIL_PTR; } /* * Allocate size bytes of memory. */ char *alloc(size) unsigned size; { register char *p = free_list; register char *next; char *new; unsigned len = align(size, POINTER_SIZE) + POINTER_SIZE; p = free_list; while ((next = succ(p)) != NIL_PTR) { if (is_busy(next)) /* Already in use */ p = (char *) (cast(next) & ~BUSY); else { while ((new = succ(next)) != NIL_PTR && !is_busy(new)) next = new; if (next - p >= len) { /* fits */ if ((new = p + len) < next) { /* too big */ succ(new) = next; succ(p) = set_busy(new); } else succ(p) = set_busy(next); free_list = p; return (p + POINTER_SIZE); } p = next; } } if (loading == TRUE) panic("File too big."); panic("Out of memory."); } free_space(p) register char *p; { p = (char *) (cast(p) - POINTER_SIZE); *(vir_bytes *) (p) &= ~BUSY; /* Pointer to free list should point to lowest address freed. */ if (free_list > p) free_list = p; } #else char *alloc(bytes) int bytes; { extern char *malloc(); return malloc((unsigned) bytes); } free_space(p) char *p; { free(p); } #endif lint /* ======================================================================== * * Main loops * * ======================================================================== */ /* The mapping between input codes and functions. */ extern UP(), DN(), LF(), RT(), MN(), MP(), GOTO(); extern SD(), SU(), PD(), PU(), HO(), EF(), BL(), EL(), HIGH(), LOW(); extern S(), LIB(), DPC(), DCC(), DLN(), DNW(), DPW(), CTRL(); extern XT(), WT(), VI(), RD(), SH(), I(), FS(), ESC(); extern SF(), SR(), LR(), GR(); extern MA(), YA(), DT(), PT(), WB(), IF(); #ifdef UNIX int (*key_map[128])() = { /* map ASCII characters to functions */ /* 000-017 */ MA, BL, MP, YA, SD, RD, MN, IF, DPC, S, S, DT, LR, S, DNW, LIB, /* 020-037 */ DPW, WB, GR, SH, DLN, SU, VI, WT, XT, PT, EL, ESC, I, GOTO, HIGH, LOW, /* 040-057 */ S, S, S, S, S, S, S, S, S, S, S, PD, S, PU, S, S, /* 060-077 */ S, S, DN, S, LF, FS, RT, S, UP, S, S, S, S, S, S, S, /* 100-117 */ S, S, S, CTRL, S, EF, SF, S, HO, S, S, S, S, S, S, S, /* 120-137 */ S, S, SR, S, S, S, S, S, S, S, S, S, S, S, S, S, /* 140-157 */ S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, /* 160-177 */ S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, DCC }; #else int (*key_map[256])() = { /* map ASCII characters to functions */ /* 000-017 */ I, BL, MP, YA, SD, RD, MN, IF, DPC, S, S, DT, LR, S, DNW, LIB, /* 020-037 */ DPW, WB, GR, SH, DLN, SU, VI, WT, XT, PT, EL, ESC, I, GOTO, HIGH, LOW, /* 040-057 */ S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, /* 060-077 */ S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, /* 100-117 */ S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, /* 120-137 */ S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, /* 140-157 */ S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, /* 160-177 */ S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, DCC, /* 200-217 */ I, I, I, I, I, I, I, I, I, SR, I, I, SF, I, I, I, /* 220-237 */ MA, I, I, I, I, I, I, I, I, I, I, CTRL, I, I, I, I, /* 240-257 */ I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, /* 260-277 */ I, EF, DN, PD, LF, FS, RT, HO, UP, PU, I, I, I, I, I, I, /* 300-317 */ I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, /* 320-337 */ I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, /* 340-357 */ I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, /* 360-377 */ I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I }; #endif UNIX int nlines; /* Number of lines in file */ LINE *header; /* Head of line list */ LINE *tail; /* Last line in line list */ LINE *cur_line; /* Current line in use */ LINE *top_line; /* First line of screen */ LINE *bot_line; /* Last line of screen */ char *cur_text; /* Current char on current line in use */ int last_y; /* Last y of screen. Usually SCREENMAX */ char screen[SCREEN_SIZE]; /* Output buffer for "writes" and "reads" */ int x, y; /* x, y coordinates on screen */ FLAG modified = FALSE; /* Set when file is modified */ FLAG stat_visible; /* Set if status_line is visible */ FLAG writable; /* Set if file cannot be written */ FLAG loading; /* Set if we are loading a file. */ FLAG quit = FALSE; /* Set when quit character is typed */ FLAG rpipe = FALSE; /* Set if file should be read from stdin */ int input_fd = 0; /* Fd for command input */ int out_count; /* Index in output buffer */ char file_name[LINE_LEN]; /* Name of file in use */ char text_buffer[MAX_CHARS]; /* Buffer for modifying text */ char blank_line[LINE_LEN]; /* Line filled with spaces */ /* Escape sequences. */ #ifdef UNIX char *CE, *VS, *SO, *SE, *CL, *AL, *CM; #else char *enter_string = "\033 8\033~0"; /* String printed on entering mined */ char *pos_string = "\033"; /* Absolute cursor position */ char *rev_scroll = "\033~1"; /* String for reverse scrolling */ char *rev_video = "\033z\160"; /* String for starting reverse video */ char *normal_video = "\033z\007"; /* String for leaving reverse video */ #endif UNIX /* * Yank variables. */ FLAG yank_status = NOT_VALID; /* Status of yank_file */ char yank_file[] = "/tmp/mined.XXXXXX"; long chars_saved; /* Nr of chars in buffer */ /* * Initialize is called when a another file is edited. It free's the allocated * space and sets modified back to FALSE and fixes the header/tail pointer. */ initialize() { register LINE *line, *next_line; /* Delete the whole list */ for (line = header->next; line != tail; line = next_line) { next_line = line->next; free_space(line->text); free_space(line); } /* header and tail should point to itself */ line->next = line->prev = line; x = y = 0; rpipe = modified = FALSE; } /* * Basename() finds the absolute name of the file out of a given path_name. */ char *basename(path) char *path; { register char *ptr = path; register char *last = NIL_PTR; while (*ptr != '\0') { if (*ptr == '/') last = ptr; ptr++; } if (last == NIL_PTR) return path; if (*(last + 1) == '\0') { /* E.g. /usr/tmp/pipo/ */ *last = '\0'; return basename(path);/* Try again */ } return last + 1; } /* * Load_file loads the file `file' into core. If file is a NIL_PTR or the file * couldn't be opened, just some initializations are done, and a line consisting * of a `\n' is installed. */ load_file(file) char *file; { register LINE *line = header; register int len; long nr_of_chars = 0L; int fd = -1; /* Filedescriptor for file */ nlines = 0; /* Zero lines to start with */ /* Open file */ writable = TRUE; /* Benefit of the doubt */ if (file == NIL_PTR) { if (rpipe == FALSE) status_line("No file.", NIL_PTR); else { fd = 0; file = "standard input"; } file_name[0] = '\0'; } else { copy_string(file_name, file); /* Save file name */ if (access(file, 0) < 0) /* Cannot access file. */ status_line("New file ", file); else if ((fd = open(file, 0)) < 0) status_line("Cannot open ", file); else if (access(file, 2) != 0) /* Set write flag */ writable = FALSE; } /* Read file */ loading = TRUE; /* Loading file, so set flag */ if (fd >= 0) { status_line("Reading ", file); while ((len = get_line(fd, text_buffer)) != ERRORS) { line = line_insert(line, text_buffer, len); nr_of_chars += (long) len; } if (nlines == 0) /* The file was empty! */ line = line_insert(line, "\n", 1); clear_buffer(); /* Clear output buffer */ cur_line = header->next; fstatus("Read", nr_of_chars); (void) close(fd); /* Close file */ } else /* Just install a "\n" */ (void) line_insert(line, "\n", 1); reset(header->next, 0); /* Initialize pointers */ /* Print screen */ display (0, 0, header->next, last_y); move_to (0, 0); flush(); /* Flush buffer */ loading = FALSE; /* Stop loading, reset flag */ } /* * Get_line reads one line from filedescriptor fd. If EOF is reached on fd, * get_line() returns ERRORS, else it returns the length of the string. */ get_line(fd, buffer) int fd; register char *buffer; { static char *last = NIL_PTR; static char *current = NIL_PTR; static int read_chars; register char *cur_pos = current; char *begin = buffer; do { if (cur_pos == last) { if ((read_chars = read(fd, screen, SCREEN_SIZE)) <= 0) break; last = &screen[read_chars]; cur_pos = screen; } if ((unsigned char) *cur_pos >= 0177 || *cur_pos == '\0') panic ("File contains non-ascii characters"); } while ((*buffer++ = *cur_pos++) != '\n'); current = cur_pos; if (read_chars <= 0) { if (buffer == begin) return ERRORS; if (*(buffer - 1) != '\n') if (loading == TRUE) /* Add '\n' to last line of file */ *buffer++ = '\n'; else { *buffer = '\0'; return NO_LINE; } } *buffer = '\0'; return buffer - begin; } /* * Install_line installs the buffer into a LINE structure It returns a pointer * to the allocated structure. */ LINE *install_line(buffer, length) char *buffer; int length; { register LINE *new_line = (LINE *) alloc(sizeof(LINE)); new_line->text = alloc(length + 1); new_line->shift_count = 0; copy_string(new_line->text, buffer); return new_line; } main(argc, argv) int argc; char *argv[]; { /* mined is the Monix editor for the IBM PC. */ register int index; /* Index in key table */ #ifdef UNIX get_term(); tputs(VS, 0, _putchar); tputs(CL, 0, _putchar); #else string_print(enter_string); /* Hello world */ for (index = 0; index < XMAX; index++) /* Fill blank_line with spaces*/ blank_line[index] = ' '; blank_line[XMAX] = '\0'; #endif UNIX if (!isatty(0)) { /* Reading from pipe */ if (argc != 1) { write(2, "Cannot find terminal.\n", 22); exit (1); } rpipe = TRUE; modified = TRUE; /* Set modified so he can write */ open_device(); } raw_mode(ON); /* Set tty to appropriate mode */ init_alloc(); header = tail = (LINE *) alloc(sizeof(LINE)); /* Make header of list*/ header->text = NIL_PTR; header->next = tail->prev = header; /* Load the file (if any) */ if (argc < 2) load_file(NIL_PTR); else { (void) get_file(NIL_PTR, argv[1]); /* Truncate filename */ load_file(argv[1]); } /* Main loop of the editor. */ for (;;) { index = getchar(); if (stat_visible == TRUE) clear_status(); if (quit == TRUE) abort_mined(); else { /* Call the function for this key */ (*key_map[index])(index); flush(); /* Flush output (if any) */ if (quit == TRUE) quit = FALSE; } } /* NOTREACHED */ } /* ======================================================================== * * Miscellaneous * * ======================================================================== */ /* * Redraw the screen */ RD() { /* Clear screen */ #ifdef UNIX tputs(VS, 0, _putchar); tputs(CL, 0, _putchar); #else string_print(enter_string); #endif UNIX /* Print first page */ display(0, 0, top_line, last_y); /* Clear last line */ set_cursor(0, YMAX); #ifdef UNIX tputs(CE, 0, _putchar); #else string_print(blank_line); #endif UNIX move_to(x, y); } /* * Ignore this keystroke. */ I() { } /* * Leave editor. If the file has changed, ask if the user wants to save it. */ XT() { if (modified == TRUE && ask_save() == ERRORS) return; raw_mode(OFF); set_cursor(0, YMAX); putchar('\n'); flush(); (void) unlink(yank_file); /* Might not be necessary */ exit(0); } /* * ESC() prompts for a count and wants a command after that. It repeats the * command count times. If a ^\ is given during repeating, stop looping and * return to main loop. */ ESC() { register int count; register int (*func)(); int index, number; extern int (*key_map[])(), I(); if ((index = get_number("Please enter repeat count.", &number)) == ERRORS) return; if ((func = key_map[index]) == I) { /* Function assigned? */ clear_status(); return; } count = number; while (count-- > 0 && quit == FALSE) { if (stat_visible == TRUE) clear_status(); (*func)(index); flush(); } if (quit == TRUE) /* Abort has been given */ error("Aborted", NIL_PTR); else clear_status(); } /* * Ask the user if he wants to save his file or not. */ ask_save() { register int c; status_line(file_name[0] ? basename(file_name) : "[buffer]" , " has been modified. Save? (y/n)"); while((c = getchar()) != 'y' && c != 'n' && quit == FALSE) { ring_bell(); flush(); } clear_status(); if (c == 'y') return WT(); if (c == 'n') return FINE; quit = FALSE; /* Abort character has been given */ return ERRORS; } /* * Line_number() finds the line number we're on. */ line_number() { register LINE *line = header->next; register int count = 1; while (line != cur_line) { count++; line = line->next; } return count; } /* * Display a line telling how many chars and lines the file contains. Also tell * whether the file is readonly and/or modified. */ file_status(message, count, file, lines, writefl, changed) char *message; register long count; /* Contains number of characters in file */ char *file; int lines; FLAG writefl, changed; { register LINE *line; char msg[LINE_LEN + 40];/* Buffer to hold line */ char yank_msg[LINE_LEN];/* Buffer for msg of yank_file */ if (count < 0) /* Not valid. Count chars in file */ for (line = header->next; line != tail; line = line->next) count += length_of(line->text); if (yank_status != NOT_VALID) /* Append buffer info */ build_string(yank_msg, " Buffer: %D char%s.", chars_saved, (chars_saved == 1L) ? "" : "s"); else yank_msg[0] = '\0'; build_string(msg, "%s %s%s%s %d line%s %D char%s.%s Line %d", message, (rpipe == TRUE && *message != '[') ? "standard input" : basename(file), (changed == TRUE) ? "*" : "", (writefl == FALSE) ? " (Readonly)" : "", lines, (lines == 1) ? "" : "s", count, (count == 1L) ? "" : "s", yank_msg, line_number()); if (length_of(msg) + 1 > LINE_LEN - 4) { msg[LINE_LEN - 4] = SHIFT_MARK; /* Overflow on status line */ msg[LINE_LEN - 3] = '\0'; } status_line(msg, NIL_PTR); /* Print the information */ } /* * Build_string() prints the arguments as described in fmt, into the buffer. * %s indicates an argument string, %d indicated an argument number. */ /* VARARGS */ build_string(buf, fmt, args) register char *buf, *fmt; int args; { int *argptr = &args; char *scanp; while (*fmt) { if (*fmt == '%') { fmt++; switch (*fmt++) { case 's' : scanp = (char *) *argptr; break; case 'd' : scanp = num_out((long) *argptr); break; case 'D' : scanp = num_out((long) *((long *) #ifdef UNIX argptr)); #else argptr++)); #endif UNIX break; default : scanp = ""; } while (*buf++ = *scanp++) ; buf--; argptr++; } else *buf++ = *fmt++; } *buf = '\0'; } /* * Output an (unsigned) long in a 10 digit field without leading zeros. * It returns a pointer to the first digit in the buffer. */ char *num_out(number) long number; { static char num_buf[11]; /* Buffer to build number */ register long digit; /* Next digit of number */ register long pow = 1000000000L; /* Highest ten power of long */ FLAG digit_seen = FALSE; int i; for (i = 0; i < 10; i++) { digit = number / pow; /* Get next digit */ if (digit == 0L && digit_seen == FALSE && i != 9) num_buf[i] = ' '; else { num_buf[i] = '0' + (char) digit; number -= digit * pow; /* Erase digit */ digit_seen = TRUE; } pow /= 10L; /* Get next digit */ } for (i = 0; num_buf[i] == ' '; i++) /* Skip leading spaces */ ; return (&num_buf[i]); } /* * Get_number() read a number from the terminal. The last character typed in is * returned. ERRORS is returned on a bad number. The resulting number is put * into the integer the arguments points to. */ get_number(message, result) char *message; int *result; { register int index; register int count = 0; status_line(message, NIL_PTR); index = getchar(); if (quit == FALSE && (index < '0' || index > '9')) { error("Bad count", NIL_PTR); return ERRORS; } /* Convert input to a decimal number */ while (index >= '0' && index <= '9' && quit == FALSE) { count *= 10; count += index - '0'; index = getchar(); } if (quit == TRUE) { clear_status(); return ERRORS; } *result = count; return index; } /* * Input() reads a string from the terminal. When the KILL character is typed, * it returns ERRORS. */ input(inbuf, clearfl) char *inbuf; FLAG clearfl; { register char *ptr; register char c; /* Character read */ ptr = inbuf; *ptr = '\0'; while (quit == FALSE) { flush(); switch (c = getchar()) { case '\b' : /* Erase previous char */ if (ptr > inbuf) { ptr--; #ifdef UNIX tputs(SE, 0, _putchar); #else string_print(normal_video); #endif UNIX if (is_tab(*ptr)) string_print(" \b\b\b \b\b"); else string_print(" \b\b \b"); #ifdef UNIX tputs(SO, 0, _putchar); #else string_print(rev_video); #endif UNIX string_print(" \b"); *ptr = '\0'; } else ring_bell(); break; case '\n' : /* End of input */ /* If inbuf is empty clear status_line */ return (ptr == inbuf && clearfl == TRUE) ? NO_INPUT :FINE; default : /* Only read ASCII chars */ if ((c >= ' ' && c <= '~') || c == '\t') { *ptr++ = c; *ptr = '\0'; if (c == '\t') string_print("^I"); else putchar(c); string_print(" \b"); } else ring_bell(); } } quit = FALSE; return ERRORS; } /* * Get_file() reads a filename from the terminal. Filenames longer than * FILE_LENGHT chars are truncated. */ get_file(message, file) char *message, *file; { char *ptr; int ret; if (message == NIL_PTR || (ret = get_string(message, file, TRUE)) == FINE) { if (length_of((ptr = basename(file))) > FILE_LENGTH) ptr[FILE_LENGTH] = '\0'; } return ret; } /* ======================================================================== * * UNIX I/O Routines * * ======================================================================== */ #ifdef UNIX #undef putchar _getchar() { char c; if (read(input_fd, &c, 1) != 1 && quit == FALSE) panic ("Cannot read 1 byte from input"); return c & 0177; } _flush() { (void) fflush(stdout); } _putchar(c) char c; { (void) write_char(STD_OUT, c); } get_term() { static char termbuf[50]; extern char *tgetstr(), *getenv(); char *loc = termbuf; char entry[1024]; if (tgetent(entry, getenv("TERM")) <= 0) { printf("Unknown terminal.\n"); exit(1); } AL = tgetstr("al", &loc); CE = tgetstr("ce", &loc); VS = tgetstr("vs", &loc); CL = tgetstr("cl", &loc); SO = tgetstr("so", &loc); SE = tgetstr("se", &loc); CM = tgetstr("cm", &loc); if (!CE || !SO || !SE || !CL || !AL || !CM) { printf("Sorry, no mined on this type of terminal\n"); exit(1); } } #endif UNIX /* * Part 2 of the mined editor. */ /* ======================================================================== * * Move Commands * * ======================================================================== */ #include "mined.h" /* * Move one line up. */ UP() { if (y == 0) { /* Top line of screen. Scroll one line */ (void) reverse_scroll(); move_to(x, y); } else /* Move to previous line */ move_to(x, y - 1); } /* * Move one line down. */ DN() { if (y == last_y) { /* Last line of screen. Scroll one line */ if (bot_line->next == tail && bot_line->text[0] != '\n') { dummy_line(); /* Create new empty line */ DN(); return; } else { (void) forward_scroll(); move_to(x, y); } } else /* Move to next line */ move_to(x, y + 1); } /* * Move left one position. */ LF() { if (x == 0 && get_shift(cur_line->shift_count) == 0) {/* Begin of line */ if (cur_line->prev != header) { UP(); /* Move one line up */ move_to(LINE_END, y); } } else move_to(x - 1, y); } /* * Move right one position. */ RT() { if (*cur_text == '\n') { if (cur_line->next != tail) { /* Last char of file */ DN(); /* Move one line down */ move_to(LINE_START, y); } } else move_to(x + 1, y); } /* * Move to coordinates [0, 0] on screen. */ HIGH() { move_to(0, 0); } /* * Move to coordinates [0, YMAX] on screen. */ LOW() { move_to(0, last_y); } /* * Move to begin of line. */ BL() { move_to(LINE_START, y); } /* * Move to end of line. */ EL() { move_to(LINE_END, y); } /* * GOTO() prompts for a linenumber and moves to that line. */ GOTO() { int number; LINE *line; if (get_number("Please enter line number.", &number) == ERRORS) return; if (number <= 0 || (line = proceed(header->next, number - 1)) == tail) error("Illegal line number: ", num_out((long) number)); else move_to(x, find_y(line)); } /* * Scroll forward one page or to eof, whatever comes first. (Bot_line becomes * top_line of display.) Try to leave the cursor on the same line. If this is * not possible, leave cursor on the line halfway the page. */ PD() { register int i; for (i = 0; i < SCREENMAX; i++) if (forward_scroll() == ERRORS) break; /* EOF reached */ if (y - i < 0) /* Line no longer on screen */ move_to(0, SCREENMAX >> 1); else move_to(0, y - i); } /* * Scroll backwards one page or to top of file, whatever comes first. (Top_line * becomes bot_line of display). The very bottom line (YMAX) is always blank. * Try to leave the cursor on the same line. If this is not possible, leave * cursor on the line halfway the page. */ PU() { register int i; for (i = 0; i < SCREENMAX; i++) if (reverse_scroll() == ERRORS) break; /* Top of file reached */ set_cursor(0, YMAX); /* Erase very bottom line */ #ifdef UNIX tputs(CE, 0, _putchar); #else string_print(blank_line); #endif UNIX if (y + i > SCREENMAX) /* line no longer on screen */ move_to(0, SCREENMAX >> 1); else move_to(0, y + i); } /* * Go to top of file, scrolling if possible, else redrawing screen. */ HO() { if (proceed(top_line, -SCREENMAX) == header) PU(); /* It fits. Let PU do it */ else { reset(header->next, 0);/* Reset top_line, etc. */ RD(); /* Display full page */ } move_to(LINE_START, 0); } /* * Go to last line of file, scrolling if possible, else redrawing screen */ EF() { if (tail->prev->text[0] != '\n') dummy_line(); if (proceed(bot_line, SCREENMAX) == tail) PD(); /* It fits. Let PD do it */ else { reset(proceed(tail->prev, -SCREENMAX), SCREENMAX); RD(); /* Display full page */ } move_to(LINE_START, last_y); } /* * Scroll one line up. Leave the cursor on the same line (if possible). */ SU() { if (top_line->prev == header) /* Top of file. Can't scroll */ return; (void) reverse_scroll(); set_cursor(0, YMAX); /* Erase very bottom line */ #ifdef UNIX tputs(CE, 0, _putchar); #else string_print(blank_line); #endif UNIX move_to(x, (y == SCREENMAX) ? SCREENMAX : y + 1); } /* * Scroll one line down. Leave the cursor on the same line (if possible). */ SD() { if (forward_scroll() != ERRORS) move_to(x, (y == 0) ? 0 : y - 1); else set_cursor(x, y); } /* * Perform a forward scroll. It returns ERRORS if we're at the last line of the * file. */ forward_scroll() { if (bot_line->next == tail) /* Last line of file. No dice */ return ERRORS; top_line = top_line->next; bot_line = bot_line->next; cur_line = cur_line->next; set_cursor(0, YMAX); line_print(bot_line); return FINE; } /* * Perform a backwards scroll. It returns ERRORS if we're at the first line * of the file. */ reverse_scroll() { if (top_line->prev == header) return ERRORS; /* Top of file. Can't scroll */ if (last_y != SCREENMAX) /* Reset last_y if necessary */ last_y++; else bot_line = bot_line->prev; /* Else adjust bot_line */ top_line = top_line->prev; cur_line = cur_line->prev; /* Perform the scroll */ set_cursor(0, 0); #ifdef UNIX tputs(AL, 0, _putchar); #else string_print(rev_scroll); #endif UNIX set_cursor(0, 0); line_print(top_line); return FINE; } /* * A word is defined as a number of non-blank characters separated by tabs * spaces or linefeeds. */ /* * MP() moves to the start of the previous word. A word is defined as a * number of non-blank characters separated by tabs spaces or linefeeds. */ MP() { move_previous_word(NO_DELETE); } move_previous_word(remove) FLAG remove; { register char *begin_line; register char *textp; char start_char = *cur_text; char *start_pos = cur_text; /* Fist check if we're at the beginning of line. */ if (cur_text == cur_line->text) { if (cur_line->prev == header) return; start_char = '\0'; } LF(); begin_line = cur_line->text; textp = cur_text; /* Check if we're in the middle of a word. */ if (!alpha(*textp) || !alpha(start_char)) { while (textp != begin_line && (white_space(*textp) || *textp == '\n')) textp--; } /* Now we're at the end of previous word. Skip non-blanks until a blank comes */ while (textp != begin_line && alpha(*textp)) textp--; /* Go to the next char if we're not at the beginning of the line */ if (textp != begin_line && *textp != '\n') textp++; /* Find the x-coordinate of this address, and move to it */ move_address(textp); if (remove == DELETE) delete(cur_line, textp, cur_line, start_pos); } /* * MN() moves to the start of the next word. A word is defined as a number of * non-blank characters separated by tabs spaces or linefeeds. Always keep in * mind that the pointer shouldn't pass the '\n'. */ MN() { move_next_word(NO_DELETE); } move_next_word(remove) FLAG remove; { register char *textp = cur_text; /* Move to the end of the current word. */ while (*textp != '\n' && alpha(*textp)) textp++; /* Skip all white spaces */ while (*textp != '\n' && white_space(*textp)) textp++; /* If we're deleting. delete the text in between */ if (remove == DELETE) { delete(cur_line, cur_text, cur_line, textp); return; } /* If we're at end of line. move to the first word on the next line. */ if (*textp == '\n' && cur_line->next != tail) { DN(); move_to(LINE_START, y); textp = cur_text; while (*textp != '\n' && white_space(*textp)) textp++; } move_address(textp); } /* ======================================================================== * * Modify Commands * * ======================================================================== */ /* * DCC deletes the character under the cursor. If this character is a '\n' the * current line is joined with the next one. * If this character is the only character of the line, the current line will * be deleted. */ DCC() { if (*cur_text == '\n') delete(cur_line,cur_text, cur_line->next,cur_line->next->text); else delete(cur_line, cur_text, cur_line, cur_text + 1); } /* * DPC deletes the character on the left side of the cursor. If the cursor is * at the beginning of the line, the last character if the previous line is *  deleted. */ DPC() { if (x == 0 && cur_line->prev == header) return; /* Top of file */ LF(); /* Move one left */ DCC(); /* Delete character under cursor */ } /* * DLN deletes all characters until the end of the line. If the current * character is a '\n', then delete that char. */ DLN() { if (*cur_text == '\n') DCC(); else delete(cur_line, cur_text, cur_line, cur_text + length_of(cur_text) -1); } /* * DNW() deletes the next word (as described in MN()) */ DNW() { if (*cur_text == '\n') DCC(); else move_next_word(DELETE); } /* * DPW() deletes the next word (as described in MP()) */ DPW() { if (cur_text == cur_line->text) DPC(); else move_previous_word(DELETE); } /* * Insert character `character' at current location. */ S(character) register char character; { static char buffer[2]; buffer[0] = character; /* Insert the character */ if (insert(cur_line, cur_text, buffer) == ERRORS) return; /* Fix screen */ if (character == '\n') { set_cursor(0, y); if (y == SCREENMAX) { /* Can't use display */ line_print(cur_line); (void) forward_scroll(); } else { reset(top_line, y); /* Reset pointers */ display(0, y, cur_line, last_y - y); } move_to(0, (y == SCREENMAX) ? y : y + 1); } else if (x + 1 == XBREAK)/* If line must be shifted, just call move_to*/ move_to(x + 1, y); else { /* else display rest of line */ put_line(cur_line, x, FALSE); move_to(x + 1, y); } } /* * CTRL inserts a control-char at the current location. A message that this * function is called is displayed at the status line. */ CTRL() { register char ctrl; status_line("Enter control character.", NIL_PTR); if ((ctrl = getchar()) >= '\01' && ctrl <= '\037') { S(ctrl); /* Insert the char */ clear_status(); } else error ("Unknown control character", NIL_PTR); } /* * LIB insert a line at the current position and moves back to the end of * the previous line. */ LIB() { S('\n'); /* Insert the line */ UP(); /* Move one line up */ move_to(LINE_END, y); /* Move to end of this line */ } /* * Line_insert() inserts a new line with text pointed to by `string'. * It returns the address of the new line. */ LINE *line_insert(line, string, len) register LINE *line; char *string; int len; { register LINE *new_line; /* Allocate space for LINE structure and text */ new_line = install_line(string, len); /* Install the line into the double linked list */ new_line->prev = line; new_line->next = line->next; line->next = new_line; new_line->next->prev = new_line; /* Increment nlines */ nlines++; return new_line; } /* * Insert() insert the string `string' at the given line and location. */ insert(line, location, string) register LINE *line; char *location, *string; { register char *bufp = text_buffer; /* Buffer for building line */ register char *textp = line->text; if (length_of(textp) + length_of(string) >= MAX_CHARS) { error("Line too long", NIL_PTR); return ERRORS; } modified = TRUE; /* File has been modified */ /* Copy part of line until `location' has been reached */ while (textp != location) *bufp++ = *textp++; /* Insert string at this location */ while (*string != '\0') *bufp++ = *string++; *bufp = '\0'; if (*(string - 1) == '\n') /* Insert a new line */ (void) line_insert(line, location, length_of(location)); else /* Append last part of line */ copy_string(bufp, location); /* Install the new text in this line */ free_space(line->text); line->text = alloc(length_of(text_buffer) + 1); copy_string(line->text, text_buffer); return FINE; } /* * Line_delete() deletes the argument line out of the line list. The pointer to * the next line is returned. */ LINE *line_delete(line) register LINE *line; { register LINE *next_line = line->next; /* Delete the line */ line->prev->next = line->next; line->next->prev = line->prev; /* Free allocated space */ free_space(line->text); free_space(line); /* Decrement nlines */ nlines--; return next_line; } /* * Delete() deletes all the characters (including newlines) between the * startposition and endposition and fixes the screen accordingly. It * returns the number of lines deleted. */ delete(start_line, start_textp, end_line, end_textp) register LINE *start_line; LINE *end_line; char *start_textp, *end_textp; { register char *textp = start_line->text; register char *bufp = text_buffer; /* Storage for new line->text */ LINE *line; int line_cnt = 0; /* Nr of lines deleted */ int count = 0; int shift = 0; /* Used in shift calculation */ int nx = x; modified = TRUE; /* File has been modified */ /* Set up new line. Copy first part of start line until start_position. */ while (textp < start_textp) { *bufp++ = *textp++; count++; } /* Check if line doesn't exceed MAX_CHARS */ if (count + length_of(end_textp) >= MAX_CHARS) { error("Line too long", NIL_PTR); return; } /* Copy last part of end_line if end_line is not tail */ copy_string(bufp, (end_textp != NIL_PTR) ? end_textp : "\n"); /* Delete all lines between start and end_position (including end_line) */ line = start_line->next; while (line != end_line->next && line != tail) { line = line_delete(line); line_cnt++; } /* Check if last line of file should be deleted */ if (end_textp == NIL_PTR && length_of(start_line->text) == 1 && nlines > 1) { start_line = start_line->prev; (void) line_delete(start_line->next); line_cnt++; } else { /* Install new text */ free_space(start_line->text); start_line->text = alloc(length_of(text_buffer) + 1); copy_string(start_line->text, text_buffer); } /* Fix screen. First check if line is shifted. Perhaps we should shift it back*/ if (get_shift(start_line->shift_count)) { shift = (XBREAK - count_chars(start_line)) / SHIFT_SIZE; if (shift > 0) { /* Shift line `shift' back */ if (shift >= get_shift(start_line->shift_count)) start_line->shift_count = 0; else start_line->shift_count -= shift; nx += shift * SHIFT_SIZE;/* Reset x value */ } } if (line_cnt == 0) { /* Check if only one line changed */ if (shift > 0) { /* Reprint whole line */ set_cursor(0, y); line_print(start_line); } else { /* Just display last part of line */ set_cursor(x, y); put_line(start_line, x, TRUE); } move_to(nx, y); /* Reset cur_text */ return; } shift = last_y; /* Save value */ reset(top_line, y); display(0, y, start_line, shift - y); move_to((line_cnt == 1) ? nx : 0, y); } /* ======================================================================== * * Yank Commands * * ======================================================================== */ LINE *mark_line; /* For marking position. */ char *mark_text; int lines_saved; /* Nr of lines in buffer */ /* * PT() inserts the buffer at the current location. */ PT() { register int fd; /* File descriptor for buffer */ if ((fd = scratch_file(READ)) == ERRORS) error("Buffer is empty.", NIL_PTR); else { file_insert(fd, FALSE);/* Insert the buffer */ (void) close(fd); } } /* * IF() prompt for a filename and inserts the file at the current location * in the file. */ IF() { register int fd; /* File descriptor of file */ char name[LINE_LEN]; /* Buffer for file name */ /* Get the file name */ if (get_file("Get and insert file:", name) != FINE) return; if ((fd = open(name, 0)) < 0) error("Cannot open ", name); else { file_insert(fd, TRUE); /* Insert the file */ (void) close(fd); } } /* * File_insert() inserts a an opened file (as given by filedescriptor fd) * at the current location. */ file_insert(fd, old_pos) int fd; FLAG old_pos; { char line_buffer[MAX_CHARS]; /* Buffer for next line */ register LINE *line = cur_line; register int line_count = nlines; /* Nr of lines inserted */ LINE *page = cur_line; int ret = ERRORS; /* Get the first piece of text (might be ended with a '\n') from fd */ if (get_line(fd, line_buffer) == ERRORS) return; /* Empty file */ /* Insert this text at the current location. */ if (insert(line, cur_text, line_buffer) == ERRORS) return; /* Repeat getting lines (and inserting lines) until EOF is reached */ while ((ret = get_line(fd, line_buffer)) != ERRORS && ret != NO_LINE) line = line_insert(line, line_buffer, ret); if (ret == NO_LINE) { /* Last line read not ended by a '\n' */ line = line->next; (void) insert(line, line->text, line_buffer); } /* Calculate nr of lines added */ line_count = nlines - line_count; /* Fix the screen */ if (line_count == 0) { /* Only one line changed */ set_cursor(0, y); line_print(line); move_to((old_pos == TRUE) ? x : x + length_of(line_buffer), y); } else { /* Several lines changed */ reset(top_line, y); /* Reset pointers */ while (page != line && page != bot_line->next) page = page->next; if (page != bot_line->next || old_pos == TRUE) display(0, y, cur_line, SCREENMAX - y); if (old_pos == TRUE) move_to(x, y); else if (ret == NO_LINE) move_to(length_of(line_buffer), find_y(line)); else move_to(0, find_y(line->next)); } /* If nr of added line >= REPORT, print the count */ if (line_count >= REPORT) status_line(num_out((long) line_count), " lines added."); } /* * WB() writes the buffer (yank_file) into another file, which * is prompted for. */ WB() { register int new_fd; /* Filedescriptor to copy file */ int yank_fd; /* Filedescriptor to buffer */ register int cnt; /* Count check for read/write */ int ret; /* Error check for write */ char file[LINE_LEN]; /* Output file */ /* Checkout the buffer */ if ((yank_fd = scratch_file(READ)) == ERRORS) { error("Buffer is empty.", NIL_PTR); return; } /* Get file name */ if (get_file("Write buffer to file:", file) != FINE) return; /* Creat the new file */ if ((new_fd = creat(file, 0644)) < 0) { error("Cannot create ", file); return; } status_line("Writing ", file); /* Copy buffer into file */ while ((cnt = read(yank_fd, text_buffer, sizeof(text_buffer))) > 0) if (write(new_fd, text_buffer, cnt) != cnt) { bad_write(new_fd); ret = ERRORS; break; } /* Clean up open files and status_line */ (void) close(new_fd); (void) close(yank_fd); if (ret != ERRORS) /* Bad write */ file_status("Wrote", chars_saved, file, lines_saved, TRUE, FALSE); } /* * MA sets mark_line (mark_text) to the current line (text pointer). */ MA() { mark_line = cur_line; mark_text = cur_text; status_line("Mark set", NIL_PTR); } /* * YA() puts the text between the marked position and the current * in the buffer. */ YA() { set_up(NO_DELETE); } /* * DT() is essentially the same as YA(), but in DT() the text is deleted. */ DT() { set_up(DELETE); } /* * Set_up is an interface to the actual yank. It calls checkmark () to check * if the marked position is still valid. If it is, yank is called with the * arguments in the right order. */ set_up(remove) FLAG remove; /* DELETE if text should be deleted */ { FLAG checkmark(); switch (checkmark()) { case NOT_VALID : error("Mark not set.", NIL_PTR); return; case SMALLER : yank(mark_line, mark_text, cur_line, cur_text, remove); break; case BIGGER : yank(cur_line, cur_text, mark_line, mark_text, remove); break; case SAME : /* Ignore stupid behaviour */ yank_status = EMPTY; chars_saved = 0L; status_line("0 characters saved in buffer.", NIL_PTR); break; } } /* * Check_mark() checks if mark_line and mark_text are still valid pointers. If * they are it returns SMALLER if the marked position is before the current, * BIGGER if it isn't or SAME if somebody didn't get the point. * NOT_VALID is returned when mark_line and/or mark_text are no longer valid. * Legal() checks if mark_text is valid on the mark_line. */ FLAG checkmark() { register LINE *line; FLAG cur_seen = FALSE; /* Special case: check is mark_line and cur_line are the same. */ if (mark_line == cur_line) { if (mark_text == cur_text) /* Even same place */ return SAME; if (legal() == ERRORS) /* mark_text out of range */ return NOT_VALID; return (mark_text < cur_text) ? SMALLER : BIGGER; } /* Start looking for mark_line in the line structure */ for (line = header->next; line != tail; line = line->next) { if (line == cur_line) cur_seen = TRUE; else if (line == mark_line) break; } /* If we found mark_line (line != tail) check for legality of mark_text */ if (line == tail || legal() == ERRORS) return NOT_VALID; /* cur_seen is TRUE if cur_line is before mark_line */ return (cur_seen == TRUE) ? BIGGER : SMALLER; } /* * Legal() checks if mark_text is still a valid pointer. */ legal() { register char *textp = mark_line->text; /* Locate mark_text on mark_line */ while (textp != mark_text && *textp++ != '\0') ; return (*textp == '\0') ? ERRORS : FINE; } /* * Yank puts all the text between start_position and end_position into * the buffer. * The caller must check that the arguments to yank() are valid. (E.g. in * the right order) */ yank(start_line, start_textp, end_line, end_textp, remove) LINE *start_line, *end_line; char *start_textp, *end_textp; FLAG remove; /* DELETE if text should be deleted */ { register LINE *line = start_line; register char *textp = start_textp; int fd; /* Creat file to hold buffer */ if ((fd = scratch_file(WRITE)) == ERRORS) return; chars_saved = 0L; lines_saved = 0; status_line("Saving text.", NIL_PTR); /* Keep writing chars until the end_location is reached. */ while (textp != end_textp) { if (write_char(fd, *textp) == ERRORS) { (void) close(fd); return; } if (*textp++ == '\n') { /* Move to the next line */ line = line->next; textp = line->text; lines_saved++; } chars_saved++; } /* Flush the I/O buffer and close file */ if (flush_buffer(fd) == ERRORS) { (void) close(fd); return; } (void) close(fd); yank_status = VALID; /* * Check if the text should be deleted as well. If it should, the following * hack is used to save a lot of code. First move back to the start_position. * (This might be the location we're on now!) and them delete the text. * It might be a bit confusing the first time somebody uses it. * Delete() will fix the screen. */ if (remove == DELETE) { move_to(find_x(start_line, start_textp), find_y(start_line)); delete(start_line, start_textp, end_line, end_textp); } status_line(num_out(chars_saved), " characters saved in buffer."); } /* * Scratch_file() creates a uniq file in /usr/tmp. If the file couldn't * be created other combinations of files are tried until a maximum * of MAXTRAILS times. After MAXTRAILS times, an error message is given * and ERRORS is returned. */ #define MAXTRAILS 26 scratch_file(mode) FLAG mode; /* Can be READ or WRITE permission */ { static int trials = 0; /* Keep track of trails */ register char *y_ptr, *n_ptr; int fd; /* Filedescriptor to buffer */ /* If yank_status == NOT_VALID, scratch_file is called for the first time */ if (yank_status == NOT_VALID && mode == WRITE) { /* Create new file */ /* Generate file name. */ y_ptr = &yank_file[11]; n_ptr = num_out((long) getpid()); while ((*y_ptr = *n_ptr++) != '\0') y_ptr++; *y_ptr++ = 'a' + trials; *y_ptr = '\0'; /* Check file existence */ if (access(yank_file, 0) == 0 || (fd = creat(yank_file, 0644)) < 0) { if (trials++ >= MAXTRAILS) { error("Unable to creat scratchfile.", NIL_PTR); return ERRORS; } else return scratch_file(mode);/* Have another go */ } } else if ((mode == READ && (fd = open(yank_file, 0)) < 0) || (mode == WRITE && (fd = creat(yank_file, 0644)) < 0)) { yank_status = NOT_VALID; return ERRORS; } clear_buffer(); return fd; } /* ======================================================================== * * Search Routines * * ======================================================================== */ /* * A regular expression consists of a sequence of: * 1. A normal character matching that character. * 2. A . matching any character. * 3. A ^ matching the begin of a line. * 4. A $ (as last character of the pattern) mathing the end of a line. * 5. A \ matching . * 6. A number of characters enclosed in [] pairs matching any of these * characters. A list of characters can be indicated by a '-'. So * [a-z] matches any letter of the alphabet. If the first character * after the '[' is a '^' then the set is negated (matching none of * the characters). * A ']', '^' or '-' can be escaped by putting a '\' in front of it. * 7. If one of the expressions as described in 1-6 is followed by a * '*' than that expressions matches a sequence of 0 or more of * that expression. */ char typed_expression[LINE_LEN]; /* Holds previous expr. */ /* * SF searches forward for an expression. */ SF() { search("Search forward:", FORWARD); } /* * SF searches backwards for an expression. */ SR() { search("Search reverse:", REVERSE); } /* * Get_expression() prompts for an expression. If just a return is typed, the * old expression is used. If the expression changed, compile() is called and * the returning REGEX structure is returned. It returns NIL_REG upon error. * The save flag indicates whether the expression should be appended at the * message pointer. */ REGEX *get_expression(message) char *message; { static REGEX program; /* Program of expression */ char exp_buf[LINE_LEN]; /* Buffer for new expr. */ if (get_string(message, exp_buf, FALSE) == ERRORS) return NIL_REG; if (exp_buf[0] == '\0' && typed_expression[0] == '\0') { error("No previous expression.", NIL_PTR); return NIL_REG; } if (exp_buf[0] != '\0') { /* A new expr. is typed */ copy_string(typed_expression, exp_buf);/* Save expr. */ compile(exp_buf, &program); /* Compile new expression */ } if (program.status == REG_ERROR) { /* Error during compiling */ error(program.result.err_mess, NIL_PTR); return NIL_REG; } return &program; } /* * GR() a replaces all matches from the current position until the end * of the file. */ GR() { change("Global replace:", VALID); } /* * LR() replaces all matches on the current line. */ LR() { change("Line replace:", NOT_VALID); } /* * Change() prompts for an expression and a substitution pattern and changes * all matches of the expression into the substitution. change() start looking * for expressions at the current line and continues until the end of the file * if the FLAG file is VALID. */ change(message, file) char *message; /* Message to prompt for expression */ FLAG file; { char mess_buf[LINE_LEN]; /* Buffer to hold message */ char replacement[LINE_LEN]; /* Buffer to hold subst. pattern */ REGEX *program; /* Program resulting from compilation */ register LINE *line = cur_line; register char *textp; char *substitute(); long lines = 0L; /* Nr of lines on which subs occurred */ long subs = 0L; /* Nr of subs made */ int page = y; /* Index to check if line is on screen*/ /* Save message and get expression */ copy_string(mess_buf, message); if ((program = get_expression(mess_buf)) == NIL_REG) return; /* Get substitution pattern */ build_string(mess_buf, "%s %s by:", mess_buf, typed_expression); if (get_string(mess_buf, replacement, FALSE) == ERRORS) return; set_cursor(0, YMAX); flush(); /* Substitute until end of file */ do { if (line_check(program, line->text, FORWARD)) { lines++; /* Repeat sub. on this line as long as we find a match*/ do { subs++; /* Increment subs */ if ((textp = substitute(line, program,replacement)) == NIL_PTR) return; /* Line too long */ } while ((program->status & BEGIN_LINE) != BEGIN_LINE && (program->status & END_LINE) != END_LINE && line_check(program, textp, FORWARD)); /* Check to see if we can print the result */ if (page <= SCREENMAX) { set_cursor(0, page); line_print(line); } } if (page <= SCREENMAX) page++; line = line->next; } while (line != tail && file == VALID && quit == FALSE); copy_string(mess_buf, (quit == TRUE) ? "(Aborted) " : ""); /* Fix the status line */ if (subs == 0L && quit == FALSE) error("Pattern not found.", NIL_PTR); else if (lines >= REPORT || quit == TRUE) { build_string(mess_buf, "%s %D substitutions on %D lines.", mess_buf, subs, lines); status_line(mess_buf, NIL_PTR); } else if (file == NOT_VALID && subs >= REPORT) status_line(num_out(subs), " substitutions."); else clear_status(); move_to (x, y); } /* * Substitute() replaces the match on this line by the substitute pattern * as indicated by the program. Every '&' in the replacement is replaced by * the original match. A \ in the replacement escapes the next character. */ char *substitute(line, program, replacement) LINE *line; REGEX *program; char *replacement; /* Contains replacement pattern */ { register char *textp = text_buffer; register char *subp = replacement; char *linep = line->text; char *amp; modified = TRUE; /* Copy part of line until the beginning of the match */ while (linep != program->start_ptr) *textp++ = *linep++; /* * Replace the match by the substitution pattern. Each occurrence of '&' is * replaced by the original match. A \ escapes the next character. */ while (*subp != '\0' && textp < &text_buffer[MAX_CHARS]) { if (*subp == '&') { /* Replace the original match */ amp = program->start_ptr; while (amp < program->end_ptr && textp<&text_buffer[MAX_CHARS]) *textp++ = *amp++; subp++; } else { if (*subp == '\\' && *(subp + 1) != '\0') subp++; *textp++ = *subp++; } } /* Check for line length not exceeding MAX_CHARS */ if (length_of(text_buffer) + length_of(program->end_ptr) >= MAX_CHARS) { error("Substitution result: line too big", NIL_PTR); return NIL_PTR; } /* Append last part of line to the new build line */ copy_string(textp, program->end_ptr); /* Free old line and install new one */ free_space(line->text); line->text = alloc(length_of(text_buffer) + 1); copy_string(line->text, text_buffer); return(line->text + (textp - text_buffer)); } /* * Search() calls get_expression to fetch the expression. If this went well, * the function match() is called which returns the line with the next match. * If this line is the NIL_LINE, it means that a match could not be found. * Find_x() and find_y() display the right page on the screen, and return * the right coordinates for x and y. These coordinates are passed to move_to() */ search(message, method) char *message; FLAG method; { register REGEX *program; register LINE *match_line; /* Get the expression */ if ((program = get_expression(message)) == NIL_REG) return; set_cursor(0, YMAX); flush(); /* Find the match */ if ((match_line = match(program, cur_text, method)) == NIL_LINE) { if (quit == TRUE) status_line("Aborted", NIL_PTR); else status_line("Pattern not found.", NIL_PTR); return; } move(0, program->start_ptr, find_y(match_line)); clear_status(); } /* * find_y() checks if the matched line is on the current page. If it is, it * returns the new y coordinate, else it displays the correct page with the * matched line in the middle and returns the new y value; */ find_y(match_line) LINE *match_line; { register LINE *line; register int count = 0; /* Check if match_line is on the same page as currently displayed. */ for (line = top_line; line != match_line && line != bot_line->next; line = line->next) count++; if (line != bot_line->next) return count; /* Display new page, with match_line in center. */ if ((line = proceed(match_line, -(SCREENMAX >> 1))) == header) { /* Can't display in the middle. Make first line of file top_line */ count = 0; for (line = header->next; line != match_line; line = line->next) count++; line = header->next; } else /* New page is displayed. Set cursor to middle of page */ count = SCREENMAX >> 1; /* Reset pointers and redraw the screen */ reset(line, 0); RD(); return count; } /* Opcodes for characters */ #define NORMAL 0x0200 #define DOT 0x0400 #define EOLN 0x0800 #define STAR 0x1000 #define BRACKET 0x2000 #define NEGATE 0x0100 #define DONE 0x4000 /* Mask for opcodes and characters */ #define LOW_BYTE 0x00FF #define HIGH_BYTE 0xFF00 /* Previous is the contents of the previous address (ptr) points to */ #define previous(ptr) (*((ptr) - 1)) /* Buffer to store outcome of compilation */ int exp_buffer[BLOCK_SIZE]; /* Errors often used */ char *too_long = "Regular expression too long"; /* * Reg_error() is called by compile() is something went wrong. It set the * status of the structure to error, and assigns the error field of the union. */ #define reg_error(str) program->status = REG_ERROR, \ program->result.err_mess = (str) /* * Finished() is called when everything went right during compilation. It * allocates space for the expression, and copies the expression buffer into * this field. */ finished(program, last_exp) register REGEX *program; int *last_exp; { register int length = (last_exp - exp_buffer) * sizeof(int); /* Allocate space */ program->result.expression = (int *) alloc(length); /* Copy expression. (expression consists of ints!) */ bcopy(exp_buffer, program->result.expression, length); } /* * Bcopy copies `bytes' bytes from the `from' address into the `to' address. */ bcopy(from, to, bytes) register char *from, *to; register int bytes; { while (bytes--) *to++ = *from++; } /* * Compile compiles the pattern into a more comprehensible form and returns a * REGEX structure. If something went wrong, the status field of the structure * is set to REG_ERROR and an error message is set into the err_mess field of * the union. If all went well the expression is saved and the expression * pointer is set to the saved (and compiled) expression. */ compile(pattern, program) register char *pattern; /* Pointer to pattern */ REGEX *program; { register int *expression = exp_buffer; int *prev_char; /* Pointer to previous compiled atom */ int *acct_field; /* Pointer to last BRACKET start */ FLAG negate; /* Negate flag for BRACKET */ char low_char; /* Index for chars in BRACKET */ char c; /* Check for begin of line */ if (*pattern == '^') { program->status = BEGIN_LINE; pattern++; } else { program->status = 0; /* If the first character is a '*' we have to assign it here. */ if (*pattern == '*') { *expression++ = '*' + NORMAL; pattern++; } } for (; ;) { switch (c = *pattern++) { case '.' : *expression++ = DOT; break; case '$' : /* * Only means EOLN if it is the last char of the pattern */ if (*pattern == '\0') { *expression++ = EOLN | DONE; program->status |= END_LINE; finished(program, expression); return; } else *expression++ = NORMAL + '$'; break; case '\0' : *expression++ = DONE; finished(program, expression); return; case '\\' : /* If last char, it must! mean a normal '\' */ if (*pattern == '\0') *expression++ = NORMAL + '\\'; else *expression++ = NORMAL + *pattern++; break; case '*' : /* * If the previous expression was a [] find out the * begin of the list, and adjust the opcode. */ prev_char = expression - 1; if (*prev_char & BRACKET) *(expression - (*acct_field & LOW_BYTE))|= STAR; else *prev_char |= STAR; break; case '[' : /* * First field in expression gives information about * the list. * The opcode consists of BRACKET and if necessary * NEGATE to indicate that the list should be negated * and/or STAR to indicate a number of sequence of this * list. * The lower byte contains the length of the list. */ acct_field = expression++; if (*pattern == '^') { /* List must be negated */ pattern++; negate = TRUE; } else negate = FALSE; while (*pattern != ']') { if (*pattern == '\0') { reg_error("Missing ]"); return; } if (*pattern == '\\') pattern++; *expression++ = *pattern++; if (*pattern == '-') { /* Make list of chars */ low_char = previous(pattern); pattern++; /* Skip '-' */ if (low_char++ > *pattern) { reg_error("Bad range in [a-z]"); return; } /* Build list */ while (low_char <= *pattern) *expression++ = low_char++; pattern++; } if (expression >= &exp_buffer[BLOCK_SIZE]) { reg_error(too_long); return; } } pattern++; /* Skip ']' */ /* Assign length of list in acct field */ if ((*acct_field = (expression - acct_field)) == 1) { reg_error("Empty []"); return; } /* Assign negate and bracket field */ *acct_field |= BRACKET; if (negate == TRUE) *acct_field |= NEGATE; /* * Add BRACKET to opcode of last char in field because * a '*' may be following the list. */ previous(expression) |= BRACKET; break; default : *expression++ = c + NORMAL; } if (expression == &exp_buffer[BLOCK_SIZE]) { reg_error(too_long); return; } } /* NOTREACHED */ } /* * Match gets as argument the program, pointer to place in current line to * start from and the method to search for (either FORWARD or REVERSE). * Match() will look through the whole file until a match is found. * NIL_LINE is returned if no match could be found. */ LINE *match(program, string, method) REGEX *program; char *string; register FLAG method; { register LINE *line = cur_line; char old_char; /* For saving chars */ /* Corrupted program */ if (program->status == REG_ERROR) return NIL_LINE; /* Check part of text first */ if (!(program->status & BEGIN_LINE)) { if (method == FORWARD) { if (line_check(program, string + 1, method) == MATCH) return cur_line; /* Match found */ } else if (!(program->status & END_LINE)) { old_char = *string; /* Save char and */ *string = '\n'; /* Assign '\n' for line_check */ if (line_check(program, line->text, method) == MATCH) { *string = old_char; /* Restore char */ return cur_line; /* Found match */ } *string = old_char; /* No match, but restore char */ } } /* No match in last (or first) part of line. Check out rest of file */ do { line = (method == FORWARD) ? line->next : line->prev; if (line->text == NIL_PTR) /* Header/tail */ continue; if (line_check(program, line->text, method) == MATCH) return line; } while (line != cur_line && quit == FALSE); /* No match found. */ return NIL_LINE; } /* * Line_check() checks the line (or rather string) for a match. Method * indicates FORWARD or REVERSE search. It scans through the whole string * until a match is found, or the end of the string is reached. */ line_check(program, string, method) register REGEX *program; char *string; FLAG method; { register char *textp = string; /* Assign start_ptr field. We might find a match right away! */ program->start_ptr = textp; /* If the match must be anchored, just check the string. */ if (program->status & BEGIN_LINE) return check_string(program, string, NIL_INT); if (method == REVERSE) { /* First move to the end of the string */ for (textp = string; *textp != '\n'; textp++) ; /* Start checking string until the begin of the string is met */ while (textp >= string) { program->start_ptr = textp; if (check_string(program, textp--, NIL_INT)) return MATCH; } } else { /* Move through the string until the end of is found */ while (quit == FALSE && *textp != '\0') { program->start_ptr = textp; if (check_string(program, textp, NIL_INT)) return MATCH; if (*textp == '\n') break; textp++; } } return NO_MATCH; } /* * Check() checks of a match can be found in the given string. Whenever a STAR * is found during matching, then the begin position of the string is marked * and the maximum number of matches is performed. Then the function star() * is called which starts to finish the match from this position of the string * (and expression). Check() return MATCH for a match, NO_MATCH is the string * couldn't be matched or REG_ERROR for an illegal opcode in expression. */ check_string(program, string, expression) REGEX *program; register char *string; int *expression; { register int opcode; /* Holds opcode of next expr. atom */ char c; /* Char that must be matched */ char *mark; /* For marking position */ int star_fl; /* A star has been born */ if (expression == NIL_INT) expression = program->result.expression; /* Loop until end of string or end of expression */ while (quit == FALSE && !(*expression & DONE) && *string != '\0' && *string != '\n') { c = *expression & LOW_BYTE; /* Extract match char */ opcode = *expression & HIGH_BYTE; /* Extract opcode */ if (star_fl = (opcode & STAR)) { /* Check star occurrence */ opcode &= ~STAR; /* Strip opcode */ mark = string; /* Mark current position */ } expression++; /* Increment expr. */ switch (opcode) { case NORMAL : if (star_fl) while (*string++ == c) /* Skip all matches */ ; else if (*string++ != c) return NO_MATCH; break; case DOT : string++; if (star_fl) /* Skip to eoln */ while (*string != '\0' && *string++ != '\n') ; break; case NEGATE | BRACKET: case BRACKET : if (star_fl) while (in_list(expression, *string++, c, opcode) == MATCH) ; else if (in_list(expression, *string++, c, opcode) == NO_MATCH) return NO_MATCH; expression += c - 1; /* Add length of list */ break; default : panic("Corrupted program in check_string()"); } if (star_fl) return star(program, mark, string, expression); } if (*expression & DONE) { program->end_ptr = string; /* Match ends here */ /* * We might have found a match. The last thing to do is check * whether a '$' was given at the end of the expression, or * the match was found on a null string. (E.g. [a-z]* always * matches) unless a ^ or $ was included in the pattern. */ if ((*expression & EOLN) && *string != '\n' && *string != '\0') return NO_MATCH; if (string == program->start_ptr && !(program->status & BEGIN_LINE) && !(*expression & EOLN)) return NO_MATCH; return MATCH; } return NO_MATCH; } /* * Star() calls check_string() to find out the longest match possible. * It searches backwards until the (in check_string()) marked position * is reached, or a match is found. */ star(program, end_position, string, expression) REGEX *program; register char *end_position; register char *string; int *expression; { do { string--; if (check_string(program, string, expression)) return MATCH; } while (string != end_position); return NO_MATCH; } /* * In_list() checks if the given character is in the list of []. If it is * it returns MATCH. if it isn't it returns NO_MATCH. These returns values * are reversed when the NEGATE field in the opcode is present. */ in_list(list, c, list_length, opcode) register int *list; char c; register int list_length; int opcode; { if (c == '\0' || c == '\n') /* End of string, never matches */ return NO_MATCH; while (list_length-- > 1) { /* > 1, don't check acct_field */ if ((*list & LOW_BYTE) == c) return (opcode & NEGATE) ? NO_MATCH : MATCH; list++; } return (opcode & NEGATE) ? MATCH : NO_MATCH; } /* * Dummy_line() adds an empty line at the end of the file. This is sometimes * useful in combination with the EF and DN command in combination with the * Yank command set. */ dummy_line() { (void) line_insert(tail->prev, "\n", 1); tail->prev->shift_count = DUMMY; if (last_y != SCREENMAX) { last_y++; bot_line = bot_line->next; } } /* -------- sh.h -------- */ /* * shell */ #define NULL 0 #define LINELIM 1000 #define NPUSH 8 /* limit to input nesting */ #define NOFILE 20 /* Number of open files */ #define NUFILE 10 /* Number of user-accessible files */ #define FDBASE 10 /* First file usable by Shell */ /* * values returned by wait */ #define WAITSIG(s) ((s)&0177) #define WAITVAL(s) (((s)>>8)&0377) #define WAITCORE(s) (((s)&0200)!=0) /* * library and system defintions */ typedef int xint; /* base type of jmp_buf, for broken compilers */ /* * shell components */ /* #include "area.h" */ /* #include "word.h" */ /* #include "io.h" */ /* #include "var.h" */ #define QUOTE 0200 #define NOBLOCK ((struct op *)NULL) #define NOWORD ((char *)NULL) #define NOWORDS ((char **)NULL) #define NOPIPE ((int *)NULL) /* * Description of a command or an operation on commands. * Might eventually use a union. */ struct op { int type; /* operation type, see below */ char **words; /* arguments to a command */ struct ioword **ioact; /* IO actions (eg, < > >>) */ struct op *left; struct op *right; char *str; /* identifier for case and for */ }; #define TCOM 1 /* command */ #define TPAREN 2 /* (c-list) */ #define TPIPE 3 /* a | b */ #define TLIST 4 /* a [&;] b */ #define TOR 5 /* || */ #define TAND 6 /* && */ #define TFOR 7 #define TDO 8 #define TCASE 9 #define TIF 10 #define TWHILE 11 #define TUNTIL 12 #define TELIF 13 #define TPAT 14 /* pattern in case */ #define TBRACE 15 /* {c-list} */ #define TASYNC 16 /* c & */ /* * actions determining the environment of a process */ #define BIT(i) (1<<(i)) #define FEXEC BIT(0) /* execute without forking */ /* * flags to control evaluation of words */ #define DOSUB 1 /* interpret $, `, and quotes */ #define DOBLANK 2 /* perform blank interpretation */ #define DOGLOB 4 /* interpret [?* */ #define DOKEY 8 /* move words with `=' to 2nd arg. list */ #define DOTRIM 16 /* trim resulting string */ #define DOALL (DOSUB|DOBLANK|DOGLOB|DOKEY|DOTRIM) char **dolv; int dolc; int exstat; char gflg; int talking; /* interactive (talking-type wireless) */ int execflg; int multiline; /* \n changed to ; */ struct op *outtree; /* result from parser */ xint *failpt; xint *errpt; struct brkcon { jmp_buf brkpt; struct brkcon *nextlev; } *brklist; int isbreak; /* * redirection */ struct ioword { short io_unit; /* unit affected */ short io_flag; /* action (below) */ union { char *io_name; /* file name */ struct block *io_here; /* here structure pointer */ } io_un; }; #define IOREAD 1 /* < */ #define IOHERE 2 /* << (here file) */ #define IOWRITE 4 /* > */ #define IOCAT 8 /* >> */ #define IOXHERE 16 /* ${}, ` in << */ #define IODUP 32 /* >&digit */ #define IOCLOSE 64 /* >&- */ #define IODEFAULT (-1) /* token for default IO unit */ struct wdblock *wdlist; struct wdblock *iolist; /* * parsing & execution environment */ extern struct env { char *linep; struct io *iobase; struct io *iop; xint *errpt; int iofd; struct env *oenv; } e; /* * flags: * -e: quit on error * -k: look for name=value everywhere on command line * -n: no execution * -t: exit after reading and executing one command * -v: echo as read * -x: trace * -u: unset variables net diagnostic */ char *flag; char *null; /* null value for variable */ int intr; /* interrupt pending */ char *trap[NSIG]; char ourtrap[NSIG]; int trapset; /* trap pending */ int inword; /* defer traps and interrupts */ int yynerrs; /* yacc */ char line[LINELIM]; char *elinep; /* * other functions */ int (*inbuilt())(); /* find builtin command */ char *rexecve(); char *space(); char *getwd(); char *strsave(); char *evalstr(); char *putn(); char *itoa(); char *unquote(); struct var *lookup(); struct wdblock *add2args(); struct wdblock *glob(); char **makenv(); struct ioword *addio(); char **eval(); int setstatus(); int waitfor(); int onintr(); /* SIGINT handler */ /* * error handling */ void leave(); /* abort shell (or fail in subshell) */ void fail(); /* fail but return to process next command */ int sig(); /* default signal handler */ /* * library functions and system calls */ long lseek(); char *strncpy(); int strlen(); extern int errno; /* -------- var.h -------- */ struct var { char *value; char *name; struct var *next; char status; }; #define COPYV 1 /* flag to setval, suggesting copy */ #define RONLY 01 /* variable is read-only */ #define EXPORT 02 /* variable is to be exported */ #define GETCELL 04 /* name & value space was got with getcell */ struct var *vlist; /* dictionary */ struct var *homedir; /* home directory */ struct var *prompt; /* main prompt */ struct var *cprompt; /* continuation prompt */ struct var *path; /* search path for commands */ struct var *shell; /* shell to interpret command files */ struct var *ifs; /* field separators */ struct var *lookup(/* char *s */); void setval(/* struct var *, char * */); void nameval(/* struct var *, char *val, *name */); void export(/* struct var * */); void ronly(/* struct var * */); int isassign(/* char *s */); int checkname(/* char *name */); int assign(/* char *s, int copyflag */); void putvlist(/* int key, int fd */); int eqname(/* char *n1, char *n2 */); /* -------- io.h -------- */ /* possible arguments to an IO function */ struct ioarg { char *aword; char **awordlist; int afile; /* file descriptor */ }; /* an input generator's state */ struct io { int (*iofn)(); struct ioarg arg; int peekc; char nlcount; /* for `'s */ char xchar; /* for `'s */ char task; /* reason for pushed IO */ }; struct io iostack[NPUSH]; #define XOTHER 0 /* none of the below */ #define XDOLL 1 /* expanding ${} */ #define XGRAVE 2 /* expanding `'s */ #define XIO 3 /* file IO */ /* in substitution */ #define INSUB() (e.iop->task == XGRAVE || e.iop->task == XDOLL) /* * input generators for IO structure */ int nlchar(); int strchar(); int filechar(); int linechar(); int nextchar(); int gravechar(); int qgravechar(); int dolchar(); int wdchar(); /* * IO functions */ int getc(); int readc(); void unget(); void ioecho(); void prs(); void putc(); void prn(); void closef(); void closeall(); /* * IO control */ void pushio(/* struct ioarg arg, int (*gen)() */); int remap(); int openpipe(); void closepipe(); struct io *setbase(/* struct io * */); struct ioarg temparg; /* temporary for PUSHIO */ #define PUSHIO(what,arg,gen) ((temparg.what = (arg)),pushio(temparg,(gen))) #define RUN(what,arg,gen) ((temparg.what = (arg)), run(temparg,(gen))) /* -------- word.h -------- */ #ifndef WORD_H #define WORD_H 1 struct wdblock { short w_bsize; short w_nword; /* bounds are arbitrary */ char *w_words[1]; }; struct wdblock *addword(); struct wdblock *newword(); char **getwords(); #endif /* -------- area.h -------- */ /* * storage allocation */ char *getcell(/* unsigned size */); void garbage(); void setarea(/* char *obj, int to */); void freearea(/* int area */); void freecell(/* char *obj */); int areanum; /* current allocation area */ #define NEW(type) (type *)getcell(sizeof(type)) #define DELETE(obj) freecell((char *)obj) #include "signal.h" #include "errno.h" #include "setjmp.h" #include "sh.h" /* -------- sh.c -------- */ /* * shell */ /* #include "sh.h" */ int intr; int inparse; char flags['z'-'a'+1]; char *flag = flags-'a'; char *elinep = line+sizeof(line)-5; char *null = ""; int inword =1; struct env e ={line, iostack, iostack-1, NULL, FDBASE, NULL}; char **environ; /* environment pointer */ /* * default shell, search rules */ char shellname[] = "/bin/sh"; char search[] = ":/bin:/usr/bin"; int (*qflag)() = SIG_IGN; main(argc, argv) int argc; register char **argv; { register int f; register char *s; int cflag; char *name, **ap; int (*iof)(); if ((ap = environ) != NULL) { while (*ap) assign(*ap++, !COPYV); for (ap = environ; *ap;) export(lookup(*ap++)); } closeall(); areanum = 1; shell = lookup("SHELL"); if (shell->value == null) setval(shell, shellname); export(shell); homedir = lookup("HOME"); if (homedir->value == null) setval(homedir, "/"); export(homedir); setval(lookup("$"), itoa(getpid(), 5)); path = lookup("PATH"); if (path->value == null) setval(path, search); export(path); ifs = lookup("IFS"); if (ifs->value == null) setval(ifs, " \t\n"); prompt = lookup("PS1"); if (prompt->value == null) #ifndef UNIXSHELL setval(prompt, "$ "); #else setval(prompt, "% "); #endif if (geteuid() == 0) { setval(prompt, "# "); prompt->status &= ~EXPORT; } cprompt = lookup("PS2"); if (cprompt->value == null) setval(cprompt, "> "); iof = filechar; cflag = 0; name = *argv++; if (--argc >= 1) { if(argv[0][0] == '-' && argv[0][1] != '\0') { for (s = argv[0]+1; *s; s++) switch (*s) { case 'c': prompt->status &= ~EXPORT; cprompt->status &= ~EXPORT; setval(prompt, ""); setval(cprompt, ""); cflag = 1; if (--argc > 0) PUSHIO(aword, *++argv, iof = nlchar); break; case 'q': qflag = SIG_DFL; break; case 's': /* standard input */ break; case 't': prompt->status &= ~EXPORT; setval(prompt, ""); iof = linechar; break; case 'i': talking++; default: if (*s>='a' && *s<='z') flag[*s]++; } } else { argv--; argc++; } if (iof == filechar && --argc > 0) { setval(prompt, ""); setval(cprompt, ""); prompt->status &= ~EXPORT; cprompt->status &= ~EXPORT; if (newfile(*++argv)) exit(1); } } setdash(); if (e.iop < iostack) { PUSHIO(afile, 0, iof); if (isatty(0) && isatty(1) && !cflag) talking++; } signal(SIGQUIT, qflag); if (name[0] == '-') { talking++; if ((f = open("/etc/profile", 0)) >= 0) next(remap(f)); if ((f = open(".profile", 0)) >= 0) next(remap(f)); } if (talking) { signal(SIGTERM, sig); signal(SIGINT, SIG_IGN); } dolv = argv; dolc = argc; dolv[0] = name; if (dolc > 1) for (ap = ++argv; --argc > 0;) if (assign(*ap = *argv++, !COPYV)) dolc--; /* keyword */ else ap++; setval(lookup("#"), putn(dolc)); for (;;) { if (talking && e.iop <= iostack) prs(prompt->value); onecommand(); } } setdash() { register char *cp, c; char m['z'-'a'+1]; cp = m; for (c='a'; c<='z'; c++) if (flag[c]) *cp++ = c; *cp = 0; setval(lookup("-"), m); } newfile(s) register char *s; { register f; if (strcmp(s, "-") != 0) { f = open(s, 0); if (f < 0) { prs(s); err(": cannot open"); return(1); } } else f = 0; next(remap(f)); return(0); } onecommand() { register char *cp; register i; jmp_buf m1; inword++; while (e.oenv) quitenv(); freearea(areanum = 1); cp = getcell(258); garbage(); DELETE(cp); wdlist = 0; iolist = 0; e.errpt = 0; e.linep = line; yynerrs = 0; multiline = 0; inparse = 1; if (talking) signal(SIGINT, onintr); if (setjmp(failpt = m1) || yyparse() || intr) { while (e.oenv) quitenv(); scraphere(); inparse = 0; intr = 0; return; } inparse = 0; inword = 0; if ((i = trapset) != 0) { trapset = 0; runtrap(i); } brklist = 0; intr = 0; execflg = 0; if (!flag['n']) { if (talking) signal(SIGINT, onintr); execute(outtree, NOPIPE, NOPIPE, 0); intr = 0; if (talking) signal(SIGINT, SIG_IGN); } } void fail() { longjmp(failpt, 1); /* NOTREACHED */ } void leave() { if (execflg) fail(); runtrap(0); sync(); exit(exstat); /* NOTREACHED */ } warn(s) register char *s; { if(*s) { prs(s); exstat = -1; } prs("\n"); if (flag['e']) leave(); } err(s) char *s; { warn(s); if (flag['n']) return; if (!talking) leave(); if (e.errpt) longjmp(e.errpt, 1); closeall(); e.iop = e.iobase = iostack; } newenv(f) { register struct env *ep; if (f) { quitenv(); return(1); } ep = (struct env *) space(sizeof(*ep)); if (ep == NULL) { while (e.oenv) quitenv(); fail(); } *ep = e; e.oenv = ep; e.errpt = errpt; return(0); } quitenv() { register struct env *ep; register fd; if ((ep = e.oenv) != NULL) { fd = e.iofd; e = *ep; /* should close `'d files */ DELETE(ep); while (--fd >= e.iofd) close(fd); } } /* * Is any character from s1 in s2? */ int anys(s1, s2) register char *s1, *s2; { while (*s1) if (any(*s1++, s2)) return(1); return(0); } /* * Is character c in s? */ int any(c, s) register int c; register char *s; { while (*s) if (*s++ == c) return(1); return(0); } char * putn(n) register n; { return(itoa(n, -1)); } char * itoa(u, n) register unsigned u; { register char *cp; static char s[20]; int m; m = 0; if (n < 0 && (int) u < 0) { m++; u = -u; } cp = s+sizeof(s); *--cp = 0; do { *--cp = u%10 + '0'; u /= 10; } while (--n > 0 || u); if (m) *--cp = '-'; return(cp); } next(f) { PUSHIO(afile, f, nextchar); } onintr() { signal(SIGINT, SIG_IGN); if (inparse) { prs("\n"); fail(); } intr++; } letter(c) register c; { return(c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c == '_'); } digit(c) register c; { return(c >= '0' && c <= '9'); } letnum(c) register c; { return(letter(c) || digit(c)); } char * space(n) int n; { register char *cp; inword++; if ((cp = getcell(n)) == 0) err("out of string space"); inword--; return(cp); } char * strsave(s, a) register char *s; { register char *cp, *xp; if ((cp = space(strlen(s)+1)) != NULL) { setarea((char *)cp, a); for (xp = cp; (*xp++ = *s++) != '\0';) ; return(cp); } return(""); } /* * if inword is set, traps * are delayed, avoiding * having two people allocating * at once. */ xfree(s) register char *s; { inword++; DELETE(s); inword--; } /* * trap handling */ sig(i) register i; { if (inword == 0) { signal(i, SIG_IGN); runtrap(i); } else trapset = i; signal(i, sig); } runtrap(i) { char *trapstr; if ((trapstr = trap[i]) == NULL) return; if (i == 0) trap[i] = 0; RUN(aword, trapstr, nlchar); } /* -------- var.c -------- */ /* #include "sh.h" */ static char *findeq(); /* * Find the given name in the dictionary * and return its value. If the name was * not previously there, enter it now and * return a null value. */ struct var * lookup(n) register char *n; { register struct var *vp; register char *cp; register int c; static struct var dummy; if (digit(*n)) { dummy.name = n; for (c = 0; digit(*n) && c < 1000; n++) c = c*10 + *n-'0'; dummy.status = RONLY; dummy.value = c <= dolc? dolv[c]: null; return(&dummy); } for (vp = vlist; vp; vp = vp->next) if (eqname(vp->name, n)) return(vp); cp = findeq(n); vp = (struct var *)space(sizeof(*vp)); if (vp == 0 || (vp->name = space(cp-n+2)) == 0) { dummy.name = dummy.value = ""; return(&dummy); } for (cp = vp->name; (*cp = *n++) && *cp != '='; cp++) ; if (*cp == 0) *cp = '='; *++cp = 0; setarea((char *)vp, 0); setarea((char *)vp->name, 0); vp->value = null; vp->next = vlist; vp->status = GETCELL; vlist = vp; return(vp); } /* * give variable at `vp' the value `val'. */ void setval(vp, val) struct var *vp; char *val; { nameval(vp, val, (char *)NULL); } /* * if name is not NULL, it must be * a prefix of the space `val', * and end with `='. * this is all so that exporting * values is reasonably painless. */ void nameval(vp, val, name) register struct var *vp; char *val, *name; { register char *cp, *xp; char *nv; int fl; if (vp->status & RONLY) { for (xp = vp->name; *xp && *xp != '=';) putc(*xp++); err(" is read-only"); return; } fl = 0; if (name == NULL) { xp = space(strlen(vp->name)+strlen(val)+2); if (xp == 0) return; /* make string: name=value */ setarea((char *)xp, 0); name = xp; for (cp = vp->name; (*xp = *cp++) && *xp!='='; xp++) ; if (*xp++ == 0) xp[-1] = '='; nv = xp; for (cp = val; (*xp++ = *cp++) != '\0';) ; val = nv; fl = GETCELL; } if (vp->status & GETCELL) xfree(vp->name); /* form new string `name=value' */ vp->name = name; vp->value = val; vp->status |= fl; } void export(vp) struct var *vp; { vp->status |= EXPORT; } void ronly(vp) struct var *vp; { if (letter(vp->name[0])) /* not an internal symbol ($# etc) */ vp->status |= RONLY; } int isassign(s) register char *s; { if (!letter(*s)) return(0); for (; *s != '='; s++) if (*s == 0 || !letnum(*s)) return(0); return(1); } int assign(s, cf) register char *s; int cf; { register char *cp; struct var *vp; if (!letter(*s)) return(0); for (cp = s; *cp != '='; cp++) if (*cp == 0 || !letnum(*cp)) return(0); vp = lookup(s); nameval(vp, ++cp, cf == COPYV? NULL: s); if (cf != COPYV) vp->status &= ~GETCELL; return(1); } int checkname(cp) register char *cp; { if (!letter(*cp++)) return(0); while (*cp) if (!letnum(*cp++)) return(0); return(1); } void putvlist(f, out) register int f, out; { register struct var *vp; for (vp = vlist; vp; vp = vp->next) if (vp->status & f && letter(*vp->name)) { if (vp->status & EXPORT) write(out, "export ", 7); if (vp->status & RONLY) write(out, "readonly ", 9); write(out, vp->name, findeq(vp->name) - vp->name); write(out, "\n", 1); } } int eqname(n1, n2) register char *n1, *n2; { for (; *n1 != '=' && *n1 != 0; n1++) if (*n2++ != *n1) return(0); return(*n2 == 0 || *n2 == '='); } static char * findeq(cp) register char *cp; { while (*cp != '\0' && *cp != '=') cp++; return(cp); } /* -------- gmatch.c -------- */ /* * int gmatch(string, pattern) * char *string, *pattern; * * Match a pattern as in sh(1). */ #define NULL 0 #define CMASK 0377 #define QUOTE 0200 #define QMASK (CMASK&~QUOTE) #define NOT '!' /* might use ^ */ static char *cclass(); int gmatch(s, p) register char *s, *p; { register int sc, pc; if (s == NULL || p == NULL) return(0); while ((pc = *p++ & CMASK) != '\0') { sc = *s++ & QMASK; switch (pc) { case '[': if ((p = cclass(p, sc)) == NULL) return(0); break; case '?': if (sc == 0) return(0); break; case '*': s--; do { if (*p == '\0' || gmatch(s, p)) return(1); } while (*s++ != '\0'); return(0); default: if (sc != (pc&~QUOTE)) return(0); } } return(*s == 0); } static char * cclass(p, sub) register char *p; register int sub; { register int c, d, not, found; if ((not = *p == NOT) != 0) p++; found = not; do { if (*p == '\0') return(NULL); c = *p & CMASK; if (p[1] == '-' && p[2] != ']') { d = p[2] & CMASK; p++; } else d = c; if (c == sub || c <= sub && sub <= d) found = !not; } while (*++p != ']'); return(found? p+1: NULL); } /* -------- area.c -------- */ #define GROWBY 1024 #define SHRINKBY 256 #define FREE 32767 #define BUSY 0 #define ALIGN (sizeof(int)-1) /* #include "area.h" */ #define NULL 0 struct region { struct region *next; int area; }; /*initial empty arena*/ extern struct region area1; static struct region area2 = {&area1, BUSY}; static struct region area1 = {&area2, BUSY}; static struct region *areap = &area1; static struct region *areatop = &area1; static struct region *areabrk; char *sbrk(); char *brk(); char * getcell(nbytes) unsigned nbytes; { register int rbytes; register struct region *p, *q; struct region *newbrk; if (areabrk == NULL) areabrk = (struct region *)(((int)sbrk(0)+ALIGN)&~ALIGN); rbytes = (nbytes+sizeof(struct region)-1)/sizeof(struct region) + 1; p=areap; for (;;) { do { if (p->area > areanum) { while ((q = p->next)->area > areanum) p->next = q->next; if (q >= p+rbytes) { areap = p+rbytes; if (q > areap) { areap->next = p->next; areap->area = FREE; } p->next = areap; p->area = areanum; return((char *)(p+1)); } } q = p; p = p->next; } while (q >= areap || p < areap); newbrk = (struct region *)sbrk(rbytes>=GROWBY? rbytes: GROWBY); if ((int)newbrk == -1) return(NULL); newbrk = (struct region *)sbrk(0); areatop->next = areabrk; areatop->area = ((q=areabrk)!=areatop+1)? BUSY: FREE; areatop = areabrk->next = newbrk-1; areabrk->area = FREE; areabrk = newbrk; areatop->next = &area2; areatop->area=BUSY; } } void freecell(cp) char *cp; { register struct region *p; if ((p = (struct region *)cp) != NULL) { p--; if (p < areap) areap = p; p->area = FREE; } } void freearea(a) register int a; { register struct region *p, *top; top = areatop; p = &area1; do { if (p->area >= a) p->area = FREE; p = p->next; } while (p != top); } void setarea(cp,a) char *cp; int a; { register struct region *p; if ((p = (struct region *)cp) != NULL) (p-1)->area = a; } void garbage() { register struct region *p, *q, *top; register int nu; top = areatop; areap = p = &area1; do { if (p->area>areanum) { while ((q = p->next)->area > areanum) p->next = q->next; areap = p; } q = p; p = p->next; } while (p != top); #if STOPSHRINK == 0 nu = (SHRINKBY+sizeof(struct region)-1)/sizeof(struct region); if (areatop >= q+nu && q->area > areanum) { brk((char *)(areabrk -= nu)); q->next = areatop = areabrk-1; areatop->next = &area2; areatop->area = BUSY; } #endif } #include "signal.h" #include "errno.h" #include "setjmp.h" #include "sh.h" /* -------- csyn.c -------- */ /* * shell: syntax (C version) */ typedef union { char *cp; char **wp; int i; struct op *o; } YYSTYPE; #define WORD 256 #define LOGAND 257 #define LOGOR 258 #define BREAK 259 #define IF 260 #define THEN 261 #define ELSE 262 #define ELIF 263 #define FI 264 #define CASE 265 #define ESAC 266 #define FOR 267 #define WHILE 268 #define UNTIL 269 #define DO 270 #define DONE 271 #define IN 272 #define YYERRCODE 300 /* flags to yylex */ #define CONTIN 01 /* skip new lines to complete command */ /* #include "sh.h" */ #define SYNTAXERR zzerr() static int startl = 1; static int peeksym = 0; static void zzerr(); static void word(); static char **copyw(); static struct op *block(), *namelist(), *list(), *newtp(); static struct op *pipeline(), *andor(), *command(); static struct op *nested(), *simple(), *c_list(); static struct op *dogroup(), *thenpart(), *casepart(), *caselist(); static struct op *elsepart(); static char **wordlist(), **pattern(); static void musthave(); static int yylex(); static struct ioword *io(); static struct ioword **copyio(); static char *tree(); static void diag(); static int nlseen; static int iounit = IODEFAULT; static struct op *tp; struct op *newtp(); static YYSTYPE yylval; int yyparse() { peeksym = 0; yynerrs = 0; outtree = c_list(); musthave('\n', 0); return(yynerrs!=0); } static struct op * pipeline(cf) int cf; { register struct op *t, *p; register int c; t = command(cf); if (t != NULL) { while ((c = yylex(0)) == '|') { if ((p = command(CONTIN)) == NULL) SYNTAXERR; if (t->type != TPAREN && t->type != TCOM) { /* shell statement */ t = block(TPAREN, t, NOBLOCK, NOWORDS); } t = block(TPIPE, t, p, NOWORDS); } peeksym = c; } return(t); } static struct op * andor() { register struct op *t, *p; register int c; t = pipeline(0); if (t != NULL) { while ((c = yylex(0)) == LOGAND || c == LOGOR) { if ((p = pipeline(CONTIN)) == NULL) SYNTAXERR; t = block(c == LOGAND? TAND: TOR, t, p, NOWORDS); } peeksym = c; } return(t); } static struct op * c_list() { register struct op *t, *p; register int c; t = andor(); if (t != NULL) { while ((c = yylex(0)) == ';' || c == '&' || multiline && c == '\n') { if (c == '&') t = block(TASYNC, t, NOBLOCK, NOWORDS); if ((p = andor()) == NULL) return(t); t = list(t, p); } peeksym = c; } return(t); } static int synio(cf) int cf; { register struct ioword *iop; register int i; register int c; if ((c = yylex(cf)) != '<' && c != '>') { peeksym = c; return(0); } i = yylval.i; musthave(WORD, 0); iop = io(iounit, i, yylval.cp); iounit = IODEFAULT; if (i & IOHERE) markhere(yylval.cp, iop); } static void musthave(c, cf) int c, cf; { if ((peeksym = yylex(cf)) != c) SYNTAXERR; peeksym = 0; } static struct op * simple() { register struct op *t; t = NULL; for (;;) { switch (peeksym = yylex(0)) { case '<': case '>': (void) synio(0); break; case WORD: if (t == NULL) { t = newtp(); t->type = TCOM; } peeksym = 0; word(yylval.cp); break; default: return(t); } } } static struct op * nested(type, mark) int type, mark; { register struct op *t; multiline++; t = c_list(); musthave(mark, 0); multiline--; return(block(type, t, NOBLOCK, NOWORDS)); } static struct op * command(cf) int cf; { register struct ioword *io; register struct op *t; struct wdblock *iosave; register int c; iosave = iolist; iolist = NULL; if (multiline) cf |= CONTIN; while (synio(cf)) cf = 0; switch (c = yylex(cf)) { default: peeksym = c; if ((t = simple()) == NULL) { if (iolist == NULL) return(NULL); t = newtp(); t->type = TCOM; } break; case '(': t = nested(TPAREN, ')'); break; case '{': t = nested(TBRACE, '}'); break; case FOR: t = newtp(); t->type = TFOR; musthave(WORD, 0); startl = 1; t->str = yylval.cp; multiline++; t->words = wordlist(); if ((c = yylex(0)) != '\n' && c != ';') SYNTAXERR; t->left = dogroup(0); multiline--; break; case WHILE: case UNTIL: multiline++; t = newtp(); t->type = c == WHILE? TWHILE: TUNTIL; t->left = c_list(); t->right = dogroup(1); t->words = NULL; multiline--; break; case CASE: t = newtp(); t->type = TCASE; musthave(WORD, 0); t->str = yylval.cp; startl++; multiline++; musthave(IN, CONTIN); startl++; t->left = caselist(); musthave(ESAC, 0); multiline--; break; case IF: multiline++; t = newtp(); t->type = TIF; t->left = c_list(); t->right = thenpart(); musthave(FI, 0); multiline--; break; } while (synio(0)) ; t = namelist(t); iolist = iosave; return(t); } static struct op * dogroup(onlydone) int onlydone; { register int c; register struct op *list; c = yylex(CONTIN); if (c == DONE && onlydone) return(NULL); if (c != DO) SYNTAXERR; list = c_list(); musthave(DONE, 0); return(list); } static struct op * thenpart() { register int c; register struct op *t; if ((c = yylex(0)) != THEN) { peeksym = c; return(NULL); } t = newtp(); t->type = 0; t->left = c_list(); if (t->left == NULL) SYNTAXERR; t->right = elsepart(); return(t); } static struct op * elsepart() { register int c; register struct op *t; switch (c = yylex(0)) { case ELSE: if ((t = c_list()) == NULL) SYNTAXERR; return(t); case ELIF: t = newtp(); t->type = TELIF; t->left = c_list(); t->right = thenpart(); return(t); default: peeksym = c; return(NULL); } } static struct op * caselist() { register struct op *t; register int c; t = NULL; while ((peeksym = yylex(CONTIN)) != ESAC) t = list(t, casepart()); return(t); } static struct op * casepart() { register struct op *t; register int c; t = newtp(); t->type = TPAT; t->words = pattern(); musthave(')', 0); t->left = c_list(); if ((peeksym = yylex(CONTIN)) != ESAC) musthave(BREAK, CONTIN); return(t); } static char ** pattern() { register int c, cf; cf = CONTIN; do { musthave(WORD, cf); word(yylval.cp); cf = 0; } while ((c = yylex(0)) == '|'); peeksym = c; word(NOWORD); return(copyw()); } static char ** wordlist() { register int c; if ((c = yylex(0)) != IN) { peeksym = c; return(NULL); } startl = 0; while ((c = yylex(0)) == WORD) word(yylval.cp); word(NOWORD); peeksym = c; return(copyw()); } /* * supporting functions */ static struct op * list(t1, t2) register struct op *t1, *t2; { if (t1 == NULL) return(t2); if (t2 == NULL) return(t1); return(block(TLIST, t1, t2, NOWORDS)); } static struct op * block(type, t1, t2, wp) struct op *t1, *t2; char **wp; { register struct op *t; t = newtp(); t->type = type; t->left = t1; t->right = t2; t->words = wp; return(t); } struct res { char *r_name; int r_val; } restab[] = { "for", FOR, "case", CASE, "esac", ESAC, "while", WHILE, "do", DO, "done", DONE, "if", IF, "in", IN, "then", THEN, "else", ELSE, "elif", ELIF, "until", UNTIL, "fi", FI, ";;", BREAK, "||", LOGOR, "&&", LOGAND, "{", '{', "}", '}', 0, }; rlookup(n) register char *n; { register struct res *rp; for (rp = restab; rp->r_name; rp++) if (strcmp(rp->r_name, n) == 0) return(rp->r_val); return(0); } static struct op * newtp() { register struct op *t; t = (struct op *)tree(sizeof(*t)); t->type = 0; t->words = NULL; t->ioact = NULL; t->left = NULL; t->right = NULL; t->str = NULL; return(t); } static struct op * namelist(t) register struct op *t; { if (iolist) { iolist = addword((char *)NULL, iolist); t->ioact = copyio(); } else t->ioact = NULL; if (t->type != TCOM) { if (t->type != TPAREN && t->ioact != NULL) { t = block(TPAREN, t, NOBLOCK, NOWORDS); t->ioact = t->left->ioact; t->left->ioact = NULL; } return(t); } word(NOWORD); t->words = copyw(); return(t); } static char ** copyw() { register char **wd; wd = getwords(wdlist); wdlist = 0; return(wd); } static void word(cp) char *cp; { wdlist = addword(cp, wdlist); } static struct ioword ** copyio() { register struct ioword *')*+,*iop; iop = (struct ioword **) getwords(iolist); iolist = 0; return(iop); } static struct ioword * io(u, f, cp) char *cp; { register struct ioword *iop; iop = (struct ioword *) tree(sizeof(*iop)); iop->io_unit = u; iop->io_flag = f; iop->io_un.io_name = cp; iolist = addword((char *)iop, iolist); return(iop); } static void zzerr() { yyerror("syntax error"); } yyerror(s) char *s; { yynerrs++; if (talking) { if (multiline && nlseen) unget('\n'); multiline = 0; while (yylex(0) != '\n') ; } err(s); fail(); } static int yylex(cf) int cf; { register int c, c1; int atstart; if ((c = peeksym) > 0) { peeksym = 0; if (c == '\n') startl = 1; return(c); } nlseen = 0; e.linep = line; atstart = startl; startl = 0; yylval.i = 0; loop: while ((c = getc(0)) == ' ' || c == '\t') ; switch (c) { default: if (any(c, "0123456789")) { unget(c1 = getc(0)); if (c1 == '<' || c1 == '>') { iounit = c - '0'; goto loop; } *e.linep++ = c; c = c1; } break; case '#': while ((c = getc(0)) != 0 && c != '\n') ; unget(c); goto loop; case 0: return(c); case '$': *e.linep++ = c; if ((c = getc(0)) == '{') { if ((c = collect(c, '}')) != '\0') return(c); goto pack; } break; case '`': case '\'': case '"': if ((c = collect(c, c)) != '\0') return(c); goto pack; case '|': case '&': case ';': if ((c1 = dual(c)) != '\0') { startl = 1; return(c1); } startl = 1; return(c); case '^': startl = 1; return('|'); case '>': case '<': diag(c); return(c); case '\n': nlseen++; gethere(); startl = 1; if (multiline || cf & CONTIN) { if (talking && e.iop <= iostack) prs(cprompt->value); if (cf & CONTIN) goto loop; } return(c); case '(': case ')': startl = 1; return(c); } unget(c); pack: while ((c = getc(0)) != 0 && !any(c, "`$ '\"\t;&<>()|^\n")) if (e.linep >= elinep) err("word too long"); else *e.linep++ = c; unget(c); if(any(c, "\"'`$")) goto loop; *e.linep++ = '\0'; if (atstart && (c = rlookup(line))!=0) { startl = 1; return(c); } yylval.cp = strsave(line, areanum); return(WORD); } int collect(c, c1) register c, c1; { char s[2]; *e.linep++ = c; while ((c = getc(c1)) != c1) { if (c == 0) { unget(c); s[0] = c1; s[1] = 0; prs("no closing "); yyerror(s); return(YYERRCODE); } if (talking && c == '\n' && e.iop <= iostack) prs(cprompt->value); *e.linep++ = c; } *e.linep++ = c; return(0); } int dual(c) register c; { char s[3]; register char *cp = s; *cp++ = c; *cp++ = getc(0); *cp = 0; if ((c = rlookup(s)) == 0) unget(*--cp); return(c); } static void diag(ec) register int ec; { register int c; c = getc(0); if (c == '>' || c == '<') { if (c != ec) zzerr(); yylval.i = ec == '>'? IOWRITE|IOCAT: IOHERE; c = getc(0); } else yylval.i = ec == '>'? IOWRITE: IOREAD; if (c != '&' || yylval.i == IOHERE) unget(c); else yylval.i |= IODUP; } static char * tree(size) unsigned size; { register char *t; if ((t = getcell(size)) == NULL) { prs("command line too complicated\n"); fail(); /* NOTREACHED */ } return(t); } /* VARARGS1 */ /* ARGSUSED */ printf(s) /* yyparse calls it */ char *s; { } #include "signal.h" #include "errno.h" #include "setjmp.h" #include "sh.h" /* -------- exec.c -------- */ /* #include "sh.h" */ /* * execute tree */ static char *signame[] = { "Signal 0", "Hangup", NULL, /* interrupt */ "Quit", "Illegal instruction", "Trace/BPT trap", "abort", "EMT trap", "Floating exception", "Killed", "Bus error", "Memory fault", "Bad system call", NULL, /* broken pipe */ "Alarm clock", "Terminated", }; #define NSIGNAL (sizeof(signame)/sizeof(signame[0])) static struct op *findcase(); static void brkset(); static void echo(); static int forkexec(); static int parent(); int execute(t, pin, pout, act) register struct op *t; int *pin, *pout; int act; { register struct op *t1; int i, pv[2], rv, child, a; char *cp, **wp, **wp2; struct var *vp; struct brkcon bc; if (t == NULL) return(0); rv = 0; a = areanum++; wp = (wp2 = t->words) != NULL? eval(wp2, DOALL): NULL; switch(t->type) { case TPAREN: case TCOM: rv = forkexec(t, pin, pout, act, wp, &child); if (child) { exstat = rv; leave(); } break; case TPIPE: if ((rv = openpipe(pv)) < 0) break; pv[0] = remap(pv[0]); pv[1] = remap(pv[1]); (void) execute(t->left, pin, pv, 0); rv = execute(t->right, pv, pout, 0); break; case TLIST: (void) execute(t->left, pin, pout, 0); rv = execute(t->right, pin, pout, 0); break; case TASYNC: i = parent(); if (i != 0) { if (i != -1) { if (pin != NULL) closepipe(pin); if (talking) { prs(putn(i)); prs("\n"); } } else rv = -1; setstatus(rv); } else { signal(SIGINT, SIG_IGN); signal(SIGQUIT, SIG_IGN); if (talking) signal(SIGTERM, SIG_DFL); talking = 0; if (pin == NULL) { close(0); open("/dev/null", 0); } exit(execute(t->left, pin, pout, FEXEC)); } break; case TOR: case TAND: rv = execute(t->left, pin, pout, 0); if ((t1 = t->right)!=NULL && (rv == 0) == (t->type == TAND)) rv = execute(t1, pin, pout, 0); break; case TFOR: if (wp == NULL) { wp = dolv+1; if ((i = dolc-1) < 0) i = 0; } else i = -1; vp = lookup(t->str); while (setjmp(bc.brkpt)) if (isbreak) goto broken; brkset(&bc); for (t1 = t->left; i-- && *wp != NULL;) { setval(vp, *wp++); rv = execute(t1, pin, pout, 0); } brklist = brklist->nextlev; break; case TWHILE: case TUNTIL: while (setjmp(bc.brkpt)) if (isbreak) goto broken; brkset(&bc); t1 = t->left; while ((execute(t1, pin, pout, 0) == 0) == (t->type == TWHILE)) rv = execute(t->right, pin, pout, 0); brklist = brklist->nextlev; break; case TIF: case TELIF: rv = !execute(t->left, pin, pout, 0)? execute(t->right->left, pin, pout, 0): execute(t->right->right, pin, pout, 0); break; case TCASE: if ((cp = evalstr(t->str, DOSUB|DOTRIM)) == 0) cp = ""; if ((t1 = findcase(t->left, cp)) != NULL) rv = execute(t1, pin, pout, 0); break; case TBRACE: /* if (iopp = t->ioact) while (*iopp) if (iosetup(*iopp++, pin!=NULL, pout!=NULL)) { rv = -1; break; } */ if (rv >= 0 && (t1 = t->left)) rv = execute(t1, pin, pout, 0); break; } broken: t->words = wp2; isbreak = 0; freearea(areanum); areanum = a; if (intr) { closeall(); fail(); } return(rv); } static int forkexec(t, pin, pout, act, wp, pforked) register struct op *t; int *pin, *pout; int act; char **wp; int *pforked; { int i, rv, (*shcom)(); int doexec(); register int f; char *cp; struct ioword **iopp; int resetsig; resetsig = 0; *pforked = 0; shcom = NULL; rv = -1; /* system-detected error */ if (t->type == TCOM) { /* strip all initial assignments */ /* not correct wrt PATH=yyy command etc */ if (flag['x']) echo(wp); while ((cp = *wp++) != NULL && assign(cp, COPYV)) ; wp--; if (cp == NULL && t->ioact == NULL) return(setstatus(0)); else shcom = inbuilt(cp); } t->words = wp; f = act; if (shcom == NULL && (f & FEXEC) == 0) { i = parent(); if (i != 0) { if (i == -1) return(rv); if (pin != NULL) closepipe(pin); return(pout==NULL? setstatus(waitfor(i,0)): 0); } if (talking) { signal(SIGINT, SIG_IGN); signal(SIGQUIT, SIG_IGN); resetsig = 1; } talking = 0; intr = 0; (*pforked)++; brklist = 0; execflg = 0; } #ifdef COMPIPE if ((pin != NULL || pout != NULL) && shcom != NULL && shcom != doexec) { err("piping to/from shell builtins not yet done"); return(-1); } #endif if (pin != NULL) { dup2(pin[0], 0); closepipe(pin); } if (pout != NULL) { dup2(pout[1], 1); closepipe(pout); } if ((iopp = t->ioact) != NULL) { if (shcom != NULL && shcom != doexec) { prs(cp); err(": cannot redirect shell command"); return(-1); } while (*iopp) if (iosetup(*iopp++, pin!=NULL, pout!=NULL)) return(rv); } if (shcom) return(setstatus((*shcom)(t))); /* should use FIOCEXCL */ for (i=FDBASE; itype == TPAREN) exit(execute(t->left, NOPIPE, NOPIPE, FEXEC)); if (resetsig) { signal(SIGINT, SIG_DFL); signal(SIGQUIT, SIG_DFL); } if (wp[0] == NULL) exit(0); cp = rexecve(wp[0], wp, makenv(wp)); prs(wp[0]); prs(": "); warn(cp); if (!execflg) trap[0] = NULL; leave(); /* NOTREACHED */ } /* * common actions when creating a new child */ static int parent() { register int i; i = fork(); if (i != 0) { if (i == -1) warn("try again"); setval(lookup("!"), putn(i)); } return(i); } /* * 0< 1> are ignored as required * within pipelines. */ iosetup(iop, pipein, pipeout) register struct ioword *iop; int pipein, pipeout; { register u; char *cp, *msg; if (iop->io_unit == IODEFAULT) /* take default */ iop->io_unit = iop->io_flag&(IOREAD|IOHERE)? 0: 1; if (pipein && iop->io_unit == 0) return(0); if (pipeout && iop->io_unit == 1) return(0); msg = iop->io_flag&(IOREAD|IOHERE)? "open": "create"; if ((iop->io_flag & IOHERE) == 0) { cp = iop->io_un.io_name; if ((cp = evalstr(cp, DOSUB|DOTRIM)) == NULL) return(1); } if (iop->io_flag & IODUP) { if (cp[1] || !digit(*cp) && *cp != '-') { prs(cp); err(": illegal >& argument"); return(1); } if (*cp == '-') iop->io_flag = IOCLOSE; iop->io_flag &= ~(IOREAD|IOWRITE); } switch (iop->io_flag) { case IOREAD: u = open(cp, 0); break; case IOHERE: case IOHERE|IOXHERE: u = herein(iop->io_un.io_here, iop->io_flag&IOXHERE); cp = "here file"; break; case IOWRITE|IOCAT: if ((u = open(cp, 1)) >= 0) { lseek(u, (long)0, 2); break; } case IOWRITE: u = creat(cp, 0666); break; case IODUP: u = dup2(*cp-'0', iop->io_unit); break; case IOCLOSE: close(iop->io_unit); return(0); } if (u < 0) { prs(cp); prs(": cannot "); warn(msg); return(1); } else { if (u != iop->io_unit) { dup2(u, iop->io_unit); close(u); } } return(0); } static void echo(wp) register char **wp; { register i; prs("+"); for (i=0; wp[i]; i++) { if (i) prs(" "); prs(wp[i]); } prs("\n"); } static struct op ** find1case(t, w) struct op *t; char *w; { register struct op *t1; struct op **tp; register char **wp, *cp; if (t == NULL) return(NULL); if (t->type == TLIST) { if ((tp = find1case(t->left, w)) != NULL) return(tp); t1 = t->right; /* TPAT */ } else t1 = t; for (wp = t1->words; *wp;) if ((cp = evalstr(*wp++, DOSUB)) && gmatch(w, cp)) return(&t1->left); return(NULL); } static struct op * findcase(t, w) struct op *t; char *w; { register struct op **tp; return((tp = find1case(t, w)) != NULL? *tp: NULL); } /* * Enter a new loop level (marked for break/continue). */ static void brkset(bc) struct brkcon *bc; { bc->nextlev = brklist; brklist = bc; } /* * Wait for the last process created. * Print a message for each process found * that was killed by a signal. * Ignore interrupt signals while waiting * unless `canintr' is true. */ int waitfor(lastpid, canintr) register int lastpid; int canintr; { register int pid, rv; int s; rv = 0; do { pid = wait(&s); if (pid == -1) { if (errno != EINTR || canintr) break; } else { if ((rv = WAITSIG(s)) != 0) { if (rv < NSIGNAL) { if (signame[rv] != NULL) { 46789:;<=> if (pid != lastpid) { prn(pid); prs(": "); } prs(signame[rv]); } } else { if (pid != lastpid) { prn(pid); prs(": "); } prs("Signal "); prn(rv); prs(" "); } if (WAITCORE(s)) prs(" - core dumped"); prs("\n"); rv = -1; } else rv = WAITVAL(s); } /* Special patch for MINIX: sync before each command */ sync(); } while (pid != lastpid); return(rv); } int setstatus(s) register int s; { exstat = s; setval(lookup("?"), putn(s)); return(s); } /* * PATH-searching interface to execve. * If getenv("PATH") were kept up-to-date, * execvp might be used. */ char * rexecve(c, v, envp) char *c, **v, **envp; { register int i; register char *sp, *tp; int eacces = 0, asis = 0; extern int errno; sp = any('/', c)? "": path->value; asis = *sp == '\0'; while (asis || *sp != '\0') { asis = 0; tp = e.linep; for (; *sp != '\0'; tp++) if ((*tp = *sp++) == ':') { asis = *sp == '\0'; break; } if (tp != e.linep) *tp++ = '/'; for (i = 0; (*tp++ = c[i++]) != '\0';) ; execve(e.linep, v, envp); switch (errno) { case ENOEXEC: *v = e.linep; tp = *--v; *v = "/bin/sh"; execve(*v, v, envp); *v = tp; return("no Shell"); case ENOMEM: return("program too big"); case E2BIG: return("argument list too long"); case EACCES: eacces++; break; } } return(errno==ENOENT ? "not found" : "cannot execute"); } /* * Run the command produced by generator `f' * applied to stream `arg'. */ run(arg, f) struct ioarg arg; int (*f)(); { struct op *otree; struct wdblock *swdlist; struct wdblock *siolist; jmp_buf ev, rt; xint *ofail; int rv; areanum++; swdlist = wdlist; siolist = iolist; otree = outtree; ofail = failpt; rv = -1; if (newenv(setjmp(errpt = ev)) == 0) { wdlist = 0; iolist = 0; pushio(arg, f); e.iobase = e.iop; yynerrs = 0; if (setjmp(failpt = rt) == 0 && yyparse() == 0) rv = execute(outtree, NOPIPE, NOPIPE, 0); quitenv(); } wdlist = swdlist; iolist = siolist; failpt = ofail; outtree = otree; freearea(areanum--); return(rv); } /* -------- do.c -------- */ /* #include "sh.h" */ /* * built-in commands: doX */ static void rdexp(); static void badid(); static int brkcontin(); dolabel() { return(0); } dochdir(t) register struct op *t; { register char *cp, *er; if ((cp = t->words[1]) == NULL && (cp = homedir->value) == NULL) er = ": no home directory"; else if(chdir(cp) < 0) er = ": bad directory"; else return(0); prs(cp != NULL? cp: "cd"); err(er); return(1); } doshift(t) register struct op *t; { register n; n = t->words[1]? getn(t->words[1]): 1; if(dolc < n) { err("nothing to shift"); return(1); } dolv[n] = dolv[0]; dolv += n; dolc -= n; setval(lookup("#"), putn(dolc)); return(0); } /* * execute login and newgrp directly */ dologin(t) struct op *t; { register char *cp; if (talking) { signal(SIGINT, SIG_DFL); signal(SIGQUIT, SIG_DFL); } cp = rexecve(t->words[0], t->words, makenv(t->words)); prs(t->words[0]); prs(": "); err(cp); return(1); } doumask(t) register struct op *t; { register int i, n; register char *cp; if ((cp = t->words[1]) == NULL) { i = umask(0); umask(i); for (n=3*4; (n-=3) >= 0;) putc('0'+((i>>n)&07)); putc('\n'); } else { for (n=0; *cp>='0' && *cp<='9'; cp++) n = n*8 + (*cp-'0'); umask(n); } return(0); } doexec(t) register struct op *t; { register i; jmp_buf ex; xint *ofail; t->ioact = NULL; for(i = 0; (t->words[i]=t->words[i+1]) != NULL; i++) ; if (i == 0) return(1); execflg = 1; ofail = failpt; if (setjmp(failpt = ex) == 0) execute(t, NOPIPE, NOPIPE, FEXEC); failpt = ofail; execflg = 0; return(1); } dodot(t) struct op *t; { register i; register char *sp, *tp; char *cp; if ((cp = t->words[1]) == NULL) return(0); sp = any('/', cp)? ":": path->value; while (*sp) { tp = e.linep; while (*sp && (*tp = *sp++) != ':') tp++; if (tp != e.linep) *tp++ = '/'; for (i = 0; (*tp++ = cp[i++]) != '\0';) ; if ((i = open(e.linep, 0)) >= 0) { exstat = 0; next(remap(i)); return(exstat); } } prs(cp); err(": not found"); return(-1); } dowait(t) struct op *t; { register i; register char *cp; if ((cp = t->words[1]) != NULL) { i = getn(cp); if (i == 0) return(0); } else i = -1; if (talking) signal(SIGINT, onintr); setstatus(waitfor(i, 1)); if (talking) signal(SIGINT, SIG_IGN); return(0); } doread(t) struct op *t; { register char *cp, **wp; register nb; if (t->words[1] == NULL) { err("Usage: read name ..."); return(1); } for (wp = t->words+1; *wp; wp++) { for (cp = e.linep; cp < elinep-1; cp++) if ((nb = read(0, cp, sizeof(*cp))) != sizeof(*cp) || *cp == '\n' || wp[1] && any(*cp, ifs->value)) break; *cp = 0; if (nb <= 0) break; setval(lookup(*wp), e.linep); } return(nb <= 0); } doeval(t) register struct op *t; { int wdchar(); return(RUN(awordlist, t->words+1, wdchar)); } dotrap(t) register struct op *t; { register char *s; register n, i; if (t->words[1] == NULL) { for (i=0; iwords[2])!=NULL? s: t->words[1]); xfree(trap[n]); trap[n] = 0; if (s != NULL) { if ((i = strlen(s = t->words[1])) != 0) { trap[n] = strsave(s, 0); setsig(n, sig); } else setsig(n, SIG_IGN); } else setsig(n, (n == SIGINT || n == SIGQUIT) && talking? SIG_IGN: SIG_DFL); return(0); } getsig(s) char *s; { register int n; if ((n = getn(s)) < 0 || n >= NSIG) { err("trap: bad signal number"); n = 0; } return(n); } setsig(n, f) register n; int (*f)(); { if (n == 0) return; if (signal(n, SIG_IGN) != SIG_IGN || ourtrap[n]) { ourtrap[n] = 1; signal(n, f); } } getn(as) char *as; { register char *s; register n, m; s = as; m = 1; if (*s == '-') { m = -1; s++; } for (n = 0; digit(*s); s++) n = (n*10) + (*s-'0'); if (*s) { prs(as); err(": bad number"); } return(n*m); } dobreak(t) struct op *t; { return(brkcontin(t->words[1], 1)); } docontinue(t) struct op *t; { return(brkcontin(t->words[1], 0)); } static int brkcontin(cp, val) register char *cp; { register struct brkcon *bc; register nl; nl = cp == NULL? 1: getn(cp); if (nl <= 0) nl = 999; do { if ((bc = brklist) == NULL) break; brklist = bc->nextlev; } while (--nl); if (nl) { err("bad break/continue level"); return(1); } isbreak = val; longjmp(bc->brkpt, 1); /* NOTREACHED */ } doexit(t) struct op *t; { register char *cp; execflg = 0; if ((cp = t->words[1]) != NULL) exstat = getn(cp); leave(); } doexport(t) struct op *t; { rdexp(t->words+1, export, EXPORT); return(0); } doreadonly(t) struct op *t; { rdexp(t->words+1, ronly, RONLY); return(0); } static void rdexp(wp, f, key) register char **wp; void (*f)(); int key; { if (*wp != NULL) { for (; *wp != NULL; wp++) if (checkname(*wp)) (*f)(lookup(*wp)); else badid(*wp); } else putvlist(key, 1); } static void badid(s) register char *s; { prs(s); err(": bad identifier"); } doset(t) register struct op *t; { register struct var *vp; register char *cp; register n; if ((cp = t->words[1]) == NULL) { for (vp = vlist; vp; vp = vp->next) varput(vp->name, 1); return(0); } if (*cp == '-') { t->words++; if (*++cp == 0) flag['x'] = flag['v'] = 0; else for (; *cp; cp++) switch (*cp) { case 'e': if (!talking) flag['e']++; break; default: if (*cp>='a' && *cp<='z') flag[*cp]++; break; } setdash(); } if (t->words[1]) { t->words[0] = dolv[0]; for (n=1; t->words[n]; n++) setarea((char *)t->words[n], 0); dolc = n-1; dolv = t->words; setval(lookup("#"), putn(dolc)); setarea((char *)(dolv-1), 0); } return(0); } varput(s, out) register char *s; { if (letnum(*s)) { write(out, s, strlen(s)); write(out, "\n", 1); } } struct builtin { char *command; int (*fn)(); }; static struct builtin builtin[] = { ":", dolabel, "cd", dochdir, "shift", doshift, "exec", doexec, "wait", dowait, "read", doread, "eval", doeval, "trap", dotrap, "break", dobreak, "continue", docontinue, "exit", doexit, "export", doexport, "readonly", doreadonly, "set", doset, ".", dodot, "umask", doumask, "login", dologin, "newgrp", dologin, 0, }; int (*inbuilt(s))() register char *s; { register struct builtin *bp; for (bp = builtin; bp->command != NULL; bp++) if (strcmp(bp->command, s) == 0) return(bp->fn); return(NULL); } #include "signal.h" #include "errno.h" #include "setjmp.h" #include "stat.h" #include "sh.h" /* -------- eval.c -------- */ /* #include "sh.h" */ /* #include "word.h" */ /* * ${} * `command` * blank interpretation * quoting * glob */ static char *blank(); static int grave(); static int expand(); static int dollar(); char ** eval(ap, f) register char **ap; { struct wdblock *wb; char **wp; jmp_buf ev; inword++; wp = NULL; wb = NULL; if (newenv(setjmp(errpt = ev)) == 0) { wb = addword((char *)0, wb); /* space for shell name, if command file */ while (expand(*ap++, &wb, f)) ; wb = addword((char *)0, wb); wp = getwords(wb) + 1; quitenv(); } else gflg = 1; inword--; return(gflg? NULL: wp); } /* * Make the exported environment from the exported * names in the dictionary. Keyword assignments * ought to be taken from wp (the list of words on the command line) * but aren't, yet. Until then: ARGSUSED */ char ** makenv(wp) char **wp; { register struct wdblock *wb; register struct var *vp; wb = NULL; for (vp = vlist; vp; vp = vp->next) if (vp->status & EXPORT) wb = addword(vp->name, wb); wb = addword((char *)0, wb); return(getwords(wb)); } char * evalstr(cp, f) register char *cp; int f; { struct wdblock *wb; inword++; wb = NULL; if (expand(cp, &wb, f)) { if (wb == NULL || wb->w_nword == 0 || (cp = wb->w_words[0]) == NULL) cp = ""; DELETE(wb); } else cp = NULL; inword--; return(cp); } static int expand(cp, wbp, f) register char *cp; register struct wdblock **wbp; { jmp_buf ev; gflg = 0; if (cp == NULL) return(0); if (!anys("$`'\"", cp) && !anys(ifs->value, cp) && ((f&DOGLOB)==0 || !anys("[*?", cp))) { cp = strsave(cp, areanum); if (f & DOTRIM) unquote(cp); *wbp = addword(cp, *wbp); return(1); } if (newenv(setjmp(errpt = ev)) == 0) { PUSHIO(aword, cp, strchar); e.iobase = e.iop; while ((cp = blank(f)) && gflg == 0) { e.linep = cp; cp = strsave(cp, areanum); if ((f&DOGLOB) == 0) { if (f & DOTRIM) unquote(cp); *wbp = addword(cp, *wbp); } else *wbp = glob(cp, *wbp); } quitenv(); } else gflg = 1; return(gflg == 0); } /* * Blank interpretation and quoting */ static char * blank(f) { register c, c1; register char *sp; sp = e.linep; loop: switch (c = subgetc('"', 0)) { case 0: if (sp == e.linep) return(0); *e.linep++ = 0; return(sp); default: if (f & DOBLANK && any(c, ifs->value)) goto loop; break; case '"': case '\'': if (INSUB()) break; for (c1 = c; (c = subgetc(c1, 1)) != c1;) { if (c == 0) break; if (c == '\'' || !any(c, "$`\"")) c |= QUOTE; *e.linep++ = c; } c = 0; } unget(c); for (;;) { c = subgetc('"', 0); if (c == 0 || f & DOBLANK && any(c, ifs->value) || !INSUB() && any(c, "\"'`")) { unget(c); if (any(c, "\"'`")) goto loop; break; } *e.linep++ = c; } *e.linep++ = 0; return(sp); } /* * Get characters, substituting for ` and $ */ int subgetc(ec, quoted) register char ec; int quoted; { register char c; again: c = getc(ec); if (!INSUB() && ec != '\'') { if (c == '`') { if (grave(quoted) == 0) return(0); e.iop->task = XGRAVE; goto again; } if (c == '$' && (c = dollar(quoted)) == 0) { e.iop->task = XDOLL; goto again; } } return(c); } /* * Prepare to generate the string returned by ${} substitution. */ static int dollar(quoted) int quoted; { int otask; struct io *oiop; char *dolp; register char *s, c, *cp; struct var *vp; c = readc(); s = e.linep; if (c != '{') { *e.linep++ = c; if (letter(c)) { while ((c = readc())!=0 && letnum(c)) if (e.linep < elinep) *e.linep++ = c; unget(c); } c = 0; } else { oiop = e.iop; otask = e.iop->task; e.iop->task = XOTHER; while ((c = subgetc('"', 0))!=0 && c!='}' && c!='\n') if (e.linep < elinep) *e.linep++ = c; if (oiop == e.iop) e.iop->task = otask; if (c != '}') { err("unclosed ${"); gflg++; return(c); } } if (e.linep >= elinep) { err("string in ${} too long"); gflg++; e.linep -= 10; } *e.linep = 0; if (*s) for (cp = s+1; *cp; cp++) if (any(*cp, "=-+?")) { c = *cp; *cp++ = 0; break; } if (s[1] == 0 && (*s == '*' || *s == '@')) { if (dolc > 1) { /* currently this does not distinguish $* and $@ */ /* should check dollar */ e.linep = s; PUSHIO(awordlist, dolv+1, dolchar); return(0); } else { /* trap the nasty ${=} */ s[0] = '1'; s[1] = 0; } } vp = lookup(s); if ((dolp = vp->value) == null) { switch (c) { case '=': if (digit(*s)) { err("cannot use ${...=...} with $n"); gflg++; break; } setval(vp, cp); dolp = vp->value; break; case '-': dolp = strsave(cp, areanum); break; case '?': if (*cp == 0) { prs("missing value for "); err(s); } else err(cp); gflg++; break; } } else if (c == '+') dolp = strsave(cp, areanum); if (flag['u'] && dolp == null) { prs("unset variable: "); err(s); gflg++; } e.linep = s; PUSHIO(aword, dolp, strchar); return(0); } /* * Run the command in `...` and read its output. */ static int grave(quoted) int quoted; { register char *cp; register int i; int pf[2]; for (cp = e.iop->arg.aword; *cp != '`'; cp++) if (*cp == 0) { err("no closing `"); return(0); } if (openpipe(pf) < 0) return(0); if ((i = fork()) == -1) { closepipe(pf); err("try again"); return(0); } if (i != 0) { e.iop->arg.aword = ++cp; close(pf[1]); PUSHIO(afile, remap(pf[0]), quoted? qgravechar: gravechar); return(1); } *cp = 0; /* allow trapped signals */ for (i=0; iarg.aword, 0); freearea(areanum = 1); /* free old space */ e.oenv = NULL; e.iop = (e.iobase = iostack) - 1; unquote(cp); talking = 0; PUSHIO(aword, cp, nlchar); onecommand(); exit(1); } char * unquote(as) register char *as; { register char *s; if ((s = as) != NULL) while (*s) *s++ &= ~QUOTE; return(as); } /* -------- glob.c -------- */ /* #include "sh.h" */ #define DIRSIZ 14 struct direct { unsigned short d_ino; char d_name[DIRSIZ]; }; /* * glob */ #define scopy(x) strsave((x), areanum) #define BLKSIZ 512 #define NDENT ((BLKSIZ+sizeof(struct direct)-1)/sizeof(struct direct)) static struct wdblock *cl, *nl; static char spcl[] = "[?*"; static int xstrcmp(); static char *generate(); static int anyspcl(); struct wdblock * glob(cp, wb) char *cp; struct wdblock *wb; { register i; register char *pp; if (cp == 0) return(wb); i = 0; for (pp = cp; *pp; pp++) if (any(*pp, spcl)) i++; else if (!any(*pp & ~QUOTE, spcl)) *pp &= ~QUOTE; if (i != 0) { for (cl = addword(scopy(cp), (struct wdblock *)0); anyspcl(cl); cl = nl) { nl = newword(cl->w_nword*2); for(i=0; iw_nword; i++) { /* for each argument */ for (pp = cl->w_words[i]; *pp; pp++) if (any(*pp, spcl)) { globname(cl->w_words[i], pp); break; } if (*pp == '\0') nl = addword(scopy(cl->w_words[i]), nl); } for(i=0; iw_nword; i++) DELETE(cl->w_words[i]); DELETE(cl); } for(i=0; iw_nword; i++) unquote(cl->w_words[i]); glob0((char *)cl->w_words, cl->w_nword, sizeof(char *), xstrcmp); if (cl->w_nword) { for (i=0; iw_nword; i++) wb = addword(cl->w_words[i], wb); DELETE(cl); return(wb); } } wb = addword(unquote(cp), wb); return(wb); } globname(we, pp) char *we; register char *pp; { register char *np, *cp; char *name, *gp, *dp; int dn, j, n, k; struct direct ent[NDENT]; char dname[DIRSIZ+1]; struct stat dbuf; for (np = we; np != pp; pp--) if (pp[-1] == '/') break; for (dp = cp = space(pp-np+3); np < pp;) *cp++ = *np++; *cp++ = '.'; *cp = '\0'; for (gp = cp = space(strlen(pp)+1); *np && *np != '/';) *cp++ = *np++; *cp = '\0'; dn = open(dp, 0); if (dn < 0) { DELETE(dp); DELETE(gp); return; } dname[DIRSIZ] = '\0'; while ((n = read(dn, (char *)ent, sizeof(ent))) >= sizFHIJKeof(*ent)) { n /= sizeof(*ent); for (j=0; jw_words; for (i=0; iw_nword; i++) if (anys(spcl, *wd++)) return(1); return(0); } static int xstrcmp(p1, p2) char *p1, *p2; { return(strcmp(*(char **)p1, *(char **)p2)); } /* -------- word.c -------- */ /* #include "sh.h" */ /* #include "word.h" */ char *memcpy(); #define NSTART 16 /* default number of words to allow for initially */ struct wdblock * newword(nw) register nw; { register struct wdblock *wb; wb = (struct wdblock *) space(sizeof(*wb) + nw*sizeof(char *)); wb->w_bsize = nw; wb->w_nword = 0; return(wb); } struct wdblock * addword(wd, wb) char *wd; register struct wdblock *wb; { register struct wdblock *wb2; register nw; if (wb == NULL) wb = newword(NSTART); if ((nw = wb->w_nword) >= wb->w_bsize) { wb2 = newword(nw * 2); memcpy((char *)wb2->w_words, (char *)wb->w_words, nw*sizeof(char *)); wb2->w_nword = nw; DELETE(wb); wb = wb2; } wb->w_words[wb->w_nword++] = wd; return(wb); } char ** getwords(wb) register struct wdblock *wb; { register char **wd; register nb; if (wb == NULL) return(NULL); if (wb->w_nword == 0) { DELETE(wb); return(NULL); } wd = (char **) space(nb = sizeof(*wd) * wb->w_nword); memcpy((char *)wd, (char *)wb->w_words, nb); DELETE(wb); /* perhaps should done by caller */ return(wd); } int (*func)(); int globv; glob0(a0, a1, a2, a3) char *a0; unsigned a1; int a2; int (*a3)(); { func = a3; globv = a2; glob1(a0, a0 + a1 * a2); } glob1(base, lim) char *base, *lim; { register char *i, *j; int v2; char **k; char *lptr, *hptr; int c; unsigned n; v2 = globv; top: if ((n=lim-base) <= v2) return; n = v2 * (n / (2*v2)); hptr = lptr = base+n; i = base; j = lim-v2; for(;;) { if (i < lptr) { if ((c = (*func)(i, lptr)) == 0) { glob2(i, lptr -= v2); continue; } if (c < 0) { i += v2; continue; } } begin: if (j > hptr) { if ((c = (*func)(hptr, j)) == 0) { glob2(hptr += v2, j); goto begin; } if (c > 0) { if (i == lptr) { glob3(i, hptr += v2, j); i = lptr += v2; goto begin; } glob2(i, j); j -= v2; i += v2; continue; } j -= v2; goto begin; } if (i == lptr) { if (lptr-base >= lim-hptr) { glob1(hptr+v2, lim); lim = lptr; } else { glob1(base, lptr); base = hptr+v2; } goto top; } glob3(j, lptr -= v2, i); j = hptr -= v2; } } glob2(i, j) char *i, *j; { register char *index1, *index2, c; int m; m = globv; index1 = i; index2 = j; do { c = *index1; *index1++ = *index2; *index2++ = c; } while(--m); } glob3(i, j, k) char *i, *j, *k; { register char *index1, *index2, *index3; int c; int m; m = globv; index1 = i; index2 = j; index3 = k; do { c = *index1; *index1++ = *index3; *index3++ = *index2; *index2++ = c; } while(--m); } #include "signal.h" #include "errno.h" #include "setjmp.h" #include "sh.h" /* -------- io.c -------- */ /* #include "sh.h" */ /* * shell IO */ int getc(ec) register int ec; { register int c; if(e.linep > elinep) { while((c=readc()) != '\n' && c) ; err("input line too long"); gflg++; return(c); } c = readc(); if (ec != '\'') { if(c == '\\') { c = readc(); if (c == '\n' && ec != '\"') return(getc(ec)); c |= QUOTE; } } return(c); } void unget(c) { if (e.iop >= e.iobase) e.iop->peekc = c; } int readc() { register c; static int eofc; for (; e.iop >= e.iobase; e.iop--) if ((c = e.iop->peekc) != '\0') { e.iop->peekc = 0; return(c); } else if ((c = (*e.iop->iofn)(&e.iop->arg, e.iop)) != '\0') { if (c == -1) { e.iop++; continue; } if (e.iop == iostack) ioecho(c); return(c); } if (e.iop >= iostack || multiline && eofc++ < 3) return(0); leave(); /* NOTREACHED */ } void ioecho(c) char c; { if (flag['v']) write(2, &c, sizeof c); } void pushio(arg, fn) struct ioarg arg; int (*fn)(); { if (++e.iop >= &iostack[NPUSH]) { e.iop--; err("Shell input nested too deeply"); gflg++; return; } e.iop->iofn = fn; e.iop->arg = arg; e.iop->peekc = 0; e.iop->xchar = 0; e.iop->nlcount = 0; if (fn == filechar || fn == linechar || fn == nextchar) e.iop->task = XIO; else if (fn == gravechar || fn == qgravechar) e.iop->task = XGRAVE; else e.iop->task = XOTHER; } struct io * setbase(ip) struct io *ip; { register struct io *xp; xp = e.iobase; e.iobase = ip; return(xp); } /* * Input generating functions */ /* * Produce the characters of a string, then a newline, then EOF. */ int nlchar(ap) register struct ioarg *ap; { register int c; if (ap->aword == NULL) return(0); if ((c = *ap->aword++) == 0) { ap->aword = NULL; return('\n'); } return(c); } /* * Given a list of words, produce the characters * in them, with a space after each word. */ int wdchar(ap) register struct ioarg *ap; { register char c; register char **wl; if ((wl = ap->awordlist) == NULL) return(0); if (*wl != NULL) { if ((c = *(*wl)++) != 0) return(c & 0177); ap->awordlist++; return(' '); } ap->awordlist = NULL; return('\n'); } /* * Return the characters of a list of words, * producing a space between them. */ static int xxchar(), qqchar(); int dolchar(ap) register struct ioarg *ap; { register char *wp; if ((wp = *ap->awordlist++) != NULL) { PUSHIO(aword, wp, *ap->awordlist == NULL? qqchar: xxchar); return(-1); } return(0); } static int xxchar(ap) register struct ioarg *ap; { register int c; if (ap->aword == NULL) return(0); if ((c = *ap->aword++) == '\0') { ap->aword = NULL; return(' '); } return(c); } static int qqchar(ap) register struct ioarg *ap; { register int c; if (ap->aword == NULL || (c = *ap->aword++) == '\0') return(0); return(c); } /* * Produce the characters from a single word (string). */ int strchar(ap) register struct ioarg *ap; { register int c; if (ap->aword == 0 || (c = *ap->aword++) == 0) return(0); return(c); } /* * Return the characters from a file. */ int filechar(ap) register struct ioarg *ap; { register int i; char c; extern int errno; do { i = read(ap->afile, &c, sizeof(c)); } while (i < 0 && errno == EINTR); return(i == sizeof(c)? c&0177: (closef(ap->afile), 0)); } /* * Return the characters produced by a process (`...`). * Quote them if required, and remove any trailing newline characters. */ int gravechar(ap, iop) struct ioarg *ap; struct io *iop; { register int c; if ((c = qgravechar(ap, iop)&~QUOTE) == '\n') c = ' '; return(c); } int qgravechar(ap, iop) register struct ioarg *ap; struct io *iop; { register int c; if (iop->xchar) { if (iop->nlcount) { iop->nlcount--; return('\n'|QUOTE); } c = iop->xchar; iop->xchar = 0; } else if ((c = filechar(ap)) == '\n') { iop->nlcount = 1; while ((c = filechar(ap)) == '\n') iop->nlcount++; iop->xchar = c; if (c == 0) return(c); iop->nlcount--; c = '\n'; } return(c!=0? c|QUOTE: 0); } /* * Return a single command (usually the first line) from a file. */ int linechar(ap) register struct ioarg *ap; { register int c; if ((c = filechar(ap)) == '\n') { if (!multiline) { closef(ap->afile); ap->afile = -1; /* illegal value */ } } return(c); } /* * Return the next character from the command source, * prompting when required. */ int nextchar(ap) register struct ioarg *ap; { register int c; if ((c = filechar(ap)) != 0) return(c); if (talking && e.iop <= iostack+1) prs(prompt->value); return(0); } void prs(s) register char *s; { if (*s) write(2, s, strlen(s)); } void putc(c) char c; { write(2, &c, sizeof c); } void prn(u) unsigned u; { prs(itoa(u, 0)); } void closef(i) register i; { if (i > 2) close(i); } void closeall() { register u; for (u=NUFILE; u= 0 && fd < e.iofd); for (i=0; ih_tag = evalstr(s, DOSUB); if (h->h_tag == 0) return; h->h_iop = iop; h->h_next = NULL; if (herelist == 0) herelist = h; else for (lh = herelist; lh!=NULL; lh = lh->h_next) if (lh->h_next == 0) { lh->h_next = h; break; } iop->io_flag |= IOHERE|IOXHERE; for (s = h->h_tag; *s; s++) if (*s & QUOTE) { iop->io_flag &= ~ IOXHERE; *s &= ~ QUOTE; } h->h_dosub = iop->io_flag & IOXHERE; } gethere() { register struct here *h; for (h = herelist; h != NULL; h = h->h_next) h->h_iop->io_un.io_here = readhere(h->h_tag, h->h_dosub? 0: '\''); herelist = NULL; } static struct block * readhere(s, ec) register char *s; { register struct block *bp; register c; jmp_buf ev; bp = (struct block *) space(sizeof(*bp)); if (bp == 0) return(0); if (newenv(setjmp(errpt = ev)) == 0) { if (e.iop == iostack && e.iop->iofn == filechar) { pushio(e.iop->arg, filechar); e.iobase = e.iop; } bp->b_size = 0; bp->b_line = 0; bp->b_next = 0; bp->b_start = 0; for (;;) { while ((c = getc(ec)) != '\n' && c) { if (ec == '\'') c &= ~ QUOTE; if (savec(c, bp) == 0) { c = 0; break; } } savec(0, bp); if (strcmp(s, bp->b_line) == 0 || c == 0) break; bp->b_next[-1] = '\n'; bp->b_line = bp->b_next; } *bp->b_line = 0; if (c == 0) { prs("here document `"); prs(s); err("' unclosed"); } quitenv(); } return(bp); } static savec(c, bp) register struct block *bp; { register char *np; if (bp->b_start == NULL || bp->b_next+1 >= bp->b_start+bp->b_size) { np = space(bp->b_size + NCPB); if (np == 0) return(0); memcpy(np, bp->b_start, bp->b_size); bp->b_size += NCPB; bp->b_line = np + (bp->b_line-bp->b_start); bp->b_next = np + (bp->b_next-bp->b_start); xfree(bp->b_start); bp->b_start = np; } *bp->b_next++ = c; return(1); } herein(bp, xdoll) struct block *bp; { register tf; char tname[50]; static int inc; register char *cp, *lp; if (bp == 0) return(-1); for (cp = tname, lp = "/tmp/shtm"; (*cp = *lp++) != '\0'; cp++) ; lp = putn(getpid()*100 + incSU++); for (; (*cp = *lp++) != '\0'; cp++) ; if ((tf = creat(tname, 0666)) >= 0) { if (xdoll) { char c; jmp_buf ev; if (newenv(setjmp(errpt = ev)) == 0) { PUSHIO(aword, bp->b_start, strchar); setbase(e.iop); while ((c = subgetc(0, 0)) != 0) { c &= ~ QUOTE; write(tf, &c, sizeof c); } quitenv(); } else unlink(tname); } else write(tf, bp->b_start, bp->b_line-bp->b_start); close(tf); tf = open(tname, 0); unlink(tname); } return(tf); } scraphere() { herelist = NULL; } char * memcpy(ato, from, nb) register char *ato, *from; register int nb; { register char *to; to = ato; while (--nb >= 0) *to++ = *from++; return(ato); } 6...READ_MExThe book went to press before the software, so last-minute changes are noted here. In particular, after the book went to press, the software was ported to the PC/AT and various PC clones, which necessitated some minor patches here and there. 1. GENERAL There were a number of minor changes made to the code after the book was printed, as mentioned above. As a result, line X of file Y in the book may actually appear at X+3, or X-5, etc. on the disk version. 2. HARD DISK MINIX supports the use of the standard IBM hard disk. To use a hard disk with MINIX, you need one or more MINIX partitions. You may also have MS-DOS, PC-IX, XENIX, or other partitions as well if you like. If you have a version of FDISK that is able to make several partitions, do so, reserving one for MINIX. If not, you can make several partitions as follows: 1. Backup all the files on the entire hard disk to floppies. 2. Run FDISK to delete all partitions and make a partition for MINIX. 3. Run diskfix (see below) to mark the new partition as type MINIX. 4. Run FDISK to make a new MS-DOS partition (optional). 5. Restore the files on the MS-DOS partition. 6. Boot MINIX and make a MINIX file system on its partition (see below). The basic problem with MS-DOS FDISK is that it can only make one MS-DOS partition and nothing else. It is not very flexible. Therefore diskfix is needed to mark the one partition as non-DOS, so that FDISK will be willing to make another one. Diskfix.asm is a MASM (assembly code) program located in the MINIX tools source directory. Copy it to an MS-DOS disk using doswrite, then assemble it. When it runs, it reads in the partition table, changes all partitions to non-DOS type, and writes it back. Once you have a partition available for MINIX (the type does not matter as MINIX does not check), make a file system by booting MINIX from floppy the usual way and run mkfs. If, for example, you have chosen partition 2, which has, say, 40 cylinders (i.e., 40 x 68 = 2720 sectors or 1360 1K blocks), type: mkfs /dev/hd2 1360 to make an empty file system. However, for partition 1 use 1 block less because block 0 is not available (it contains the MS-DOS partition table). In other words, a 40 cylinder partition 1 has 1359 blocks but a 40 cylinder partition 2 or higher has 1360 blocks. Then mount the file system by typing: /etc/mount /dev/hd2 /user Next, make whatever directories you like, typically bin, lib, and others, and copy files to the hard disk. With the /usr floppy in drive 0, the command cp /usr/bin/* /user/bin will copy all the binaries from /usr/bin to the hard disk, for example. Finally, edit /etc/rc to have the hard disk mounted when the system is booted. A line such as /etc/mount /dev/hd2 /usr can be used as a replacement for the mount command initially in /etc/rc. After editing /etc/rc, mount the root file system diskette and copy it to the diskette; otherwise the changes will be lost when the system is rebooted. (There is nothing special about the root file system except its size; it can be mounted and written on like any other file system.) After these steps have been taken, the system can be booted from floppy in the usual way, and the root file system also read in from floppy. The hard disk will automatically be mounted by the /etc/rc. The root device remains on the RAM disk, and the boot process still goes via floppy (for compatibility and to prevent disaster in the event that something goes wrong with the hard disk file system). Hard disk file systems can be checked using fsck by typing 'x' or 'a' when the initial menu is display. The special file /dev/hd0 refers to the whole disk, without regard to partitions, whereas /dev/hd1 ... /dev/hd4 refer to partitions 1 to 4. If you have a second hard disk, you can make /dev/hd5 ... /dev/hd9 with mknod (major device 3, minor device 5 ... 9) for the second drive, with hd5 for the whole drive, hd6 for partition 1, etc. Fsck also uses this convention. The MINIX program mkfs writes a file system on whatever partition you tell it to. If you tell it to use partition 2, it does. Consequently, a typing error here can result in erasing all your files. (In MS-DOS typing del *.c instead of del *.o will remove all the sources instead of all the object files.) Therefore, it is strongly recommended that you back up your entire hard disk before installing MINIX. Better safe than sorry. Many hard disks are used on IBM PCs, XTs, and ATs. Each one needs a different driver. The files xt_wini.c and at_wini.c are drivers for the standard IBM XT and AT disks respectively. Their drivers are totally different. When generating a new system, you must copy the appropriate driver to the file wini.c (in the kernel directory) depending on whether you have a PC or XT (use xt_wini.c) or an AT (use at_wini.c). You MUST choose one of these drivers even if you have no hard disk at all. 3. PC/AT The distribution for the PC/AT differs from that for the PC in minor ways. For example, there is no /user diskette. All the files that would normally be on /user fit on /usr. Furthermore, the number and organization of the source diskettes is different, but the same programs are available. Source diskette 1 has been arranged so that it can be mounted and you can change to the kernel, mm, or fs directory, and just type make to compile. Two additional special files are present in /dev: /dev/at0 and /dev/at1. These should be used to access 1.2M diskettes. To access 360K diskettes on the PC/AT, use /dev/fd0 and /dev/fd1. Special files are used by commands such as mount, mkfs, and df, among others. For example, to copy part of a 1.2M diskette, type dd if=/dev/at0 of=file count=100. The difference between /dev/at0 and /dev/fd0 is that in MINIX, special files have sizes to prevent access beyond the end. For /dev/fd0 the size is 360K. For /dev/at0 it is 1.2M. 4. USING MS-DOS AS A DEVELOPMENT SYSTEM The conversion of MINIX to make it possible to use MS-DOS as the development system was done by Paul Ogilvie. It was done in order to to provide some assistance to people who wish to modify MINIX but are unable to use MINIX or UNIX for that purpose. MS-DOS definitely should be regarded as the method of last resort, only used if there is no other alternative. The assembly code files and batch files provided have all been tested with the Computer Innovations C86 compiler, release 2.4, and specific versions of the linker etc. The use of any other compiler or configuration is virtually guaranteed not to work the first time because every MS-DOS C compiler is different, their libraries are different, etc. MS-DOS users must approach this project with the expectation that minor problems will arise and small patches to the code may be needed to work around bugs and deficiencies that abound with MS-DOS compilers. When problems appear with files that are different for MS-DOS and MINIX, check the MINIX version, since these have been tested much more thoroughly than the MS-DOS versions. 5. BUGS in MS-DOS Some machines and/or versions of MS-DOS have a bug when doing absolute diskio, as used by mkfs and build. This bug will manifest itself by creating an unbootable disk, saying a file-system is bad, or sometimes the program will just hang. The primary remedy if your machine suffers from this bug is to first format the disk. A permanent remedy is to change the module "diskio.asm" to use bios I/O calls instead of using the DOS interrupts 25 and 26 (see fsck1.asm). Disadvantage of the latter is that you cannot extend these programs anymore to work on a hard disk (e.g. making a MINIX filesystem on a hard disk). Though the cause is not^`abcdefgE2