|
1 #include <stdlib.h> |
|
2 #include <assert.h> |
|
3 |
|
4 #include "op_base.h" |
|
5 #include "../lib/log.h" |
|
6 |
|
7 /* |
|
8 * Free the op. |
|
9 * |
|
10 * The op must any oustanding request responded to first, must not be open, and must not have a transaction. |
|
11 * |
|
12 * The op will be free'd. |
|
13 */ |
|
14 static void dbfs_op_free (struct dbfs_op *op) { |
|
15 assert(op); |
|
16 assert(!op->open); |
|
17 assert(!op->req); |
|
18 assert(!op->trans); |
|
19 |
|
20 // free_fn |
|
21 if (op->free_fn) |
|
22 op->free_fn(op); |
|
23 |
|
24 // and then free the op |
|
25 free(op); |
|
26 } |
|
27 |
|
28 void dbfs_op_fail (struct dbfs_op *op, int err) { |
|
29 assert(op->req); |
|
30 |
|
31 if (op->trans) { |
|
32 // abort the trans |
|
33 evsql_trans_abort(op->trans); |
|
34 |
|
35 op->trans = NULL; |
|
36 } |
|
37 |
|
38 // send an error reply |
|
39 if ((err = fuse_reply_err(op->req, err))) |
|
40 // XXX: handle these failures /somehow/, or requests will hang and interrupts might handle invalid ops |
|
41 EFATAL(err, "\tdbfs_op.fail %p:%p -> reply with fuse_reply_err", op, op->req); |
|
42 |
|
43 // drop the req |
|
44 op->req = NULL; |
|
45 |
|
46 // is it open? |
|
47 if (!op->open) { |
|
48 // no, we can free it now and then forget about the whole thing |
|
49 dbfs_op_free(op); |
|
50 |
|
51 } else { |
|
52 // we need to wait for release |
|
53 |
|
54 } |
|
55 } |
|
56 |
|
57 /* |
|
58 * The op_open transaction is ready for use. |
|
59 */ |
|
60 static void dbfs_op_ready (struct evsql_trans *trans, void *arg) { |
|
61 struct dbfs_op *op = arg; |
|
62 |
|
63 assert(trans == op->trans); |
|
64 assert(op->req); |
|
65 assert(!op->open); |
|
66 |
|
67 INFO("\tdbfs_op.ready %p:%p -> trans=%p", op, op->req, trans); |
|
68 |
|
69 // remember the transaction |
|
70 op->trans = trans; |
|
71 |
|
72 // ready |
|
73 op->open_fn(op); |
|
74 |
|
75 // good |
|
76 return; |
|
77 } |
|
78 |
|
79 /* |
|
80 * The op trans was committed, i.e. release has completed |
|
81 */ |
|
82 static void dbfs_op_done (struct evsql_trans *trans, void *arg) { |
|
83 struct dbfs_op *op = arg; |
|
84 int err; |
|
85 |
|
86 assert(trans == op->trans); |
|
87 assert(op->req); |
|
88 assert(!op->open); // should not be considered as open anymore at this point, as errors should release |
|
89 |
|
90 INFO("\tdbfs_op.done %p:%p -> OK", op, op->req); |
|
91 |
|
92 // forget trans |
|
93 op->trans = NULL; |
|
94 |
|
95 // just reply |
|
96 if ((err = fuse_reply_err(op->req, 0))) |
|
97 // XXX: handle these failures /somehow/, or requests will hang and interrupts might handle invalid ops |
|
98 EFATAL(err, "dbfs_op.done %p:%p -> reply with fuse_reply_err", op, op->req); |
|
99 |
|
100 // req is done |
|
101 op->req = NULL; |
|
102 |
|
103 // then we can just free op |
|
104 dbfs_op_free(op); |
|
105 } |
|
106 |
|
107 /* |
|
108 * The op trans has failed, somehow, at some point, some where. |
|
109 * |
|
110 * This might happend during the open evsql_trans, during a read evsql_query, during the release |
|
111 * evsql_trans_commit, or at any point in between. |
|
112 * |
|
113 * 1) loose the transaction |
|
114 * 2) if op has a req, we handle failing it |
|
115 */ |
|
116 static void dbfs_op_error (struct evsql_trans *trans, void *arg) { |
|
117 struct dbfs_op *op = arg; |
|
118 |
|
119 // unless we fail |
|
120 assert(trans == op->trans); |
|
121 |
|
122 INFO("\tdbfs_op.error %p:%p -> evsql transaction error: %s", op, op->req, evsql_trans_error(trans)); |
|
123 |
|
124 // deassociate the trans |
|
125 op->trans = NULL; |
|
126 |
|
127 // if we were answering a req, error it out, and if the op isn't open, free |
|
128 // if we didn't have a req outstanding, the op must be open, so we wouldn't free it in any case, and must wait |
|
129 // for the next read/release to detect this and return an error reply |
|
130 if (op->req) |
|
131 dbfs_op_fail(op, EIO); |
|
132 else |
|
133 assert(op->open); |
|
134 } |
|
135 |
|
136 int dbfs_op_open (struct dbfs *ctx, struct dbfs_op *op, struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi, dbfs_op_free_cb free_fn, dbfs_op_open_cb open_fn) { |
|
137 int err; |
|
138 |
|
139 assert(op && req && ino && fi); |
|
140 assert(!(op->req || op->ino)); |
|
141 |
|
142 // initialize the op |
|
143 op->req = req; |
|
144 op->ino = ino; |
|
145 // copy *fi since it's on the stack |
|
146 op->fi = *fi; |
|
147 op->fi.fh = (uint64_t) op; |
|
148 op->free_fn = free_fn; |
|
149 op->open_fn = open_fn; |
|
150 |
|
151 // start a new transaction |
|
152 if ((op->trans = evsql_trans(ctx->db, EVSQL_TRANS_SERIALIZABLE, dbfs_op_error, dbfs_op_ready, dbfs_op_done, op)) == NULL) |
|
153 SERROR(err = EIO); |
|
154 |
|
155 // XXX: handle interrupts |
|
156 |
|
157 // good |
|
158 return 0; |
|
159 |
|
160 error: |
|
161 // nothing of ours to cleanup |
|
162 return err; |
|
163 } |
|
164 |
|
165 int dbfs_op_open_reply (struct dbfs_op *op) { |
|
166 int err; |
|
167 |
|
168 // detect earlier failures |
|
169 if (!op->trans && (err = EIO)) |
|
170 ERROR("op trans has failed"); |
|
171 |
|
172 // send the openddir reply |
|
173 if ((err = fuse_reply_open(op->req, &op->fi))) |
|
174 EERROR(err, "fuse_reply_open"); |
|
175 |
|
176 // req is done |
|
177 op->req = NULL; |
|
178 |
|
179 // op is now open |
|
180 op->open = 1; |
|
181 |
|
182 // good |
|
183 return 0; |
|
184 |
|
185 error: |
|
186 return err; |
|
187 } |
|
188 |
|
189 struct dbfs_op *dbfs_op_req (struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi) { |
|
190 struct dbfs_op *op = (struct dbfs_op *) fi->fh; |
|
191 int err; |
|
192 |
|
193 // validate |
|
194 assert(op); |
|
195 assert(!op->req); |
|
196 assert(op->open); |
|
197 assert(op->ino == ino); |
|
198 |
|
199 // store the new req |
|
200 op->req = req; |
|
201 |
|
202 // detect earlier failures |
|
203 if (!op->trans && (err = EIO)) |
|
204 ERROR("op trans has failed"); |
|
205 |
|
206 // good |
|
207 return op; |
|
208 |
|
209 error: |
|
210 dbfs_op_fail(op, err); |
|
211 |
|
212 return NULL; |
|
213 } |
|
214 |
|
215 int dbfs_op_req_done (struct dbfs_op *op) { |
|
216 // validate |
|
217 assert(op); |
|
218 assert(op->req); |
|
219 assert(op->open); |
|
220 |
|
221 // unassign the req |
|
222 op->req = NULL; |
|
223 |
|
224 // k |
|
225 return 0; |
|
226 } |
|
227 |
|
228 void dbfs_op_release (struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi) { |
|
229 struct dbfs_op *op = (struct dbfs_op *) fi->fh; |
|
230 int err; |
|
231 |
|
232 assert(op); |
|
233 assert(!op->req); |
|
234 assert(op->ino == ino); |
|
235 |
|
236 // update to this req |
|
237 op->req = req; |
|
238 |
|
239 // fi is irrelevant, we don't touch the flags anyways |
|
240 (void) fi; |
|
241 |
|
242 // handle failed trans |
|
243 if (!op->trans && (err = EIO)) |
|
244 ERROR("trans has failed"); |
|
245 |
|
246 // log |
|
247 INFO("\tdbfs_op.release %p:%p : ino=%lu, fi=%p : trans=%p", op, req, ino, fi, op->trans); |
|
248 |
|
249 // we must commit the transaction. |
|
250 // Note that this might cause dbfs_op_error to be called, we can tell if that happaned by looking at op->req |
|
251 // or op->trans - this means that we need to keep the op open when calling trans_commit, so that op_error |
|
252 // doesn't free it out from underneath us. |
|
253 if (evsql_trans_commit(op->trans)) |
|
254 SERROR(err = EIO); |
|
255 |
|
256 // fall-through to cleanup |
|
257 err = 0; |
|
258 |
|
259 error: |
|
260 // the op is not open anymore and can be free'd next, because we either: |
|
261 // a) already caught an error |
|
262 // b) we get+send an error later on |
|
263 // c) we get+send the done/no-error later on |
|
264 op->open = 0; |
|
265 |
|
266 // did the commit/pre-commit-checks fail? |
|
267 if (err) { |
|
268 // a) the trans failed earlier (read), so we have a req but no trans |
|
269 // b) the trans commit failed, op_error got called -> no req and no trans |
|
270 // c) the trans commit failed, op_error did not get called -> have req and trans |
|
271 // we either have a req (may or may not have trans), or we don't have a trans either |
|
272 // i.e. there is no situation where we don't have a req but do have a trans |
|
273 |
|
274 if (op->req) |
|
275 dbfs_op_fail(op, err); |
|
276 else |
|
277 assert(!op->trans); |
|
278 |
|
279 } else { |
|
280 // shouldn't slip by, op_done should not get called directly. Once it does, it will handle both. |
|
281 assert(op->req); |
|
282 assert(op->trans); |
|
283 } |
|
284 } |
|
285 |