home *** CD-ROM | disk | FTP | other *** search
- /*****************************************************************************/
- /* */
- /* ls - a Unix-like directory listing program for MS-DOS 2.x + */
- /* */
- /* 1984 R. Edward Nather */
- /* 1985 Larry A. Shurr */
- /* 1989 R. Ray Hensel Comverted to Microsoft V.5 */
- /* cleaned up code */
- /* added many coments */
- /* replaced heap sort with quick sort */
- /* */
- /*****************************************************************************/
- #include <stdio.h>
- #include <dos.h>
-
- char* malloc(); /*Assure that malloc() is char* type*/
-
- typedef int bool; /*Define "boolean" type*/
- typedef int mchar; /*Define "metachar" type*/
-
- #define FALSE 0 /*Define "false" value*/
- #define TRUE 1 /*Define "true" value*/
- #define is == /*Define some readable stuff*/
- #define isnot !=
- #define and &&
- #define or ||
- /*****************************************************************************/
- /* customizing constants */
-
- #define NAMESIZ 13 /*12 character name + NULL*/
- #define ONECS 512 /*cluster size on one-sided floppy*/
- #define TWOCS 1024 /*cluster size on two-sided floppy*/
- #define HARDCS 4096 /*cluster size on hard disk*/
- #define SCRSIZ 22 /*scrolling size of display screen*/
- /*****************************************************************************/
- struct dta { /*DOS Disk Transfer Address table*/
- char reserved[21]; /*used in "find next" operation*/
- char attr; /*file attribute byte*/
- unsigned ftime; /*time of last modification*/
- unsigned fdate; /*date of last modification*/
- long fsize; /*file size in bytes*/
- char fname[NAMESIZ]; /*filename and extension*/
- };
- typedef struct dta DTA;
- /*****************************************************************************/
- struct outbuf { /*output buffer -- array of file info*/
- unsigned oattr; /*file attributes*/
- unsigned odate; /*file creation/modification date*/
- unsigned otime; /*file creation/modification time*/
- long osize; /*file size in bytes*/
- char oname[NAMESIZ+1]; /*file name*/
- } *obuf;
- typedef struct outbuf OUTBUF;
- /*****************************************************************************/
- char spath[80]; /*holds current pathname string*/
-
- mchar qs = '\\'; /*filename separator character*/
- char dqs[] = "\\"; /*filename separator (string)*/
-
- bool df_all = FALSE, /*1 => show hidden files by default*/
- df_colm = FALSE, /*1 => 1-column listing by default*/
- df_du = FALSE, /*1 => include disk use by default*/
- df_id = FALSE, /*1 => always identify directory*/
- df_long = FALSE, /*1 => long listing by default*/
- df_more = FALSE, /*1 => pause for more*/
- df_rsort = FALSE, /*1 => reverse sort by default*/
- df_tsort = FALSE, /*1 => time sort by default*/
- f_help = FALSE /*Help screen requested*/
- ;
- /* global variables and flags */
-
- int f_all, /*Include hidden & system files*/
- f_colm, /*1-column format*/
- f_du, /*Print disk usage*/
- f_long, /*Long listing*/
- f_rsort, /*Reverse sort*/
- f_tsort, /*Timesort the listing*/
- f_more = FALSE, /*Pause for more*/
- f_recd = FALSE, /*Recursive descent requested*/
- f_so = FALSE, /*Print sizes only*/
- f_tsc = FALSE, /*Output is to console screen*/
- np, /*number of groups printed*/
- nargs, /*number of non-option arguments*/
- clsize = 0, /*size of a cluster, in bytes*/
- clmask, /*clsize-1 for rounding & chopping*/
- drive /*code number for drive requested*/
- ;
-
- long left, /*unused space left on disk*/
- total /*total of sizes encountered*/
- ;
- /*****************************************************************************/
- main(argc, argv)
- int argc;
- char *argv[];
- {
- char *s
- ;
- int c = 0,
- nt = 0
- ;
-
- setbuf(stdout,malloc(BUFSIZ)); /*Force standard output buffered*/
-
- df_all = envbool("LSALL",df_all); /*1 => show hidden files by default*/
- df_colm = envbool("LSCOLM",df_colm); /*1 => 1-column listing by default*/
- df_du = envbool("LSDU",df_du); /*1 => include disk use by default*/
- df_id = envbool("LSID",df_id); /*1 => always identify directory*/
- df_long = envbool("LSLONG",df_long); /*1 => long listing by default*/
- df_more = envbool("LSMORE",df_more); /*1 => pause for more*/
- df_rsort = envbool("LSRSORT",df_rsort);/*1 => reverse sort by default*/
- df_tsort = envbool("LSTSORT",df_tsort);/*1 => time sort by default*/
-
- qs = envchar("LSQS",qs); /*Get default directory separator*/
- dqs[0] = qs;
-
- f_all = df_all; /*include hidden & system files*/
- f_colm = df_colm; /*1-column format*/
- f_du = df_du; /*print disk usage*/
- f_long = df_long; /*long listing*/
- f_more = df_more; /*pause for more*/
- f_rsort = df_rsort; /*reverse sort*/
- f_tsort = df_tsort; /*timesort the listing*/
-
- f_recd = FALSE; /*recursive descent requested*/
- f_so = FALSE; /*print sizes only*/
-
- /* process input options */
-
- while(--argc > 0 and (*++argv)[0] is '-') {
- for(s = argv[0]+1; *s isnot '\0'; s++) {
- switch(*s) {
- case 'a': /*-a: list all files*/
- f_all = !f_all;
- break;
- case 'c': /*-c: 1-column listing requested*/
- f_colm = !f_colm;
- break;
- case 'h': /*-h: print help and quit*/
- f_help = TRUE;
- break;
- case 'l': /*-l: long listing requested*/
- f_long = !f_long;
- break;
- case 'm': /*-m: pause for more*/
- f_more = TRUE;
- break;
- case 'r': /*-r: reverse sort direction*/
- f_rsort = !f_rsort;
- break;
- case 's': /*-s: print sizes only*/
- f_so = TRUE;
- if (*(s+1) is '1') {
- clsize = ONECS; /*diskuse for 1-sided floppy*/
- s++;
- nt++;
- }
- else if (*(s+1) is '2') {
- clsize = TWOCS; /*or 2-sided*/
- s++;
- nt++;
- }
- break;
- case 't': /*-t: time sort requested*/
- f_tsort = !f_tsort;
- break;
- case 'u': /*-u: print disk usage*/
- f_du = !f_du;
- break;
- case 'R': /*-R: recursively list subdirs*/
- f_recd = TRUE;
- break;
- default:
- fprintf(stderr, "unknown arg %c\n", *s);
- exit(1);
- }
- }
- }
-
- if (f_help) { /*If help requested...*/
- give_help(); /*Print the help stuff*/
- return; /*And quit now*/
- } /*Otherwise, continue...*/
-
- nargs = argc;
- f_tsc = toscreen(); /*find out if output is to console*/
-
- obuf = (OUTBUF*)malloc(32767); /*point to free memory*/
-
- if(argc is 0) {
- argc++;
- curdrv(spath); /*default to current drive*/
- }
- else
- strcpy(spath, *argv);
-
- for(;;) { /*cycle through args present*/
- int i = 0; /*spath editing index*/
- int l = strlen(spath); /*spath string length*/
-
- /*the following while loop should be looked at carefully if things*/
- /*don't look right. compilers don't process the increment the same*/
- while (i < l) /*Scan spath string...*/
- spath[i++] = tolower(spath[i]); /*Convert to lower case*/
-
- if (spath[1] is ':' and spath[2] is '\0') /*If path is drive only */
- getpath(spath); /*get path*/
- if (f_du or f_so or f_long) /*If we need the usage data...*/
- c = getsize(spath); /*get it*/
- if (c is 0)
- search(spath); /*go do the hard work*/
- if (--argc > 0)
- strcpy(spath, *++argv);
- else {
- if (f_du or f_so) {
- if(np > 1) {
- fprintf(stdout,"-------\n%7ld bytes total",total);
- if(!nt)
- fputs("; ", stdout);
- }
- if(!nt)
- fprintf(stdout,"%7ld bytes left on drive %c\n",left,drive+'a');
- }
- break;
- }
- }
- }
- /******************************************************************************/
- getsize(path) /*get file cluster size*/
- char *path;
- {
- if (clsize is 0) /*if size not already set*/
- if ((clsize = getcl(path)) is 0) { /*get cluster size for drive*/
- fprintf(stderr,"Invalid drive: %c\n",*path);
- return(1); /*also, return error flag*/
- } /*But if we got the cluster size...*/
- clmask = clsize-1; /*Set the cluster size mask as well*/
- return(0); /*And return a 'no error' flag*/
- }
- /******************************************************************************/
- toscreen() /*find out if output is to console*/
- {
- struct { /*Register file for IOCTL*/
- int ax;
- int bx;
- int cx;
- int dx;
- int si;
- int di;
- int ds;
- int es;
- } r;
-
- r.ax = 0x4400; /*Set 'i/o control' function code*/
- r.bx = 1; /*Set 'get device info' subfunction*/
- sysint(0x21, &r, &r); /*Call DOS*/
- return((int)r.dx & 1); /*Return 'is console input' status*/
- }
- /*****************************************************************************/
- search(path) /*search for filename or directory*/
- char *path;
- {
- DTA dta; /*DOS file data table*/
- int path_len; /*length of initial path*/
- int z; /*char counter*/
- int k = 0; /*counts number of entries found*/
- char work[80]; /*working path string*/
- int comp(); /*string, time comparison routine*/
- int mask = 0x0010; /*attribute mask*/
- long bytes = 0; /*count of disk usage this directory*/
-
- if (f_all) mask = 0x001F;
- strcpy(work,path);
- path_len = strlen(work); /*save original path length*/
-
- if (!find_first(work, &dta, 0) or work[path_len-1] is qs) {
- if(work[path_len-1] isnot qs) {
- strcat(work, dqs); /*if path is to a directory*/
- path_len++;
- }
- strcat(work,"*.*"); /*list everything in it*/
- }
-
- if(find_first(work, &dta, mask)) {
- do {
- if (dta.attr & 0x08) /*ignore volume label*/
- continue;
- if (dta.fname[0] is '.' and !f_all) /*unless -a option*/
- continue; /*ignore "." and ".."*/
-
- obuf[k].oattr = dta.attr; /*stash this entry*/
- obuf[k].otime = dta.ftime;
- obuf[k].odate = dta.fdate;
- obuf[k].osize = dta.fsize;
- strcpy(obuf[k].oname, dta.fname);
-
- if (f_du or f_so) {
- if((dta.attr & 0x10) and dta.fname[0] isnot '.') {
- bytes += clsize; /*sum up disk usage*/
- }
- else if(dta.fsize) {
- obuf[k].osize = ((dta.fsize + clmask) & (long)(~clmask));
- bytes += obuf[k].osize;
- }
- }
- k++;
- } while(!find_next(&dta));
- }
- else {
- work[path_len-1] = NULL;
- fprintf(stderr, "Can't find a file or directory named \"%s\"\n", work);
- return;
- }
-
- work[path_len] = NULL; /*restore directory pathname*/
- if(np++ and !f_so) {
- fputc(endlin(),stdout); /*separate listing blocks*/
- if (f_tsc)
- fflush(stdout);
- }
- if (f_du or f_so) {
- total += bytes; /*total bytes to date*/
- fprintf(stdout, "%7ld ", bytes);
- }
- if (f_recd or nargs > 1 or f_du or f_so or df_id) {
- fprintf(stdout, "%s", work); /*identify the block*/
- fputc(endlin(),stdout);
- if (f_tsc)
- fflush(stdout);
- }
-
- if (!f_so) {
- qsort(obuf,k,sizeof(obuf[0]),comp); /*sort the entries*/
- if (f_long)
- longlist(k); /*and print them*/
- else
- shortlist(k);
- }
-
- if (!f_recd)
- return; /*quit if not -R*/
-
- strcat(work, "*.*");
- if(find_first(work, &dta, mask)) /*else find all sub-dirs*/
- do {
- if(dta.attr & 0x10 and dta.fname[0] isnot '.') {
- work[path_len] = 0; /*discard old name */
- for(z=0; dta.fname[z] isnot NULL; z++)
- dta.fname[z] = tolower(dta.fname[z]);
- strcat(work, dta.fname);/*install a new one*/
- strcat(work, dqs);
- search(work); /*and recurse*/
- }
- } while(!find_next(&dta));
- return;
- }
- /******************************************************************************/
- getpath(sp) /*get drive's current directory path*/
- char *sp;
- {
- struct { /*Register file for GET PATH*/
- int ax;
- int bx;
- int cx;
- int dx;
- char* si;
- int di;
- int ds;
- int es;
- } r;
-
- strcat(sp, dqs); /*append root symbol to drive name*/
-
- /*Prepare to get current path...*/
- r.ax = 0x4700; /*Set 'get current directory' code*/
- r.dx = *sp - '`'; /*Set drive number*/
- r.ds = _showds(); /*Set pathname buffer segment*/
- r.si = sp + 3; /*Set pathname buffer offset*/
- sysint(0x21, &r, &r); /*Call DOS*/
- }
- /******************************************************************************/
- /*getcl - get cluster size & space left on requested drive*/
- getcl(pp)
- char *pp;
- {
- int cs;
-
- if(*(pp+1) is ':') /*use specified drive if any*/
- cs = *pp - 'a';
- else {
- cs = disk_drive();
- }
- drive = cs & 0x7F;
- if (!f_du and !f_so and drive is 2)
- return(HARDCS);
-
- return(disk_left(cs + 1));
-
- }
-
- /******************************************************************************/
- /*comp - compare size of two entries*/
- comp(a,b)
- struct outbuf *a, *b;
- {
- int y;
-
- if (f_tsort) {
- if(a->odate isnot b->odate) /*if dates differ*/
- y = (a->odate < b->odate) ? -1 : 1; /*that settles it*/
- else
- y = (a->otime < b->otime) ? -1 : 1; /*else compare times*/
- return((f_rsort) ? y : -y);
- }
- else {
- y = strcmp(a->oname, b->oname); /*name comparison*/
- return((f_rsort) ? -y : y);
- }
- }
- /******************************************************************************/
- shortlist(k) /*List filenames in 1-to-5 columns*/
- int k; /*total number to print*/
- {
- int i,
- m,
- n
- ;
-
- if (f_colm) { /*If single-column output selected...*/
- n = k; /*Set for 1-column listing*/
- } /*Otherwise...*/
- else
- n = (k + 4)/5; /*Set for 2-to-5-column listing*/
- for (i = 0; i < n; i++) { /*Scan the directory buffer...*/
- for (m = 0; (i+m) < k; m += n) { /*Scan a row of data...*/
- if (obuf[i+m].oattr & 0x10) { /*If file is a directory file...*/
- strcat(obuf[i+m].oname,dqs); /*Mark it so*/
- } /*Otherwise, continue...*/
- putname(i+m); /*Print the name*/
- fputs(" ", stdout); /*Output blanks to the next column*/
- } /*End of scan-a-row*/
- fputc(endlin(),stdout); /*Output an end-of-line to stdout*/
- if (f_tsc)
- fflush(stdout); /*Force line out if stdout is console*/
- } /*End of scan-directory-buffer*/
- }
- /******************************************************************************/
- /*putname - convert name to lower case and print*/
- putname(i)
- int i;
- {
- int c,
- j = 0
- ;
-
- while ((c = tolower(obuf[i].oname[j])) isnot 0) {
- fputc(c, stdout);
- j++;
- }
- while (j++ < NAMESIZ - 1) { /*pad to columnarize*/
- fputc(' ', stdout);
- }
- }
- /******************************************************************************/
- static int lc = 0; /*line counter*/
- endlin() /*End line & watch for screen full*/
- {
- int key; /*Gets user key-in*/
-
- if (f_tsc and f_more and ++lc >= SCRSIZ) { /*If output is to screen and...*/
- /*If pause for more flag set and...*/
- /*If we have shown a screenful...*/
- fputs("\n--More--",stdout); /*Pause and...*/
- fflush(stdout); /*Force output to standard output*/
- key = ci(); /*Await user action code*/
-
- fputs("\b\b\b\b\b\b\b\b \b\b\b\b\b\b\b",stdout);
- /*Blank it out*/
-
- switch(key) { /*Do what user asked for...*/
- case '\r': /*<RETURN> - show 1 more line*/
- lc = SCRSIZ - 1;
- break;
- case 'q': /*quit with "q" or "ctrl-C"*/
- case '\003':
- exit(0);
- default: /*else show another screenful*/
- lc = 0;
- break;
- }
- return('\b');
- }
- else return('\n');
- }
- /******************************************************************************/
- struct llst { /*structure to hold file information*/
- char *fattr; /*file attribute pointer*/
- long size; /*file size*/
- int day; /*the day of creation*/
- int mnum; /*month number*/
- int yr;
- int hh; /*creation times*/
- int mm;
- int ap; /*am or pm*/
- } l;
-
- longlist(k) /*list everything about the files*/
- int k; /*total number to list*/
- {
-
- int i, m, n, cdate;
- char *mon, *mname();
-
- cdate = gcdate(); /*get current date (in months)*/
- if (f_colm) {
- n = k; /*set for 1 column listing*/
- }
- else
- n = (k + 1)/2; /*or for 2 column listing*/
- for (i = 0; i < n; i++) {
- for (m = 0; (m+i) < k; m += n) {
- fill(i+m, &l); /*fill llst structure*/
- mon = mname(l.mnum); /*conv month # to name*/
- fprintf(stdout,"%s%7ld %2d %s ",l.fattr,l.size,l.day, mon);
- if (cdate >= (l.yr * 12 +l.mnum) + 12)
- fprintf(stdout, " %4d ",l.yr); /*print year if too old*/
- else
- fprintf(stdout,"%2d:%02d%c ",l.hh,l.mm,l.ap);
- putname(i+m);
- if (m+n < k)
- fputs("\272 ",stdout); /*double bar separator*/
- }
- fputc(endlin(),stdout);
- if (f_tsc)
- fflush(stdout);
- }
- }
- /******************************************************************************/
- /*fill - fill long list structure with file information*/
-
- fill(i, ll)
- int i;
- struct llst *ll;
- {
- int j, k;
- static char fbuf[16][4] = {"--w","---","-hw","-h-","s-w","s--","shw","sh-",
- "d-w","d--","dhw","dh-","d-w","d--","dhw","dh-"};
-
- if((obuf[i].oattr & 0x10) and obuf[i].oname[0] isnot '.') {
- ll->size = clsize;
- j = 8; /*if directory, use block size*/
- } /*and set dir attr offset*/
- else {
- ll->size = obuf[i].osize; /*else use file size*/
- j = 0; /*and file attr offset*/
- }
- ll->fattr = fbuf[(obuf[i].oattr & 0x07) + j]; /*point to symbolic attr*/
- ll->day = obuf[i].odate & 0x1F;
- ll->mnum = (obuf[i].odate >> 5) & 0x0F;
- ll->yr = (obuf[i].odate >> 9) + 1980;
- k = obuf[i].otime >> 5; /*this is a mess*/
- ll->mm = k & 0x3f;
- ll->ap = ((ll->hh = k >> 6) >= 12) ? 'p' : 'a';
- if(ll->hh > 12)
- ll->hh -= 12;
- if(ll->hh is 0)
- ll->hh = 12;
- return;
- }
- /*****************************************************************************/
- char* mname(n) /*convert month number to month name*/
- int n;
- {
- /*Array of month names*/
- static char *name[] = { "???","Jan","Feb","Mar","Apr","May","Jun",
- "Jul","Aug","Sep","Oct","Nov","Dec" };
-
- return((n < 1 or n > 12) ? name[0] : name[n]);
- }
- /*****************************************************************************/
- #define onoff(arg) arg ? "ON " : "OFF"
- #define forrev(arg) arg ? "REVERSE" : "FORWARD"
-
- give_help() /*Give some help to one who needs it*/
- {
- printf("\n");
- printf(" -a Reverse the \"list all files\" option default\n");
- printf(" -c Reverse the \"one-column listing\" option default\n");
- printf(" -h Print this help message and the default settings\n");
- printf(" -l Reverse the \"long listing\" option default\n");
- printf(" -m Reverse the \"pause for more\" option default\n");
- printf(" -r Reverse the default sort direction\n");
- printf(" -R Recursively list sub-directories\n");
- printf(" -s Print sizes\n");
- printf(" -s1 Print sizes for single-sided diskette\n");
- printf(" -s2 Print sizes for double-sided diskette\n");
- printf(" -t Reverse the \"time sort\" option default\n");
- printf(" -u Reverse the \"include disk usage\" option default\n");
- printf("\n");
-
- printf("List all files default is %3s ",onoff(df_all));
- printf("Default sort direction is %7s \n",forrev(df_rsort));
-
- printf("One-column listing default is %3s ",onoff(df_colm));
- printf("Include disk usage default is %3s \n",onoff(df_du));
-
- printf("Long listing default is %3s ",onoff(df_long));
- printf("Time sort option default is %3s \n",onoff(df_tsort));
-
- printf("Pause for more default is %3s ",onoff(df_more));
- printf("Always print dir default is %3s \n",onoff(df_id));
-
- printf("\n");
- }
- /*****************************************************************************/
- /* */
- /* Compiler-specific code for */
- /* */
- /* Microsoft version 5.0 and later */
- /* */
- /*****************************************************************************/
-
-
- struct sysreg { /*Generic register file*/
- int ax;
- int bx;
- int cx;
- int dx;
- int si;
- int di;
- int ds;
- int es;
- };
- typedef struct sysreg SYSREG;
-
- int _stack = 3072;
- char* getenv();
-
- /*********************************/
-
- gcdate() /*get current date for comparison*/
- {
- struct dosdate_t date;
- _dos_getdate(&date);
- return((int)date.year * 12 + date.month); /*Return date in months*/
- }
-
- /*********************************/
-
- find_first(path, dta, mask) /*find first file in directory*/
- char *path;
- struct find_t *dta;
- int mask;
- {
- return(!(_dos_findfirst(path, mask , dta)));
- }
-
- /*********************************/
-
- find_next(dta) /*find next file in same directory*/
- struct find_t *dta;
- {
- return(_dos_findnext(dta));
- }
-
- /*********************************/
-
- curdrv(sp) /*get current default drive*/
- char *sp;
- {
- *sp++ = disk_drive() + 'a';
- *sp++ = ':';
- }
- /*********************************/
-
- int _showds() /*Obtain DS register value*/
- {
- struct SREGS sfile;
-
- segread(&sfile);
-
- return(sfile.ds);
- }
-
- /*********************************/
-
- bool envbool(env,val) /*Get environment boolean*/
- char* env;
- bool val;
- {
- char* envval = getenv(env);
-
- if (*envval)
- val = *envval-'0';
- return(val);
- }
-
- /*********************************/
-
- mchar envchar(env,val) /*Get environment character*/
- char* env;
- mchar val;
- {
- char* envval = getenv(env);
-
- if (*envval)
- val = *envval;
- return(val);
- }
-
- /*********************************/
-
- ci() /*Quick and dirty 'console input'*/
- {
- return( (bdos(0xc,0,7)) & 0xFF);
- }
-
- /*********************************/
-
- sysint(interupt,r_in,r_out) /*Interface sysint() to int86x()*/
- int interupt;
- SYSREG* r_in;
- SYSREG* r_out;
- {
- int result;
-
- union REGS rfile;
- struct SREGS sfile;
-
- segread(&sfile);
-
- rfile.x.ax = r_in->ax;
- rfile.x.bx = r_in->bx;
- rfile.x.cx = r_in->cx;
- rfile.x.dx = r_in->dx;
- rfile.x.si = r_in->si;
- rfile.x.di = r_in->di;
-
- sfile.ds = r_in->ds;
- sfile.es = r_in->es;
-
- result = int86x(interupt,&rfile,&rfile,&sfile);
-
- r_out->ax = rfile.x.ax;
- r_out->bx = rfile.x.bx;
- r_out->cx = rfile.x.cx;
- r_out->dx = rfile.x.dx;
- r_out->si = rfile.x.si;
- r_out->di = rfile.x.di;
-
- r_out->ds = sfile.ds;
- r_out->es = sfile.es;
-
- return(result);
- }
-
- /*********************************/
-
- disk_drive()
- {
- unsigned drive;
- _dos_getdrive(&drive);
- return(drive - 1);
- }
-
- /*********************************/
-
- disk_left(drive)
- int drive;
- {
- struct diskfree_t s;
-
- if(_dos_getdiskfree(drive,&s))
- return(0);
- left = (long)s.avail_clusters * (long)s.sectors_per_cluster *
- (long)s.bytes_per_sector;
-
- return(s.sectors_per_cluster * s.bytes_per_sector);
- }
-
-
- /*****************************************************************************/
- /******************** end of code ******************************************/
- /******************** end of code ******************************************/
- /******************** end of code ******************************************/
- /*****************************************************************************/