Added basic implementation of destroying a GPT table: just delete the
[AROS.git] / tools / adflib / adf_file.c
blob37df6f859c6c5cb8cfcb5c834d15281cbd4ec440
1 /*
2 * ADF Library. (C) 1997-1999 Laurent Clevy
4 * adf_file.c
6 * file code
7 */
9 #include<stdlib.h>
10 #include<string.h>
12 #include"adf_util.h"
13 #include"adf_file.h"
14 #include"adf_str.h"
15 #include"defendian.h"
16 #include"adf_raw.h"
17 #include"adf_disk.h"
18 #include"adf_dir.h"
19 #include"adf_bitm.h"
20 #include"adf_cache.h"
22 extern struct Env adfEnv;
24 void adfFileTruncate(struct Volume *vol, SECTNUM nParent, char *name)
31 * adfFileFlush
34 void adfFlushFile(struct File *file)
36 struct bEntryBlock parent;
37 struct bOFSDataBlock *data;
39 if (file->currentExt) {
40 if (file->writeMode)
41 adfWriteFileExtBlock(file->volume, file->currentExt->headerKey,
42 file->currentExt);
44 if (file->currentData) {
45 if (file->writeMode) {
46 file->fileHdr->byteSize = file->pos;
47 if (isOFS(file->volume->dosType)) {
48 data = (struct bOFSDataBlock *)file->currentData;
49 data->dataSize = file->posInDataBlk;
51 if (file->fileHdr->byteSize>0)
52 adfWriteDataBlock(file->volume, file->curDataPtr,
53 file->currentData);
56 if (file->writeMode) {
57 file->fileHdr->byteSize = file->pos;
58 //printf("pos=%ld\n",file->pos);
59 adfTime2AmigaTime(adfGiveCurrentTime(),
60 &(file->fileHdr->days),&(file->fileHdr->mins),&(file->fileHdr->ticks) );
61 adfWriteFileHdrBlock(file->volume, file->fileHdr->headerKey, file->fileHdr);
63 if (isDIRCACHE(file->volume->dosType)) {
64 //printf("parent=%ld\n",file->fileHdr->parent);
65 adfReadEntryBlock(file->volume, file->fileHdr->parent, &parent);
66 adfUpdateCache(file->volume, &parent, (struct bEntryBlock*)file->fileHdr,FALSE);
68 adfUpdateBitmap(file->volume);
74 * adfGetFileBlocks
77 RETCODE adfGetFileBlocks(struct Volume* vol, struct bFileHeaderBlock* entry,
78 struct FileBlocks* fileBlocks)
80 long n, m;
81 SECTNUM nSect;
82 struct bFileExtBlock extBlock;
83 long i;
85 fileBlocks->header = entry->headerKey;
86 adfFileRealSize( entry->byteSize, vol->datablockSize,
87 &(fileBlocks->nbData), &(fileBlocks->nbExtens) );
89 fileBlocks->data=(SECTNUM*)malloc(fileBlocks->nbData * sizeof(SECTNUM));
90 if (!fileBlocks->data) {
91 (*adfEnv.eFct)("adfGetFileBlocks : malloc");
92 return RC_MALLOC;
95 fileBlocks->extens=(SECTNUM*)malloc(fileBlocks->nbExtens * sizeof(SECTNUM));
96 if (!fileBlocks->extens) {
97 (*adfEnv.eFct)("adfGetFileBlocks : malloc");
98 return RC_MALLOC;
101 n = m = 0;
102 /* in file header block */
103 for(i=0; i<entry->highSeq; i++)
104 fileBlocks->data[n++] = entry->dataBlocks[MAX_DATABLK-1-i];
106 /* in file extension blocks */
107 nSect = entry->extension;
108 while(nSect!=0) {
109 fileBlocks->extens[m++] = nSect;
110 adfReadFileExtBlock(vol, nSect, &extBlock);
111 for(i=0; i<extBlock.highSeq; i++)
112 fileBlocks->data[n++] = extBlock.dataBlocks[MAX_DATABLK-1-i];
113 nSect = extBlock.extension;
115 if ( (fileBlocks->nbExtens+fileBlocks->nbData) != (n+m) )
116 (*adfEnv.wFct)("adfGetFileBlocks : less blocks than expected");
118 return RC_OK;
122 * adfFreeFileBlocks
125 RETCODE adfFreeFileBlocks(struct Volume* vol, struct bFileHeaderBlock *entry)
127 int i;
128 struct FileBlocks fileBlocks;
129 RETCODE rc = RC_OK;
131 adfGetFileBlocks(vol,entry,&fileBlocks);
133 for(i=0; i<fileBlocks.nbData; i++) {
134 adfSetBlockFree(vol, fileBlocks.data[i]);
136 for(i=0; i<fileBlocks.nbExtens; i++) {
137 adfSetBlockFree(vol, fileBlocks.extens[i]);
140 free(fileBlocks.data);
141 free(fileBlocks.extens);
143 return rc;
148 * adfFileRealSize
150 * Compute and return real number of block used by one file
151 * Compute number of datablocks and file extension blocks
154 long adfFileRealSize(unsigned long size, int blockSize, long *dataN, long *extN)
156 long data, ext;
158 /*--- number of data blocks ---*/
159 data = size / blockSize;
160 if ( size % blockSize )
161 data++;
163 /*--- number of header extension blocks ---*/
164 ext = 0;
165 if (data>MAX_DATABLK) {
166 ext = (data-MAX_DATABLK) / MAX_DATABLK;
167 if ( (data-MAX_DATABLK) % MAX_DATABLK )
168 ext++;
171 if (dataN)
172 *dataN = data;
173 if (extN)
174 *extN = ext;
176 return(ext+data+1);
181 * adfWriteFileHdrBlock
184 RETCODE adfWriteFileHdrBlock(struct Volume *vol, SECTNUM nSect, struct bFileHeaderBlock* fhdr)
186 unsigned char buf[512];
187 unsigned long newSum;
188 RETCODE rc = RC_OK;
189 //printf("adfWriteFileHdrBlock %ld\n",nSect);
190 fhdr->type = T_HEADER;
191 fhdr->dataSize = 0;
192 fhdr->secType = ST_FILE;
194 memcpy(buf, fhdr, sizeof(struct bFileHeaderBlock));
195 #ifdef LITT_ENDIAN
196 swapEndian(buf, SWBL_FILE);
197 #endif
198 newSum = adfNormalSum(buf,20,sizeof(struct bFileHeaderBlock));
199 swLong(buf+20, newSum);
200 // *(unsigned long*)(buf+20) = swapLong((unsigned char*)&newSum);
202 adfWriteBlock(vol, nSect, buf);
204 return rc;
209 * adfFileSeek
212 void adfFileSeek(struct File *file, unsigned long pos)
214 SECTNUM extBlock, nSect;
215 unsigned long nPos;
216 int i;
218 nPos = min(pos, file->fileHdr->byteSize);
219 file->pos = nPos;
220 extBlock = adfPos2DataBlock(nPos, file->volume->datablockSize,
221 &(file->posInExtBlk), &(file->posInDataBlk), &(file->curDataPtr) );
222 if (extBlock==-1) {
223 adfReadDataBlock(file->volume,
224 file->fileHdr->dataBlocks[MAX_DATABLK-1-file->curDataPtr],
225 file->currentData);
227 else {
228 nSect = file->fileHdr->extension;
229 i = 0;
230 while( i<extBlock && nSect!=0 ) {
231 adfReadFileExtBlock(file->volume, nSect, file->currentExt );
232 nSect = file->currentExt->extension;
234 if (i!=extBlock)
235 (*adfEnv.wFct)("error");
236 adfReadDataBlock(file->volume,
237 file->currentExt->dataBlocks[file->posInExtBlk], file->currentData);
243 * adfFileOpen
246 struct File* adfOpenFile(struct Volume *vol, char* name, char *mode)
248 struct File *file;
249 SECTNUM nSect;
250 struct bEntryBlock entry, parent;
251 BOOL write;
252 char filename[200];
254 write=( strcmp("w",mode)==0 || strcmp("a",mode)==0 );
256 if (write && vol->dev->readOnly) {
257 (*adfEnv.wFct)("adfFileOpen : device is mounted 'read only'");
258 return NULL;
261 adfReadEntryBlock(vol, vol->curDirPtr, &parent);
263 nSect = adfNameToEntryBlk(vol, parent.hashTable, name, &entry, NULL);
264 if (!write && nSect==-1) {
265 sprintf(filename,"adfFileOpen : file \"%s\" not found.",name);
266 (*adfEnv.wFct)(filename);
267 //fprintf(stdout,"filename %s %d, parent =%d\n",name,strlen(name),vol->curDirPtr);
268 return NULL;
270 if (!write && hasR(entry.access)) {
271 (*adfEnv.wFct)("adfFileOpen : access denied"); return NULL; }
272 /* if (entry.secType!=ST_FILE) {
273 (*adfEnv.wFct)("adfFileOpen : not a file"); return NULL; }
274 if (write && (hasE(entry.access)||hasW(entry.access))) {
275 (*adfEnv.wFct)("adfFileOpen : access denied"); return NULL; }
276 */ if (write && nSect!=-1) {
277 (*adfEnv.wFct)("adfFileOpen : file already exists"); return NULL; }
279 file = (struct File*)malloc(sizeof(struct File));
280 if (!file) { (*adfEnv.wFct)("adfFileOpen : malloc"); return NULL; }
281 file->fileHdr = (struct bFileHeaderBlock*)malloc(sizeof(struct bFileHeaderBlock));
282 if (!file->fileHdr) {
283 (*adfEnv.wFct)("adfFileOpen : malloc");
284 free(file); return NULL;
286 file->currentData = malloc(512*sizeof(unsigned char));
287 if (!file->currentData) {
288 (*adfEnv.wFct)("adfFileOpen : malloc");
289 free(file->fileHdr); free(file); return NULL;
292 file->volume = vol;
293 file->pos = 0;
294 file->posInExtBlk = 0;
295 file->posInDataBlk = 0;
296 file->writeMode = write;
297 file->currentExt = NULL;
298 file->nDataBlock = 0;
300 if (strcmp("w",mode)==0) {
301 memset(file->fileHdr,0,512);
302 adfCreateFile(vol,vol->curDirPtr,name,file->fileHdr);
303 file->eof = TRUE;
305 else if (strcmp("a",mode)==0) {
306 memcpy(file->fileHdr,&entry,sizeof(struct bFileHeaderBlock));
307 file->eof = TRUE;
308 adfFileSeek(file, file->fileHdr->byteSize);
310 else if (strcmp("r",mode)==0) {
311 memcpy(file->fileHdr,&entry,sizeof(struct bFileHeaderBlock));
312 file->eof = FALSE;
315 //puts("adfOpenFile");
316 return(file);
321 * adfCloseFile
324 void adfCloseFile(struct File *file)
327 if (file==0)
328 return;
329 //puts("adfCloseFile in");
331 adfFlushFile(file);
333 if (file->currentExt)
334 free(file->currentExt);
336 if (file->currentData)
337 free(file->currentData);
339 free(file->fileHdr);
340 free(file);
342 //puts("adfCloseFile out");
347 * adfReadFile
350 long adfReadFile(struct File* file, long n, unsigned char *buffer)
352 long bytesRead;
353 unsigned char *dataPtr, *bufPtr;
354 int blockSize, size;
356 if (n==0) return(n);
357 blockSize = file->volume->datablockSize;
358 /*puts("adfReadFile");*/
359 if (file->pos+n > file->fileHdr->byteSize)
360 n = file->fileHdr->byteSize - file->pos;
362 if (isOFS(file->volume->dosType))
363 dataPtr = (unsigned char*)(file->currentData)+24;
364 else
365 dataPtr = file->currentData;
367 if (file->pos==0 || file->posInDataBlk==blockSize) {
368 adfReadNextFileBlock(file);
369 file->posInDataBlk = 0;
372 bytesRead = 0; bufPtr = buffer;
373 size = 0;
374 while ( bytesRead < n ) {
375 size = min(n-bytesRead, blockSize-file->posInDataBlk);
376 memcpy(bufPtr, dataPtr+file->posInDataBlk, size);
377 bufPtr += size;
378 file->pos += size;
379 bytesRead += size;
380 file->posInDataBlk += size;
381 if (file->posInDataBlk==blockSize && bytesRead<n) {
382 adfReadNextFileBlock(file);
383 file->posInDataBlk = 0;
386 file->eof = (file->pos==file->fileHdr->byteSize);
387 return( bytesRead );
392 * adfEndOfFile
395 BOOL adfEndOfFile(struct File* file)
397 return(file->eof);
402 * adfReadNextFileBlock
405 RETCODE adfReadNextFileBlock(struct File* file)
407 SECTNUM nSect;
408 struct bOFSDataBlock *data;
409 RETCODE rc = RC_OK;
411 data =(struct bOFSDataBlock *) file->currentData;
413 if (file->nDataBlock==0) {
414 nSect = file->fileHdr->firstData;
416 else if (isOFS(file->volume->dosType)) {
417 nSect = data->nextData;
419 else {
420 if (file->nDataBlock<MAX_DATABLK)
421 nSect = file->fileHdr->dataBlocks[MAX_DATABLK-1-file->nDataBlock];
422 else {
423 if (file->nDataBlock==MAX_DATABLK) {
424 file->currentExt=(struct bFileExtBlock*)malloc(sizeof(struct bFileExtBlock));
425 if (!file->currentExt) (*adfEnv.eFct)("adfReadNextFileBlock : malloc");
426 adfReadFileExtBlock(file->volume, file->fileHdr->extension,
427 file->currentExt);
428 file->posInExtBlk = 0;
430 else if (file->posInExtBlk==MAX_DATABLK) {
431 adfReadFileExtBlock(file->volume, file->currentExt->extension,
432 file->currentExt);
433 file->posInExtBlk = 0;
435 nSect = file->currentExt->dataBlocks[MAX_DATABLK-1-file->posInExtBlk];
436 file->posInExtBlk++;
439 adfReadDataBlock(file->volume,nSect,file->currentData);
441 if (isOFS(file->volume->dosType) && data->seqNum!=file->nDataBlock+1)
442 (*adfEnv.wFct)("adfReadNextFileBlock : seqnum incorrect");
444 file->nDataBlock++;
446 return rc;
451 * adfWriteFile
454 long adfWriteFile(struct File *file, long n, unsigned char *buffer)
456 long bytesWritten;
457 unsigned char *dataPtr, *bufPtr;
458 int size, blockSize;
459 struct bOFSDataBlock *dataB;
461 if (n==0) return (n);
462 //puts("adfWriteFile");
463 blockSize = file->volume->datablockSize;
464 if (isOFS(file->volume->dosType)) {
465 dataB =(struct bOFSDataBlock *)file->currentData;
466 dataPtr = dataB->data;
468 else
469 dataPtr = file->currentData;
471 if (file->pos==0 || file->posInDataBlk==blockSize) {
472 if (adfCreateNextFileBlock(file)==-1)
473 (*adfEnv.wFct)("adfWritefile : no more free sector availbale");
474 file->posInDataBlk = 0;
477 bytesWritten = 0; bufPtr = buffer;
478 while( bytesWritten<n ) {
479 size = min(n-bytesWritten, blockSize-file->posInDataBlk);
480 memcpy(dataPtr+file->posInDataBlk, bufPtr, size);
481 bufPtr += size;
482 file->pos += size;
483 bytesWritten += size;
484 file->posInDataBlk += size;
485 if (file->posInDataBlk==blockSize && bytesWritten<n) {
486 if (adfCreateNextFileBlock(file)==-1)
487 (*adfEnv.wFct)("adfWritefile : no more free sector availbale");
488 file->posInDataBlk = 0;
491 return( bytesWritten );
496 * adfCreateNextFileBlock
499 SECTNUM adfCreateNextFileBlock(struct File* file)
501 SECTNUM nSect, extSect;
502 struct bOFSDataBlock *data;
503 unsigned int blockSize;
504 int i;
505 //puts("adfCreateNextFileBlock");
506 blockSize = file->volume->datablockSize;
507 data = file->currentData;
509 /* the first data blocks pointers are inside the file header block */
510 if (file->nDataBlock<MAX_DATABLK) {
511 nSect = adfGet1FreeBlock(file->volume);
512 if (nSect==-1) return -1;
513 //printf("adfCreateNextFileBlock fhdr %ld\n",nSect);
514 if (file->nDataBlock==0)
515 file->fileHdr->firstData = nSect;
516 file->fileHdr->dataBlocks[MAX_DATABLK-1-file->nDataBlock] = nSect;
517 file->fileHdr->highSeq++;
519 else {
520 /* one more sector is needed for one file extension block */
521 if ((file->nDataBlock%MAX_DATABLK)==0) {
522 extSect = adfGet1FreeBlock(file->volume);
523 //printf("extSect=%ld\n",extSect);
524 if (extSect==-1) return -1;
526 /* the future block is the first file extension block */
527 if (file->nDataBlock==MAX_DATABLK) {
528 file->currentExt=(struct bFileExtBlock*)malloc(sizeof(struct bFileExtBlock));
529 if (!file->currentExt) {
530 adfSetBlockFree(file->volume, extSect);
531 (*adfEnv.eFct)("adfCreateNextFileBlock : malloc");
532 return -1;
534 file->fileHdr->extension = extSect;
537 /* not the first : save the current one, and link it with the future */
538 if (file->nDataBlock>=2*MAX_DATABLK) {
539 file->currentExt->extension = extSect;
540 //printf ("write ext=%d\n",file->currentExt->headerKey);
541 adfWriteFileExtBlock(file->volume, file->currentExt->headerKey,
542 file->currentExt);
545 /* initializes a file extension block */
546 for(i=0; i<MAX_DATABLK; i++)
547 file->currentExt->dataBlocks[i] = 0L;
548 file->currentExt->headerKey = extSect;
549 file->currentExt->parent = file->fileHdr->headerKey;
550 file->currentExt->highSeq = 0L;
551 file->currentExt->extension = 0L;
552 file->posInExtBlk = 0L;
553 //printf("extSect=%ld\n",extSect);
555 nSect = adfGet1FreeBlock(file->volume);
556 if (nSect==-1)
557 return -1;
559 //printf("adfCreateNextFileBlock ext %ld\n",nSect);
561 file->currentExt->dataBlocks[MAX_DATABLK-1-file->posInExtBlk] = nSect;
562 file->currentExt->highSeq++;
563 file->posInExtBlk++;
566 /* builds OFS header */
567 if (isOFS(file->volume->dosType)) {
568 /* writes previous data block and link it */
569 if (file->pos>=blockSize) {
570 data->nextData = nSect;
571 adfWriteDataBlock(file->volume, file->curDataPtr, file->currentData);
572 //printf ("writedata=%d\n",file->curDataPtr);
574 /* initialize a new data block */
575 for(i=0; i<(int)blockSize; i++)
576 data->data[i]=0;
577 data->seqNum = file->nDataBlock+1;
578 data->dataSize = blockSize;
579 data->nextData = 0L;
580 data->headerKey = file->fileHdr->headerKey;
582 else
583 if (file->pos>=blockSize) {
584 adfWriteDataBlock(file->volume, file->curDataPtr, file->currentData);
585 //printf ("writedata=%d\n",file->curDataPtr);
586 memset(file->currentData,0,512);
589 //printf("datablk=%d\n",nSect);
590 file->curDataPtr = nSect;
591 file->nDataBlock++;
593 return(nSect);
598 * adfPos2DataBlock
601 long adfPos2DataBlock(long pos, int blockSize,
602 int *posInExtBlk, int *posInDataBlk, long *curDataN )
604 long extBlock;
606 *posInDataBlk = pos%blockSize;
607 *curDataN = pos/blockSize;
608 if (*posInDataBlk==0)
609 (*curDataN)++;
610 if (*curDataN<72) {
611 *posInExtBlk = 0;
612 return -1;
614 else {
615 *posInExtBlk = (pos-72*blockSize)%blockSize;
616 extBlock = (pos-72*blockSize)/blockSize;
617 if (*posInExtBlk==0)
618 extBlock++;
619 return extBlock;
625 * adfReadDataBlock
628 RETCODE adfReadDataBlock(struct Volume *vol, SECTNUM nSect, void *data)
630 unsigned char buf[512];
631 struct bOFSDataBlock *dBlock;
632 RETCODE rc = RC_OK;
634 adfReadBlock(vol, nSect,buf);
636 memcpy(data,buf,512);
638 if (isOFS(vol->dosType)) {
639 #ifdef LITT_ENDIAN
640 swapEndian(data, SWBL_DATA);
641 #endif
642 dBlock = (struct bOFSDataBlock*)data;
643 //printf("adfReadDataBlock %ld\n",nSect);
645 if (dBlock->checkSum!=adfNormalSum(buf,20,sizeof(struct bOFSDataBlock)))
646 (*adfEnv.wFct)("adfReadDataBlock : invalid checksum");
647 if (dBlock->type!=T_DATA)
648 (*adfEnv.wFct)("adfReadDataBlock : id T_DATA not found");
649 if (dBlock->dataSize<0 || dBlock->dataSize>488)
650 (*adfEnv.wFct)("adfReadDataBlock : dataSize incorrect");
651 if ( !isSectNumValid(vol,dBlock->headerKey) )
652 (*adfEnv.wFct)("adfReadDataBlock : headerKey out of range");
653 if ( !isSectNumValid(vol,dBlock->nextData) )
654 (*adfEnv.wFct)("adfReadDataBlock : nextData out of range");
657 return rc;
662 * adfWriteDataBlock
665 RETCODE adfWriteDataBlock(struct Volume *vol, SECTNUM nSect, void *data)
667 unsigned char buf[512];
668 unsigned long newSum;
669 struct bOFSDataBlock *dataB;
670 RETCODE rc = RC_OK;
672 newSum = 0L;
673 if (isOFS(vol->dosType)) {
674 dataB = (struct bOFSDataBlock *)data;
675 dataB->type = T_DATA;
676 memcpy(buf,dataB,512);
677 #ifdef LITT_ENDIAN
678 swapEndian(buf, SWBL_DATA);
679 #endif
680 newSum = adfNormalSum(buf,20,512);
681 swLong(buf+20,newSum);
682 // *(long*)(buf+20) = swapLong((unsigned char*)&newSum);
683 adfWriteBlock(vol,nSect,buf);
685 else {
686 adfWriteBlock(vol,nSect,data);
688 //printf("adfWriteDataBlock %ld\n",nSect);
690 return rc;
695 * adfReadFileExtBlock
698 RETCODE adfReadFileExtBlock(struct Volume *vol, SECTNUM nSect, struct bFileExtBlock* fext)
700 unsigned char buf[sizeof(struct bFileExtBlock)];
701 RETCODE rc = RC_OK;
703 adfReadBlock(vol, nSect,buf);
704 /*printf("read fext=%d\n",nSect);*/
705 memcpy(fext,buf,sizeof(struct bFileExtBlock));
706 #ifdef LITT_ENDIAN
707 swapEndian((unsigned char*)fext, SWBL_FEXT);
708 #endif
709 if (fext->checkSum!=adfNormalSum(buf,20,sizeof(struct bFileExtBlock)))
710 (*adfEnv.wFct)("adfReadFileExtBlock : invalid checksum");
711 if (fext->type!=T_LIST)
712 (*adfEnv.wFct)("adfReadFileExtBlock : type T_LIST not found");
713 if (fext->secType!=ST_FILE)
714 (*adfEnv.wFct)("adfReadFileExtBlock : stype ST_FILE not found");
715 if (fext->headerKey!=nSect)
716 (*adfEnv.wFct)("adfReadFileExtBlock : headerKey!=nSect");
717 if (fext->highSeq<0 || fext->highSeq>MAX_DATABLK)
718 (*adfEnv.wFct)("adfReadFileExtBlock : highSeq out of range");
719 if ( !isSectNumValid(vol, fext->parent) )
720 (*adfEnv.wFct)("adfReadFileExtBlock : parent out of range");
721 if ( fext->extension!=0 && !isSectNumValid(vol, fext->extension) )
722 (*adfEnv.wFct)("adfReadFileExtBlock : extension out of range");
724 return rc;
729 * adfWriteFileExtBlock
732 RETCODE adfWriteFileExtBlock(struct Volume *vol, SECTNUM nSect, struct bFileExtBlock* fext)
734 unsigned char buf[512];
735 unsigned long newSum;
736 RETCODE rc = RC_OK;
738 fext->type = T_LIST;
739 fext->secType = ST_FILE;
740 fext->dataSize = 0L;
741 fext->firstData = 0L;
743 memcpy(buf,fext,512);
744 #ifdef LITT_ENDIAN
745 swapEndian(buf, SWBL_FEXT);
746 #endif
747 newSum = adfNormalSum(buf,20,512);
748 swLong(buf+20,newSum);
749 // *(long*)(buf+20) = swapLong((unsigned char*)&newSum);
751 adfWriteBlock(vol,nSect,buf);
753 return rc;
755 /*###########################################################################*/