Implement RESIZE-FILE
[pforth] / csrc / stdio / pf_fileio_stdio.c
CommitLineData
e0701bfb
HE
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
23typedef int bool_t;
24
25/* Copy SIZE bytes from File FROM to File TO. Return non-FALSE on error. */
26static 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 */
66static 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. */
88static 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 */
112static bool_t IsGreaterThanLongMax( ucell_t Lo, ucell_t Hi )
113{
114 return (Hi != 0) || (Lo > LONG_MAX);
115}
116
117ThrowCode 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 */