terom@29: #include terom@29: #include terom@29: terom@29: #include "op_base.h" terom@29: #include "../lib/log.h" terom@29: terom@29: /* terom@29: * Free the op. terom@29: * terom@29: * The op must any oustanding request responded to first, must not be open, and must not have a transaction. terom@29: * terom@29: * The op will be free'd. terom@29: */ terom@29: static void dbfs_op_free (struct dbfs_op *op) { terom@29: assert(op); terom@29: assert(!op->open); terom@29: assert(!op->req); terom@29: assert(!op->trans); terom@29: terom@29: // free_fn terom@29: if (op->free_fn) terom@29: op->free_fn(op); terom@29: terom@29: // and then free the op terom@29: free(op); terom@29: } terom@29: terom@29: void dbfs_op_fail (struct dbfs_op *op, int err) { terom@29: assert(op->req); terom@29: terom@29: if (op->trans) { terom@29: // abort the trans terom@29: evsql_trans_abort(op->trans); terom@29: terom@29: op->trans = NULL; terom@29: } terom@29: terom@29: // send an error reply terom@29: if ((err = fuse_reply_err(op->req, err))) terom@29: // XXX: handle these failures /somehow/, or requests will hang and interrupts might handle invalid ops terom@29: EFATAL(err, "\tdbfs_op.fail %p:%p -> reply with fuse_reply_err", op, op->req); terom@29: terom@29: // drop the req terom@29: op->req = NULL; terom@29: terom@29: // is it open? terom@29: if (!op->open) { terom@29: // no, we can free it now and then forget about the whole thing terom@29: dbfs_op_free(op); terom@29: terom@29: } else { terom@29: // we need to wait for release terom@29: terom@29: } terom@29: } terom@29: terom@29: /* terom@29: * The op_open transaction is ready for use. terom@29: */ terom@29: static void dbfs_op_ready (struct evsql_trans *trans, void *arg) { terom@29: struct dbfs_op *op = arg; terom@29: terom@29: assert(trans == op->trans); terom@29: assert(op->req); terom@29: assert(!op->open); terom@29: terom@29: INFO("\tdbfs_op.ready %p:%p -> trans=%p", op, op->req, trans); terom@29: terom@29: // remember the transaction terom@29: op->trans = trans; terom@29: terom@29: // ready terom@29: op->open_fn(op); terom@29: terom@29: // good terom@29: return; terom@29: } terom@29: terom@29: /* terom@29: * The op trans was committed, i.e. release has completed terom@29: */ terom@29: static void dbfs_op_done (struct evsql_trans *trans, void *arg) { terom@29: struct dbfs_op *op = arg; terom@29: int err; terom@29: terom@29: assert(trans == op->trans); terom@29: assert(op->req); terom@29: assert(!op->open); // should not be considered as open anymore at this point, as errors should release terom@29: terom@29: INFO("\tdbfs_op.done %p:%p -> OK", op, op->req); terom@29: terom@29: // forget trans terom@29: op->trans = NULL; terom@29: terom@29: // just reply terom@29: if ((err = fuse_reply_err(op->req, 0))) terom@29: // XXX: handle these failures /somehow/, or requests will hang and interrupts might handle invalid ops terom@29: EFATAL(err, "dbfs_op.done %p:%p -> reply with fuse_reply_err", op, op->req); terom@29: terom@29: // req is done terom@29: op->req = NULL; terom@29: terom@29: // then we can just free op terom@29: dbfs_op_free(op); terom@29: } terom@29: terom@29: /* terom@29: * The op trans has failed, somehow, at some point, some where. terom@29: * terom@29: * This might happend during the open evsql_trans, during a read evsql_query, during the release terom@29: * evsql_trans_commit, or at any point in between. terom@29: * terom@29: * 1) loose the transaction terom@29: * 2) if op has a req, we handle failing it terom@29: */ terom@29: static void dbfs_op_error (struct evsql_trans *trans, void *arg) { terom@29: struct dbfs_op *op = arg; terom@29: terom@29: // unless we fail terom@29: assert(trans == op->trans); terom@29: terom@29: INFO("\tdbfs_op.error %p:%p -> evsql transaction error: %s", op, op->req, evsql_trans_error(trans)); terom@29: terom@29: // deassociate the trans terom@29: op->trans = NULL; terom@29: terom@29: // if we were answering a req, error it out, and if the op isn't open, free terom@29: // 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 terom@29: // for the next read/release to detect this and return an error reply terom@29: if (op->req) terom@29: dbfs_op_fail(op, EIO); terom@29: else terom@29: assert(op->open); terom@29: } terom@29: terom@29: 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) { terom@29: int err; terom@29: terom@29: assert(op && req && ino && fi); terom@29: assert(!(op->req || op->ino)); terom@29: terom@29: // initialize the op terom@29: op->req = req; terom@29: op->ino = ino; terom@29: // copy *fi since it's on the stack terom@29: op->fi = *fi; terom@29: op->fi.fh = (uint64_t) op; terom@29: op->free_fn = free_fn; terom@29: op->open_fn = open_fn; terom@29: terom@29: // start a new transaction terom@29: if ((op->trans = evsql_trans(ctx->db, EVSQL_TRANS_SERIALIZABLE, dbfs_op_error, dbfs_op_ready, dbfs_op_done, op)) == NULL) terom@29: SERROR(err = EIO); terom@29: terom@29: // XXX: handle interrupts terom@29: terom@29: // good terom@29: return 0; terom@29: terom@29: error: terom@29: // nothing of ours to cleanup terom@29: return err; terom@29: } terom@29: terom@29: int dbfs_op_open_reply (struct dbfs_op *op) { terom@29: int err; terom@29: terom@29: // detect earlier failures terom@29: if (!op->trans && (err = EIO)) terom@29: ERROR("op trans has failed"); terom@29: terom@29: // send the openddir reply terom@29: if ((err = fuse_reply_open(op->req, &op->fi))) terom@29: EERROR(err, "fuse_reply_open"); terom@29: terom@29: // req is done terom@29: op->req = NULL; terom@29: terom@29: // op is now open terom@29: op->open = 1; terom@29: terom@29: // good terom@29: return 0; terom@29: terom@29: error: terom@29: return err; terom@29: } terom@29: terom@29: struct dbfs_op *dbfs_op_req (struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi) { terom@29: struct dbfs_op *op = (struct dbfs_op *) fi->fh; terom@29: int err; terom@29: terom@29: // validate terom@29: assert(op); terom@29: assert(op->open); terom@29: assert(op->ino == ino); terom@40: terom@40: // detect concurrent requests terom@40: if (op->req) { terom@40: // must handle req ourself, shouldn't fail the other req terom@40: WARNING("op.%p: concurrent req: %p -> %p", op, op->req, req); terom@40: terom@40: // XXX: ignore error errors... terom@40: fuse_reply_err(req, EBUSY); terom@29: terom@40: return NULL; terom@40: terom@40: } else terom@40: // store the new req terom@40: op->req = req; terom@40: terom@40: // inodes change? terom@40: if (op->ino != ino) terom@40: XERROR(err = EBADF, "op.%p: wrong ino: %u -> %lu", op, op->ino, ino); terom@29: terom@29: // detect earlier failures terom@29: if (!op->trans && (err = EIO)) terom@29: ERROR("op trans has failed"); terom@29: terom@29: // good terom@29: return op; terom@29: terom@29: error: terom@29: dbfs_op_fail(op, err); terom@29: terom@29: return NULL; terom@29: } terom@29: terom@29: int dbfs_op_req_done (struct dbfs_op *op) { terom@29: // validate terom@29: assert(op); terom@29: assert(op->req); terom@29: assert(op->open); terom@29: terom@29: // unassign the req terom@29: op->req = NULL; terom@29: terom@29: // k terom@29: return 0; terom@29: } terom@29: terom@29: void dbfs_op_release (struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi) { terom@29: struct dbfs_op *op = (struct dbfs_op *) fi->fh; terom@29: int err; terom@29: terom@29: assert(op); terom@29: assert(!op->req); terom@29: assert(op->ino == ino); terom@29: terom@29: // update to this req terom@29: op->req = req; terom@29: terom@29: // fi is irrelevant, we don't touch the flags anyways terom@29: (void) fi; terom@29: terom@29: // handle failed trans terom@29: if (!op->trans && (err = EIO)) terom@29: ERROR("trans has failed"); terom@29: terom@29: // log terom@29: INFO("\tdbfs_op.release %p:%p : ino=%lu, fi=%p : trans=%p", op, req, ino, fi, op->trans); terom@29: terom@29: // we must commit the transaction. terom@29: // Note that this might cause dbfs_op_error to be called, we can tell if that happaned by looking at op->req terom@29: // or op->trans - this means that we need to keep the op open when calling trans_commit, so that op_error terom@29: // doesn't free it out from underneath us. terom@29: if (evsql_trans_commit(op->trans)) terom@29: SERROR(err = EIO); terom@29: terom@29: // fall-through to cleanup terom@29: err = 0; terom@29: terom@29: error: terom@29: // the op is not open anymore and can be free'd next, because we either: terom@29: // a) already caught an error terom@29: // b) we get+send an error later on terom@29: // c) we get+send the done/no-error later on terom@29: op->open = 0; terom@29: terom@29: // did the commit/pre-commit-checks fail? terom@29: if (err) { terom@29: // a) the trans failed earlier (read), so we have a req but no trans terom@29: // b) the trans commit failed, op_error got called -> no req and no trans terom@29: // c) the trans commit failed, op_error did not get called -> have req and trans terom@29: // we either have a req (may or may not have trans), or we don't have a trans either terom@29: // i.e. there is no situation where we don't have a req but do have a trans terom@29: terom@29: if (op->req) terom@29: dbfs_op_fail(op, err); terom@29: else terom@29: assert(!op->trans); terom@29: terom@29: } else { terom@29: // shouldn't slip by, op_done should not get called directly. Once it does, it will handle both. terom@29: assert(op->req); terom@29: assert(op->trans); terom@29: } terom@29: } terom@29: