1 | /* |
---|
2 | * |
---|
3 | * Copyright (C) 1988, 1989 by the Massachusetts Institute of Technology |
---|
4 | * Developed by the MIT Student Information Processing Board (SIPB). |
---|
5 | * For copying information, see the file mit-copyright.h in this release. |
---|
6 | * |
---|
7 | */ |
---|
8 | /* |
---|
9 | * |
---|
10 | * atom.c -- File to support the idea of 'atomic' files in C. These are |
---|
11 | * files that support the ideas of coordination and failure |
---|
12 | * atomicity. Coordination is handled by using flock on the |
---|
13 | * file to get an exclusive lock (supposedly other accessors |
---|
14 | * will honor this). Failure is handled currently by keeping |
---|
15 | * all the changes in memory, and writing it out when it is |
---|
16 | * closed. If the system crashes before this happens, the old |
---|
17 | * version will still be around. If the system crashes after |
---|
18 | * this happens, the new version will be around. |
---|
19 | * |
---|
20 | * This is banking on the fact that system crashes during the |
---|
21 | * atomic close is not likely. If this is an incorrect |
---|
22 | * assumption, then this can be recoded to update a new version |
---|
23 | * of the file, and 'mv' it over. Or we could play with shadow |
---|
24 | * blocks or something else. |
---|
25 | * |
---|
26 | */ |
---|
27 | |
---|
28 | |
---|
29 | #include "../include/atom.h" |
---|
30 | #include <sys/types.h> |
---|
31 | #include <sys/stat.h> |
---|
32 | #include <sys/file.h> |
---|
33 | #include <unistd.h> |
---|
34 | #if HAVE_FCNTL_H |
---|
35 | #include <fcntl.h> |
---|
36 | #endif |
---|
37 | #define max(a, b) (a > b ? a : b) |
---|
38 | #define min(a, b) (a < b ? a : b) |
---|
39 | |
---|
40 | |
---|
41 | /* chunk size of atomic blocks */ |
---|
42 | #define ABLOCKSIZE 512 |
---|
43 | #define ABLKSINDIR 24 |
---|
44 | |
---|
45 | typedef unsigned short block_num; |
---|
46 | |
---|
47 | struct dir_blk { |
---|
48 | struct dir_blk *next; |
---|
49 | short used; |
---|
50 | char *bptr [ABLKSINDIR]; |
---|
51 | block_num bnum [ABLKSINDIR]; |
---|
52 | }; |
---|
53 | |
---|
54 | |
---|
55 | char *find_block(); |
---|
56 | |
---|
57 | static int maxdirty = 0; /* meter: maximum dirty blks */ |
---|
58 | |
---|
59 | /* |
---|
60 | * |
---|
61 | * aopen -- opens up an atomic file, returning an AFILE back to the caller. |
---|
62 | * The caller gives a UNIX file number that he wants to go |
---|
63 | * nuclear. |
---|
64 | * |
---|
65 | */ |
---|
66 | afile aopen (d) |
---|
67 | int d; |
---|
68 | { |
---|
69 | afile af; |
---|
70 | struct stat buf; |
---|
71 | struct flock lock; |
---|
72 | |
---|
73 | lock.l_type = F_WRLCK; |
---|
74 | lock.l_start = 0; |
---|
75 | lock.l_whence = 0; |
---|
76 | lock.l_len = 0; |
---|
77 | if (fcntl(d, F_SETLKW, &lock)) |
---|
78 | return (NULL); |
---|
79 | if (fstat (d, &buf) < 0) |
---|
80 | goto punt; |
---|
81 | |
---|
82 | af = (afile) malloc (sizeof (*af)); |
---|
83 | if (af == NULL) |
---|
84 | goto punt; /* no memory, ack! */ |
---|
85 | |
---|
86 | af -> desc = d; |
---|
87 | af -> dir_list = NULL; |
---|
88 | af -> dirty_blks = 0; /* no dirty blocks yet */ |
---|
89 | af -> file_size = buf.st_size; |
---|
90 | |
---|
91 | return (af); |
---|
92 | |
---|
93 | punt: |
---|
94 | lock.l_type = F_UNLCK; |
---|
95 | lock.l_start = 0; |
---|
96 | lock.l_whence = 0; |
---|
97 | lock.l_len = 0; |
---|
98 | fcntl(d, F_SETLK, &lock); |
---|
99 | return (NULL); |
---|
100 | } |
---|
101 | |
---|
102 | /* |
---|
103 | * |
---|
104 | * aread -- Atomic read. Reads from an atomic file. Pos is the place to |
---|
105 | * lseek to. This checks to see if the desired data is in |
---|
106 | * memory, and if so, reads it. Otherwise it simply reads from |
---|
107 | * the file. |
---|
108 | * |
---|
109 | */ |
---|
110 | aread(af,buf,nbytes,pos) |
---|
111 | afile af; |
---|
112 | int nbytes, pos; |
---|
113 | char *buf; |
---|
114 | { |
---|
115 | int hi, numleft, toread, offset; |
---|
116 | char *bptr,*dest_ptr; |
---|
117 | block_num bstart,bend,bn; |
---|
118 | int result; |
---|
119 | |
---|
120 | hi = pos + nbytes - 1; /* high end of range */ |
---|
121 | bstart = pos / ABLOCKSIZE; |
---|
122 | bend = hi / ABLOCKSIZE; |
---|
123 | offset = pos % ABLOCKSIZE; /* offset of blk we're playing with */ |
---|
124 | |
---|
125 | numleft = nbytes; |
---|
126 | dest_ptr = buf; /* caller's buffer */ |
---|
127 | |
---|
128 | for (bn = bstart; bn <= bend; bn++) { |
---|
129 | toread = min (numleft, ABLOCKSIZE - offset); |
---|
130 | if ((bptr = find_block (af, bn)) == NULL) { /* not there, read file */ |
---|
131 | lseek (af -> desc, (long)(bn * ABLOCKSIZE + offset), SEEK_SET); |
---|
132 | result = read (af -> desc, dest_ptr, toread); |
---|
133 | if (result != toread) |
---|
134 | goto read_error; |
---|
135 | } else { |
---|
136 | memcpy (dest_ptr, bptr + offset, toread); |
---|
137 | } |
---|
138 | dest_ptr += toread; |
---|
139 | numleft -= toread; |
---|
140 | offset = 0; /* start from blk beginning next time */ |
---|
141 | } |
---|
142 | |
---|
143 | return (nbytes); |
---|
144 | |
---|
145 | read_error: |
---|
146 | return (result); |
---|
147 | } |
---|
148 | |
---|
149 | /* |
---|
150 | * |
---|
151 | * awrite () -- Routine to handle atomic writing of the file. This |
---|
152 | * hairy beast allocates blocks when needed. |
---|
153 | * |
---|
154 | */ |
---|
155 | awrite(af,buf,nbytes,pos) |
---|
156 | afile af; |
---|
157 | int nbytes, pos; |
---|
158 | char *buf; |
---|
159 | { |
---|
160 | int hi, numleft, towrite, offset; |
---|
161 | char *bptr,*src_ptr; |
---|
162 | block_num bstart,bend,bn; |
---|
163 | int result; |
---|
164 | |
---|
165 | hi = pos + nbytes - 1; /* high end of range */ |
---|
166 | bstart = pos / ABLOCKSIZE; |
---|
167 | bend = hi / ABLOCKSIZE; |
---|
168 | offset = pos % ABLOCKSIZE; |
---|
169 | |
---|
170 | numleft = nbytes; |
---|
171 | src_ptr = buf; /* caller's buffer */ |
---|
172 | for (bn = bstart; bn <= bend; bn++) { |
---|
173 | towrite = min (ABLOCKSIZE - offset, numleft); |
---|
174 | if ((bptr = find_block (af, bn)) == NULL) { /* not there, make new one */ |
---|
175 | bptr = calloc (1, ABLOCKSIZE); |
---|
176 | lseek (af -> desc, (long)(bn * ABLOCKSIZE), SEEK_SET); |
---|
177 | result = read (af -> desc, bptr, ABLOCKSIZE); |
---|
178 | if (result < 0) |
---|
179 | goto write_error; |
---|
180 | /* write block back out, thus reserving quota */ |
---|
181 | lseek (af -> desc, (long)(bn * ABLOCKSIZE), SEEK_SET); |
---|
182 | result = write (af -> desc, bptr, ABLOCKSIZE); |
---|
183 | if (result < 0) |
---|
184 | goto write_error; |
---|
185 | |
---|
186 | add_block (af, bn, bptr); |
---|
187 | } |
---|
188 | memcpy (bptr+offset, src_ptr, towrite); |
---|
189 | src_ptr += towrite; |
---|
190 | numleft -= towrite; |
---|
191 | offset = 0; /* after first, no offset */ |
---|
192 | } |
---|
193 | return (nbytes); |
---|
194 | |
---|
195 | write_error: |
---|
196 | return (result); |
---|
197 | } |
---|
198 | |
---|
199 | /* |
---|
200 | * |
---|
201 | * aclose -- Atomic closes the file. This is where the action is. Our |
---|
202 | * changes are in memory, and we have to write it all back out. |
---|
203 | * |
---|
204 | * |
---|
205 | */ |
---|
206 | aclose(af) |
---|
207 | afile af; |
---|
208 | { |
---|
209 | struct dir_blk *db,*olddb; |
---|
210 | register i; |
---|
211 | struct flock lock; |
---|
212 | |
---|
213 | /* loop thru dir blocks, writing all blocks */ |
---|
214 | for (db = (struct dir_blk *) af -> dir_list; db != NULL;) { |
---|
215 | for (i = 0; i < db -> used; i++) { |
---|
216 | lseek (af -> desc, (long)(db -> bnum[i] * ABLOCKSIZE), |
---|
217 | SEEK_SET); |
---|
218 | write (af -> desc, db -> bptr[i], ABLOCKSIZE); |
---|
219 | free (db -> bptr[i]); |
---|
220 | db -> bptr[i] = 0; |
---|
221 | } |
---|
222 | olddb = db; |
---|
223 | db = db -> next; |
---|
224 | free((char *)olddb); |
---|
225 | } |
---|
226 | |
---|
227 | fsync(af -> desc); /* tell kernel to get a move on */ |
---|
228 | lock.l_type = F_UNLCK; |
---|
229 | lock.l_start = 0; |
---|
230 | lock.l_whence = 0; |
---|
231 | lock.l_len = 0; |
---|
232 | fcntl(af -> desc, F_SETLK, &lock); |
---|
233 | af -> dir_list = NULL; |
---|
234 | maxdirty = max(maxdirty, af -> dirty_blks); |
---|
235 | af -> desc = -1; /* to prevent reuse */ |
---|
236 | (void) free ((char *)af); |
---|
237 | } |
---|
238 | |
---|
239 | /* |
---|
240 | * |
---|
241 | * aabort -- Routine to close an atomic file, without writing the changes |
---|
242 | * out. |
---|
243 | * |
---|
244 | */ |
---|
245 | |
---|
246 | aabort(af) |
---|
247 | afile af; |
---|
248 | { |
---|
249 | struct dir_blk *db,*olddb; |
---|
250 | register i; |
---|
251 | struct flock lock; |
---|
252 | |
---|
253 | |
---|
254 | /* loop thru dir blocks, freeing all blocks */ |
---|
255 | for (db = (struct dir_blk *) af -> dir_list; db != NULL;) { |
---|
256 | for (i = 0; i < db -> used; i++) { |
---|
257 | (void) free (db -> bptr[i]); |
---|
258 | db -> bptr[i] = 0; |
---|
259 | } |
---|
260 | olddb = db; |
---|
261 | db = db -> next; |
---|
262 | (void) free((char *)olddb); |
---|
263 | } |
---|
264 | |
---|
265 | ftruncate(af -> desc, (long)(af -> file_size)); |
---|
266 | lock.l_type = F_UNLCK; |
---|
267 | lock.l_start = 0; |
---|
268 | lock.l_whence = 0; |
---|
269 | lock.l_len = 0; |
---|
270 | fcntl(af -> desc, F_SETLK, &lock); |
---|
271 | |
---|
272 | af -> dir_list = NULL; |
---|
273 | maxdirty = max(maxdirty, af -> dirty_blks); |
---|
274 | af -> desc = -1; /* to prevent reuse */ |
---|
275 | (void) free ((char *)af); |
---|
276 | } |
---|
277 | |
---|
278 | |
---|
279 | /* |
---|
280 | * |
---|
281 | * find_block -- Checks the block list for the given block, and returns |
---|
282 | * a pointer to the block. |
---|
283 | * |
---|
284 | */ |
---|
285 | |
---|
286 | char *find_block(af, bnum) |
---|
287 | afile af; |
---|
288 | block_num bnum; |
---|
289 | { |
---|
290 | int i; |
---|
291 | struct dir_blk *db; |
---|
292 | |
---|
293 | if (af -> dir_list != NULL) { /* anything there? */ |
---|
294 | for (db = (struct dir_blk *) af -> dir_list; db != NULL; db = db -> next) { |
---|
295 | for (i = 0; i < db -> used; i++) |
---|
296 | if (bnum == db -> bnum [i]) /* found it */ |
---|
297 | return (db -> bptr [i]); |
---|
298 | } |
---|
299 | } |
---|
300 | |
---|
301 | return (NULL); |
---|
302 | } |
---|
303 | |
---|
304 | /* |
---|
305 | * |
---|
306 | * add_block -- Add a new block to the dir structure. |
---|
307 | * |
---|
308 | */ |
---|
309 | add_block (af, bnum, bptr) |
---|
310 | afile af; |
---|
311 | block_num bnum; |
---|
312 | char *bptr; |
---|
313 | { |
---|
314 | struct dir_blk *db; |
---|
315 | |
---|
316 | if (af -> dir_list == NULL) { /* create dir list */ |
---|
317 | af -> dir_list = calloc (1, sizeof (struct dir_blk)); |
---|
318 | /* calloc initializes everything we need... */ |
---|
319 | } |
---|
320 | |
---|
321 | db = (struct dir_blk *) af -> dir_list; |
---|
322 | if (db -> used >= ABLKSINDIR) { /* no space, make new */ |
---|
323 | db = (struct dir_blk *) calloc (1, sizeof (struct dir_blk)); |
---|
324 | db -> next = (struct dir_blk *) af -> dir_list; |
---|
325 | af -> dir_list = (char *) db; |
---|
326 | /* fall thru to add into db (which has space now) */ |
---|
327 | } |
---|
328 | |
---|
329 | db -> bptr [db -> used] = bptr; |
---|
330 | db -> bnum [db -> used] = bnum; |
---|
331 | db -> used++; |
---|
332 | |
---|
333 | af -> dirty_blks++; |
---|
334 | |
---|
335 | return; |
---|
336 | } |
---|
337 | |
---|