9a2509251439f52886fca665cf8956d704b99762
[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 /* Return non-FALSE if the double-cell unsigned number LO/HI
110  * is greater then LONG_MAX.
111  */
112 static bool_t IsGreaterThanLongMax( ucell_t Lo, ucell_t Hi )
113 {
114     return (Hi != 0) || (Lo > LONG_MAX);
115 }
116
117 ThrowCode sdResizeFile( FileStream *File, ucell_t SizeLo, ucell_t SizeHi )
118 {
119     bool_t Error = TRUE;
120     if( !IsGreaterThanLongMax( SizeLo, SizeHi ) )
121     {
122         long Newsize = (long) SizeLo;
123         if( fseek( File, 0, SEEK_END ) == 0 )
124         {
125             long Oldsize = ftell( File );
126             if( Oldsize != -1L )
127             {
128                 Error = ( Oldsize <= Newsize
129                           ? ExtendFile( File, Newsize - Oldsize )
130                           : TruncateFile( File, Newsize ));
131             }
132         }
133     }
134     return Error ? THROW_RESIZE_FILE : 0;
135 }
136
137 #endif /* !PF_NO_FILEIO */