32V/usr/src/standalone/fboot.c

Compare this file to the similar file:
Show the results in this format:

# include "rel.h"
 
# define RP6CYL  815  /*  no. RP06 cylinders/pack */
# define RP6TRK  19  /*  no. tracks/cyl  */
# define RP6SEC  22  /*  no. sectors/track  */
# define RM3CYL 823  /*  RM03  */
#define RM3TRK 5
#define RM3SEC 32
 
# define RP6ST  (rptrk*rpsec)
# define MAXSEC  (rpcyl*rptrk*rpsec)  /*  sectors/pack  */
# define M0  0x20010000  /* phys addr MBA 0  */
# define M1  0x20012000  /*  phys addr MBA 1  */
# define M_cr  1  /*  MBA 0 control reg addr , longword offset */
# define M_sr 2  /* MBA 0 status reg offset */
# define M_map  0x200  /* start MBA 0 map reg's longword offset */
# define M_var  3  /* MBA 0 virt addr reg offset */
# define M_bc  4  /*  MBA 0 byte count reg offset */
# define MBAinit 01  /*  MBA init bit */
/*		*/
# define EXTREG  0x400  /*  base for external reg's on MBA  */
/*		*/
# define RP_cr  0  /* RP06 control reg offset, longword */
# define RP_sr  1  /*  RP06 status reg offset */
# define RP_stk  5  /*  RP06 sector/track reg offset */
# define RP_typ 06  /* drive type reg */
# define RP_off  011  /*  RP offset reg */
# define RP_cyl  10  /*  RP06 cylinder reg offset */
/*		*/
# define RP_GO  1  /*  go bit */
# define RP_RED  070  /*  RP06 read function code */
# define RP_DC  010  /*  drive clear function code */
# define RP_DRY  0200  /*  drive ready, status reg */
# define RP_ERR 040000  /* composite error, status reg */
# define RP_RIP 020  /* RP readin preset function code */
# define RP_FMT  0x1000  /* format bit in RP offset reg */
# define RP_MOL  0x1000  /* medium on-line bit in RP status reg */
/*		*/
# define RXCS	32  /*  receiver control/staus */
# define RXDB  33  /*  receiver data */
# define TXCS  34  /*  transmitter control/status */
# define TXDB  35  /*  transmitter data */
# define RXCS_DONE  0x80  /*  receiver done */
# define TXCS_RDY  0x80  /*  transmitter ready */
/*		*/
# define MAG410 0410
# define MAG411 0411
# define MAXUNI  1
# define NL  012
# define CR 015
# define CDEL 0x23
# define LDEL 0x40
# define BLKSIZ 512
# define HDRSIZ 040  /*  task header size  */
# define INOSIZ  64  /*  no. bytes per inode entry */
# define INOBLK  (BLKSIZ/INOSIZ)  /*  no. inodes/disk block */
# define NAMSIZ 14  /*  no. char's in dir name field */
# define DIRSIZ  16  /*  no. bytes/directory entry */
# define SLASH  057  /* '/'  */
# define NADDR  13  /* no. addr blocks in inode entry */
# define KSP 0
/*		*/
# define IFMT 0170000
# define IFDIR 040000
# define IFREG 0100000
# define ROOTINO  2  /*  root dir inode no. */

/*		*/
#define RP6typ  022  /*  RP06 */
#define RM3typ  024  /*  RM03 type */
struct dirent { short ino ; char dname[NAMSIZ] ; } ;
struct inod {
	short i_mode ;
	short fill1[3] ;
	int i_size ;
	char i_addr[40] ;
	int fill2[3] ;
	} ;
struct thdr {
	int hmagic ;
	int htsiz ;
	int hdsiz ;
	int hbsiz ;
	int hsmsiz ;
	int hentry ;
	int hrelb ;
	int hrflg ;
	} ;
int filsiz ;
char *entry ;
char buf[BLKSIZ] , *namep ;
char dbuf[BLKSIZ] ;
short mode ;
int addr[NADDR] ;
int *mbap ; /* MBA ptr */
int *RPptr ; /* RP06 ptr */
int rpsec,rpcyl,rptrk; /* get set up in 'init' for RP06 or RM03 */
char *bufloc;
int dtmp1[128] , dtmp2[128] , dtmp3[128] ;
 
/*
*  This program, '/fboot', is always read in by code in the RP
*  boot block (block 0). It boots in the file specified by the
*  user.
*/
 
main()
{
/* set stack ptr and move code up to high end of core */
reloc() ;
/* jump to re-located code */
asm("	.text") ;
asm("	.globl	_main1") ;
asm("	jmp	*$(_main1+2)") ;
asm("	.align	2") ;
}
 
/*		*/
 
main1()
{
char ntmp[10*NAMSIZ] ;
register int inum ;
register struct inod *inp ;
struct inod *getiblk() ;
struct dirent *dsearch() , *pde ;
int j ;
 
floop :
 
putnl() ;
putstr("file : ") ;
getcon(ntmp) ; /* null-terminated filename */
putnl() ;
namep = ntmp ;
inum = ROOTINO ; /* root dir inode no. */
 
while ((*namep) == SLASH) namep++ ;
if ((*namep) == '\0') goto floop ;
 
mbap = M0 ;
RPptr = M0 + EXTREG ;
 
if (init()) {
	putlin("init error") ;
	return(-1) ;
	}
 
giblk :
 
/* get disk block that has inode entry for inode no. 'inum' */
inp = getiblk(inum) ; /* 'cp' = ptr. to inode entry in 'buf[]' */
if (inp == (-1)) {
	ioerr :
		putlin("disk i/o error") ;
		goto floop ;
	}
 
/* Get 'mode' and 'addr[]' entries for selected inode & save off */
iexpand(inp) ;
 
/*  inode mode is in 'mode'.
*   block no.'s are in 'addr[]'.
*   If inode is a directory, search all its blocks for pathname
*    component pointed to by 'namep'.
*   If inode is regular file, load blocks of file into core
*    for execution.
*/
 
if ((j = mode&IFMT) == IFDIR) { /* Directory */
	if (*namep == '\0') goto floop ; /* no more pathname */
	pde = dsearch() ; /* search all directory blocks */
	if (pde == (-1)) goto ioerr ; /* i/o error */
	if (pde == 0) goto floop ; /* pathname component not found */
	/* Found pathname component (directory or file) - go get
	  its inode and continue search.
	*/
	inum = pde->ino ;
	goto giblk ;
	}
else {
	if (j == IFREG) { /* regular file */
		if (*namep != '\0') goto floop ; /* should have been
			last component in pathname */
		if (lodfil()) goto floop ; /* load file whose
		  inode 'addr' blocks are in 'addr[]' */
/*
*  Code to check for task header, move code down to 0,
*  get task entry address, clear core, etc.
*/
 
		fexec() ;
		goto floop ; /* returned from execution - go to loop */
		}
	}
goto floop ; /* wasn't directory or file */
}
 
/*		*/
 
struct inod *getiblk(in)
register int in ;
{
/*
*  Read in disk inode block which contains inode
*  no. 'in' - return ptr. to start of inode entry.
*/
register i ;
 
i = (in + (INOBLK*2) - 1)/INOBLK ;
if (dread(i,buf)) /*  read block 'i' into 'buf[]' */
	return(-1) ; /* i/o error */
return((int)buf + (((in-1) & (INOBLK-1))*INOSIZ)) ;
}
 
/*		*/
 
iexpand(ip)
register struct inod *ip ;
{
/*
*  Get mode, file size and block no.'s from disk inode and store
*  away.
*  'ip' = ptr. to inode entry.
*/
register int i ;
register char *f , *t ;
 
mode = ip->i_mode ;
filsiz = ip->i_size ;
f = (&ip->i_addr[0]) ;
t = (&addr[0]) ;
 
for (i = NADDR ; i ; i--) {
	(*t++) = (*f++) ;
	(*t++) = (*f++) ;
	(*t++) = (*f++) ;
	(*t++) = '\0' ;
	}
return(0) ;
}
 
/*		*/
 
