From d7135403a8341e865b80519f13587ed32a73a359 Mon Sep 17 00:00:00 2001 From: Ilya <markovilya197@gmail.com> Date: Thu, 28 Sep 2017 13:34:22 +0300 Subject: [PATCH] fio: extend functionality * Add listdir function based on dirent:readdir * Add mktree, rmtree, copyfile, copytree to fio module Closes #2751 --- src/coio_file.c | 140 ++++++++++++++++++++++++++++++++++++++++- src/coio_file.h | 2 + src/lua/fio.c | 29 +++++++++ src/lua/fio.lua | 132 +++++++++++++++++++++++++++++++++++++++ test/app/fio.result | 141 ++++++++++++++++++++++++++++++++++++++++++ test/app/fio.test.lua | 49 +++++++++++++++ 6 files changed, 492 insertions(+), 1 deletion(-) diff --git a/src/coio_file.c b/src/coio_file.c index 8b5bbde919..c5b2db781e 100644 --- a/src/coio_file.c +++ b/src/coio_file.c @@ -32,9 +32,10 @@ #include "coio_task.h" #include "fiber.h" #include "say.h" +#include "fio.h" #include <stdio.h> #include <stdlib.h> - +#include <dirent.h> /** * A context of libeio request for any @@ -91,6 +92,16 @@ struct coio_file_task { struct { char *tpl; } tempdir; + + struct { + char **bufp; + const char *pathname; + } readdir; + + struct { + const char *source; + const char *dest; + } copyfile; }; }; @@ -490,3 +501,130 @@ coio_fdatasync(int fd) eio_req *req = eio_fdatasync(fd, 0, coio_complete, &eio); return coio_wait_done(req, &eio); } + +static void +coio_do_readdir(eio_req *req) +{ + struct coio_file_task *eio = (struct coio_file_task *)req->data; + DIR *dirp = opendir(eio->readdir.pathname); + if (dirp == NULL) + goto error; + size_t capacity = 128; + size_t len = 0; + struct dirent *entry; + char *buf = (char *) malloc(capacity); + if (buf == NULL) + goto mem_error; + req->result = 0; + do { + entry = readdir(dirp); + if (entry == NULL || + strcmp(entry->d_name, ".") == 0 || + strcmp(entry->d_name, "..") == 0) + continue; + size_t namlen = strlen(entry->d_name); + size_t needed = len + namlen + 1; + if (needed > capacity) { + if (needed <= capacity * 2) + capacity *= 2; + else + capacity = needed * 2; + char *new_buf = (char *) realloc(buf, capacity); + if (new_buf == NULL) + goto mem_error; + buf = new_buf; + } + memcpy(&buf[len], entry->d_name, namlen); + len += namlen; + buf[len++] = '\n'; + req->result++; + } while(entry != NULL); + + if (len > 0) + buf[len - 1] = 0; + else + buf[0] = 0; + + *eio->readdir.bufp = buf; + closedir(dirp); + return; + +mem_error: + free(buf); + closedir(dirp); +error: + req->result = -1; + req->errorno = errno; +} + +int +coio_readdir(const char *dir_path, char **buf) +{ + INIT_COEIO_FILE(eio) + eio.readdir.bufp = buf; + eio.readdir.pathname = dir_path; + eio_req *req = eio_custom(coio_do_readdir, 0, coio_complete, &eio); + return coio_wait_done(req, &eio); +} + +static void +coio_do_copyfile(eio_req *req) +{ + struct coio_file_task *eio = (struct coio_file_task *)req->data; + + struct stat st; + if (stat(eio->copyfile.source, &st) < 0) { + goto error; + } + + int source_fd = open(eio->copyfile.source, O_RDONLY); + if (source_fd < 0) { + goto error; + } + + int dest_fd = open(eio->copyfile.dest, O_WRONLY | O_CREAT, + st.st_mode & 0777); + if (dest_fd < 0) { + goto error_dest; + } + + enum { COPY_FILE_BUF_SIZE = 4096 }; + + char buf[COPY_FILE_BUF_SIZE]; + + while (true) { + ssize_t nread = fio_read(source_fd, buf, sizeof(buf)); + if (nread < 0) + goto error_copy; + + if (nread == 0) + break; /* eof */ + + ssize_t nwritten = fio_writen(dest_fd, buf, nread); + if (nwritten < 0) + goto error_copy; + } + req->result = 0; + close(source_fd); + close(dest_fd); + return; + +error_copy: + close(dest_fd); +error_dest: + close(source_fd); +error: + req->errorno = errno; + req->result = -1; + return; +} + +int +coio_copyfile(const char *source, const char *dest) +{ + INIT_COEIO_FILE(eio) + eio.copyfile.source = source; + eio.copyfile.dest = dest; + eio_req *req = eio_custom(coio_do_copyfile, 0, coio_complete, &eio); + return coio_wait_done(req, &eio); +} diff --git a/src/coio_file.h b/src/coio_file.h index 4bb295dad2..7db5611d4e 100644 --- a/src/coio_file.h +++ b/src/coio_file.h @@ -82,6 +82,8 @@ int coio_fdatasync(int fd); int coio_tempdir(char *path, size_t path_len); +int coio_readdir(const char *path, char **buf); +int coio_copyfile(const char *source, const char *dest); #if defined(__cplusplus) } /* extern "C" */ #endif /* defined(__cplusplus) */ diff --git a/src/lua/fio.c b/src/lua/fio.c index 7113b15748..ccbd9aaed5 100644 --- a/src/lua/fio.c +++ b/src/lua/fio.c @@ -507,6 +507,24 @@ lbox_fio_rmdir(struct lua_State *L) return 1; } +static int +lbox_fio_listdir(struct lua_State *L) { + const char *pathname; + if (lua_gettop(L) < 1) { + luaL_error(L, "Usage: fio.listdir(pathname)"); + } + pathname = lua_tostring(L, 1); + char *buf; + if (coio_readdir(pathname, &buf) >= 0) { + lua_pushstring(L, buf); + free(buf); + } else { + lua_pushnil(L); + } + return 1; +} + + static int lbox_fio_glob(struct lua_State *L) { @@ -674,6 +692,15 @@ lbox_fio_close(struct lua_State *L) return 1; } +static int +lbox_fio_copyfile(struct lua_State *L) +{ + const char *source = lua_tostring(L, -2); + const char *dest = lua_tostring(L, -1); + assert(source != NULL && dest != NULL); + lua_pushboolean(L, coio_copyfile(source, dest) == 0); + return 1; +} @@ -716,7 +743,9 @@ tarantool_lua_fio_init(struct lua_State *L) { "ftruncate", lbox_fio_ftruncate }, { "fsync", lbox_fio_fsync }, { "fdatasync", lbox_fio_fdatasync }, + { "listdir", lbox_fio_listdir }, { "fstat", lbox_fio_fstat }, + { "copyfile", lbox_fio_copyfile, }, { NULL, NULL } }; luaL_register(L, NULL, internal_methods); diff --git a/src/lua/fio.lua b/src/lua/fio.lua index 32b23ef83b..15a178ab22 100644 --- a/src/lua/fio.lua +++ b/src/lua/fio.lua @@ -262,4 +262,136 @@ fio.chdir = function(path) return ffi.C.chdir(path) == 0 end +fio.listdir = function(path) + if path == nil or type(path) ~= 'string' then + return nil + end + local str = internal.listdir(path) + if str == nil then + return nil + end + local t = {} + if str == "" then + return t + end + local names = string.split(str, "\n") + for i, name in ipairs(names) do + table.insert(t, name) + end + return t +end + +fio.mktree = function(path, mode) + path = fio.abspath(path) + if path == nil then + return false + end + local path = string.gsub(path, '^/', '') + local dirs = string.split(path, "/") + + if #dirs == 1 then + if mode then + return fio.mkdir(path, mode) + else + return fio.mkdir(path) + end + end + + local current_dir = "/" + for i, dir in ipairs(dirs) do + current_dir = fio.pathjoin(current_dir, dir) + if not fio.stat(current_dir) then + local res + if mode then + if not fio.mkdir(current_dir, mode) then + res = false + end + else + if not fio.mkdir(current_dir) then + res = false + end + end + end + end + return true +end + +fio.rmtree = function(path) + if path == nil then + return false + end + local path = tostring(path) + path = fio.abspath(path) + local ls = fio.listdir(path) + + for i, f in ipairs(ls) do + local tmppath = fio.pathjoin(path, f) + if fio.stat(tmppath):is_dir() then + if not fio.rmtree(tmppath) then + return false + end + end + end + return fio.rmdir(path) +end + +fio.copyfile = function(from, to) + if type(from) ~= 'string' or type(to) ~= 'string' then + error('Usage: fio.copyfile(from, to)') + end + local st = fio.stat(to) + if st and st:is_dir() then + to = fio.pathjoin(to, fio.basename(from)) + end + return internal.copyfile(from, to) +end + +fio.copytree = function(from, to) + if type(from) ~= 'string' or type(to) ~= 'string' then + error('Usage: fio.copytree(from, to)') + end + local status, reason + from = fio.abspath(from) + local st = fio.stat(from) + if st == nil or not st:is_dir() then + return false, errno.strerror(errno.ENOTDIR) + end + local ls = fio.listdir(from) + to = fio.abspath(to) + + -- create tree of destination + local status, reason = fio.mktree(to) + if not status then + return status, reason + end + for i, f in ipairs(ls) do + local ffrom = fio.pathjoin(from, f) + local fto = fio.pathjoin(to, f) + local st = fio.lstat(ffrom) + if st:is_dir() then + status, reason = fio.copytree(ffrom, fto) + if not status then + return status, reason + end + end + if st:is_reg() then + status, reason = fio.copyfile(ffrom, fto) + if not status then + return status, reason + end + end + if st:is_link() then + local link_to, reason = fio.readlink(ffrom) + if not link_to then + return status, reason + end + status, reason = fio.symlink(link_to, fto) + if not status then + return nil, "can't create symlink in place of existing file "..fto + end + end + end + return true +end + return fio diff --git a/test/app/fio.result b/test/app/fio.result index 60d69cd161..4266f4fe7a 100644 --- a/test/app/fio.result +++ b/test/app/fio.result @@ -505,3 +505,144 @@ fio.cwd() == old_cwd --- - true ... +-- listdir +tmpdir = fio.tempdir() +--- +... +dir3 = fio.pathjoin(tmpdir, "dir3") +--- +... +fio.mkdir(dir3) +--- +- true +... +fio.mkdir(fio.pathjoin(dir3, "1")) +--- +- true +... +fio.mkdir(fio.pathjoin(dir3, "2")) +--- +- true +... +fio.mkdir(fio.pathjoin(dir3, "3")) +--- +- true +... +ls = fio.listdir(dir3) +--- +... +table.sort(ls, function(a, b) return tonumber(a) < tonumber(b) end) +--- +... +ls +--- +- - '1' + - '2' + - '3' +... +-- rmtree +fio.stat(dir3) ~= nil +--- +- true +... +fio.rmtree(dir3) +--- +- true +... +fio.stat(dir3) == nil +--- +- true +... +-- mktree +tmp1 = fio.pathjoin(tmpdir, "1") +--- +... +tmp2 = fio.pathjoin(tmp1, "2") +--- +... +tree = fio.pathjoin(tmp2, "3") +--- +... +fio.mktree(tree) +--- +- true +... +fio.stat(tree) ~= nil +--- +- true +... +fio.stat(tmp2) ~= nil +--- +- true +... +-- copy and copytree +file1 = fio.pathjoin(tmp1, 'file.1') +--- +... +file2 = fio.pathjoin(tmp2, 'file.2') +--- +... +file3 = fio.pathjoin(tree, 'file.3') +--- +... +fh1 = fio.open(file1, { 'O_RDWR', 'O_TRUNC', 'O_CREAT' }, 0777) +--- +... +fh1:write("gogo") +--- +- true +... +fh1:close() +--- +- true +... +fh1 = fio.open(file2, { 'O_RDWR', 'O_TRUNC', 'O_CREAT' }, 0777) +--- +... +fh1:write("lolo") +--- +- true +... +fh1:close() +--- +- true +... +fio.symlink(file1, file3) +--- +- true +... +fio.copyfile(file1, tmp2) +--- +- true +... +fio.stat(fio.pathjoin(tmp2, "file.1")) ~= nil +--- +- true +... +fio.copyfile(fio.pathjoin(tmp1, 'not_exists.txt'), tmp1) +--- +- false +... +newdir = fio.pathjoin(tmpdir, "newdir") +--- +... +fio.copytree(fio.pathjoin(tmpdir, "1"), newdir) +--- +- true +... +fio.stat(fio.pathjoin(newdir, "file.1")) ~= nil +--- +- true +... +fio.stat(fio.pathjoin(newdir, "2", "file.2")) ~= nil +--- +- true +... +fio.stat(fio.pathjoin(newdir, "2", "3", "file.3")) ~= nil +--- +- true +... +fio.readlink(fio.pathjoin(newdir, "2", "3", "file.3")) == file1 +--- +- true +... diff --git a/test/app/fio.test.lua b/test/app/fio.test.lua index e95f6397b2..7826993131 100644 --- a/test/app/fio.test.lua +++ b/test/app/fio.test.lua @@ -163,3 +163,52 @@ fio.chdir('/') fio.cwd() fio.chdir(old_cwd) fio.cwd() == old_cwd + +-- listdir +tmpdir = fio.tempdir() +dir3 = fio.pathjoin(tmpdir, "dir3") +fio.mkdir(dir3) +fio.mkdir(fio.pathjoin(dir3, "1")) +fio.mkdir(fio.pathjoin(dir3, "2")) +fio.mkdir(fio.pathjoin(dir3, "3")) +ls = fio.listdir(dir3) +table.sort(ls, function(a, b) return tonumber(a) < tonumber(b) end) +ls + +-- rmtree +fio.stat(dir3) ~= nil +fio.rmtree(dir3) +fio.stat(dir3) == nil + +-- mktree +tmp1 = fio.pathjoin(tmpdir, "1") +tmp2 = fio.pathjoin(tmp1, "2") +tree = fio.pathjoin(tmp2, "3") +fio.mktree(tree) +fio.stat(tree) ~= nil +fio.stat(tmp2) ~= nil + + +-- copy and copytree +file1 = fio.pathjoin(tmp1, 'file.1') +file2 = fio.pathjoin(tmp2, 'file.2') +file3 = fio.pathjoin(tree, 'file.3') + +fh1 = fio.open(file1, { 'O_RDWR', 'O_TRUNC', 'O_CREAT' }, 0777) +fh1:write("gogo") +fh1:close() +fh1 = fio.open(file2, { 'O_RDWR', 'O_TRUNC', 'O_CREAT' }, 0777) +fh1:write("lolo") +fh1:close() +fio.symlink(file1, file3) +fio.copyfile(file1, tmp2) +fio.stat(fio.pathjoin(tmp2, "file.1")) ~= nil + +fio.copyfile(fio.pathjoin(tmp1, 'not_exists.txt'), tmp1) + +newdir = fio.pathjoin(tmpdir, "newdir") +fio.copytree(fio.pathjoin(tmpdir, "1"), newdir) +fio.stat(fio.pathjoin(newdir, "file.1")) ~= nil +fio.stat(fio.pathjoin(newdir, "2", "file.2")) ~= nil +fio.stat(fio.pathjoin(newdir, "2", "3", "file.3")) ~= nil +fio.readlink(fio.pathjoin(newdir, "2", "3", "file.3")) == file1 -- GitLab