ext-array.c: Ensure that fseek is called before switching between read and write.
[pspp] / src / libpspp / ext-array.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2007, 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 /* An interface to an array of octets that is stored on disk as a temporary
18    file. */
19
20 #include <config.h>
21
22 #include "libpspp/ext-array.h"
23
24 #include <errno.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27
28 #include "libpspp/assertion.h"
29 #include "libpspp/cast.h"
30 #include "libpspp/temp-file.h"
31
32 #include "gl/error.h"
33 #include "gl/unlocked-io.h"
34 #include "gl/xalloc.h"
35
36 #include "gettext.h"
37 #define _(msgid) gettext (msgid)
38
39 enum op
40   {
41     OP_WRITE, /* writing */
42     OP_READ   /* reading */
43   };
44
45 struct ext_array
46   {
47     FILE *file;                 /* Underlying file. */
48
49     /* Current byte offset in file.  We track this manually,
50        instead of using ftello, because in glibc ftello flushes
51        the stream buffer, making the common case of sequential
52        access to cases unreasonably slow. */
53     off_t position;
54
55     /* The most recent operation performed */
56     enum op op;
57   };
58
59 /* Creates and returns a new external array. */
60 struct ext_array *
61 ext_array_create (void)
62 {
63   struct ext_array *ea = xmalloc (sizeof *ea);
64   ea->file = create_temp_file ();
65   if (ea->file == NULL)
66     error (0, errno, _("failed to create temporary file"));
67   ea->position = 0;
68   ea->op = OP_WRITE;
69   return ea;
70 }
71
72 /* Closes and destroys external array EA.  Returns true if I/O on EA always
73    succeeded, false if an I/O error occurred at some point. */
74 bool
75 ext_array_destroy (struct ext_array *ea)
76 {
77   bool ok = true;
78   if (ea != NULL)
79     {
80       ok = !ext_array_error (ea);
81       if (ea->file != NULL)
82         close_temp_file (ea->file);
83       free (ea);
84     }
85   return ok;
86 }
87
88 /* Seeks EA's underlying file to the start of `union value'
89    VALUE_IDX within case CASE_IDX.
90    Returns true if the seek is successful and EA is not
91    otherwise tainted, false otherwise. */
92 static bool
93 do_seek (const struct ext_array *ea_, off_t offset, enum op op)
94 {
95   struct ext_array *ea = CONST_CAST (struct ext_array *, ea_);
96   if (!ext_array_error (ea))
97     {
98       if (ea->position == offset && ea->op == op)
99         return true;
100       else if (fseeko (ea->file, offset, SEEK_SET) == 0)
101         {
102           ea->position = offset;
103           return true;
104         }
105       else
106         error (0, errno, _("seeking in temporary file"));
107     }
108
109   return false;
110 }
111
112 /* Reads BYTES bytes from EA's underlying file into BUFFER.
113    EA must not be tainted upon entry into this function.
114    Returns true if successful, false upon an I/O error (in which
115    case EA is marked tainted). */
116 static bool
117 do_read (const struct ext_array *ea_, void *buffer, size_t bytes)
118 {
119   struct ext_array *ea = CONST_CAST (struct ext_array *, ea_);
120
121   assert (!ext_array_error (ea));
122   if (bytes > 0 && fread (buffer, bytes, 1, ea->file) != 1)
123     {
124       if (ferror (ea->file))
125         error (0, errno, _("reading temporary file"));
126       else if (feof (ea->file))
127         error (0, 0, _("unexpected end of file reading temporary file"));
128       else
129         NOT_REACHED ();
130       return false;
131     }
132   ea->position += bytes;
133   ea->op = OP_READ;
134   return true;
135 }
136
137 /* Writes BYTES bytes from BUFFER into EA's underlying file.
138    EA must not be tainted upon entry into this function.
139    Returns true if successful, false upon an I/O error (in which
140    case EA is marked tainted). */
141 static bool
142 do_write (struct ext_array *ea, const void *buffer, size_t bytes)
143 {
144   assert (!ext_array_error (ea));
145   if (bytes > 0 && fwrite (buffer, bytes, 1, ea->file) != 1)
146     {
147       error (0, errno, _("writing to temporary file"));
148       return false;
149     }
150   ea->position += bytes;
151   ea->op = OP_WRITE;
152   return true;
153 }
154
155 /* Reads N bytes from EA at byte offset OFFSET into DATA.
156    Returns true if successful, false on failure.  */
157 bool
158 ext_array_read (const struct ext_array *ea, off_t offset, size_t n, void *data)
159 {
160   return do_seek (ea, offset, OP_READ) && do_read (ea, data, n);
161 }
162
163
164 /* Writes the N bytes in DATA to EA at byte offset OFFSET.
165    Returns true if successful, false on failure.  */
166 bool
167 ext_array_write (struct ext_array *ea, off_t offset, size_t n,
168                  const void *data)
169 {
170   return do_seek (ea, offset, OP_WRITE) && do_write (ea, data, n);
171 }
172
173 /* Returns true if an error has occurred in I/O on EA,
174    false if no error has been detected. */
175 bool
176 ext_array_error (const struct ext_array *ea)
177 {
178   return ea->file == NULL || ferror (ea->file) || feof (ea->file);
179 }