struct dirent *dsearch()
{
/*
*  Search blocks of directory inode 'inum', whose block
*  no.'s are in 'addr[]', for pathname component pointed
*  to by 'namep'.
*  Return (-1) for error.
*  Return (0)  for no match.
*  Return ptr. to directory entry for match.
*/
register char c ;
register struct dirent *cp ;
register int i , j ;
struct dirent *pfind() ;
 
/*  direct  */
cp = pfind(addr,NADDR-3) ; /* search 'NADDR-3' blocks(in 'naddr[]') */
if (cp == (-1)) return(-1) ; /* i/o error */
if (cp == 0) return(0) ;  /* zero block encountered - no more blocks */
if (cp == 1) goto level1 ; /* more blocks to search */
/* 'cp' must be ptr. to matched directory */
found :
	/*  point to next pathname component */
while (((c = (*namep)) != '/') && (c !='\0')) namep++ ;
if (c == '/') while (*namep == '/') namep++ ;
return(cp) ;
 
/*
*/
 
level1 : /*  1st level indirection */
 
if ((j = addr[NADDR-3]) == 0) return(0) ;
if (dread(j,dtmp1)) return(-1) ; /* i/o error */
cp = pfind(dtmp1,128) ;
if (cp == (-1)) return(-1) ;
if (cp == 0) return(0) ;
if (cp == 1) goto level2 ;
goto found ;
 
level2 :
 
if ((j = addr[NADDR-2]) == 0) return(0) ;
if (dread(j,dtmp1)) return(-1) ;
for (i = 0 ; i < 128 ; i++) {
	if (dtmp1[i] == 0) return(0) ;
	if (dread(dtmp1[i],dtmp2)) return(-1) ;
	cp = pfind(dtmp2,128) ;
	if (cp == (-1)) return(-1) ;
	if (cp == 0) return(0) ;
	if (cp == 1) continue ;
	goto found ;
	}
 
level3 :
 
if ((j = addr[NADDR-1]) == 0) return(0) ;
if (dread(j,dtmp1)) return(-1) ;
for (i = 0 ; i < 128 ; i++) {
	if (dread(dtmp1[i],dtmp2)) return(-1) ;
	for (j = 0 ; j < 128 ; j++) {
		if (dread(dtmp2[i],dtmp3)) return(-1) ;
		cp = pfind(dtmp3,128) ;
		if (cp == (-1)) return(-1) ;
		if (cp == 0) return(0) ;
		if (cp == 1) continue ;
		goto found ;
		}
	}
return(0) ;
}
 
/*		*/
 
struct dirent *pfind(ia,knt)
int ia[] ;
register int knt ;
{
/*
*  'ia' :  array of integer block no.'s.
*  'knt' :  no. entries in 'ia'
*  A zero block in 'ia[]' -> no match -> return(0) .
*  Return (-1) on i/o error.
*  Return (1) if no zero blocks and all blocks searched but
*    no match.
*  Return ptr. to directory entry for match.
*/
 
register int *bp , bn ;
register struct dirent *ix ;
 
bp = ia ;
while (knt--) {
	if ((bn = (*bp++)) == 0) return(0) ;
	if (dread(bn,dbuf)) return(-1) ;
	for (ix = dbuf ; ix < &dbuf[BLKSIZ] ; ix++) {
		if (dnmatch(ix->dname,namep)) return(ix) ;
		}
	}
return(1) ;
}
 
/*		*/
 
dnmatch(p1,p2)
register char *p1 , *p2 ;
{
/*
*  'p1' : ptr. to directory filename field
*  'p2' : ptr. to null-terminated file pathname
*  Return(1) if match,
*   else, return(0).
*/
register int i ;
register char c1 , c2 ;
 
for (i = NAMSIZ ; i ; i--) {
	c1 = (*p1++) ;
	c2 = (*p2++) ;
	if (((c1 == '\0') && (c2 == '\0')) || (c2 == SLASH)) return(1) ;
	if (c1 != c2) return(0) ;
	}
if ((c2 == '\0') || (c2 == SLASH)) return(1) ;
return(0) ;
}
 
/*		*/
 
dread(dbn,cbuf)
char *cbuf ;
{
/*
*  Read physical block no. 'dbn' from disk and
*    load into array 'cbuf[]'.
*  Return (-1) for i/o error.
*  Else return (0).
*/
register int i , j ;
 
*(RPptr+RP_cr) = RP_DC | RP_GO ; /*  RP06 drive clear function code */
*(RPptr+RP_cyl) = dbn/RP6ST ; /* cylinder no. */
i = dbn%RP6ST ;
j = (i/rpsec)<<8 ; /* track */
*(RPptr+RP_stk) = j | (i%rpsec) ; /* sector : track */
*(mbap+M_bc) = (-BLKSIZ) ;
i = (int)cbuf&0777;
*(mbap+M_var) = i ;
*(mbap+M_map) = (i = 0x80000000 | ((int)((int)cbuf>>9)&07777777)) ;
*(mbap+M_map+1) = (++i) ;
*(RPptr+RP_cr) = RP_RED | RP_GO ; /* read */
 
dwait() ; /* wait for i/o to finish */
if (derror()) { /* error */
	putlin("fboot : disk read error");
	return(-1);
	}
return(0) ; /* normal return */
}
 
