This introduces a RESIZE-FILE-LIMIT
[debian/pforth] / csrc / stdio / pf_fileio_stdio.c
1 /***************************************************************
2 ** File access routines based on ANSI C (no Unix stuff).
3 **
4 ** This file is part of pForth
5 **
6 ** The pForth software code is dedicated to the public domain,
7 ** and any third party may reproduce, distribute and modify
8 ** the pForth software code or any derivative works thereof
9 ** without any compensation or license.  The pForth software
10 ** code is provided on an "as is" basis without any warranty
11 ** of any kind, including, without limitation, the implied
12 ** warranties of merchantability and fitness for a particular
13 ** purpose and their equivalents under the laws of any jurisdiction.
14 **
15 ****************************************************************/
16
17 #include "../pf_all.h"
18
19 #ifndef PF_NO_FILEIO
20
21 #include <limits.h>             /* For LONG_MAX */
22
23 typedef int bool_t;
24
25 /* Copy SIZE bytes from File FROM to File TO.  Return non-FALSE on error. */
26 static bool_t CopyFile( FileStream *From, FileStream *To, long Size)
27 {
28     bool_t Error = TRUE;
29     size_t Diff = Size;
30     size_t BufSize = 512;
31     char *Buffer = pfAllocMem( BufSize );
32     if( Buffer != 0 )
33     {
34         while( Diff > 0 )
35         {
36             size_t N = MIN( Diff, BufSize );
37             if( fread( Buffer, 1, N, From ) < N ) goto cleanup;
38             if( fwrite( Buffer, 1, N, To ) < N ) goto cleanup;
39             Diff -= N;
40         }
41         Error = FALSE;
42
43       cleanup:
44         pfFreeMem( Buffer );
45     }
46     return Error;
47 }
48
49 /* Shrink the file FILE to NEWSIZE.  Return non-FALSE on error.
50  *
51  * There's no direct way to do this in ANSI C.  The closest thing we
52  * have is freopen(3), which truncates a file to zero length if we use
53  * "w+b" as mode argument.  So we do this:
54  *
55  *   1. copy original content to temporary file
56  *   2. re-open and truncate FILE
57  *   3. copy the temporary file to FILE
58  *
59  * Unfortunately, "w+b" may not be the same mode as the original mode
60  * of FILE.  I don't see a away to avoid this, though.
61  *
62  * We call freopen with NULL as path argument, because we don't know
63  * the actual file-name.  It seems that the trick with path=NULL is
64  * not part of C89 but it's in C99.
65  */
66 static bool_t TruncateFile( FileStream *File, long Newsize )
67 {
68     bool_t Error = TRUE;
69     if( fseek( File, 0, SEEK_SET ) == 0)
70     {
71         FileStream *TmpFile = tmpfile();
72         if( TmpFile != NULL )
73         {
74             if( CopyFile( File, TmpFile, Newsize )) goto cleanup;
75             if( fseek( TmpFile, 0, SEEK_SET ) != 0 ) goto cleanup;
76             if( freopen( NULL, "w+b", File ) == NULL ) goto cleanup;
77             if( CopyFile( TmpFile, File, Newsize )) goto cleanup;
78             Error = FALSE;
79
80           cleanup:
81             fclose( TmpFile );
82         }
83     }
84     return Error;
85 }
86
87 /* Write DIFF 0 bytes to FILE. Return non-FALSE on error. */
88 static bool_t ExtendFile( FileStream *File, size_t Diff )
89 {
90     bool_t Error = TRUE;
91     size_t BufSize = 512;
92     char * Buffer = pfAllocMem( BufSize );
93     if( Buffer != 0 )
94     {
95         pfSetMemory( Buffer, 0, BufSize );
96         while( Diff > 0 )
97         {
98             size_t N = MIN( Diff, BufSize );
99             if( fwrite( Buffer, 1, N, File ) < N ) goto cleanup;
100             Diff -= N;
101         }
102         Error = FALSE;
103       cleanup:
104         pfFreeMem( Buffer );
105     }
106     return Error;
107 }
108
109 ThrowCode sdResizeFile( FileStream *File, uint64_t Size )
110 {
111     bool_t Error = TRUE;
112     if( Size <= LONG_MAX )
113     {
114         long Newsize = (long) Size;
115         if( fseek( File, 0, SEEK_END ) == 0 )
116         {
117             long Oldsize = ftell( File );
118             if( Oldsize != -1L )
119             {
120                 Error = ( Oldsize <= Newsize
121                           ? ExtendFile( File, Newsize - Oldsize )
122                           : TruncateFile( File, Newsize ));
123             }
124         }
125     }
126     return Error ? THROW_RESIZE_FILE : 0;
127 }
128
129 #endif /* !PF_NO_FILEIO */