heim_string_t heim_error_copy_string(heim_error_t error) { if (heim_get_tid(error) != HEIM_TID_ERROR) { if (heim_get_tid(error) == heim_number_get_type_id()) return __heim_string_constant(strerror(heim_number_get_int((heim_number_t)error))); heim_abort("invalid heim_error_t"); } /* XXX concat all strings */ return heim_retain(error->msg); }
int heim_error_get_code(heim_error_t error) { if (error == NULL) return -1; if (heim_get_tid(error) != HEIM_TID_ERROR) { if (heim_get_tid(error) == heim_number_get_type_id()) return heim_number_get_int((heim_number_t)error); heim_abort("invalid heim_error_t"); } return error->error_code; }
heim_error_t heim_error_append(heim_error_t top, heim_error_t append) { if (heim_get_tid(top) != HEIM_TID_ERROR) { if (heim_get_tid(top) == heim_number_get_type_id()) return top; heim_abort("invalid heim_error_t"); } if (top->next) heim_release(top->next); top->next = heim_retain(append); return top; }
static void eval_object(heim_object_t o) { heim_tid_t t = heim_get_tid(o); if (t == heim_array_get_type_id()) { heim_array_iterate_f(o, NULL, eval_array_element); } else if (t == heim_dict_get_type_id()) { const char *op = heim_dict_get_value(o, HSTR("op")); heim_assert(op != NULL, "op missing"); if (strcmp(op, "repeat") == 0) { eval_repeat(o); } else if (strcmp(op, "kinit") == 0) { eval_kinit(o); } else if (strcmp(op, "kgetcred") == 0) { eval_kgetcred(o); } else if (strcmp(op, "kdestroy") == 0) { eval_kdestroy(o); } else { errx(1, "unsupported ops %s", op); } } else errx(1, "unsupported"); }
int heim_base2json(heim_object_t obj, void (*out)(char *, void *), void *ctx) { heim_tid_t type = heim_get_tid(obj); __block int fail = 0, needcomma = 0; switch (type) { case HEIM_TID_ARRAY: out("[ ", ctx); heim_array_iterate(obj, ^(heim_object_t sub) { if (needcomma) out(", ", ctx); fail |= heim_base2json(sub, out, ctx); needcomma = 1; }); out("]", ctx); break; case HEIM_TID_DICT: out("{ ", ctx); heim_dict_iterate(obj, ^(heim_object_t key, heim_object_t value) { if (needcomma) out(", ", ctx); fail |= heim_base2json(key, out, ctx); out(" = ", ctx); fail |= heim_base2json(value, out, ctx); needcomma = 1; });
static int base2json(heim_object_t obj, struct twojson *j) { heim_tid_t type; if (obj == NULL) { indent(j); j->out(j->ctx, "<NULL>\n"); } type = heim_get_tid(obj); switch (type) { case HEIM_TID_ARRAY: indent(j); j->out(j->ctx, "[\n"); j->indent++; heim_array_iterate_f(obj, j, array2json); j->indent--; indent(j); j->out(j->ctx, "]\n"); break; case HEIM_TID_DICT: indent(j); j->out(j->ctx, "{\n"); j->indent++; heim_dict_iterate_f(obj, j, dict2json); j->indent--; indent(j); j->out(j->ctx, "}\n"); break; case HEIM_TID_STRING: indent(j); j->out(j->ctx, "\""); j->out(j->ctx, heim_string_get_utf8(obj)); j->out(j->ctx, "\""); break; case HEIM_TID_NUMBER: { char num[16]; indent(j); snprintf(num, sizeof(num), "%d", heim_number_get_int(obj)); j->out(j->ctx, num); break; } case HEIM_TID_NULL: indent(j); j->out(j->ctx, "null"); break; case HEIM_TID_BOOL: indent(j); j->out(j->ctx, heim_bool_val(obj) ? "true" : "false"); break; default: return 1; } return 0; }
/** * Delete a key and its value from the DB * * * @param db Open DB handle * @param key Key * @param error Output error object * * @return 0 on success, system error otherwise * * @addtogroup heimbase */ int heim_db_delete_key(heim_db_t db, heim_string_t table, heim_data_t key, heim_error_t *error) { heim_string_t key64 = NULL; int ret; if (error != NULL) *error = NULL; if (table == NULL) table = HSTR(""); if (heim_get_tid(db) != HEIM_TID_DB) return EINVAL; if (db->plug->delf == NULL) return EBADF; if (!db->in_transaction) { ret = heim_db_begin(db, 0, error); if (ret) goto err; heim_assert(db->in_transaction, "Internal error"); ret = heim_db_delete_key(db, table, key, error); if (ret) { (void) heim_db_rollback(db, NULL); return ret; } return heim_db_commit(db, error); } /* Transaction emulation */ heim_assert(db->set_keys != NULL, "Internal error"); key64 = to_base64(key, error); if (key64 == NULL) return HEIM_ENOMEM(error); if (db->ro_tx) { ret = heim_db_begin(db, 0, error); if (ret) goto err; } ret = heim_path_create(db->del_keys, 29, heim_number_create(1), error, table, key64, NULL); if (ret) goto err; heim_path_delete(db->set_keys, error, table, key64, NULL); heim_release(key64); return 0; err: heim_release(key64); return HEIM_ERROR(error, ret, (ret, N_("Could not set a dict value while while " "deleting a DB value", ""))); }
static int db_replay_log(heim_db_t db, heim_error_t *error) { int ret; heim_string_t journal_fname = NULL; heim_object_t journal; size_t len; heim_assert(!db->in_transaction, "DB transaction not open"); heim_assert(db->set_keys == NULL && db->set_keys == NULL, "DB transaction not open"); if (error) *error = NULL; if (db->options == NULL) return 0; journal_fname = heim_dict_get_value(db->options, HSTR("journal-filename")); if (journal_fname == NULL) return 0; ret = read_json(heim_string_get_utf8(journal_fname), &journal, error); if (ret == ENOENT) return 0; if (ret == 0 && journal == NULL) return 0; if (ret != 0) return ret; if (heim_get_tid(journal) != HEIM_TID_ARRAY) return HEIM_ERROR(error, EINVAL, (ret, N_("Invalid journal contents; delete journal", ""))); len = heim_array_get_length(journal); if (len > 0) db->set_keys = heim_array_get_value(journal, 0); if (len > 1) db->del_keys = heim_array_get_value(journal, 1); ret = db_do_log_actions(db, error); if (ret) return ret; /* Truncate replay log and we're done */ ret = open_file(heim_string_get_utf8(journal_fname), 1, 0, NULL, error); if (ret) return ret; heim_release(db->set_keys); heim_release(db->del_keys); db->set_keys = NULL; db->del_keys = NULL; return 0; }
static heim_dict_t parse_dict(struct parse_ctx *ctx) { heim_dict_t dict; size_t count = 0; int ret; heim_assert(*ctx->p == '{', "string doesn't start with {"); dict = heim_dict_create(11); if (dict == NULL) { ctx->error = heim_error_create_enomem(); return NULL; } ctx->p += 1; /* safe because parse_pair() calls white_spaces() first */ while ((ret = parse_pair(dict, ctx)) > 0) count++; if (ret < 0) { heim_release(dict); return NULL; } if (count == 1 && !(ctx->flags & HEIM_JSON_F_NO_DATA_DICT)) { heim_object_t v = heim_dict_copy_value(dict, heim_tid_data_uuid_key); /* * Binary data encoded as a dict with a single magic key with * base64-encoded value? Decode as heim_data_t. */ if (v != NULL && heim_get_tid(v) == HEIM_TID_STRING) { void *buf; size_t len; buf = malloc(strlen(heim_string_get_utf8(v))); if (buf == NULL) { heim_release(dict); heim_release(v); ctx->error = heim_error_create_enomem(); return NULL; } len = base64_decode(heim_string_get_utf8(v), buf); heim_release(v); if (len == -1) { free(buf); return dict; /* assume aliasing accident */ } heim_release(dict); return (heim_dict_t)heim_data_ref_create(buf, len, free); } } return dict; }
/** * Open a transaction on the given db. * * @param db Open DB handle * @param error Output error object * * @return 0 on success, system error otherwise * * @addtogroup heimbase */ int heim_db_begin(heim_db_t db, int read_only, heim_error_t *error) { int ret; if (heim_get_tid(db) != HEIM_TID_DB) return EINVAL; if (db->in_transaction && (read_only || !db->ro_tx || (!read_only && !db->ro_tx))) heim_abort("DB already in transaction"); if (db->plug->setf == NULL || db->plug->delf == NULL) return EINVAL; if (db->plug->beginf) { ret = db->plug->beginf(db->db_data, read_only, error); } else if (!db->in_transaction) { /* Try to emulate transactions */ if (db->plug->lockf == NULL) return EINVAL; /* can't lock? -> no transactions */ /* Assume unlock provides sync/durability */ ret = db->plug->lockf(db->db_data, read_only, error); if (ret) return ret; ret = db_replay_log(db, error); if (ret) { ret = db->plug->unlockf(db->db_data, error); return ret; } db->set_keys = heim_dict_create(11); if (db->set_keys == NULL) return ENOMEM; db->del_keys = heim_dict_create(11); if (db->del_keys == NULL) { heim_release(db->set_keys); db->set_keys = NULL; return ENOMEM; } } else { heim_assert(read_only == 0, "Internal error"); ret = db->plug->lockf(db->db_data, 0, error); if (ret) return ret; } db->in_transaction = 1; db->ro_tx = !!read_only; return 0; }
/** * Iterate a callback function over keys and values from a DB. * * @param db Open DB handle * @param iter_data Callback function's private data * @param iter_f Callback function, called once per-key/value pair * @param error Output error object * * @addtogroup heimbase */ void heim_db_iterate_f(heim_db_t db, heim_string_t table, void *iter_data, heim_db_iterator_f_t iter_f, heim_error_t *error) { if (error != NULL) *error = NULL; if (heim_get_tid(db) != HEIM_TID_DB) return; if (!db->in_transaction) db->plug->iterf(db->db_data, table, iter_data, iter_f, error); }
/** * Lookup a key's value in the DB. * * Returns 0 on success, -1 if the key does not exist in the DB, or a * system error number on failure. * * @param db Open DB handle * @param key Key * @param error Output error object * * @return the value (retained), if there is one for the given key * * @addtogroup heimbase */ heim_data_t heim_db_copy_value(heim_db_t db, heim_string_t table, heim_data_t key, heim_error_t *error) { heim_object_t v; heim_data_t result; if (heim_get_tid(db) != HEIM_TID_DB) return NULL; if (error != NULL) *error = NULL; if (table == NULL) table = HSTR(""); if (db->in_transaction) { heim_string_t key64; key64 = to_base64(key, error); if (key64 == NULL) { if (error) *error = heim_error_create_enomem(); return NULL; } v = heim_path_copy(db->set_keys, error, table, key64, NULL); if (v != NULL) { heim_release(key64); return v; } v = heim_path_copy(db->del_keys, error, table, key64, NULL); /* can't be NULL */ heim_release(key64); if (v != NULL) return NULL; } result = db->plug->copyf(db->db_data, table, key, error); return result; }
/** * Clone (duplicate) an open DB handle. * * This is useful for multi-threaded applications. Applications must * synchronize access to any given DB handle. * * Returns EBUSY if there is an open transaction for the input db. * * @param db Open DB handle * @param error Output error object * * @return a DB handle * * @addtogroup heimbase */ heim_db_t heim_db_clone(heim_db_t db, heim_error_t *error) { heim_db_t result; int ret; if (heim_get_tid(db) != HEIM_TID_DB) heim_abort("Expected a database"); if (db->in_transaction) heim_abort("DB handle is busy"); if (db->plug->clonef == NULL) { return heim_db_create(heim_string_get_utf8(db->dbtype), heim_string_get_utf8(db->dbname), db->options, error); } result = _heim_alloc_object(&db_object, sizeof(*result)); if (result == NULL) { if (error) *error = heim_error_create_enomem(); return NULL; } result->set_keys = NULL; result->del_keys = NULL; ret = db->plug->clonef(db->db_data, &result->db_data, error); if (ret) { heim_release(result); if (error && !*error) *error = heim_error_create(ENOENT, N_("Could not re-open DB while cloning", "")); return NULL; } db->db_data = NULL; return result; }
/** * Rollback an open transaction on the given db. * * @param db Open DB handle * @param error Output error object * * @return 0 on success, system error otherwise * * @addtogroup heimbase */ int heim_db_rollback(heim_db_t db, heim_error_t *error) { int ret = 0; if (heim_get_tid(db) != HEIM_TID_DB) return EINVAL; if (!db->in_transaction) return 0; if (db->plug->rollbackf != NULL) ret = db->plug->rollbackf(db->db_data, error); else if (db->plug->unlockf != NULL) ret = db->plug->unlockf(db->db_data, error); heim_release(db->set_keys); heim_release(db->del_keys); db->set_keys = NULL; db->del_keys = NULL; db->in_transaction = 0; db->ro_tx = 0; return ret; }
OM_uint32 GSSAPI_CALLCONV _gss_krb5_acquire_cred_ext(OM_uint32 * minor_status, const gss_name_t desired_name, gss_const_OID credential_type, const void *credential_data, OM_uint32 time_req, gss_const_OID desired_mech, gss_cred_usage_t cred_usage, gss_cred_id_t * output_cred_handle) { krb5_init_creds_context ctx = NULL; krb5_get_init_creds_opt *opt = NULL; krb5_principal principal; krb5_context context; krb5_error_code kret; gsskrb5_cred handle = NULL; krb5_ccache ccache = NULL, ccachereplace = NULL; char *passwordstr = NULL; char *cache_name = NULL; char *lkdc_hostname = NULL; hx509_cert hxcert = NULL; heim_array_t bundleacl = NULL; krb5_principal new_name = NULL; GSSAPI_KRB5_INIT(&context); cred_usage &= GSS_C_OPTION_MASK; if (cred_usage != GSS_C_INITIATE && cred_usage != GSS_C_BOTH) { *minor_status = GSS_KRB5_S_G_BAD_USAGE; return GSS_S_FAILURE; } if (desired_name == GSS_C_NO_NAME) return GSS_S_FAILURE; if (gss_oid_equal(credential_type, GSS_C_CRED_HEIMBASE)) { heim_object_t pw, cname, cert, lkdc; heim_dict_t dict = (heim_dict_t)credential_data; pw = heim_dict_copy_value(dict, _gsskrb5_kGSSICPassword); if (pw) { if (heim_get_tid(pw) == heim_string_get_type_id()) { passwordstr = heim_string_copy_utf8(pw); if (passwordstr == NULL) { kret = ENOMEM; goto out; } } else if (heim_get_tid(pw) == heim_data_get_type_id()) { passwordstr = malloc(heim_data_get_length(pw) + 1); if (passwordstr == NULL) { kret = ENOMEM; goto out; } memcpy(passwordstr, heim_data_get_bytes(pw), heim_data_get_length(pw)); passwordstr[heim_data_get_length(pw)] = '\0'; } heim_release(pw); } cname = heim_dict_copy_value(dict, _gsskrb5_kGSSICKerberosCacheName); if (cname) { cache_name = heim_string_copy_utf8(cname); heim_release(cname); } bundleacl = heim_dict_copy_value(dict, _gsskrb5_kGSSICAppIdentifierACL); #ifdef PKINIT cert = heim_dict_copy_value(dict, _gsskrb5_kGSSICCertificate); if (cert) { kret = hx509_cert_init_SecFramework(context->hx509ctx, cert, &hxcert); if (kret) goto out; heim_release(cert); } #endif lkdc = heim_dict_copy_value(dict, _gsskrb5_kGSSICLKDCHostname); if (lkdc) { lkdc_hostname = heim_string_copy_utf8(lkdc); heim_release(lkdc); } } else if (gss_oid_equal(credential_type, GSS_C_CRED_PASSWORD)) { gss_buffer_t password = (gss_buffer_t)credential_data; passwordstr = malloc(password->length + 1); if (passwordstr == NULL) { kret = ENOMEM; goto out; } memcpy(passwordstr, password->value, password->length); passwordstr[password->length] = '\0'; } else { *minor_status = KRB5_NOCREDS_SUPPLIED; /* XXX */ return GSS_S_FAILURE; } if (passwordstr == NULL && hxcert == NULL) { *minor_status = KRB5_NOCREDS_SUPPLIED; /* XXX */ return GSS_S_FAILURE; } *output_cred_handle = NULL; handle = calloc(1, sizeof(*handle)); if (handle == NULL) { *minor_status = ENOMEM; return (GSS_S_FAILURE); } principal = (krb5_principal)desired_name; HEIMDAL_MUTEX_init(&handle->cred_id_mutex); kret = krb5_copy_principal(context, principal, &handle->principal); if (kret) goto out; kret = krb5_cc_new_unique(context, NULL, NULL, &ccache); if (kret) goto out; kret = krb5_get_init_creds_opt_alloc(context, &opt); if (kret) goto out; krb5_get_init_creds_opt_set_default_flags(context, "gss", krb5_principal_get_realm(context, principal), opt); krb5_get_init_creds_opt_set_forwardable(opt, 1); krb5_get_init_creds_opt_set_proxiable(opt, 1); krb5_get_init_creds_opt_set_renew_life(opt, 3600 * 24 * 30); /* 1 month */ if (hxcert) { char *cert_pool[2] = { "KEYCHAIN:", NULL }; kret = krb5_get_init_creds_opt_set_pkinit(context, opt, principal, NULL, "KEYCHAIN:", cert_pool, NULL, 8, NULL, NULL, NULL); if (kret) goto out; } kret = krb5_init_creds_init(context, handle->principal, NULL, NULL, NULL, opt, &ctx); if (kret) goto out; if (passwordstr) { kret = krb5_init_creds_set_password(context, ctx, passwordstr); memset(passwordstr, 0, strlen(passwordstr)); free(passwordstr); passwordstr = NULL; if (kret) goto out; } if (hxcert) { kret = krb5_init_creds_set_pkinit_client_cert(context, ctx, hxcert); if (kret) goto out; } if (lkdc_hostname) { kret = krb5_init_creds_set_kdc_hostname(context, ctx, lkdc_hostname); free(lkdc_hostname); lkdc_hostname = NULL; if (kret) goto out; } kret = krb5_init_creds_get(context, ctx); if (kret) goto out; handle->endtime = _krb5_init_creds_get_cred_endtime(context, ctx); /* * If we where subjected to a referral, update the name of the credential */ new_name = _krb5_init_creds_get_cred_client(context, ctx); if (new_name && !krb5_principal_compare(context, new_name, handle->principal)) { krb5_free_principal(context, handle->principal); kret = krb5_copy_principal(context, new_name, &handle->principal); if (kret) goto out; } /* * Now store the credential */ if (cache_name) { /* check if caller told us to use a specific cache */ kret = krb5_cc_resolve(context, cache_name, &ccachereplace); if (kret) goto out; } else { /* * check if there an existing cache to overwrite before we lay * down the new cache */ (void)krb5_cc_cache_match(context, principal, &ccachereplace); } kret = krb5_init_creds_store(context, ctx, ccache); if (kret == 0) kret = krb5_init_creds_store_config(context, ctx, ccache); if (bundleacl) krb5_cc_set_acl(context, ccache, "kHEIMAttrBundleIdentifierACL", bundleacl); krb5_init_creds_free(context, ctx); ctx = NULL; if (kret) goto out; krb5_get_init_creds_opt_free(context, opt); opt = NULL; /* * If we have a credential with the same naame, lets overwrite it */ if (ccachereplace) { kret = krb5_cc_move(context, ccache, ccachereplace); if (kret) goto out; handle->ccache = ccachereplace; ccachereplace = NULL; } else { handle->ccache = ccache; } handle->usage = cred_usage; *minor_status = 0; *output_cred_handle = (gss_cred_id_t)handle; if (cache_name) free(cache_name); heim_release(bundleacl); return GSS_S_COMPLETE; out: if (bundleacl) heim_release(bundleacl); if (opt) krb5_get_init_creds_opt_free(context, opt); if (ctx) krb5_init_creds_free(context, ctx); if (lkdc_hostname) free(lkdc_hostname); if (cache_name) free(cache_name); if (passwordstr) { memset(passwordstr, 0, strlen(passwordstr)); free(passwordstr); } if (ccachereplace) krb5_cc_close(context, ccachereplace); if (ccache) krb5_cc_destroy(context, ccache); if (handle) { if (handle->principal) krb5_free_principal(context, handle->principal); HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex); free(handle); } *minor_status = kret; return GSS_S_FAILURE; }
static int base2json(heim_object_t obj, struct twojson *j) { heim_tid_t type; int first = 0; if (obj == NULL) { if (j->flags & HEIM_JSON_F_CNULL2JSNULL) { obj = heim_null_create(); } else if (j->flags & HEIM_JSON_F_NO_C_NULL) { return EINVAL; } else { indent(j); j->out(j->ctx, "<NULL>\n"); /* This is NOT valid JSON! */ return 0; } } type = heim_get_tid(obj); switch (type) { case HEIM_TID_ARRAY: indent(j); j->out(j->ctx, "[\n"); j->indent++; first = j->first; j->first = 1; heim_array_iterate_f(obj, j, array2json); j->indent--; if (!j->first) j->out(j->ctx, "\n"); indent(j); j->out(j->ctx, "]\n"); j->first = first; break; case HEIM_TID_DICT: indent(j); j->out(j->ctx, "{\n"); j->indent++; first = j->first; j->first = 1; heim_dict_iterate_f(obj, j, dict2json); j->indent--; if (!j->first) j->out(j->ctx, "\n"); indent(j); j->out(j->ctx, "}\n"); j->first = first; break; case HEIM_TID_STRING: indent(j); j->out(j->ctx, "\""); j->out(j->ctx, heim_string_get_utf8(obj)); j->out(j->ctx, "\""); break; case HEIM_TID_DATA: { heim_dict_t d; heim_string_t v; const heim_octet_string *data; char *b64 = NULL; int ret; if (j->flags & HEIM_JSON_F_NO_DATA) return EINVAL; /* JSON doesn't do binary */ data = heim_data_get_data(obj); ret = base64_encode(data->data, data->length, &b64); if (ret < 0 || b64 == NULL) return ENOMEM; if (j->flags & HEIM_JSON_F_NO_DATA_DICT) { indent(j); j->out(j->ctx, "\""); j->out(j->ctx, b64); /* base64-encode; hope there's no aliasing */ j->out(j->ctx, "\""); free(b64); } else { /* * JSON has no way to represent binary data, therefore the * following is a Heimdal-specific convention. * * We encode binary data as a dict with a single very magic * key with a base64-encoded value. The magic key includes * a uuid, so we're not likely to alias accidentally. */ d = heim_dict_create(2); if (d == NULL) { free(b64); return ENOMEM; } v = heim_string_ref_create(b64, free); if (v == NULL) { free(b64); heim_release(d); return ENOMEM; } ret = heim_dict_set_value(d, heim_tid_data_uuid_key, v); heim_release(v); if (ret) { heim_release(d); return ENOMEM; } ret = base2json(d, j); heim_release(d); if (ret) return ret; } break; } case HEIM_TID_NUMBER: { char num[32]; indent(j); snprintf(num, sizeof (num), "%d", heim_number_get_int(obj)); j->out(j->ctx, num); break; } case HEIM_TID_NULL: indent(j); j->out(j->ctx, "null"); break; case HEIM_TID_BOOL: indent(j); j->out(j->ctx, heim_bool_val(obj) ? "true" : "false"); break; default: return 1; } return 0; }
/** * Commit an open transaction on the given db. * * @param db Open DB handle * @param error Output error object * * @return 0 on success, system error otherwise * * @addtogroup heimbase */ int heim_db_commit(heim_db_t db, heim_error_t *error) { int ret, ret2; heim_string_t journal_fname = NULL; if (heim_get_tid(db) != HEIM_TID_DB) return EINVAL; if (!db->in_transaction) return 0; if (db->plug->commitf == NULL && db->plug->lockf == NULL) return EINVAL; if (db->plug->commitf != NULL) { ret = db->plug->commitf(db->db_data, error); if (ret) (void) db->plug->rollbackf(db->db_data, error); db->in_transaction = 0; db->ro_tx = 0; return ret; } if (db->ro_tx) { ret = 0; goto done; } if (db->options == NULL) journal_fname = heim_dict_get_value(db->options, HSTR("journal-filename")); if (journal_fname != NULL) { heim_array_t a; heim_string_t journal_contents; size_t len, bytes; int save_errno; /* Create contents for replay log */ ret = ENOMEM; a = heim_array_create(); if (a == NULL) goto err; ret = heim_array_append_value(a, db->set_keys); if (ret) { heim_release(a); goto err; } ret = heim_array_append_value(a, db->del_keys); if (ret) { heim_release(a); goto err; } journal_contents = heim_json_copy_serialize(a, 0, error); heim_release(a); /* Write replay log */ if (journal_fname != NULL) { int fd; ret = open_file(heim_string_get_utf8(journal_fname), 1, 0, &fd, error); if (ret) { heim_release(journal_contents); goto err; } len = strlen(heim_string_get_utf8(journal_contents)); bytes = write(fd, heim_string_get_utf8(journal_contents), len); save_errno = errno; heim_release(journal_contents); ret = close(fd); if (bytes != len) { /* Truncate replay log */ (void) open_file(heim_string_get_utf8(journal_fname), 1, 0, NULL, error); ret = save_errno; goto err; } if (ret) goto err; } } /* Apply logged actions */ ret = db_do_log_actions(db, error); if (ret) return ret; if (db->plug->syncf != NULL) { /* fsync() or whatever */ ret = db->plug->syncf(db->db_data, error); if (ret) return ret; } /* Truncate replay log and we're done */ if (journal_fname != NULL) { int fd; ret2 = open_file(heim_string_get_utf8(journal_fname), 1, 0, &fd, error); if (ret2 == 0) (void) close(fd); } /* * Clean up; if we failed to remore the replay log that's OK, we'll * handle that again in heim_db_commit() */ done: heim_release(db->set_keys); heim_release(db->del_keys); db->set_keys = NULL; db->del_keys = NULL; db->in_transaction = 0; db->ro_tx = 0; ret2 = db->plug->unlockf(db->db_data, error); if (ret == 0) ret = ret2; return ret; err: return HEIM_ERROR(error, ret, (ret, N_("Error while committing transaction: %s", ""), strerror(ret))); }
static int json_db_open(void *plug, const char *dbtype, const char *dbname, heim_dict_t options, void **db, heim_error_t *error) { json_db_t jsondb; heim_dict_t contents = NULL; heim_string_t dbname_s = NULL; heim_string_t bkpname_s = NULL; if (error) *error = NULL; if (dbtype && *dbtype && strcmp(dbtype, "json")) return HEIM_ERROR(error, EINVAL, (EINVAL, N_("Wrong DB type", ""))); if (dbname && *dbname && strcmp(dbname, "MEMORY") != 0) { char *ext = strrchr(dbname, '.'); char *bkpname; size_t len; int ret; if (ext == NULL || strcmp(ext, ".json") != 0) return HEIM_ERROR(error, EINVAL, (EINVAL, N_("JSON DB files must end in .json", ""))); if (options) { heim_object_t vc, ve, vt; vc = heim_dict_get_value(options, HSTR("create")); ve = heim_dict_get_value(options, HSTR("exclusive")); vt = heim_dict_get_value(options, HSTR("truncate")); if (vc && vt) { ret = open_file(dbname, 1, ve ? 1 : 0, NULL, error); if (ret) return ret; } else if (vc || ve || vt) { return HEIM_ERROR(error, EINVAL, (EINVAL, N_("Invalid JSON DB open options", ""))); } /* * We don't want cloned handles to truncate the DB, eh? * * We should really just create a copy of the options dict * rather than modify the caller's! But for that it'd be * nicer to have copy utilities in heimbase, something like * this: * * heim_object_t heim_copy(heim_object_t src, int depth, * heim_error_t *error); * * so that options = heim_copy(options, 1); means copy the * dict but nothing else (whereas depth == 0 would mean * heim_retain(), and depth > 1 would be copy that many * levels). */ heim_dict_delete_key(options, HSTR("create")); heim_dict_delete_key(options, HSTR("exclusive")); heim_dict_delete_key(options, HSTR("truncate")); } dbname_s = heim_string_create(dbname); if (dbname_s == NULL) return HEIM_ENOMEM(error); len = snprintf(NULL, 0, "%s~", dbname); bkpname = malloc(len + 2); if (bkpname == NULL) { heim_release(dbname_s); return HEIM_ENOMEM(error); } (void) snprintf(bkpname, len + 1, "%s~", dbname); bkpname_s = heim_string_create(bkpname); free(bkpname); if (bkpname_s == NULL) { heim_release(dbname_s); return HEIM_ENOMEM(error); } ret = read_json(dbname, (heim_object_t *)&contents, error); if (ret) return ret; if (contents != NULL && heim_get_tid(contents) != HEIM_TID_DICT) return HEIM_ERROR(error, EINVAL, (EINVAL, N_("JSON DB contents not valid JSON", ""))); } jsondb = heim_alloc(sizeof (*jsondb), "json_db", NULL); if (jsondb == NULL) { heim_release(contents); heim_release(dbname_s); return ENOMEM; } jsondb->last_read_time = time(NULL); jsondb->fd = -1; jsondb->dbname = dbname_s; jsondb->bkpname = bkpname_s; jsondb->read_only = 0; if (contents != NULL) jsondb->dict = contents; else { jsondb->dict = heim_dict_create(29); if (jsondb->dict == NULL) { heim_release(jsondb); return ENOMEM; } } *db = jsondb; return 0; }