/*		*/
 
dwait() {
/*
* Function to wait MBA 0 RP06 disc unit to be ready.
*/
while ((*(RPptr+RP_sr)&RP_DRY) == 0) ;
}
 
/*		*/
 
derror()
{
/*
*  Function to check for MBA 0 RP06 error.
*/
if (*(RPptr+RP_sr) & RP_ERR) return(-1) ;
return(0) ;
}
 
/*		*/
 
init()
{
/*
*  Initialization.
*/
register int i;
 
*(mbap+M_cr) = MBAinit ; /* MBA initialize */
if ((*(RPptr+RP_sr) & RP_MOL) == 0 ){
	putlin("unit not online") ;
	return(-1) ;
	}
*(RPptr+RP_cr) = RP_RIP | RP_GO ; /* readin preset */
*(RPptr+RP_off) = RP_FMT ; /* format bit in offset reg */
bufloc = 0 ;
i = *(RPptr+RP_typ)&0777 ;  /* get disk type */
if (i==RP6typ) { /* RP06 */
	rpsec=RP6SEC; rpcyl=RP6CYL; rptrk=RP6TRK;
	}
else {
	if (i==RM3typ) {
		rpsec=RM3SEC; rpcyl=RM3CYL; rptrk=RM3TRK;
		}
	else return(-1);
	}
return(0) ;
}
 
/*		*/
 
lodfil()
{
/*
*  Function to load a file into low core - disk blocks no.'s
*  which comprise file are in 'addr[]'.
*  Return (-1) if i/o error,
*  else return (0).
*/
 
register int i , j , k , n ;
int dtmp1[128] , dtmp2[128] ;
 
/*  direct  */
for (i = 0 ; i < NADDR-3 ; i++) {
	if ((j = addr[i]) == 0) return(0) ;
	if (dread(j,bufloc)) return(-1) ;
	bufloc += BLKSIZ ;
	}
 
level1 : /*  1st level indirection */
 
if ((j = addr[NADDR-3]) == 0) return(0) ;
/*  read in <=128 blocks into low core */
if ((k = r128(j)) < 0) return(-1) ; /* i/o error */
if (k > 0) return(0) ; /* no more blocks */
 
level2 :
 
if ((j = addr[NADDR-2]) == 0) return(0) ;
if (dread(j,dtmp1)) return(-1) ;
for (i = 0 ; i < 128 ; i++) {
	if ((j = dtmp1[i]) == 0) return(0) ;
	if ((k = r128(j)) < 0) return(-1) ;
	if (k > 0) return(0) ;
	}
 
level3 :
 
if ((j = addr[NADDR-1]) == 0) return(0) ;
if (dread(j,dtmp1)) return(-1) ;
for (i = 0 ; i < 128 ; i++) {
	if ((k = dtmp1[i]) == 0) return(0) ;
	if (dread(k,dtmp2)) return(-1) ;
	for (j = 0 ; j < 128 ; j++) {
		if ((k = dtmp2[j]) == 0) return(0) ;
		if ((n = r128(k)) < 0) return(-1) ;
		if (n > 0) return(0) ;
		}
	}
return(0) ;
}
 
/*		*/
 
r128(blk)
int blk ;
{
/*
*  Read in disk block no. 'blk' into buffer.
*  Then read in the 128 disk blocks whose block no.'s are
*  in the buffer into low core.
*  Stop on a zero block no. - return(1).
*  Return(-1) on i/o error.
*  Return(0) if all 128 blocks read.
*/
int btmp[128] ;
register int i , j ;
 
if (dread(blk,btmp)) return(-1) ;
 
for (i = 0 ; i < 128 ; i++) {
	if ((j = btmp[i]) == 0) return(1) ; /* no more blocks */
	if (dread(j,bufloc)) return(-1) ; /* i/o error */
	bufloc += BLKSIZ ; /* next core block loc */
	}
return(0) ;
}
 
