/* Copyright (c) 2015 DataLab, s.l. This file is part of GlusterFS. This file is licensed to you under your choice of the GNU Lesser General Public License, version 3 or any later version (LGPLv3 or later), or the GNU General Public License, version 2 (GPLv2), in all cases as published by the Free Software Foundation. */ #include #include #include #include #include #include #include "ec-mem-types.h" #include "ec-code.h" #include "ec-messages.h" #include "ec-code-c.h" #include "ec-helpers.h" #ifdef USE_EC_DYNAMIC_X64 #include "ec-code-x64.h" #endif #ifdef USE_EC_DYNAMIC_SSE #include "ec-code-sse.h" #endif #ifdef USE_EC_DYNAMIC_AVX #include "ec-code-avx.h" #endif #define EC_CODE_SIZE (1024 * 64) #define EC_CODE_ALIGN 4096 #define EC_CODE_CHUNK_MIN_SIZE 512 #define EC_PROC_BUFFER_SIZE 4096 #define PROC_CPUINFO "/proc/cpuinfo" struct _ec_code_proc; typedef struct _ec_code_proc ec_code_proc_t; struct _ec_code_proc { int32_t fd; gf_boolean_t eof; gf_boolean_t error; gf_boolean_t skip; ssize_t size; ssize_t pos; char buffer[EC_PROC_BUFFER_SIZE]; }; static ec_code_gen_t *ec_code_gen_table[] = { #ifdef USE_EC_DYNAMIC_AVX &ec_code_gen_avx, #endif #ifdef USE_EC_DYNAMIC_SSE &ec_code_gen_sse, #endif #ifdef USE_EC_DYNAMIC_X64 &ec_code_gen_x64, #endif NULL}; static void ec_code_arg_set(ec_code_arg_t *arg, uint32_t value) { arg->value = value; } static void ec_code_arg_assign(ec_code_builder_t *builder, ec_code_op_t *op, ec_code_arg_t *arg, uint32_t reg) { arg->value = reg; if (builder->regs <= reg) { builder->regs = reg + 1; } } static void ec_code_arg_use(ec_code_builder_t *builder, ec_code_op_t *op, ec_code_arg_t *arg, uint32_t reg) { arg->value = reg; } static void ec_code_arg_update(ec_code_builder_t *builder, ec_code_op_t *op, ec_code_arg_t *arg, uint32_t reg) { arg->value = reg; } static ec_code_op_t * ec_code_op_next(ec_code_builder_t *builder) { ec_code_op_t *op; op = &builder->ops[builder->count++]; memset(op, 0, sizeof(ec_code_op_t)); return op; } static void ec_code_load(ec_code_builder_t *builder, uint32_t bit, uint32_t offset) { ec_code_op_t *op; op = ec_code_op_next(builder); op->op = EC_GF_OP_LOAD; ec_code_arg_assign(builder, op, &op->arg1, builder->map[bit]); ec_code_arg_set(&op->arg2, offset); ec_code_arg_set(&op->arg3, bit); } static void ec_code_store(ec_code_builder_t *builder, uint32_t reg, uint32_t bit) { ec_code_op_t *op; op = ec_code_op_next(builder); op->op = EC_GF_OP_STORE; ec_code_arg_use(builder, op, &op->arg1, builder->map[reg]); ec_code_arg_set(&op->arg2, 0); ec_code_arg_set(&op->arg3, bit); } static void ec_code_copy(ec_code_builder_t *builder, uint32_t dst, uint32_t src) { ec_code_op_t *op; op = ec_code_op_next(builder); op->op = EC_GF_OP_COPY; ec_code_arg_assign(builder, op, &op->arg1, builder->map[dst]); ec_code_arg_use(builder, op, &op->arg2, builder->map[src]); ec_code_arg_set(&op->arg3, 0); } static void ec_code_xor2(ec_code_builder_t *builder, uint32_t dst, uint32_t src) { ec_code_op_t *op; op = ec_code_op_next(builder); op->op = EC_GF_OP_XOR2; ec_code_arg_update(builder, op, &op->arg1, builder->map[dst]); ec_code_arg_use(builder, op, &op->arg2, builder->map[src]); ec_code_arg_set(&op->arg3, 0); } static void ec_code_xor3(ec_code_builder_t *builder, uint32_t dst, uint32_t src1, uint32_t src2) { ec_code_op_t *op; if (builder->code->gen->xor3 == NULL) { ec_code_copy(builder, dst, src1); ec_code_xor2(builder, dst, src2); return; } op = ec_code_op_next(builder); op->op = EC_GF_OP_XOR3; ec_code_arg_assign(builder, op, &op->arg1, builder->map[dst]); ec_code_arg_use(builder, op, &op->arg2, builder->map[src1]); ec_code_arg_use(builder, op, &op->arg3, builder->map[src2]); } static void ec_code_xorm(ec_code_builder_t *builder, uint32_t bit, uint32_t offset) { ec_code_op_t *op; op = ec_code_op_next(builder); op->op = EC_GF_OP_XORM; ec_code_arg_update(builder, op, &op->arg1, builder->map[bit]); ec_code_arg_set(&op->arg2, offset); ec_code_arg_set(&op->arg3, bit); } static void ec_code_dup(ec_code_builder_t *builder, ec_gf_op_t *op) { switch (op->op) { case EC_GF_OP_COPY: ec_code_copy(builder, op->arg1, op->arg2); break; case EC_GF_OP_XOR2: ec_code_xor2(builder, op->arg1, op->arg2); break; case EC_GF_OP_XOR3: ec_code_xor3(builder, op->arg1, op->arg2, op->arg3); break; default: break; } } static void ec_code_gf_load(ec_code_builder_t *builder, uint32_t offset) { uint32_t i; for (i = 0; i < builder->code->gf->bits; i++) { ec_code_load(builder, i, offset); } } static void ec_code_gf_load_xor(ec_code_builder_t *builder, uint32_t offset) { uint32_t i; for (i = 0; i < builder->code->gf->bits; i++) { ec_code_xorm(builder, i, offset); } } static void ec_code_gf_store(ec_code_builder_t *builder) { uint32_t i; for (i = 0; i < builder->code->gf->bits; i++) { ec_code_store(builder, i, i); } } static void ec_code_gf_clear(ec_code_builder_t *builder) { uint32_t i; ec_code_xor2(builder, 0, 0); for (i = 0; i < builder->code->gf->bits; i++) { ec_code_store(builder, 0, i); } } static void ec_code_gf_mul(ec_code_builder_t *builder, uint32_t value) { ec_gf_mul_t *mul; ec_gf_op_t *op; uint32_t map[EC_GF_MAX_REGS]; int32_t i; mul = builder->code->gf->table[value]; for (op = mul->ops; op->op != EC_GF_OP_END; op++) { ec_code_dup(builder, op); } for (i = 0; i < mul->regs; i++) { map[i] = builder->map[mul->map[i]]; } memcpy(builder->map, map, sizeof(uint32_t) * mul->regs); } static ec_code_builder_t * ec_code_prepare(ec_code_t *code, uint32_t count, uint32_t width, gf_boolean_t linear) { ec_code_builder_t *builder; uint32_t i; count *= code->gf->bits + code->gf->max_ops; count += code->gf->bits; builder = GF_MALLOC( sizeof(ec_code_builder_t) + sizeof(ec_code_op_t) * count, ec_mt_ec_code_builder_t); if (builder == NULL) { return EC_ERR(ENOMEM); } builder->address = 0; builder->code = code; builder->size = 0; builder->count = 0; builder->regs = 0; builder->error = 0; builder->bits = code->gf->bits; builder->width = width; builder->data = NULL; builder->linear = linear; builder->base = -1; for (i = 0; i < EC_GF_MAX_REGS; i++) { builder->map[i] = i; } return builder; } static size_t ec_code_space_size(void) { return (sizeof(ec_code_space_t) + 15) & ~15; } static size_t ec_code_chunk_size(void) { return (sizeof(ec_code_chunk_t) + 15) & ~15; } static ec_code_chunk_t * ec_code_chunk_from_space(ec_code_space_t *space) { return (ec_code_chunk_t *)((uintptr_t)space + ec_code_space_size()); } static void * ec_code_to_executable(ec_code_space_t *space, void *addr) { return (void *)((uintptr_t)addr - (uintptr_t)space + (uintptr_t)space->exec); } static void * ec_code_from_executable(ec_code_space_t *space, void *addr) { return (void *)((uintptr_t)addr - (uintptr_t)space->exec + (uintptr_t)space); } static void * ec_code_func_from_chunk(ec_code_chunk_t *chunk, void **exec) { void *addr; addr = (void *)((uintptr_t)chunk + ec_code_chunk_size()); *exec = ec_code_to_executable(chunk->space, addr); return addr; } static ec_code_chunk_t * ec_code_chunk_from_func(ec_code_func_linear_t func) { ec_code_chunk_t *chunk; chunk = (ec_code_chunk_t *)((uintptr_t)func - ec_code_chunk_size()); return ec_code_from_executable(chunk->space, chunk); } static ec_code_chunk_t * ec_code_chunk_split(ec_code_chunk_t *chunk, size_t size) { ec_code_chunk_t *extra; ssize_t avail; avail = chunk->size - size - ec_code_chunk_size(); if (avail > 0) { extra = (ec_code_chunk_t *)((uintptr_t)chunk + chunk->size - avail); extra->space = chunk->space; extra->size = avail; list_add(&extra->list, &chunk->list); chunk->size = size; } list_del_init(&chunk->list); return chunk; } static gf_boolean_t ec_code_chunk_touch(ec_code_chunk_t *prev, ec_code_chunk_t *next) { uintptr_t end; end = (uintptr_t)prev + ec_code_chunk_size() + prev->size; return (end == (uintptr_t)next); } static ec_code_space_t * ec_code_space_create(ec_code_t *code, size_t size) { char path[] = GLUSTERFS_LIBEXECDIR "/ec-code-dynamic.XXXXXX"; ec_code_space_t *space; void *exec; int32_t fd, err; /* We need to create memory areas to store the generated dynamic code. * Obviously these areas need to be written to be able to create the * code and they also need to be executable to execute it. * * However it's a bad practice to have a memory region that is both * writable *and* executable. In fact, selinux forbids this and causes * attempts to do so to fail (unless specifically configured). * * To solve the problem we'll use two distinct memory areas mapped to * the same physical storage. One of the memory areas will have write * permission, and the other will have execute permission. Both areas * will have the same contents. The physical storage will be a regular * file that will be mmapped to both areas. */ /* We need to create a temporary file as the backend storage for the * memory mapped areas. */ /* coverity[secure_temp] mkstemp uses 0600 as the mode and is safe */ fd = mkstemp(path); if (fd < 0) { err = errno; gf_msg(THIS->name, GF_LOG_ERROR, err, EC_MSG_DYN_CREATE_FAILED, "Unable to create a temporary file for the ec dynamic " "code"); space = EC_ERR(err); goto done; } /* Once created we don't need to keep it in the file system. It will * still exist until we close the last file descriptor or unmap the * memory areas bound to the file. */ sys_unlink(path); size = (size + EC_CODE_ALIGN - 1) & ~(EC_CODE_ALIGN - 1); if (sys_ftruncate(fd, size) < 0) { err = errno; gf_msg(THIS->name, GF_LOG_ERROR, err, EC_MSG_DYN_CREATE_FAILED, "Unable to resize the file for the ec dynamic code"); space = EC_ERR(err); goto done_close; } /* This creates an executable memory area to be able to run the * generated fragments of code. */ exec = mmap(NULL, size, PROT_READ | PROT_EXEC, MAP_SHARED, fd, 0); if (exec == MAP_FAILED) { err = errno; gf_msg(THIS->name, GF_LOG_ERROR, err, EC_MSG_DYN_CREATE_FAILED, "Unable to map the executable area for the ec dynamic " "code"); space = EC_ERR(err); goto done_close; } /* It's not important to check the return value of mlock(). If it fails * everything will continue to work normally. */ mlock(exec, size); /* This maps a read/write memory area to be able to create the dynamici * code. */ space = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (space == MAP_FAILED) { err = errno; gf_msg(THIS->name, GF_LOG_ERROR, err, EC_MSG_DYN_CREATE_FAILED, "Unable to map the writable area for the ec dynamic " "code"); space = EC_ERR(err); munmap(exec, size); goto done_close; } space->exec = exec; space->size = size; space->code = code; list_add_tail(&space->list, &code->spaces); INIT_LIST_HEAD(&space->chunks); done_close: /* If everything has succeeded, we already have the memory areas * mapped. We don't need the file descriptor anymore because the * backend storage will be there until the mmap()'d regions are * unmapped. */ sys_close(fd); done: return space; } static void ec_code_space_destroy(ec_code_space_t *space) { list_del_init(&space->list); munmap(space->exec, space->size); munmap(space, space->size); } static void ec_code_chunk_merge(ec_code_chunk_t *chunk) { ec_code_chunk_t *item, *tmp; list_for_each_entry_safe(item, tmp, &chunk->space->chunks, list) { if ((uintptr_t)item > (uintptr_t)chunk) { list_add_tail(&chunk->list, &item->list); if (ec_code_chunk_touch(chunk, item)) { chunk->size += item->size + ec_code_chunk_size(); list_del_init(&item->list); } goto check; } if (ec_code_chunk_touch(item, chunk)) { item->size += chunk->size + ec_code_chunk_size(); list_del_init(&item->list); chunk = item; } } list_add_tail(&chunk->list, &chunk->space->chunks); check: if (chunk->size == chunk->space->size - ec_code_space_size() - ec_code_chunk_size()) { ec_code_space_destroy(chunk->space); } } static ec_code_chunk_t * ec_code_space_alloc(ec_code_t *code, size_t size) { ec_code_space_t *space; ec_code_chunk_t *chunk; size_t map_size; /* To minimize fragmentation, we only allocate chunks of sizes multiples * of EC_CODE_CHUNK_MIN_SIZE. */ size = ((size + ec_code_chunk_size() + EC_CODE_CHUNK_MIN_SIZE - 1) & ~(EC_CODE_CHUNK_MIN_SIZE - 1)) - ec_code_chunk_size(); list_for_each_entry(space, &code->spaces, list) { list_for_each_entry(chunk, &space->chunks, list) { if (chunk->size >= size) { goto out; } } } map_size = EC_CODE_SIZE - ec_code_space_size() - ec_code_chunk_size(); if (map_size < size) { map_size = size; } space = ec_code_space_create(code, map_size); if (EC_IS_ERR(space)) { return (ec_code_chunk_t *)space; } chunk = ec_code_chunk_from_space(space); chunk->size = map_size - ec_code_space_size() - ec_code_chunk_size(); list_add(&chunk->list, &space->chunks); out: chunk->space = space; return ec_code_chunk_split(chunk, size); } static ec_code_chunk_t * ec_code_alloc(ec_code_t *code, uint32_t size) { ec_code_chunk_t *chunk; LOCK(&code->lock); chunk = ec_code_space_alloc(code, size); UNLOCK(&code->lock); return chunk; } static void ec_code_free(ec_code_chunk_t *chunk) { gf_lock_t *lock; lock = &chunk->space->code->lock; LOCK(lock); ec_code_chunk_merge(chunk); UNLOCK(lock); } static int32_t ec_code_write(ec_code_builder_t *builder) { ec_code_gen_t *gen; ec_code_op_t *op; uint32_t i; builder->error = 0; builder->size = 0; builder->address = 0; builder->base = -1; gen = builder->code->gen; gen->prolog(builder); for (i = 0; i < builder->count; i++) { op = &builder->ops[i]; switch (op->op) { case EC_GF_OP_LOAD: gen->load(builder, op->arg1.value, op->arg2.value, op->arg3.value); break; case EC_GF_OP_STORE: gen->store(builder, op->arg1.value, op->arg3.value); break; case EC_GF_OP_COPY: gen->copy(builder, op->arg1.value, op->arg2.value); break; case EC_GF_OP_XOR2: gen->xor2(builder, op->arg1.value, op->arg2.value); break; case EC_GF_OP_XOR3: gen->xor3(builder, op->arg1.value, op->arg2.value, op->arg3.value); break; case EC_GF_OP_XORM: gen->xorm(builder, op->arg1.value, op->arg2.value, op->arg3.value); break; default: break; } } gen->epilog(builder); return builder->error; } static void * ec_code_compile(ec_code_builder_t *builder) { ec_code_chunk_t *chunk; void *func; int32_t err; err = ec_code_write(builder); if (err != 0) { return EC_ERR(err); } chunk = ec_code_alloc(builder->code, builder->size); if (EC_IS_ERR(chunk)) { return chunk; } builder->data = ec_code_func_from_chunk(chunk, &func); err = ec_code_write(builder); if (err != 0) { ec_code_free(chunk); return EC_ERR(err); } GF_FREE(builder); return func; } ec_code_t * ec_code_create(ec_gf_t *gf, ec_code_gen_t *gen) { ec_code_t *code; code = GF_MALLOC(sizeof(ec_code_t), ec_mt_ec_code_t); if (code == NULL) { return EC_ERR(ENOMEM); } memset(code, 0, sizeof(ec_code_t)); INIT_LIST_HEAD(&code->spaces); LOCK_INIT(&code->lock); code->gf = gf; code->gen = gen; return code; } void ec_code_destroy(ec_code_t *code) { if (!list_empty(&code->spaces)) { } LOCK_DESTROY(&code->lock); GF_FREE(code); } static uint32_t ec_code_value_next(uint32_t *values, uint32_t count, uint32_t *offset) { uint32_t i, next; next = 0; for (i = *offset + 1; i < count; i++) { next = values[i]; if (next != 0) { break; } } *offset = i; return next; } static void * ec_code_build_dynamic(ec_code_t *code, uint32_t width, uint32_t *values, uint32_t count, gf_boolean_t linear) { ec_code_builder_t *builder; uint32_t offset, val, next; builder = ec_code_prepare(code, count, width, linear); if (EC_IS_ERR(builder)) { return builder; } offset = -1; next = ec_code_value_next(values, count, &offset); if (next != 0) { ec_code_gf_load(builder, offset); do { val = next; next = ec_code_value_next(values, count, &offset); if (next != 0) { ec_code_gf_mul(builder, ec_gf_div(code->gf, val, next)); ec_code_gf_load_xor(builder, offset); } } while (next != 0); ec_code_gf_mul(builder, val); ec_code_gf_store(builder); } else { ec_code_gf_clear(builder); } return ec_code_compile(builder); } static void * ec_code_build(ec_code_t *code, uint32_t width, uint32_t *values, uint32_t count, gf_boolean_t linear) { void *func; if (code->gen != NULL) { func = ec_code_build_dynamic(code, width, values, count, linear); if (!EC_IS_ERR(func)) { return func; } gf_msg_debug(THIS->name, GF_LOG_DEBUG, "Unable to generate dynamic code. Falling back " "to precompiled code"); /* The dynamic code generation shouldn't fail in normal * conditions, but if it fails at some point, it's very * probable that it will fail again, so we completely disable * dynamic code generation. */ code->gen = NULL; } ec_code_c_prepare(code->gf, values, count); if (linear) { return ec_code_c_linear; } return ec_code_c_interleaved; } ec_code_func_linear_t ec_code_build_linear(ec_code_t *code, uint32_t width, uint32_t *values, uint32_t count) { return (ec_code_func_linear_t)ec_code_build(code, width, values, count, _gf_true); } ec_code_func_interleaved_t ec_code_build_interleaved(ec_code_t *code, uint32_t width, uint32_t *values, uint32_t count) { return (ec_code_func_interleaved_t)ec_code_build(code, width, values, count, _gf_false); } void ec_code_release(ec_code_t *code, ec_code_func_t *func) { if ((func->linear != ec_code_c_linear) && (func->interleaved != ec_code_c_interleaved)) { ec_code_free(ec_code_chunk_from_func(func->linear)); } } void ec_code_error(ec_code_builder_t *builder, int32_t error) { if (builder->error == 0) { gf_msg(THIS->name, GF_LOG_ERROR, error, EC_MSG_DYN_CODEGEN_FAILED, "Failed to generate dynamic code"); builder->error = error; } } void ec_code_emit(ec_code_builder_t *builder, uint8_t *bytes, uint32_t count) { if (builder->error != 0) { return; } if (builder->data != NULL) { memcpy(builder->data + builder->size, bytes, count); } builder->size += count; builder->address += count; } static char * ec_code_proc_trim_left(char *text, ssize_t *length) { ssize_t len; for (len = *length; (len > 0) && isspace(*text); len--) { text++; } *length = len; return text; } static char * ec_code_proc_trim_right(char *text, ssize_t *length, char sep) { char *last; ssize_t len; len = *length; last = text; for (len = *length; (len > 0) && (*text != sep); len--) { if (!isspace(*text)) { last = text + 1; } text++; } *last = 0; *length = len; return text; } static char * ec_code_proc_line_parse(ec_code_proc_t *file, ssize_t *length) { char *text, *end; ssize_t len; len = file->size - file->pos; text = ec_code_proc_trim_left(file->buffer + file->pos, &len); end = ec_code_proc_trim_right(text, &len, '\n'); if (len == 0) { if (!file->eof) { if (text == file->buffer) { file->size = file->pos = 0; file->skip = _gf_true; } else { file->size = file->pos = end - text; memmove(file->buffer, text, file->pos + 1); } len = sys_read(file->fd, file->buffer + file->pos, sizeof(file->buffer) - file->pos - 1); if (len > 0) { file->size += len; } file->error = len < 0; file->eof = len <= 0; return NULL; } file->size = file->pos = 0; } else { file->pos = end - file->buffer + 1; } *length = end - text; if (file->skip) { file->skip = _gf_false; text = NULL; } return text; } static char * ec_code_proc_line(ec_code_proc_t *file, ssize_t *length) { char *text; text = NULL; while (!file->eof) { text = ec_code_proc_line_parse(file, length); if (text != NULL) { break; } } return text; } static char * ec_code_proc_split(char *text, ssize_t *length, char sep) { text = ec_code_proc_trim_right(text, length, sep); if (*length == 0) { return NULL; } (*length)--; text++; return ec_code_proc_trim_left(text, length); } static uint32_t ec_code_cpu_check(uint32_t idx, char *list, uint32_t count) { ec_code_gen_t *gen; char **ptr; char *table[count + 1]; uint32_t i; for (i = 0; i < count; i++) { table[i] = list; list += strlen(list) + 1; } gen = ec_code_gen_table[idx]; while (gen != NULL) { for (ptr = gen->flags; *ptr != NULL; ptr++) { for (i = 0; i < count; i++) { if (strcmp(*ptr, table[i]) == 0) { break; } } if (i >= count) { gen = ec_code_gen_table[++idx]; break; } } if (*ptr == NULL) { break; } } return idx; } ec_code_gen_t * ec_code_detect(xlator_t *xl, const char *def) { ec_code_proc_t file; ec_code_gen_t *gen = NULL; char *line, *data, *list; ssize_t length; uint32_t count, base, select; if (strcmp(def, "none") == 0) { gf_msg(xl->name, GF_LOG_INFO, 0, EC_MSG_EXTENSION_NONE, "Not using any cpu extensions"); return NULL; } file.fd = sys_open(PROC_CPUINFO, O_RDONLY, 0); if (file.fd < 0) { goto out; } file.size = file.pos = 0; file.eof = file.error = file.skip = _gf_false; select = 0; if (strcmp(def, "auto") != 0) { while (ec_code_gen_table[select] != NULL) { if (strcmp(ec_code_gen_table[select]->name, def) == 0) { break; } select++; } if (ec_code_gen_table[select] == NULL) { gf_msg(xl->name, GF_LOG_WARNING, EINVAL, EC_MSG_EXTENSION_UNKNOWN, "CPU extension '%s' is not known. Not using any cpu " "extensions", def); return NULL; } } else { def = NULL; } while ((line = ec_code_proc_line(&file, &length)) != NULL) { data = ec_code_proc_split(line, &length, ':'); if ((data != NULL) && (strcmp(line, "flags") == 0)) { list = data; count = 0; while ((data != NULL) && (*data != 0)) { count++; data = ec_code_proc_split(data, &length, ' '); } base = select; select = ec_code_cpu_check(select, list, count); if ((base != select) && (def != NULL)) { gf_msg(xl->name, GF_LOG_WARNING, ENOTSUP, EC_MSG_EXTENSION_UNSUPPORTED, "CPU extension '%s' is not supported", def); def = NULL; } } } if (file.error) { gf_msg(xl->name, GF_LOG_WARNING, 0, EC_MSG_EXTENSION_FAILED, "Unable to determine supported CPU extensions. Not using any " "cpu extensions"); gen = NULL; } else { gen = ec_code_gen_table[select]; if (gen == NULL) { gf_msg(xl->name, GF_LOG_INFO, 0, EC_MSG_EXTENSION_NONE, "Not using any cpu extensions"); } else { gf_msg(xl->name, GF_LOG_INFO, 0, EC_MSG_EXTENSION, "Using '%s' CPU extensions", gen->name); } } sys_close(file.fd); out: return gen; }