leo@philmds.UUCP (Leo de Wit) (09/20/88)
Posting-number: Volume 4, Issue 84 Submitted-by: "Leo de Wit" <leo@philmds.UUCP> Archive-name: 3demo/Part03 : This is a shar archive. Extract with sh, not csh. : This archive ends with exit, so do not worry about trailing junk. echo 'Extracting drawlin.asm' sed 's/^X//' > drawlin.asm << '+ END-OF-FILE drawlin.asm' X****************************************************************************** X* * X* drawlin.asm version 1.0 of 19 June 1988 (c) L.J.M. de Wit 1988 * X* * X* This software may be used and distributed freely if not used commercially * X* and the originator (me) is mentioned. * X* * X****************************************************************************** X X section s.ccode X module drawlin X X xdef drawlin X xref ncolor X X* void drawlin(x1,xy1,x2,y2) X* WORD x1,y1,x2,y2; X X* frame pointer offsets for drawlin; compiler dependent X* For Lattice C each argument on the stack has 4 bytes size Xx1 equ 10 Xy1 equ 14 Xx2 equ 18 Xy2 equ 22 Xlogbase equ 3 Xxbios equ 14 X Xdrawlin: X link a6,#0 X movem.l d4-d7/a4-a5,-(sp) X move.w #logbase,-(sp) X trap #xbios X addq.l #2,sp X move.l d0,d7 * Screen start address in d7 X move.w x1(a6),d0 X move.w x2(a6),d1 X cmp.w d0,d1 X bge.s noswap * Swap end points iff x2 < x1 X move.w y2(a6),d2 X move.w y1(a6),y2(a6) X move.w d0,x2(a6) X move.w d1,x1(a6) X move.w d2,y1(a6) Xnoswap: X move.l #$80008000,d4 * Mask X move.w #$a0,d2 * Screen width for medium & low res: ystep X lea.l ncolor,a0 X cmp.w #4,(a0) X bge.s goodcol X move.w #$50,d2 * Screen width for high res: ystep Xgoodcol X move.w d2,d0 * d0 = screen width X move.w x2(a6),d5 X sub.w x1(a6),d5 * d5 = x2 - x1 ( == |x2 - x1| = A ) X move.w y2(a6),d6 X sub.w y1(a6),d6 * d6 = y2 - y1 X bpl.s posy X neg.w d6 * So d6 = |y2 - y1| ( == B) X neg.w d2 * Make ystep negative in this case Xposy X move.w x1(a6),d1 X andi.w #$f,d1 * d1 = x1 % 16 X ror.l d1,d4 * Set mask to correct starting value X move.w y1(a6),d1 X muls.w d0,d1 X add.l d7,d1 X movea.l d1,a4 * a4 = screen start + y1 * screen width X move.w x1(a6),d1 X ext.l d1 * d1 = x1 X moveq.l #0,d0 * d0 = 0 X cmp.w #4,(a0) X beq.s xy2 * Medium res (2 bit planes) X bhi xy4 * Low res (4 bit planes) X X***** start high res ***** Xxy1 X asr.l #4,d1 X asl.l #1,d1 X adda.l d1,a4 * a4 += 2 * (x1 / 16) X cmp.l d6,d5 X blt.s yx1 * Iff A < B X move.w d5,d7 X asr.w #1,d7 * E = A / 2, startvalue error term X move.w d5,d3 * d3 = # steps (== A) X bra.s xy1end Xxy1nxt X or.w d4,d0 * Or in mask into new value X sub.w d6,d7 * E -= B X bge.s xy1not * E >= 0: No 'y-correction yet X add.w d5,d7 * Do E += A and step in y direction X or.w d0,(a4) * But first write new value to screen X adda.w d2,a4 * Add ystep to screen address X moveq.l #0,d0 * Clear new value again Xxy1not X ror.w #1,d4 * Step in x always includes rotating mask X bcc.s xy1end * If pixel not yet at word boundary X or.w d0,(a4)+ * Else write new value & inc screen address X moveq.l #0,d0 * Clear new value Xxy1end X dbra d3,xy1nxt * A steps will be taken X or.w d0,(a4) * Value at end point X bra drawex * Finished X Xyx1 X move.w d6,d7 X asr.w #1,d7 * E = B / 2, startvalue error term X move.w d6,d3 * d3 = # steps (== B) X bra.s yx1end Xyx1nxt X or.w d4,(a4) * Or in mask into screen X sub.w d5,d7 * E -= A X bge.s yx1not * E >= 0: No 'x-correction' yet X add.w d6,d7 * Do E += B and step in x direction X ror.w #1,d4 * Step in x always includes rotating mask X bcc.s yx1not * If pixel not at word boundary X addq.l #2,a4 * Increment screen address (1 word) Xyx1not X adda.w d2,a4 * Update screen address for ystep Xyx1end X dbra d3,yx1nxt * B steps will be taken X bra drawex * Finished X X***** start med res ***** Xxy2 X asr.l #4,d1 X asl.l #2,d1 X adda.l d1,a4 * a4 += 4 * (x1 / 16) X cmp.l d6,d5 X blt.s yx2 * Iff A < B X move.w d5,d7 X asr.w #1,d7 * E = A / 2, startvalue error term X move.w d5,d3 * d3 = # steps (== A) X bra.s xy2end Xxy2nxt X or.l d4,d0 * Or in mask into new value X sub.w d6,d7 * E -= B X bge.s xy2not * E >= 0: No 'y-correction' yet X add.w d5,d7 * Do E += A and step in y direction X or.l d0,(a4) * But first write new value to screen X adda.w d2,a4 * Add ystep to screen address X moveq.l #0,d0 * Clear new value again Xxy2not X ror.l #1,d4 * Step in x always includes rotating mask X bcc.s xy2end * If pixel not yet at word boundary X or.l d0,(a4)+ * Else write new value & inc screen address X moveq.l #0,d0 * Clear new value Xxy2end X dbra d3,xy2nxt * A steps will be taken X or.l d0,(a4) * Value at end point X bra.s drawex * Finished X Xyx2 X move.w d6,d7 X asr.w #1,d7 * E = B / 2, startvalue error term X move.w d6,d3 * d3 = # steps (== B) X bra.s yx2end Xyx2nxt X or.l d4,(a4) * Or in mask into screen X sub.w d5,d7 * E -= A X bge.s yx2not * E >= 0: No 'x-correction' yet X add.w d6,d7 * Do E += B and step in x direction X ror.l #1,d4 * Step in x always includes rotating mask X bcc.s yx2not * If pixel not at word boundary X addq.l #4,a4 * Increment screen address (2 words) Xyx2not X adda.w d2,a4 * Update screen address for ystep Xyx2end X dbra d3,yx2nxt * B steps will be taken X bra.s drawex * Finished X X***** start low res ***** Xxy4 X asr.l #4,d1 X asl.l #3,d1 X adda.l d1,a4 * a4 += 8 * (x1 / 16) X cmp.l d6,d5 X blt.s yx4 * Iff A < B X move.w d5,d7 X asr.w #1,d7 * E = A / 2, startvalue error term X move.w d5,d3 * d3 = # steps (== A) X bra.s xy4end Xxy4nxt X or.l d4,d0 * Or in mask into new value X sub.w d6,d7 * E -= B X bge.s xy4not * E >= 0: No 'y-correction' yet X add.w d5,d7 * Do E += A and step in y direction X or.l d0,(a4)+ * But first write new value to screen X or.l d0,(a4) * And also at next address X subq.l #4,a4 * Compensate for increment X adda.w d2,a4 * Add ystep to screen address X moveq.l #0,d0 * Clear new value again Xxy4not X ror.l #1,d4 * Step in x always includes rotating mask X bcc.s xy4end * If pixel not yet at word boundary X or.l d0,(a4)+ * Else write new value & inc screen address X or.l d0,(a4)+ * And again X moveq.l #0,d0 * Clear new value Xxy4end X dbra d3,xy4nxt * A steps will be taken X or.l d0,(a4)+ * Value at end point X or.l d0,(a4) * And again X bra.s drawex * Finished X Xyx4 X move.w d6,d7 X asr.w #1,d7 * E = B / 2, startvalue error term X move.w d6,d3 * d3 = # steps (== B) X bra.s yx4end Xyx4nxt X or.l d4,(a4)+ * Or in mask into screen X or.l d4,(a4) * And another one X subq.l #4,a4 * Compensate for increment X sub.w d5,d7 * E -= A X bge.s yx4not * E >= 0: No 'x-correction' yet X add.w d6,d7 * Do E += B and step in x direction X ror.l #1,d4 * Step in x always includes rotating mask X bcc.s yx4not * If pixel not at word boundary X addq.l #8,a4 * Increment screen address (4 words) Xyx4not X adda.w d2,a4 * Update screen address for ystep Xyx4end X dbra d3,yx4nxt * B steps will be taken X bra.s drawex * Finished X nop * Jump for symmetry so nop needed 8-) Xdrawex X movem.l (sp)+,d4-d7/a4-a5 X unlk a6 X rts X X end + END-OF-FILE drawlin.asm chmod 'u=rw,g=r,o=' 'drawlin.asm' echo 'SENT: -rw-r----- 1 leo 9537 Aug 21 14:35 drawlin.asm' echo -n 'RCVD: ' /bin/ls -l drawlin.asm echo 'Extracting mxops.asm' sed 's/^X//' > mxops.asm << '+ END-OF-FILE mxops.asm' X****************************************************************************** X* * X* mxops.asm version 1.0 of 19 june 1988 (c) l.j.m. de wit 1988 * X* * X* this software may be used and distributed freely if not used commercially * X* and the originator (me) is mentioned. * X* * X****************************************************************************** X X module mxhmul X section s.ccode X X xdef mxhmul X xdef matcopy X X****************************************************************************** X* * X* mxhmul(a,b,c,p,q,r) * X* short *a, *b, *c, p, q, r; * X* * X* Matrix multiplication: * X* a points to an array of p * q shorts * X* b points to an array of q * r shorts * X* c points to an array of p * r shorts * X* The matrices pointed to by a and b are multiplied giving an array of * X* p * r longs on the stack. Then a suitable shift is chosen so that each * X* long fits into a signed short after shifting. In fact the shift is even * X* some more to garantee that the sum of four products of these shorts fit * X* into a signed long; this guarantee must also already exist for the shorts * X* in the a and b arrays and is achieved by demanding: * X* * X* - (1 << 14) <= short < (1 << 14) * X* * X* For performance reasons the case q == 4 (which is particularly * X* interesting for our purposes) is handled as a special case. * X* * X****************************************************************************** X X Xoffset equ 40 X Xmxhmul: X movem.l d3-d7/a3-a6,-(sp) X move.l offset(sp),a0 * a X move.l offset+4(sp),a2 * b X move.l offset+8(sp),a5 * c X moveq.l #0,d5 X move.w offset+14(sp),d5 * p X beq mx_done X moveq.l #0,d6 X move.w offset+18(sp),d6 * q X beq mx_done X moveq.l #0,d7 X move.w offset+22(sp),d7 * r X beq mx_done X move.w d5,d2 X mulu.w d7,d2 * p*r X add.w d7,d7 * r*2 X move.w d7,d4 X mulu.w d6,d4 * q*r*2 X move.w d5,d0 X mulu.w d6,d0 X add.w d0,d0 * p*q*2 X lea -2(a0,d0.w),a1 * ap = a + p*q*2 - 2 X move.w (a1),d3 * *ap X move.w d5,d0 X mulu.w d7,d0 * p*r*2 X lea (a5,d0.w),a5 * cp = c + p*r*2 X add.w d0,d0 * p*r*4 X suba.w d0,sp * room for p*r longs on stack X movea.l sp,a6 * a6 points to it X move.w d2,d0 X subq.l #1,d0 X moveq.l #0,d1 Xtmpclear: X move.l d1,(a6)+ X dbra d0,tmpclear X move.w d7,d5 X add.w d5,d5 * r*4 X cmp.w #8,d7 * i.e. r == 4 X beq.s mat4 Xmat: X lea (a2,d4.w),a4 * bs = b + q*r*2 X move.l a4,a3 * bp = bs X bra.s rowend Xrow: X move.w -(a3),d0 X muls.w d3,d0 X add.l d0,-(a6) * *--tmp = *--bp * *ap Xrownxt: X cmpa.l a4,a3 * bp > bs ? X bgt.s row X lea (a6,d5.w),a6 * tmp += r*4 X move.w -(a1),d3 * *--ap Xrowend: X suba.w d7,a4 * bs -= r*2 X cmpa.l a2,a4 * bs >= b ? X bge.s rownxt X suba.w d5,a6 * tmp -= r*4 X cmpa.l a0,a1 * ap >= a ? X bge.s mat X bra.s homgen Xmat4: X lea (a2,d4.w),a3 * bp = b + q*r*2 Xrow4: X move.w -(a3),d0 X muls.w d3,d0 X add.l d0,-(a6) * *--tmp = *--bp * *ap X move.w -(a3),d0 X muls.w d3,d0 X add.l d0,-(a6) * ditto X move.w -(a3),d0 X muls.w d3,d0 X add.l d0,-(a6) * ditto X move.w -(a3),d0 X muls.w d3,d0 X add.l d0,-(a6) * ditto X lea (a6,d5.w),a6 * tmp += r*4 X move.w -(a1),d3 * *--ap X cmpa.l a2,a3 * bp > b ? X bgt.s row4 X suba.w d5,a6 * tmp -= r*4 X cmpa.l a0,a1 * ap >= a ? X bge.s mat4 Xhomgen X movea.l sp,a6 * start of tmp area X move.w d2,d0 X subq.l #1,d0 X move.w d0,d5 X moveq.l #0,d1 Xsh_it X move.l (a6)+,d3 X bpl.s sh_plus X neg.l d3 Xsh_plus X or.l d3,d1 * Or in each absolute value X dbra d0,sh_it X beq.s sh_none X move.l #15,d0 Xsh_count X subq.l #1,d0 X lsr.l #1,d1 X bne.s sh_count X tst.w d0 * d0 contains: 14 - highest bit # of d1. X beq.s sh_none * No shift needed. X bpl.s sh_left * Shift to left: numbers are too small X neg.w d0 * Take abs val of shift. Xsh_right X move.l -(a6),d1 X asr.l d0,d1 X move.w d1,-(a5) * Put low word in array pointed to by c X dbra d5,sh_right * For each number in the array X bra.s sh_zero Xsh_left X move.l -(a6),d1 X asl.l d0,d1 X move.w d1,-(a5) * Ditto for left shift X dbra d5,sh_left X bra.s sh_zero Xsh_none X move.l -(a6),d1 * Just copy over X move.w d1,-(a5) * each low order word X dbra d5,sh_none * in case of no shift Xsh_zero X asl.w #2,d2 X add.w d2,sp * effectively remove temp array from stack X bra.s mx_done X nop Xmx_done X movem.l (sp)+,d3-d7/a3-a6 X rts X X X****************************************************************************** X* * X* short *matcopy(a,b,n) * X* short *a; * X* short *b; * X* short n; * X* * X* Copy over n shorts from the array pointed to by a to that pointed to by b. * X* The copy is done using longs (this being faster). For more speed the # of * X* moves inside the loop is increased (8 words at a time). * X* * X****************************************************************************** X Xmatcopy: X movea.l 4(sp),a0 * a0 = a X movea.l 8(sp),a1 * a1 = b X move.w 14(sp),d0 * d0 = n X asr.w #1,d0 X bcc.s mc_0 X move.w (a0)+,(a1)+ * 1 mod 2 words so do 1 like this. Xmc_0 X asr.w #1,d0 X bcc.s mc_1 X move.l (a0)+,(a1)+ * 2 mod 4 words so do 2 like this. Xmc_1 X asr.w #1,d0 X bcc.s mc_2 X move.l (a0)+,(a1)+ * 4 mod 8 words so do 4 like this. X move.l (a0)+,(a1)+ Xmc_2 X bra.s mc_rep Xmc_next X move.l (a0)+,(a1)+ X move.l (a0)+,(a1)+ X move.l (a0)+,(a1)+ X move.l (a0)+,(a1)+ Xmc_rep X dbra d0,mc_next * Moving 8 words at a time X rts X X module mxproj X section s.ccode X X xdef mxproj X xref maxx X xref maxy X X****************************************************************************** X* * X* mxproj(inarr,outarr,pcount,eye_z,scal_x,scal_y) * X* short *inarr, *outarr; * X* short pcount; * X* short eye_z, scal_x, scal_y; * X* * X* Projection of inarr onto the instance's screen coordinates outarr. * X* inarr points to an pcount x 4 array of shorts representing the hom. * X* coordinates of the pcount points of the instance. * X* outarr points to a pcount x 2 array of shorts representing the screen * X* coordinates to be calculated for each point (this array is part of the * X* instance). * X* eye_z, scal_x and scal_y are parameters that determine the projection: * X* eye_z: distance between observer and screen * X* scal_x: scaling factor for x direction * X* scal_y: scaling factor for y direction * X* * X****************************************************************************** X Xinarr equ 8 Xoutarr equ 12 Xpcount equ 18 Xeye_z equ 22 Xscal_x equ 26 Xscal_y equ 30 X Xmxproj: X link a6,#0 X movem.l d3-d7/a2-a5,-(sp) X lea.l maxx,a2 * a2 points to maxx X lea.l maxy,a3 * a3 points to maxy X move.w pcount(a6),d7 X move.w d7,d1 X ext.l d1 X asl.l #2,d1 * d1 = sizeof(outarr) X movea.l outarr(a6),a4 X adda.l d1,a4 * a4 points to end of outarr X add.l d1,d1 X movea.l inarr(a6),a5 X adda.l d1,a5 * a5 points to end of inarr X move.w eye_z(a6),d6 * d6 = eye_z X move.w scal_x(a6),d4 * d4 = scal_x X move.w scal_y(a6),d5 * d5 = scal_y X bra.s loop X* call homogene coordinates of a point: (x,y,z,w) Xnxtpnt X move.w -(a5),d1 X muls.w d6,d1 X move.w -(a5),d0 X ext.l d0 X sub.l d0,d1 * d1 = eye_z * w - z X move.w d5,d0 X muls.w d6,d0 X muls.w -(a5),d0 * d0 = eye_z * scal_y * y X move.l d1,d2 X swap.w d2 * We prepare division of d0 by d1 X moveq.l #1,d3 * d3 will contain shift to avoid long div. X and.w #$ffff,d2 * Was top word of d1 X bpl.s simpos X not.w d2 * Swap bits if d1 was negative Xsimpos X beq.s simplex * All bits zero: fits already into word X addq.l #8,d3 * If not, at least a 8 bits shift X and.w #$ff00,d2 * Test top byte X beq.s simplex X addq.l #8,d3 * Another 8 bits shift if also not 0 Xsimplex X asr.l d3,d0 * Shift each operand first X asr.l d3,d1 * And the other one X divs.w d1,d0 * Perform division now (d1 fits in word) X bpl.s ypos X moveq.l #0,d0 * Use 0 as minimum Xypos X cmp.w (a3),d0 X ble.s ylow X move.w (a3),d0 * And maxy as maximum Xylow X move.w d0,-(a4) * Store y coordinate X move.w d4,d0 X muls.w d6,d0 X muls.w -(a5),d0 * d0 = scal_x * eye_z * x X asr.l d3,d0 * Shift this operand too (d3 is still ok) X divs.w d1,d0 * Perform division also here X bpl.s xpos X moveq.l #0,d0 * Use 0 as minimum Xxpos X cmp.w (a2),d0 X ble.s xlow X move.w (a2),d0 * And maxx as maximum Xxlow X move.w d0,-(a4) * Store x coordinate Xloop X dbra d7,nxtpnt * Repeat for each point X movem.l (sp)+,d3-d7/a2-a5 X unlk a6 X rts X X end X + END-OF-FILE mxops.asm chmod 'u=rw,g=r,o=' 'mxops.asm' echo 'SENT: -rw-r----- 1 leo 12831 Aug 21 15:03 mxops.asm' echo -n 'RCVD: ' /bin/ls -l mxops.asm echo 'Extracting matricks.h' sed 's/^X//' > matricks.h << '+ END-OF-FILE matricks.h' X/* X ****************************************************************************** X * * X * matricks.h version 1.0 of 17 July 1988 (C) L.J.M. de Wit 1988 * X * * X * This software may be used and distributed freely if not used commercially * X * and the originator (me) is mentioned. * X * * X ****************************************************************************** X */ X X#define SV -1 /* Special value to denote end of a list */ X X#define FIXPOINT 14 /* 1<<FIXPOINT considered fixpoint unity */ X X#define QUAD(a) ((a)*(a)) X X#ifdef LATTICE /* trick to make Lattice use faster ints */ X#define int short X#endif X X#define CLS "\33H\33J" /* clear screen sequence for vt52 emul. */ X X/* matcompose: Multiplication of two 4 x 4 matrices leaving result in first */ X/* Because mxhmul uses an intermediate array a can be a result parameter */ X#define matcompose(a,b) mxhmul((a),(b),(a),4,4,4) X Xtypedef unsigned char bool; /* boolean type */ X Xtypedef struct polystruct { /* polygon def: ptr to this struct */ X int count; /* # of points in this polygon */ X struct polystruct *next; /* ptr to next polygon */ X WORD coord[1]; /* index of first point */ X} *polygon; /* the rest follows hereafter */ X Xtypedef struct itstruct { /* item definition: ptr to this struct */ X int count; /* # of points in this item */ X polygon next; /* pointer to polygon list */ X WORD r_data[1][4]; /* homogene coordinates of first point; */ X} *item; /* the rest follows hereafter */ X Xtypedef struct instruct { /* instance def: ptr to this struct */ X item in_item; /* item of which this is an instance */ X WORD trans[4][4]; /* 4x4 homogene transformation to place it*/ X WORD i_data[1][2]; /* screen coordinates of first point */ X} *instance; /* the rest follows hereafter */ X Xtypedef unsigned char uchar; /* for 'bytes' */ X Xextern WORD handle; /* handle of virtual workstation */ Xextern WORD maxx,maxy,ncolor, X pixwidth, pixheight; X Xextern double maxfactor(), /* calculate max. factor for mult. array */ X mysin(), X mycos(); X Xextern WORD *matreflect(), /* create reflection matrix */ X *matrotate(), /* create rotation matrix */ X *mattlate(), /* create translation matrix */ X *matident(), /* create identity matrix */ X *matseteye(); /* set eye coordinates */ X Xextern item matcrit(); /* create an item */ Xextern instance matcrins(); /* create an instance of an item */ Xextern void matfrit(), /* free an item */ X matfrins(); /* free an instance of an item */ X Xextern void insstore(), /* store instance into array */ X insload(), /* load instance from array */ X matproject(), /* calculate screen coords for instance */ X matdraw(), /* draw an instance, using VDI */ X matfdraw(), /* draw an instance, using drawlin */ X matapply(), /* apply a transformation onto a matrix */ X matstr_to_scr(); /* put string data onto screen */ X Xextern uchar *matscr_to_str(); /* put screen into string data */ X X/* Assembler routines for speeding up */ Xextern void mxhmul(), /* 4x4 matrix mult. (homogene coords) */ X mxproj(), /* matrix projection calculation */ X drawlin(); /* fast line drawing algorithm */ X X/* graphic functions */ Xextern void gem_init(), /* set up virt. workst & allocate screen */ X gem_exit(), /* clean up before exit */ X swapscreen(), /* swap the logical & physical screens */ X help(), /* offer help info */ X error(); /* simple error handler */ + END-OF-FILE matricks.h chmod 'u=rw,g=r,o=' 'matricks.h' echo 'SENT: -rw-r----- 1 leo 4469 Aug 21 14:45 matricks.h' echo -n 'RCVD: ' /bin/ls -l matricks.h echo 'Extracting makefile' sed 's/^X//' > makefile << '+ END-OF-FILE makefile' X#Makefile for tools X XOBJECTS = 3demo.bin mat.bin mxops.bin drawlin.bin gemfuncs.bin object.bin XSOURCES = 3demo.c mat.c mxops.asm drawlin.asm gemfuncs.c object.c X Xall: 3demo.prg X X3demo.prg : 3dlib.bin 3demo.bin X $(CC) $(CFLAGS) 3demo.bin -L 3dlib.bin -lgemlib -o $@ X X3demo.bin : 3demo.c matricks.h X $(CC) -c 3demo.c X X3dlib.bin: object.bin mat.bin mxops.bin drawlin.bin gemfuncs.bin X cat object.bin mat.bin mxops.bin drawlin.bin gemfuncs.bin >3dlib.bin X Xgemfuncs.bin : gemfuncs.c matricks.h X $(CC) -c gemfuncs.c X Xmat.bin : mat.c matricks.h X $(CC) -c mat.c X Xobject.bin : object.c matricks.h X $(CC) -c object.c X Xmxops.bin: mxops.asm X $(AS) mxops.asm X Xdrawlin.bin: drawlin.asm X $(AS) drawlin.asm + END-OF-FILE makefile chmod 'u=rw,g=r,o=' 'makefile' echo 'SENT: -rw-r----- 1 leo 755 Aug 21 14:37 makefile' echo -n 'RCVD: ' /bin/ls -l makefile exit 0