/*		*/
 
halt() {
	asm("	halt") ;
}
 
/*		*/
 
putstr(csp)
register char *csp ;
{
if (putcon(csp)) return(-1) ;
return(0) ;
}
 
/*		*/
 
putlin(sptr)
register char *sptr ;
{
if (putcon(sptr)) return(-1) ;
if (putnl()) return(-1) ;
return(0) ;
}
 
/*		*/
 
nullcon(nn)
register nn ;
{
/*
*  Output 'nn' nulls to console terminal -
*  used for delay.
*/
while (nn--) putc(0) ;
}
 
/*		*/
 
putnl()
{
if (putcon("\r\n")) return(-1) ;
return(0) ;
}
 
/*		*/
 
putcon(csp)
register char *csp ;
{
/*
*  Function to output null-terminated string pointed to 
*  by 'csp' to the VAX LSI terminal.
*/
register c ;
 
c = 0 ;
while (c = (*csp++)) putc(c) ;
return(0) ;
}
 
/*		*/
 
putc(c)
{
/*  wait for LSI printer to be ready */
while ((mfpr(TXCS) & TXCS_RDY) == 0) ;
/*  output character */
mtpr(TXDB,c&0177) ;
}
 
/*		*/
 
getcon(cs)
register char *cs ;
{
/*
*  Function to return char's from VAX LSI keyboard to
*  char array 'cs' - input stops when CR or LF received -
*  null char appended to end of input
*/
register int c , c2 ;
int getc() ;
char *ocs;
 
	ocs=cs;
inloop :
	c = getc() ; /* get 1 char from terminal */
	putc(c) ;  /*  echo char */
	if (c==CDEL) {cs--; goto inloop;}
	if (c==LDEL) {putc(CR);putc(0);putc(NL);cs=ocs; goto inloop;}
	if ((c == NL) || (c == CR)) {
		putc(CR) ;
		putc(0);
		putc(NL) ;
		(*cs++) = '\0' ;
		return(0) ;
		}
	else {
		(*cs++) = c ;
		goto inloop ;
		}
}
 
/*		*/
 
getc()
{
/*
*  Return char from VAX LSI terminal char buffer
*/
int mfpr() ;
 
/*  Wait for receiver done (user entered char)
*/
while ((mfpr(RXCS) & RXCS_DONE) == 0) ;
return (mfpr(RXDB) & 0177) ;  /* return char from receiver buffer */
}
 
/*		*/
 
mtpr(regno,value)
{
	asm("	mtpr	8(ap),4(ap)") ;
}
 
/*		*/
 
mfpr(regno)
{
	asm("	mfpr	4(ap),r0") ;
}
 
/*		*/
 
a2l(as)
register char *as ;
{
/*
*  Convert null-terminated ascii string to binary
*  and return value.
*  1st char in string :
*	0 -> octal
*	x -> hex
*	else decimal
*/
register value , base , sign , digit ;
 
digit = value = sign = 0 ;
base = 10 ;  /* default base */
 
aloop :
if ((digit = (*as++)) == 0) return(value) ; /* null */
 
if (digit == '-') {
	sign++ ;
	goto aloop ;
	}
 
if (digit == '0') base = 8 ;  /* octal base  */
else { if (digit == 'x') base = 16 ;  /* hex base */
	else value = (digit-060) ; /* 060 = '0' */
	}
 
while (digit = (*as++)) {
	if (digit < '0') return(0) ;
	switch (base) {
		case 8 : {
			if (digit > '7') return(0) ;
			digit -= 060 ;
			break ;
			}
		case 10 : {
			if (digit > '9') return(0) ;
			digit -= 060 ;
			break ;
			}
		case 16 : {
			if (digit <= '9') {
				digit -= 060 ;
				break ;
				}
			if ((digit >= 'A') && (digit <= 'F')) {
				digit = (digit - 0101 + 10) ;
					break ;
				}
			if ((digit >= 'a') && (digit <= 'f')) {
				digit = digit - 0141 + 10 ;
				break ;
				}
			return(0) ;
			break ;
			}
		}
	value = (value * base) + digit ;
	}
return (sign ? -value : value) ;
}
 
