/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Chunk I/O * ========= * Copyright 2018-2019 Eduardo Silva * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #include #include #include /* * Validate if root_path exists, if don't, create it, otherwise * check if we have write access to it. */ static int check_root_path(struct cio_ctx *ctx, const char *root_path) { int ret; int len; if (!root_path) { return -1; } len = strlen(root_path); if (len <= 0) { return -1; } ret = cio_os_isdir(root_path); if (ret == -1) { /* Try to create the path */ ret = cio_os_mkpath(root_path, 0755); if (ret == -1) { return -1; } cio_log_info(ctx, "created root path %s", root_path); return 0; } /* Directory already exists, check write access */ return access(root_path, W_OK); } void cio_options_init(struct cio_options *options) { memset(options, 0, sizeof(struct cio_options)); options->initialized = CIO_INITIALIZED; options->root_path = NULL; options->user = NULL; options->group = NULL; options->chmod = NULL; options->log_cb = NULL; options->log_level = CIO_LOG_INFO; options->flags = CIO_OPEN_RW; options->realloc_size_hint = CIO_DISABLE_REALLOC_HINT; } struct cio_ctx *cio_create(struct cio_options *options) { int ret; struct cio_ctx *ctx; struct cio_options default_options; if (options == NULL) { cio_options_init(&default_options); options = &default_options; } else { if (options->initialized != CIO_INITIALIZED) { /* the caller 'must' call cio_options_init() or pass NULL before creating a context */ fprintf(stderr, "[cio] 'options' has not been initialized properly\n"); return NULL; } } /* sanitize chunk open flags */ if (!(options->flags & CIO_OPEN_RW) && !(options->flags & CIO_OPEN_RD)) { options->flags |= CIO_OPEN_RW; } if (options->log_level < CIO_LOG_ERROR || options->log_level > CIO_LOG_TRACE) { fprintf(stderr, "[cio] invalid log level, aborting\n"); return NULL; } #ifndef CIO_HAVE_BACKEND_FILESYSTEM if (root_path) { fprintf(stderr, "[cio] file system backend not supported\n"); return NULL; } #endif /* Create context */ ctx = calloc(1, sizeof(struct cio_ctx)); if (!ctx) { perror("calloc"); return NULL; } mk_list_init(&ctx->streams); ctx->page_size = cio_getpagesize(); ctx->max_chunks_up = CIO_MAX_CHUNKS_UP; ctx->options.flags = options->flags; ctx->realloc_size_hint = CIO_DISABLE_REALLOC_HINT; if (options->user != NULL) { ctx->options.user = strdup(options->user); } if (options->group != NULL) { ctx->options.group = strdup(options->group); } if (options->chmod != NULL) { ctx->options.chmod = strdup(options->chmod); } /* Counters */ ctx->total_chunks = 0; ctx->total_chunks_up = 0; /* Logging */ cio_set_log_callback(ctx, options->log_cb); cio_set_log_level(ctx, options->log_level); /* Check or initialize file system root path */ if (options->root_path) { ret = check_root_path(ctx, options->root_path); if (ret == -1) { cio_log_error(ctx, "[chunkio] cannot initialize root path %s\n", options->root_path); free(ctx); return NULL; } ctx->options.root_path = strdup(options->root_path); } else { ctx->options.root_path = NULL; } if (ctx->options.user != NULL) { ret = cio_file_lookup_user(ctx->options.user, &ctx->processed_user); if (ret != CIO_OK) { cio_destroy(ctx); return NULL; } } else { ctx->processed_user = NULL; } if (ctx->options.group != NULL) { ret = cio_file_lookup_group(ctx->options.group, &ctx->processed_group); if (ret != CIO_OK) { cio_destroy(ctx); return NULL; } } else { ctx->processed_group = NULL; } if (options->realloc_size_hint > 0) { ret = cio_set_realloc_size_hint(ctx, options->realloc_size_hint); if (ret == -1) { cio_log_error(ctx, "[chunkio] cannot initialize with realloc size hint %d\n", options->realloc_size_hint); cio_destroy(ctx); return NULL; } } return ctx; } int cio_load(struct cio_ctx *ctx, char *chunk_extension) { int ret; if (ctx->options.root_path) { ret = cio_scan_streams(ctx, chunk_extension); return ret; } return 0; } static int qsort_stream(struct cio_stream *stream, int (*compar)(const void *, const void *)) { int i = 0; int items; struct mk_list *tmp; struct mk_list *head; struct cio_chunk **arr; struct cio_chunk *chunk; items = mk_list_size(&stream->chunks); if (items == 0) { return 0; } arr = malloc(sizeof(struct cio_chunk *) * items); if (!arr) { perror("malloc"); return -1; } /* map chunks to the array and and unlink them */ mk_list_foreach_safe(head, tmp, &stream->chunks) { chunk = mk_list_entry(head, struct cio_chunk, _head); arr[i++] = chunk; mk_list_del(&chunk->_head); } /* sort the chunks, just trust in 'compar' external function */ qsort(arr, items, sizeof(struct cio_chunk *), compar); /* link the chunks in the proper order back to the list head */ for (i = 0; i < items; i++) { chunk = arr[i]; mk_list_add(&chunk->_head, &stream->chunks); } free(arr); return 0; } /* * Sort chunks using the 'compar' callback function. This is pretty much a * wrapper over qsort(3). The sort is done inside every stream content. * * Use this function after cio_load() only. */ int cio_qsort(struct cio_ctx *ctx, int (*compar)(const void *, const void *)) { struct mk_list *head; struct cio_stream *stream; mk_list_foreach(head, &ctx->streams) { stream = mk_list_entry(head, struct cio_stream, _head); qsort_stream(stream, compar); } return 0; } void cio_destroy(struct cio_ctx *ctx) { if (!ctx) { return; } cio_stream_destroy_all(ctx); if (ctx->options.user != NULL) { free(ctx->options.user); } if (ctx->options.group != NULL) { free(ctx->options.group); } if (ctx->options.chmod != NULL) { free(ctx->options.chmod); } if (ctx->processed_user != NULL) { free(ctx->processed_user); } if (ctx->processed_group != NULL) { free(ctx->processed_group); } if (ctx->options.root_path != NULL) { free(ctx->options.root_path); } free(ctx); } void cio_set_log_callback(struct cio_ctx *ctx, void (*log_cb)) { ctx->options.log_cb = log_cb; } int cio_set_log_level(struct cio_ctx *ctx, int level) { if (level < CIO_LOG_ERROR || level > CIO_LOG_TRACE) { return -1; } ctx->options.log_level = level; return 0; } int cio_set_max_chunks_up(struct cio_ctx *ctx, int n) { if (n < 1) { return -1; } ctx->max_chunks_up = n; return 0; } int cio_set_realloc_size_hint(struct cio_ctx *ctx, size_t realloc_size_hint) { if (realloc_size_hint < CIO_REALLOC_HINT_MIN) { cio_log_error(ctx, "[chunkio] cannot specify less than %zu bytes\n", CIO_REALLOC_HINT_MIN); return -1; } else if (realloc_size_hint > CIO_REALLOC_HINT_MAX) { cio_log_error(ctx, "[chunkio] cannot specify more than %zu bytes\n", CIO_REALLOC_HINT_MAX); return -1; } ctx->realloc_size_hint = realloc_size_hint; return 0; } void cio_enable_file_trimming(struct cio_ctx *ctx) { ctx->options.flags |= CIO_TRIM_FILES; } void cio_disable_file_trimming(struct cio_ctx *ctx) { ctx->options.flags &= ~CIO_TRIM_FILES; }