1 /*
2 * Copyright (C) 2002 - David W. Durham
3 *
4 * This file is part of ReZound, an audio editing application, but
5 * could be used for other applications which could use the PoolFile
6 * class's functionality.
7 *
8 * PoolFile is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published
10 * by the Free Software Foundation; either version 2 of the License,
11 * or (at your option) any later version.
12 *
13 * PoolFile is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
21 */
22
23 #include "CMultiFile.h"
24
25 #include <limits.h>
26 #include <string.h>
27 #include <errno.h>
28 #include <stdio.h> // for rename
29
30 #include <stdexcept>
31 #include <algorithm>
32
33 #include <istring>
34
35 #include <CPath.h>
36 #include <endian_util.h>
37
38 //#if defined(__SOLARIS_GNU_CPP__) || defined(__SOLARIS_SUN_CPP__) || defined(__LINUX_GNU_CPP__)
39 // ??? if windows is posix... I think it should have these necessary files... We'll see when we try to compile there
40 #include <sys/types.h>
41 #include <sys/stat.h>
42 #include <fcntl.h>
43 #include <unistd.h>
44
45 /*
46 #elif defined(WIN32)
47 #include <sys\stat.h>
48 #include <share.h>
49 #include <io.h>
50 #include <stddef.h>
51 #include <dos.h>
52
53 #else
54 #error implementation not handled
55 #endif
56 */
57
58 #define HEADER_SIZE 512
59
60 #define PHYSICAL_MAX_FILE_SIZE ((CMultiFile::l_addr_t)2147000000) // ??? this needs to change based on off_t's type... won't be a problem when simple 64bit fs is in place commonly
61 #define LOGICAL_MAX_FILE_SIZE ((CMultiFile::l_addr_t)(PHYSICAL_MAX_FILE_SIZE-HEADER_SIZE))
62
63 #define CMULTIFILE_SIGNATURE (*((uint32_t *)"CMFL"))
64
65
66
67 /* -- CMultiFile --
68 *
69 * - This is the file I/O class which (mainly) TPoolFile uses to read and write to disk
70 * - When 64 bit file systems are the norm, I should be able to reimplement this
71 * class for TPoolFile that just calls the normal file system functions
72 *
73 * - This class maps a larger address space onto multiple files
74 *
75 * - When 64 bit file systems become the norm this class should really be obsolete (for a
76 * time).
77 *
78 * - One practical consideration is that to provide full 64bit address access by using
79 * multiple files, it would require up to 8 billion 31bit files to actually access a 64
80 * bit sized space. My purpose was not necessarily to provide 18Tb of space to
81 * TPoolFile, but to merely provide much more than 2Gb. So I have imposed a limit on the
82 * number of files, MAX_OPEN_FILES, because it has to keep all the files opened. Perhaps
83 * I avoid keeping them all open at once, but again my purpose was just to provide more
84 * than 2Gb of storage. When 64bit file systems are the normal in a few years, this
85 * won't even be an issue.
86 *
87 * - This class works by taking the address space and simply assigning the first 2 billion
88 * addresses to the first file, then the next 2 billion addresses to the second file and
89 * so on. However, there is a 512 byte header at the beginning of each file which is used
90 * to tie the files in a set together.
91 *
92 * - endianness is handled by always writing the header as the native endian except that the
93 * signature is +1 if written as big endian so that when the file is opened again, it is
94 * known whether endian swapping is necessary. Also, the signature is always written
95 * little endian
96 *
97 */
98
99 // TODO need to check return values of lseek
100
CMultiFile()101 CMultiFile::CMultiFile() :
102 opened(false),
103 initialFilename(""),
104 openFileCount(0)
105 {
106 for(size_t t=0;t<MAX_OPEN_FILES;t++)
107 openFiles[t]=-1;
108 }
109
~CMultiFile()110 CMultiFile::~CMultiFile()
111 {
112 close(false);
113 }
114
open(const string _initialFilename,const bool canCreate)115 void CMultiFile::open(const string _initialFilename,const bool canCreate)
116 {
117 if(opened)
118 throw runtime_error(string(__func__)+" -- already opened");
119
120 initialFilename=_initialFilename;
121 openFileCount=0;
122
123 try
124 {
125 if(canCreate)
126 CPath(initialFilename).touch();
127
128 // open first file
129 RFileHeader header;
130 openFile(initialFilename,header,true);
131 matchSignature=header.matchSignature;
132
133 // attempt to open all the other files
134 for(uint64_t t=1;t<header.fileCount;t++)
135 openFile(buildFilename(t),header);
136
137 writeHeaderToFiles();
138
139 opened=true;
140
141 totalSize=calcTotalSize();
142 }
143 catch(...)
144 {
145 close();
146 throw;
147 }
148 }
149
close(bool removeFiles)150 void CMultiFile::close(bool removeFiles)
151 {
152 for(size_t t=0;t<MAX_OPEN_FILES;t++)
153 {
154 if(openFiles[t]>0)
155 {
156 ::close(openFiles[t]);
157 openFiles[t]=-1;
158
159 if(removeFiles)
160 unlink(buildFilename(t).c_str());
161 }
162 }
163 openFileCount=0;
164
165 initialFilename="";
166 totalSize=0;
167 opened=false;
168 }
169
rename(const string newInitialFilename)170 void CMultiFile::rename(const string newInitialFilename)
171 {
172 const size_t origOpenFileCount=openFileCount;
173 const string origInitialFilename=initialFilename;
174 close(false);
175
176 for(size_t t=0;t<origOpenFileCount;t++)
177 ::rename(buildFilename(t,origInitialFilename).c_str(),buildFilename(t,newInitialFilename).c_str());
178
179 open(newInitialFilename,false);
180 }
181
seek(const l_addr_t _position,RHandle & handle)182 void CMultiFile::seek(const l_addr_t _position,RHandle &handle)
183 {
184 if(_position>totalSize)
185 throw runtime_error(string(__func__)+" -- attempting to seek beyond the end of the file size: "+istring(_position));
186 handle.position=_position;
187 }
188
tell(RHandle & handle) const189 const CMultiFile::l_addr_t CMultiFile::tell(RHandle &handle) const
190 {
191 return handle.position;
192 }
193
read(void * buffer,const l_addr_t count,RHandle & handle)194 void CMultiFile::read(void *buffer,const l_addr_t count,RHandle &handle)
195 {
196 if(!opened)
197 throw runtime_error(string(__func__)+" -- not opened");
198 if((totalSize-handle.position)<count)
199 throw runtime_error(string(__func__)+" -- attempting to read beyond the end of the file size; position: "+istring(handle.position)+" count: "+istring(count));
200
201 size_t whichFile=handle.position/LOGICAL_MAX_FILE_SIZE;
202 f_addr_t whereFile=handle.position%LOGICAL_MAX_FILE_SIZE;
203
204 l_addr_t lengthToRead=count;
205 while(lengthToRead>0)
206 {
207
208 const f_addr_t seekRet=lseek(openFiles[whichFile],whereFile+HEADER_SIZE,SEEK_SET);
209 if(seekRet==((f_addr_t)-1))
210 {
211 int errNO=errno;
212 throw runtime_error(string(__func__)+" -- error seeking in file: "+buildFilename(whichFile)+" -- strerror: "+strerror(errNO));
213 }
214
215 const size_t stripRead=min(lengthToRead,LOGICAL_MAX_FILE_SIZE-whereFile);
216 const ssize_t lengthRead=::read(openFiles[whichFile],(uint8_t *)buffer+(count-lengthToRead),stripRead);
217 if(lengthRead<0)
218 {
219 int errNO=errno;
220 throw runtime_error(string(__func__)+" -- error reading from file: "+buildFilename(whichFile)+" -- where: "+istring(whereFile)+"+"+istring(HEADER_SIZE)+" lengthRead/stripRead: "+istring(lengthRead)+"/"+istring(stripRead)+" strerror: "+strerror(errNO));
221 }
222 else if((size_t)lengthRead!=stripRead)
223 {
224 throw runtime_error(string(__func__)+" -- error reading from file: "+buildFilename(whichFile)+" -- where: "+istring(whereFile)+"+"+istring(HEADER_SIZE)+" lengthRead/stripRead: "+istring(lengthRead)+"/"+istring(stripRead));
225 }
226
227 lengthToRead-=stripRead;
228 handle.position+=stripRead;
229
230 whichFile++; // read from the next file next go around
231 whereFile=0; // each additional file to read from should start at 0 now
232 }
233 }
234
read(void * buffer,const l_addr_t count,const l_addr_t _position)235 void CMultiFile::read(void *buffer,const l_addr_t count,const l_addr_t _position)
236 {
237 RHandle handle;
238 seek(_position,handle);
239 read(buffer,count,handle);
240 }
241
write(const void * buffer,const l_addr_t count,RHandle & handle)242 void CMultiFile::write(const void *buffer,const l_addr_t count,RHandle &handle)
243 {
244 if(!opened)
245 throw runtime_error(string(__func__)+" -- not opened");
246 if((getAvailableSize()-handle.position)<count)
247 throw runtime_error(string(__func__)+" -- insufficient space to write "+istring(count)+" more bytes");
248
249 // grow file(s) if necessary
250 if(totalSize<(handle.position+count))
251 prvSetSize(handle.position+count,count);
252
253 size_t whichFile=handle.position/LOGICAL_MAX_FILE_SIZE;
254 f_addr_t whereFile=(handle.position%LOGICAL_MAX_FILE_SIZE);
255
256 l_addr_t lengthToWrite=count;
257 while(lengthToWrite>0)
258 {
259
260 const f_addr_t seekRet=lseek(openFiles[whichFile],whereFile+HEADER_SIZE,SEEK_SET);
261 if(seekRet==((f_addr_t)-1))
262 {
263 int errNO=errno;
264 throw runtime_error(string(__func__)+" -- error seeking in file: "+buildFilename(whichFile)+" -- where: "+istring(whereFile)+"+"+istring(HEADER_SIZE)+" strerror: "+strerror(errNO));
265 }
266
267 const size_t stripWrite=min(lengthToWrite,LOGICAL_MAX_FILE_SIZE-whereFile);
268 const ssize_t lengthWritten=::write(openFiles[whichFile],(uint8_t *)buffer+(count-lengthToWrite),stripWrite);
269 if(lengthWritten<0)
270 {
271 int errNO=errno;
272 throw runtime_error(string(__func__)+" -- error writing to file: "+buildFilename(whichFile)+" -- where: "+istring(whereFile)+"+"+istring(HEADER_SIZE)+" lengthWritten/stripWrite: "+istring(lengthWritten)+"/"+istring(stripWrite)+" strerror: "+strerror(errNO));
273 }
274 else if((size_t)lengthWritten!=stripWrite)
275 {
276 throw runtime_error(string(__func__)+" -- error writing to file: "+buildFilename(whichFile)+" -- where: "+istring(whereFile)+"+"+istring(HEADER_SIZE)+" lengthWritten/stripWrite: "+istring(lengthWritten)+"/"+istring(stripWrite)+" -- perhaps the disk is full");
277 }
278
279 lengthToWrite-=lengthWritten;
280 handle.position+=lengthWritten;
281
282 whichFile++; // write to the next file next go around
283 whereFile=0; // each additional file to write to should start at 0 now
284 }
285 }
286
write(const void * buffer,const l_addr_t count,const l_addr_t _position)287 void CMultiFile::write(const void *buffer,const l_addr_t count,const l_addr_t _position)
288 {
289 RHandle handle;
290 seek(_position,handle);
291 write(buffer,count,handle);
292 }
293
setSize(const l_addr_t newSize)294 void CMultiFile::setSize(const l_addr_t newSize)
295 {
296 prvSetSize(newSize,0);
297 }
298
prvSetSize(const l_addr_t newSize,const l_addr_t forWriteSize)299 void CMultiFile::prvSetSize(const l_addr_t newSize,const l_addr_t forWriteSize)
300 {
301 if(newSize>totalSize && getAvailableSize()<newSize)
302 throw runtime_error(string(__func__)+" -- insufficient space to grow data size to: "+istring(newSize));
303
304 size_t neededFileCount=(newSize/LOGICAL_MAX_FILE_SIZE)+1;
305 const f_addr_t currentLastFilesSize=(totalSize%LOGICAL_MAX_FILE_SIZE)+HEADER_SIZE;
306 const f_addr_t lastFilesSize=(newSize%LOGICAL_MAX_FILE_SIZE)+HEADER_SIZE;
307
308 if(neededFileCount>openFileCount)
309 { // create some new files
310 if(neededFileCount>MAX_OPEN_FILES)
311 throw runtime_error(string(__func__)+" -- would have to open too many files ("+istring(neededFileCount)+") to set size to "+istring(newSize));
312
313 // set the last file now its max size
314 setFileSize(openFiles[openFileCount-1],PHYSICAL_MAX_FILE_SIZE);
315
316 // create new files and set all but the last to their max size
317 for(size_t t=openFileCount;t<neededFileCount;t++)
318 {
319 const string filename=buildFilename(t);
320 CPath(filename).touch();
321 openFile(filename,*((RFileHeader *)NULL),false,false);
322 if(t!=neededFileCount-1)
323 setFileSize(openFiles[openFileCount-1],PHYSICAL_MAX_FILE_SIZE);
324 }
325
326 writeHeaderToFiles(); // write the new file count to all the files (could probably get by with just writing it to the first one???)
327 }
328 else
329 {
330 // close and remove some files
331 for(size_t t=neededFileCount;t<openFileCount;t++)
332 {
333 ::close(openFiles[t]);
334 openFiles[t]=-1;
335 unlink(buildFilename(t).c_str());
336 }
337 openFileCount=neededFileCount;
338
339 writeHeaderToFiles(); // write the new file count to all the files (could probably get by with just writing it to the first one???)
340 }
341
342
343
344 // set size of last file
345 if(newSize<totalSize || ((l_addr_t)lastFilesSize-forWriteSize)>(l_addr_t)currentLastFilesSize)
346 setFileSize(openFiles[openFileCount-1],lastFilesSize-forWriteSize);
347 totalSize=newSize;
348 }
349
sync() const350 void CMultiFile::sync() const
351 {
352 /*
353 for(size_t t=0;t<openFileCount;t++)
354 fsync(openFiles[t]);
355 */
356 }
357
getAvailableSize() const358 const CMultiFile::l_addr_t CMultiFile::getAvailableSize() const
359 {
360 if(!opened)
361 throw runtime_error(string(__func__)+" -- not opened");
362
363 return MAX_OPEN_FILES*LOGICAL_MAX_FILE_SIZE;
364 }
365
getActualSize() const366 const CMultiFile::l_addr_t CMultiFile::getActualSize() const
367 {
368 if(!opened)
369 throw runtime_error(string(__func__)+" -- not opened");
370
371 struct stat statBuf;
372 fstat(openFiles[openFileCount-1],&statBuf);
373 const l_addr_t sizeOfLastFile=statBuf.st_size;
374 return ((openFileCount-1)*PHYSICAL_MAX_FILE_SIZE)+sizeOfLastFile;
375 }
376
getSize() const377 const CMultiFile::l_addr_t CMultiFile::getSize() const
378 {
379 if(!opened)
380 throw runtime_error(string(__func__)+" -- not opened");
381 return totalSize;
382 }
383
writeHeaderToFiles()384 void CMultiFile::writeHeaderToFiles()
385 {
386 RFileHeader header;
387
388 header.signature=CMULTIFILE_SIGNATURE;
389 header.matchSignature=matchSignature;
390 header.fileCount=openFileCount;
391 header.encodeEndianBeforeWrite();
392
393 for(size_t t=0;t<openFileCount;t++)
394 {
395 lseek(openFiles[t],0,SEEK_SET);
396 const ssize_t wroteLength=header.write(openFiles[t]);
397 if(wroteLength<0)
398 {
399 int errNO=errno;
400 throw runtime_error(string(__func__)+" -- error writing header to file -- strerror: "+strerror(errNO));
401 }
402 else if((size_t)wroteLength!=HEADER_SIZE)
403 {
404 throw runtime_error(string(__func__)+" -- error writing header to file -- wroteLength/HEADER_SIZE: "+istring(wroteLength)+"/"+istring(HEADER_SIZE));
405 }
406 }
407 }
408
openFile(const string & filename,RFileHeader & header,const bool openingFirstFile,const bool readHeader)409 void CMultiFile::openFile(const string &filename,RFileHeader &header,const bool openingFirstFile,const bool readHeader)
410 {
411 int fileHandle=-1;
412 try
413 {
414 #ifdef WIN32
415 fileHandle=sopen(filename.c_str(),O_RDWR|O_BINARY,SH_DENYRW);
416 #else
417 // we need some way of not allowing other processes to open this file once it's open here
418 fileHandle=::open(filename.c_str(),O_RDWR);
419 #endif
420 if(fileHandle<0)
421 {
422 int errNO=errno;
423 throw runtime_error(string(__func__)+" -- expected file not found: "+filename+" -- "+strerror(errNO));
424 }
425
426 if(readHeader)
427 {
428 // read info which ties this file to other files
429 if(header.read(fileHandle)==HEADER_SIZE && header.signature==CMULTIFILE_SIGNATURE)
430 { // HEADER_SIZE bytes were read and signature matched
431
432 if(openingFirstFile)
433 matchSignature=header.matchSignature;
434 else if(header.matchSignature!=matchSignature)
435 throw runtime_error(string(__func__)+" -- matchSignature not match in header information of file: "+filename);
436
437 if(header.fileCount>MAX_OPEN_FILES)
438 throw runtime_error(string(__func__)+" -- fileCount is greater than MAX_OPEN_FILES in header information of file: "+filename);
439
440 openFiles[openFileCount++]=fileHandle;
441 fileHandle=-1;
442 }
443 else
444 {
445 if(openingFirstFile)
446 {
447 // make up new match signature
448 header.matchSignature=matchSignature=((rand()%256)*256*256*256)+((rand()%256)*256*256)+((rand()%256)*256)+(rand()%256);
449 header.fileCount=openFileCount=1;
450
451 openFiles[0]=fileHandle;
452 fileHandle=-1;
453
454 }
455 else
456 throw runtime_error(string(__func__)+" -- invalid header information of file: "+filename);
457 }
458 }
459 else
460 {
461 openFiles[openFileCount++]=fileHandle;
462 fileHandle=-1;
463 }
464
465 }
466 catch(...)
467 {
468 if(fileHandle!=-1)
469 ::close(fileHandle);
470 throw;
471 }
472 }
473
calcTotalSize() const474 const CMultiFile::l_addr_t CMultiFile::calcTotalSize() const
475 {
476 return getActualSize()-(openFileCount*HEADER_SIZE);
477 }
478
479
setFileSize(const int fileHandle,const f_addr_t newFileSize)480 void CMultiFile::setFileSize(const int fileHandle,const f_addr_t newFileSize)
481 {
482 /* Need to use flags from autoconf to set this
483 #if defined(WIN32)
484 if(chsize(fileHandle,newFileSize)!=0)
485 {
486 int err=errno;
487 printf("error changing block file size: %s\n",strerror(err));
488 exit(1);
489 }
490
491 #elif defined(__SOLARIS_GNU_CPP__) || defined(__SOLARIS_SUN_CPP__) || defined(__LINUX_GNU_CPP__)
492 */
493 if(ftruncate(fileHandle,newFileSize))
494 {
495 const int err=errno;
496 if(err==EPERM)
497 { // "Operation not Permitted" (most likely this is an fat partition, or one that doesn't support sparse files)
498
499 // In linux, the FAT file system does not support making the file bigger with ftruncate
500 // I started a thread in the linux-fsdevel mailing list, but evenutually received thsi reply
501 //
502 // Exactly. We could extended file for you, but we decided that instead of
503 // writting zeroes to disk for next couple of hours during one ftruncate()
504 // call we leave decision on you - if you really want to extend file, use
505 // something else (loop in write and paint some progress bar for impatient
506 // user). You can lookup discussion why Al Viro (if my memory serves
507 // correctly) decided to not support extending files on filesystems
508 // which do not support holes - linux-fsdevel archive should contain it
509 // (I think that ~18 months ago, but I'm not sure at all).
510 // Best regards,
511 // Petr Vandrovec
512 // vandrove@vc.cvut.cz
513
514 struct stat statBuf;
515 fstat(fileHandle,&statBuf);
516 const f_addr_t currentFileSize=statBuf.st_size;
517 if(currentFileSize<=newFileSize)
518 { // write zero to the end of the file
519 lseek(fileHandle,currentFileSize,SEEK_SET);
520
521 const char buffer[1024]={0};
522 const size_t nChunks=(newFileSize-currentFileSize)/1024;
523 for(size_t t=0;t<nChunks;t++)
524 {
525 if(::write(fileHandle,buffer,1024)!=1024)
526 {
527 printf("error changing block file size: %s\n",strerror(errno));
528 exit(1);
529 }
530 }
531 const ssize_t remainder=(newFileSize-currentFileSize)%1024;
532 if(remainder>0)
533 {
534 if(::write(fileHandle,buffer,remainder)!=remainder)
535 {
536 printf("error changing block file size: %s\n",strerror(errno));
537 exit(1);
538 }
539 }
540
541 return;
542 }
543 }
544
545 // ??? may want to seriously consider making this an exception, but if I
546 // do I need know know all the conditions underwhich it could happen and
547 // undo whever might assume that the operation was going to succeed
548 // - same for above in two places
549 printf("error changing block file size: %s\n",strerror(err));
550 exit(1);
551 }
552 /*
553 #else
554 #error no implementation for this platform defined
555 #endif
556 */
557
558 }
559
buildFilename(size_t which)560 const string CMultiFile::buildFilename(size_t which)
561 {
562 return buildFilename(which,initialFilename);
563 }
564
565
buildFilename(size_t which,const string & initialFilename)566 const string CMultiFile::buildFilename(size_t which,const string &initialFilename)
567 {
568 if(which==0)
569 return initialFilename;
570 else
571 return initialFilename+"."+istring(which);
572 }
573
read(int fd)574 ssize_t CMultiFile::RFileHeader::read(int fd)
575 {
576 if(::read(fd,&signature,sizeof(signature))!=sizeof(signature))
577 return 0;
578 if(::read(fd,&matchSignature,sizeof(matchSignature))!=sizeof(matchSignature))
579 return 0;
580 if(::read(fd,&fileCount,sizeof(fileCount))!=sizeof(fileCount))
581 return 0;
582
583 const static int padlen=HEADER_SIZE-(sizeof(signature)+sizeof(matchSignature)+sizeof(fileCount));
584 if(::lseek(fd,padlen,SEEK_CUR)==(off_t)-1)
585 return 0;
586
587 /* signature is always stored in little-endian */
588 lethe(&signature);
589
590 /* if signature is CMULTIFILE_SIGNATURE+1 then this file was written on a big endian machine */
591 #ifdef WORDS_BIGENDIAN
592 if(signature==(CMULTIFILE_SIGNATURE+1))
593 { // nothing to do, header was written as big endian
594 signature--;
595 }
596 else
597 { // header was written on a little endian machine, must convert
598 lethe(&matchSignature);
599 lethe(&fileCount);
600 }
601 #else
602 if(signature==(CMULTIFILE_SIGNATURE+1))
603 { // header was written on a big endian machine, must convert
604 signature--;
605
606 bethe(&signature);
607 bethe(&matchSignature);
608 bethe(&fileCount);
609 }
610 else
611 { // nothing to do, header was written as little endian
612 }
613 #endif
614
615 return HEADER_SIZE;
616 }
617
write(int fd)618 ssize_t CMultiFile::RFileHeader::write(int fd)
619 {
620 if(::write(fd,&signature,sizeof(signature))!=sizeof(signature))
621 return 0;
622 if(::write(fd,&matchSignature,sizeof(matchSignature))!=sizeof(matchSignature))
623 return 0;
624 if(::write(fd,&fileCount,sizeof(fileCount))!=sizeof(fileCount))
625 return 0;
626
627 static int8_t padding[HEADER_SIZE]={0};
628 const static int padlen=HEADER_SIZE-(sizeof(signature)+sizeof(matchSignature)+sizeof(fileCount));
629 if(::write(fd,padding,padlen)!=padlen)
630 return 0;
631
632 return HEADER_SIZE;
633 }
634
encodeEndianBeforeWrite()635 void CMultiFile::RFileHeader::encodeEndianBeforeWrite()
636 {
637 /* always store signature as little-endian */
638 /* if we're on a big endian platform add 1 to the signature */
639 #ifdef WORDS_BIGENDIAN
640 signature++;
641 hetle(&signature);
642 #else
643 #endif
644 }
645
646