/*		*/
 
l2a(val,rptr)
register int val ;
register char *rptr ;
{
register int i ;
register char *tp ;
int knt ;
char tmp[20] , sign ;
 
knt = sign = 0 ;
if (val < 0) {
	sign++ ;
	val = (-val) ;
	}
 
tp = tmp ;
loop :
	knt++ ;
	i = val/10  ;  /*  quotient & base 10 */
	(*tp++) = val%10 + '0' ; /*  ascii remainder  */
	val = i ;
	if (val == 0) {
		/*  done  dividing  */
		if (sign) { knt++ ; (*tp++) = '-' ; }
		for (i = knt ; i ; i--)
			(*rptr++) = tmp[i-1] ;
		(*rptr++) = '\0' ;
		return(knt) ;
		}
	else goto loop ;
}
 
/*		*/
 
fexec()
{
/*
*  Part of stand-alone programs that load programs into low core
*  and execute them.
*  This function :
*	1) Checks if user specified file has a header - if so,
*	   move start of exec. file down to 0. If file is 0410,
*	   move data up to page boundary.
*	2) Clear core.
*	3) Jump to user task (calls).
*/
register struct thdr *hdp ;
register char *corep1 , * corep2 ;
register int k ;
int i , stxt , sdat , sbss , clrmin ;
register char *clrmax ;
 
hdp = 0 ;
clrmax = RELOC - 0x400 ; /* last addr+1 to clear */
i = hdp->hmagic ; /* task type code from task header */
entry = 0 ; /* default entry addr if no header */
clrmin = filsiz ;
if ((i != MAG410) && (i != MAG411)) goto clrcor ; /* NO HEADER */
/* file has task header */
entry = hdp->hentry & 017777777777 ;
stxt = hdp->htsiz ; /* no. of text bytes in file */
sdat = hdp->hdsiz ; /* no. data bytes */
sbss = hdp->hbsiz ; /* no. bytes in bss area */
filsiz = stxt + sdat ; /* file size = text + data */
clrmax = filsiz + sbss ; /* new upper limit to clear */
/*  move file down to loc 0 */
corep1 = 0 ;
corep2 = sizeof(struct thdr) ;
for (k = filsiz ; k ; k--)
	(*corep1++) = (*corep2++) ;
clrmin = stxt ;
/* If 0410 file , move data up to page boundary */
if ((i == MAG410) && sdat ) { /*  0410  */
	i = corep2 = ((stxt + 511) & 017777777000) ; /* page boundary */
	corep2 += sdat ; /* end+1 of new data area */
	corep1 = filsiz ; /* end+1 of current data area */
	for (k = sdat ; k ; k--) /* move data up */
		*(--corep2) = *(--corep1) ;
	clrmax += (i-stxt) ; /* adjust upper limit for moved data */
	clrmin = i + sdat ;
	}
/*  clear  core  */
clrcor :
for (corep1 = clrmin ; corep1 < clrmax ; corep1++)
	*corep1 = 0 ;
 
/*  execute  user  task  */
 
asm("	.globl	_entry") ;
asm("	calls	$0,*_entry") ;
 
/*		*/
 
return(0) ;
}
 
/*		*/
 
reloc()
{
extern edata ;
register int *to , *from , i ;
 
mtpr(KSP,RELOC) ; /* set stack */
from = 0 ;
to = RELOC ;
for (i = (int)&edata-RELOC ; i > 0 ; i -= (sizeof *from))
	(*to++) = (*from++) ;
}
 
/*		*/
 
l2x(val,rptr)
register int val ;
register char *rptr ;
{
register int i , j ;
register char *tp ;
int knt ;
char tmp[20] , sign ;
 
knt = sign = 0 ;
if (val < 0) {
	sign++ ;
	val = (-val) ;
	}
 
tp = tmp ;
loop :
	knt++ ;
	i = val/16  ;  /*  quotient & base 16 */
	j = val%16 ;
	(*tp++) = j + (j<10?0x30:0x57) ;
	val = i ;
	if (val == 0) {
		/*  done  dividing  */
		if (sign) { knt++ ; (*tp++) = '-' ; }
		for (i = knt ; i ; i--)
			(*rptr++) = tmp[i-1] ;
		(*rptr++) = '\0' ;
		return(knt) ;
		}
	else goto loop ;
}