]> mj.ucw.cz Git - libucw.git/blob - ucw/fb-atomic.c
1b4a07bbd1a38296af15a0d6291d162ca6d86719
[libucw.git] / ucw / fb-atomic.c
1 /*
2  *      UCW Library -- Atomic Buffered Write to Files
3  *
4  *      (c) 2006 Martin Mares <mj@ucw.cz>
5  *
6  *      This software may be freely distributed and used according to the terms
7  *      of the GNU Lesser General Public License.
8  */
9
10 #include "ucw/lib.h"
11 #include "ucw/fastbuf.h"
12 #include "ucw/lfs.h"
13 #include "ucw/conf.h"
14 #include "ucw/trans.h"
15
16 #include <string.h>
17 #include <fcntl.h>
18 #include <unistd.h>
19
20 static uns trace;
21
22 #ifndef TEST
23
24 static struct cf_section fbatomic_config = {
25   CF_ITEMS {
26     CF_UNS("Trace", &trace)
27   }
28 };
29
30 static void CONSTRUCTOR fbatomic_init_config(void)
31 {
32   cf_declare_section("FBAtomic", &fbatomic_config, 1);
33 }
34
35 #endif
36
37 #define TRACE(m...) do { if(trace) msg(L_DEBUG, "FB_ATOMIC: " m); } while(0)
38
39 struct fb_atomic_file {
40   int fd;
41   int use_count;
42   int record_len;
43   uns locked;
44   byte name[1];
45 };
46
47 void
48 fbatomic_internal_write(struct fastbuf *f)
49 {
50   struct fb_atomic_file *af = FB_ATOMIC(f)->af;
51   int size = f->bptr - f->buffer;
52   if (size)
53     {
54       ASSERT(af->record_len < 0 || !(size % af->record_len));
55       int res = write(af->fd, f->buffer, size);
56       if (res < 0)
57         bthrow(f, "fb.write", "Error writing %s: %m", f->name);
58       if (res != size)
59         bthrow(f, "fb.write", "Unexpected partial write to %s: written only %d bytes of %d", f->name, res, size);
60       f->bptr = f->buffer;
61     }
62 }
63
64 static void
65 fbatomic_spout(struct fastbuf *f)
66 {
67   if (f->bptr < f->bufend)              /* Explicit flushes should be ignored */
68     return;
69
70   struct fb_atomic *F = FB_ATOMIC(f);
71   if (F->af->locked)
72     {
73       uns written = f->bptr - f->buffer;
74       uns size = f->bufend - f->buffer + F->slack_size;
75       F->slack_size *= 2;
76       TRACE("Reallocating buffer for atomic file %s with slack %d", f->name, F->slack_size);
77       f->buffer = xrealloc(f->buffer, size);
78       f->bufend = f->buffer + size;
79       f->bptr = f->buffer + written;
80       F->expected_max_bptr = f->bufend - F->slack_size;
81     }
82   else
83     fbatomic_internal_write(f);
84 }
85
86 static void
87 fbatomic_close(struct fastbuf *f)
88 {
89   struct fb_atomic_file *af = FB_ATOMIC(f)->af;
90   if (!(f->flags & FB_DEAD))
91     fbatomic_internal_write(f); /* Need to flush explicitly, because the file can be locked */
92   if (!--af->use_count)
93     {
94       close(af->fd);
95       xfree(af);
96     }
97   xfree(f);
98 }
99
100 struct fastbuf *
101 fbatomic_open(const char *name, struct fastbuf *master, uns bufsize, int record_len)
102 {
103   struct fb_atomic *F = xmalloc_zero(sizeof(*F));
104   struct fastbuf *f = &F->fb;
105   struct fb_atomic_file *af;
106   if (master)
107     {
108       af = FB_ATOMIC(master)->af;
109       af->use_count++;
110       ASSERT(af->record_len == record_len);
111     }
112   else
113     {
114       int fd = ucw_open(name, O_WRONLY | O_CREAT | O_TRUNC | O_APPEND, 0666);
115       if (fd < 0)
116         trans_throw("fb.open", NULL, "Cannot create %s: %m", name);
117       af = xmalloc_zero(sizeof(*af) + strlen(name));
118       af->fd = fd;
119       af->use_count = 1;
120       af->record_len = record_len;
121       af->locked = (record_len < 0);
122       strcpy(af->name, name);
123     }
124   F->af = af;
125   if (record_len > 0 && bufsize % record_len)
126     bufsize += record_len - (bufsize % record_len);
127   f->buffer = xmalloc(bufsize);
128   f->bufend = f->buffer + bufsize;
129   F->slack_size = (record_len < 0) ? -record_len : 0;
130   ASSERT(bufsize > F->slack_size);
131   F->expected_max_bptr = f->bufend - F->slack_size;
132   f->bptr = f->bstop = f->buffer;
133   f->name = af->name;
134   f->spout = fbatomic_spout;
135   f->close = fbatomic_close;
136   fb_tie(f);
137   return f;
138 }
139
140 #ifdef TEST
141
142 int main(int argc UNUSED, char **argv UNUSED)
143 {
144   struct fastbuf *f, *g;
145
146   // Always trace in the test
147   trace = 1;
148
149   msg(L_INFO, "Testing block writes");
150   f = fbatomic_open("test", NULL, 16, 4);
151   for (u32 i=0; i<17; i++)
152     bwrite(f, &i, 4);
153   bclose(f);
154
155   msg(L_INFO, "Testing interleaved var-size writes");
156   f = fbatomic_open("test2", NULL, 23, -5);
157   g = fbatomic_open("test2", f, 23, -5);
158   for (int i=0; i<100; i++)
159     {
160       struct fastbuf *x = (i%2) ? g : f;
161       bprintf(x, "%c<%d>\n", "fg"[i%2], ((259309*i) % 1000000) >> (i % 8));
162       fbatomic_commit(x);
163     }
164   bclose(f);
165   bclose(g);
166
167   return 0;
168 }
169
170 #endif