From 583e8ba2b3e0d3e919c136b6c167e41196fb5751 Mon Sep 17 00:00:00 2001
From: Ilya <markovilya197@gmail.com>
Date: Tue, 31 Oct 2017 19:07:33 +0300
Subject: [PATCH] fio: new approach for error handling

* Add golang-like approach to handle errors
* Refactor some issues after review #2751

Closes #2757
---
 src/lua/fio.c         | 151 ++++++++++++++++++++++------------------
 src/lua/fio.lua       | 155 ++++++++++++++++++++++-------------------
 test/app/fio.result   | 156 +++++++++++++++++++++++++++++++++++-------
 test/app/fio.test.lua |  53 ++++++++++----
 4 files changed, 341 insertions(+), 174 deletions(-)

diff --git a/src/lua/fio.c b/src/lua/fio.c
index ccbd9aaed5..4c9be183a9 100644
--- a/src/lua/fio.c
+++ b/src/lua/fio.c
@@ -47,6 +47,13 @@
 #include "lua/utils.h"
 #include "coio_file.h"
 
+static inline void
+lbox_fio_pushsyserror(struct lua_State *L)
+{
+	diag_set(SystemError, "fio: %s", strerror(errno));
+	luaT_pusherror(L, diag_get()->last);
+}
+
 static int
 lbox_fio_open(struct lua_State *L)
 {
@@ -62,6 +69,11 @@ lbox_fio_open(struct lua_State *L)
 	int mode = lua_tointeger(L, 3);
 
 	int fh = coio_file_open(pathname, flags, mode);
+	if (fh < 0) {
+		lua_pushnil(L);
+		lbox_fio_pushsyserror(L);
+		return 2;
+	}
 	lua_pushinteger(L, fh);
 	return 1;
 }
@@ -77,6 +89,11 @@ lbox_fio_pwrite(struct lua_State *L)
 	size_t offset = lua_tonumber(L, 4);
 
 	int res = coio_pwrite(fh, buf, len, offset);
+	if (res < 0) {
+		lua_pushnil(L);
+		lbox_fio_pushsyserror(L);
+		return 2;
+	}
 	lua_pushinteger(L, res);
 	return 1;
 }
@@ -98,23 +115,34 @@ lbox_fio_pread(struct lua_State *L)
 	if (!buf) {
 		errno = ENOMEM;
 		lua_pushnil(L);
-		return 1;
+		lbox_fio_pushsyserror(L);
+		return 2;
 	}
 
-
 	int res = coio_pread(fh, buf, len, offset);
 
-
 	if (res < 0) {
 		lua_pop(L, 1);
 		lua_pushnil(L);
-		return 1;
+		lbox_fio_pushsyserror(L);
+		return 2;
 	}
 	lua_pushlstring(L, (char *)buf, res);
 	lua_remove(L, -2);
 	return 1;
 }
 
+static inline int
+lbox_fio_pushbool(struct lua_State *L, bool res)
+{
+	lua_pushboolean(L, res);
+	if (!res) {
+		lbox_fio_pushsyserror(L);
+		return 2;
+	}
+	return 1;
+}
+
 static int
 lbox_fio_rename(struct lua_State *L)
 {
@@ -131,8 +159,7 @@ lbox_fio_rename(struct lua_State *L)
 		goto usage;
 
 	int res = coio_rename(oldpath, newpath);
-	lua_pushboolean(L, res == 0);
-	return 1;
+	return lbox_fio_pushbool(L, res == 0);
 }
 
 static int
@@ -147,8 +174,7 @@ lbox_fio_unlink(struct lua_State *L)
 	if (pathname == NULL)
 		goto usage;
 	int res = coio_unlink(pathname);
-	lua_pushboolean(L, res == 0);
-	return 1;
+	return lbox_fio_pushbool(L, res == 0);
 }
 
 static int
@@ -157,8 +183,7 @@ lbox_fio_ftruncate(struct lua_State *L)
 	int fd = lua_tointeger(L, 1);
 	off_t length = lua_tonumber(L, 2);
 	int res = coio_ftruncate(fd, length);
-	lua_pushboolean(L, res == 0);
-	return 1;
+	return lbox_fio_pushbool(L, res == 0);
 }
 
 static int
@@ -180,8 +205,7 @@ lbox_fio_truncate(struct lua_State *L)
 		length = 0;
 	int res = coio_truncate(pathname, length);
 
-	lua_pushboolean(L, res == 0);
-	return 1;
+	return lbox_fio_pushbool(L, res == 0);
 }
 
 static int
@@ -194,6 +218,11 @@ lbox_fio_write(struct lua_State *L)
 
 	size_t len = lua_tonumber(L, 3);
 	int res = coio_write(fh, buf, len);
+	if (res < 0) {
+		lua_pushnil(L);
+		lbox_fio_pushsyserror(L);
+		return 2;
+	}
 	lua_pushinteger(L, res);
 	return 1;
 }
@@ -242,8 +271,7 @@ lbox_fio_chown(struct lua_State *L)
 		group = entry->gr_gid;
 	}
 	int res = coio_chown(pathname, owner, group);
-	lua_pushboolean(L, res == 0);
-	return 1;
+	return lbox_fio_pushbool(L, res == 0);
 }
 
 static int
@@ -259,8 +287,7 @@ lbox_fio_chmod(struct lua_State *L)
 		goto usage;
 
 	mode_t mode = lua_tointeger(L, 2);
-	lua_pushboolean(L, coio_chmod(pathname, mode) == 0);
-	return 1;
+	return lbox_fio_pushbool(L, coio_chmod(pathname, mode) == 0);
 }
 
 static int
@@ -288,7 +315,8 @@ lbox_fio_read(struct lua_State *L)
 	if (res < 0) {
 		lua_pop(L, 1);
 		lua_pushnil(L);
-		return 1;
+		lbox_fio_pushsyserror(L);
+		return 2;
 	}
 	lua_pushlstring(L, (char *)buf, res);
 	lua_remove(L, -2);
@@ -357,8 +385,13 @@ DEF_STAT_METHOD(is_sock, S_ISSOCK);
 #endif
 
 static int
-lbox_fio_pushstat(struct lua_State *L, const struct stat *stat)
+lbox_fio_pushstat(struct lua_State *L, int res, const struct stat *stat)
 {
+	if (res < 0) {
+		lua_pushnil(L);
+		lbox_fio_pushsyserror(L);
+		return 2;
+	}
 	lua_newtable(L);
 
 	PUSHTABLE("dev", lua_pushinteger, stat->st_dev);
@@ -425,11 +458,7 @@ lbox_fio_lstat(struct lua_State *L)
 	struct stat stat;
 
 	int res = coio_lstat(pathname, &stat);
-	if (res < 0) {
-		lua_pushnil(L);
-		return 1;
-	}
-	return lbox_fio_pushstat(L, &stat);
+	return lbox_fio_pushstat(L, res, &stat);
 }
 
 static int
@@ -446,11 +475,7 @@ lbox_fio_stat(struct lua_State *L)
 	struct stat stat;
 
 	int res = coio_stat(pathname, &stat);
-	if (res < 0) {
-		lua_pushnil(L);
-		return 1;
-	}
-	return lbox_fio_pushstat(L, &stat);
+	return lbox_fio_pushstat(L, res, &stat);
 }
 
 static int
@@ -459,11 +484,7 @@ lbox_fio_fstat(struct lua_State *L)
 	int fd = lua_tointeger(L, 1);
 	struct stat stat;
 	int res = coio_fstat(fd, &stat);
-	if (res < 0) {
-		lua_pushnil(L);
-		return 1;
-	}
-	return lbox_fio_pushstat(L, &stat);
+	return lbox_fio_pushstat(L, res, &stat);
 }
 
 
@@ -483,12 +504,11 @@ lbox_fio_mkdir(struct lua_State *L)
 
 	mode_t mode;
 
-	if (top >= 2)
+	if (top >= 2 && !lua_isnil(L, 2))
 		mode = lua_tointeger(L, 2);
 	else
 		mode = 0777;
-	lua_pushboolean(L, coio_mkdir(pathname, mode) == 0);
-	return 1;
+	return lbox_fio_pushbool(L, coio_mkdir(pathname, mode) == 0);
 }
 
 static int
@@ -502,29 +522,28 @@ lbox_fio_rmdir(struct lua_State *L)
 	pathname = lua_tostring(L, 1);
 	if (pathname == NULL)
 		goto usage;
-
-	lua_pushboolean(L, coio_rmdir(pathname) == 0);
-	return 1;
+	return lbox_fio_pushbool(L, coio_rmdir(pathname) == 0);
 }
 
 static int
-lbox_fio_listdir(struct lua_State *L) {
+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 {
+	if (coio_readdir(pathname, &buf) < 0) {
 		lua_pushnil(L);
+		lbox_fio_pushsyserror(L);
+		return 2;
 	}
+	lua_pushstring(L, buf);
+	free(buf);
 	return 1;
 }
 
-
 static int
 lbox_fio_glob(struct lua_State *L)
 {
@@ -578,8 +597,7 @@ lbox_fio_link(struct lua_State *L)
 	linkpath = lua_tostring(L, 2);
 	if (target == NULL || linkpath == NULL)
 		goto usage;
-	lua_pushboolean(L, coio_link(target, linkpath) == 0);
-	return 1;
+	return lbox_fio_pushbool(L, coio_link(target, linkpath) == 0);
 }
 
 static int
@@ -595,8 +613,7 @@ lbox_fio_symlink(struct lua_State *L)
 	linkpath = lua_tostring(L, 2);
 	if (target == NULL || linkpath == NULL)
 		goto usage;
-	lua_pushboolean(L, coio_symlink(target, linkpath) == 0);
-	return 1;
+	return lbox_fio_pushbool(L, coio_symlink(target, linkpath) == 0);
 }
 
 static int
@@ -614,7 +631,8 @@ lbox_fio_readlink(struct lua_State *L)
 	int res = coio_readlink(pathname, path, PATH_MAX);
 	if (res < 0) {
 		lua_pushnil(L);
-		return 1;
+		lbox_fio_pushsyserror(L);
+		return 2;
 	}
 	lua_pushlstring(L, path, res);
 	lua_remove(L, -2);
@@ -628,16 +646,17 @@ lbox_fio_tempdir(struct lua_State *L)
 	if (!buf) {
 		errno = ENOMEM;
 		lua_pushnil(L);
-		return 1;
+		lbox_fio_pushsyserror(L);
+		return 2;
 	}
 
-
-	if (coio_tempdir(buf, PATH_MAX) == 0) {
-		lua_pushstring(L, buf);
-		lua_remove(L, -2);
-	} else {
+	if (coio_tempdir(buf, PATH_MAX) != 0) {
 		lua_pushnil(L);
+		lbox_fio_pushsyserror(L);
+		return 2;
 	}
+	lua_pushstring(L, buf);
+	lua_remove(L, -2);
 	return 1;
 }
 
@@ -648,7 +667,8 @@ lbox_fio_cwd(struct lua_State *L)
 	if (!buf) {
 		errno = ENOMEM;
 		lua_pushnil(L);
-		return 1;
+		lbox_fio_pushsyserror(L);
+		return 2;
 	}
 
 
@@ -656,7 +676,9 @@ lbox_fio_cwd(struct lua_State *L)
 		lua_pushstring(L, buf);
 		lua_remove(L, -2);
 	} else {
+		lbox_fio_pushsyserror(L);
 		lua_pushnil(L);
+		return 2;
 	}
 	return 1;
 }
@@ -665,31 +687,27 @@ static int
 lbox_fio_fsync(struct lua_State *L)
 {
 	int fd = lua_tointeger(L, 1);
-	lua_pushboolean(L, coio_fsync(fd) == 0);
-	return 1;
+	return lbox_fio_pushbool(L, coio_fsync(fd) == 0);
 }
 
 static int
 lbox_fio_fdatasync(struct lua_State *L)
 {
 	int fd = lua_tointeger(L, 1);
-	lua_pushboolean(L, coio_fdatasync(fd) == 0);
-	return 1;
+	return lbox_fio_pushbool(L, coio_fdatasync(fd) == 0);
 }
 
 static int
 lbox_fio_sync(struct lua_State *L)
 {
-	lua_pushboolean(L, coio_sync() == 0);
-	return 1;
+	return lbox_fio_pushbool(L, coio_sync() == 0);
 }
 
 static int
 lbox_fio_close(struct lua_State *L)
 {
 	int fd = lua_tointeger(L, 1);
-	lua_pushboolean(L, coio_file_close(fd) == 0);
-	return 1;
+	return lbox_fio_pushbool(L, coio_file_close(fd) == 0);
 }
 
 static int
@@ -698,8 +716,7 @@ 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;
+	return lbox_fio_pushbool(L, coio_copyfile(source, dest) == 0);
 }
 
 
diff --git a/src/lua/fio.lua b/src/lua/fio.lua
index 15a178ab22..32f77a16b6 100644
--- a/src/lua/fio.lua
+++ b/src/lua/fio.lua
@@ -31,7 +31,10 @@ end
 
 fio_methods.write = function(self, data)
     data = tostring(data)
-    local res = internal.write(self.fh, data, #data)
+    local res, err = internal.write(self.fh, data, #data)
+    if err ~= nil then
+        return false, err
+    end
     return res >= 0
 end
 
@@ -48,7 +51,10 @@ fio_methods.pwrite = function(self, data, offset)
         offset = tonumber(offset)
     end
 
-    local res = internal.pwrite(self.fh, data, len, offset)
+    local res, err = internal.pwrite(self.fh, data, len, offset)
+    if err ~= nil then
+        return false, err
+    end
     return res >= 0
 end
 
@@ -63,7 +69,6 @@ fio_methods.pread = function(self, len, offset)
     return internal.pread(self.fh, tonumber(len), tonumber(offset))
 end
 
-
 fio_methods.truncate = function(self, length)
     if length == nil then
         length = 0
@@ -85,16 +90,15 @@ fio_methods.seek = function(self, offset, whence)
     end
 
     local res = internal.lseek(self.fh, tonumber(offset), whence)
-
-    if res < 0 then
-        return nil
-    end
     return tonumber(res)
 end
 
 fio_methods.close = function(self)
-    local res = internal.close(self.fh)
+    local res, err = internal.close(self.fh)
     self.fh = -1
+    if err ~= nil then
+        return false, err
+    end
     return res
 end
 
@@ -106,7 +110,6 @@ fio_methods.fdatasync = function(self)
     return internal.fdatasync(self.fh)
 end
 
-
 fio_methods.stat = function(self)
     return internal.fstat(self.fh)
 end
@@ -117,12 +120,14 @@ local fio_mt = { __index = fio_methods }
 fio.open = function(path, flags, mode)
     local iflag = 0
     local imode = 0
-
+    if type(path) ~= 'string' then
+        error("Usage open(path[, flags[, mode]])")
+    end
     if type(flags) ~= 'table' then
         flags = { flags }
     end
     if type(mode) ~= 'table' then
-        mode = { mode or 0x1FF } -- 0777
+        mode = { mode or (bit.band(0x1FF, fio.umask())) }
     end
 
 
@@ -148,9 +153,9 @@ fio.open = function(path, flags, mode)
         end
     end
 
-    local fh = internal.open(tostring(path), iflag, imode)
-    if fh < 0 then
-        return nil
+    local fh, err = internal.open(tostring(path), iflag, imode)
+    if err ~= nil then
+        return nil, err
     end
 
     fh = { fh = fh }
@@ -190,8 +195,8 @@ fio.pathjoin = function(path, ...)
 end
 
 fio.basename = function(path, suffix)
-    if path == nil then
-        return nil
+    if type(path) ~= 'string' then
+        error("Usage fio.basename(path[, suffix])")
     end
 
     path = tostring(path)
@@ -209,10 +214,9 @@ fio.basename = function(path, suffix)
 end
 
 fio.dirname = function(path)
-    if path == nil then
-        return nil
+    if type(path) ~= 'string' then
+        error("Usage fio.dirname(path)")
     end
-    path = tostring(path)
     path = ffi.new('char[?]', #path + 1, path)
     return ffi.string(ffi.C.dirname(path))
 end
@@ -235,9 +239,9 @@ fio.abspath = function(path)
     -- following established conventions of fio module:
     -- letting nil through and converting path to string
     if path == nil then
-        return nil
+        error("Usage fio.abspath(path)")
     end
-    path = tostring(path)
+    path = path
     local joined_path = ''
     local path_tab = {}
     if string.sub(path, 1, 1) == '/' then
@@ -256,19 +260,19 @@ fio.abspath = function(path)
 end
 
 fio.chdir = function(path)
-    if path == nil or type(path)~='string' then
-        return false
+    if type(path)~='string' then
+        error("Usage: fio.chdir(path)")
     end
     return ffi.C.chdir(path) == 0
 end
 
 fio.listdir = function(path)
-    if path == nil or type(path) ~= 'string' then
-        return nil
+    if type(path) ~= 'string' then
+        error("Usage: fio.listdir(path)")
     end
-    local str = internal.listdir(path)
-    if str == nil then
-        return nil
+    local str, err = internal.listdir(path)
+    if err ~= nil then
+        return nil, string.format("can't listdir %s: %s", path, err)
     end
     local t = {}
     if str == "" then
@@ -282,34 +286,25 @@ fio.listdir = function(path)
 end
 
 fio.mktree = function(path, mode)
-    path = fio.abspath(path)
-    if path == nil then
-        return false
+    if type(path) ~= "string" then
+        error("Usage: fio.mktree(path[, mode])")
     end
+    path = fio.abspath(path)
+
     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
+        return fio.mkdir(path, mode)
     end
-
+    local st, err
     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
+            st, err = fio.mkdir(current_dir, mode)
+            if err ~= nil  then
+                return false, "Error create dir " .. current_dir .. err
             end
         end
     end
@@ -317,22 +312,30 @@ fio.mktree = function(path, mode)
 end
 
 fio.rmtree = function(path)
-    if path == nil then
-        return false
+    if type(path) ~= 'string' then
+        error("Usage: fio.rmtree(path)")
     end
-    local path = tostring(path)
+    local status, err
     path = fio.abspath(path)
-    local ls = fio.listdir(path)
-
+    local ls, err = fio.listdir(path)
+    if err ~= nil then
+        return nil, err
+    end
     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
+        local st = fio.stat(tmppath)
+        if st and st:is_dir() then
+            st, err = fio.rmtree(tmppath)
+            if err ~= nil  then
+                return nil, err
             end
         end
     end
-    return fio.rmdir(path)
+    status, err = fio.rmdir(path)
+    if err ~= nil then
+        return false, string.format("failed to remove %s: %s", path, err)
+    end
+    return true
 end
 
 fio.copyfile = function(from, to)
@@ -343,7 +346,11 @@ fio.copyfile = function(from, to)
     if st and st:is_dir() then
         to = fio.pathjoin(to, fio.basename(from))
     end
-    return internal.copyfile(from, to)
+    local _, err = internal.copyfile(from, to)
+    if err ~= nil then
+        return false, string.format("failed to copy %s to %s: %s", from, to, err)
+    end
+    return true
 end
 
 fio.copytree = function(from, to)
@@ -351,43 +358,47 @@ fio.copytree = function(from, to)
         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
+    if not st then
+        return false, string.format("Directory %s does not exist", from)
+    end
+    if not st:is_dir() then
         return false, errno.strerror(errno.ENOTDIR)
     end
-    local ls = fio.listdir(from)
-    to = fio.abspath(to)
+    local ls, err = fio.listdir(from)
+    if err ~= nil then
+        return false, err
+    end
 
     -- create tree of destination
-    local status, reason = fio.mktree(to)
-    if not status then
-        return status, reason
+    status, reason = fio.mktree(to)
+    if reason ~= nil then
+        return false, 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
+        if st and st:is_dir() then
             status, reason = fio.copytree(ffrom, fto)
-            if not status then
-                return status, reason
+            if reason ~= nil then
+                return false, reason
             end
         end
         if st:is_reg() then
             status, reason = fio.copyfile(ffrom, fto)
-            if not status then
-                return status, reason
+            if reason ~= nil then
+                return false, reason
             end
         end
         if st:is_link() then
             local link_to, reason = fio.readlink(ffrom)
-            if not link_to then
-                return status, reason
+            if reason ~= nil then
+                return false, 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
+            if reason ~= nil then
+                return false, "can't create symlink in place of existing file "..fto
             end
         end
     end
diff --git a/test/app/fio.result b/test/app/fio.result
index 4266f4fe7a..6dd3b414e5 100644
--- a/test/app/fio.result
+++ b/test/app/fio.result
@@ -14,9 +14,16 @@ fio.umask()
 - 0
 ...
 -- pathjoin
-fio.basename(nil, nil)
+st, err = pcall(fio.basename, nil, nil)
 ---
-- null
+...
+st
+---
+- false
+...
+err:match("basename") ~= nil
+---
+- true
 ...
 fio.pathjoin('abc', 'cde')
 ---
@@ -35,9 +42,16 @@ fio.pathjoin('/', '/cde')
 - /cde
 ...
 -- basename
-fio.basename(nil)
+st, err = pcall(fio.basename, nil)
 ---
-- null
+...
+st
+---
+- false
+...
+err:match("basename") ~= nil
+---
+- true
 ...
 fio.basename('/')
 ---
@@ -75,9 +89,16 @@ file3 = fio.pathjoin(tmpdir, 'file.3')
 file4 = fio.pathjoin(tmpdir, 'file.4')
 ---
 ...
-fio.open(nil)
+st, err = pcall(fio.open, nil)
 ---
-- null
+...
+st
+---
+- false
+...
+err:match("open") ~= nil
+---
+- true
 ...
 fh1 = fio.open(file1, { 'O_RDWR', 'O_TRUNC', 'O_CREAT' }, 0777)
 ---
@@ -341,12 +362,15 @@ string.format('%04o', bit.band(fio.stat(dir2).mode, 0x1FF))
 - - true
   - true
 ...
-{ fh1:close(), errno.strerror(), fh3:close(), errno.strerror() }
+fh1:close()
 ---
-- - false
-  - Bad file descriptor
-  - false
-  - Bad file descriptor
+- false
+- 'fio: Bad file descriptor'
+...
+fh3:close()
+---
+- false
+- 'fio: Bad file descriptor'
 ...
 fio.rmdir(nil)
 ---
@@ -373,15 +397,16 @@ fio.rmdir(dir2)
   - false
   - false
   - false
+  - 'fio: No such file or directory'
 ...
 fio.rmdir(tmpdir)
 ---
 - true
 ...
-{ fio.rmdir(tmpdir), errno.strerror() }
+fio.rmdir(tmpdir)
 ---
-- - false
-  - No such file or directory
+- false
+- 'fio: No such file or directory'
 ...
 fio.unlink()
 ---
@@ -395,9 +420,9 @@ fio.unlink(nil)
 fh4 = fio.open('newfile', {'O_RDWR','O_CREAT','O_EXCL'})
 ---
 ...
-string.format('%o', bit.band(fh4:stat().mode, 0x1FF))
+bit.band(fh4:stat().mode, 0x1FF) == bit.band(fio.umask(), 0x1ff)
 ---
-- '777'
+- true
 ...
 fh4:close()
 ---
@@ -408,9 +433,16 @@ fio.unlink('newfile')
 - true
 ...
 -- dirname
-fio.dirname(nil)
+st, err = pcall(fio.dirname, nil)
 ---
-- null
+...
+st
+---
+- false
+...
+err:match("dirname") ~= nil
+---
+- true
 ...
 fio.dirname('abc')
 ---
@@ -433,9 +465,16 @@ fio.dirname('/')
 - /
 ...
 -- abspath
-fio.abspath(nil)
+st, err = pcall(fio.abspath, nil)
 ---
-- null
+...
+st
+---
+- false
+...
+err:match("abspath") ~= nil
+---
+- true
 ...
 fio.abspath("/")
 ---
@@ -477,14 +516,28 @@ type(string.find(fio.abspath("tmp"), "tmp"))
 old_cwd = fio.cwd()
 ---
 ...
-fio.chdir(nil)
+st, err = pcall(fio.chdir, nil)
+---
+...
+st
 ---
 - false
 ...
-fio.chdir(42)
+err:match("chdir") ~= nil
+---
+- true
+...
+st, err = pcall(fio.chdir, 42)
+---
+...
+st
 ---
 - false
 ...
+err:match("chdir") ~= nil
+---
+- true
+...
 fio.chdir('/no/such/file/or/directory')
 ---
 - false
@@ -512,6 +565,17 @@ tmpdir = fio.tempdir()
 dir3 = fio.pathjoin(tmpdir, "dir3")
 ---
 ...
+st, err = pcall(fio.mkdir, nil)
+---
+...
+st
+---
+- false
+...
+err:match("mkdir") ~= nil
+---
+- true
+...
 fio.mkdir(dir3)
 ---
 - true
@@ -528,6 +592,11 @@ fio.mkdir(fio.pathjoin(dir3, "3"))
 ---
 - true
 ...
+fio.listdir("/no/such/directory/")
+---
+- null
+- 'can''t listdir /no/such/directory/: fio: No such file or directory'
+...
 ls = fio.listdir(dir3)
 ---
 ...
@@ -553,6 +622,17 @@ fio.stat(dir3) == nil
 ---
 - true
 ...
+st, err = fio.rmtree(dir3)
+---
+...
+st
+---
+- null
+...
+err:match("No such") ~= nil
+---
+- true
+...
 -- mktree
 tmp1 = fio.pathjoin(tmpdir, "1")
 ---
@@ -563,6 +643,20 @@ tmp2 = fio.pathjoin(tmp1, "2")
 tree = fio.pathjoin(tmp2, "3")
 ---
 ...
+tree2 = fio.pathjoin(tmpdir, "4")
+---
+...
+st, err = pcall(fio.mktree, nil)
+---
+...
+st
+---
+- false
+...
+err:match("mktree") ~= nil
+---
+- true
+...
 fio.mktree(tree)
 ---
 - true
@@ -575,6 +669,10 @@ fio.stat(tmp2) ~= nil
 ---
 - true
 ...
+fio.mktree(tree2, 1)
+---
+- true
+...
 -- copy and copytree
 file1 = fio.pathjoin(tmp1, 'file.1')
 ---
@@ -619,10 +717,17 @@ fio.stat(fio.pathjoin(tmp2, "file.1")) ~= nil
 ---
 - true
 ...
-fio.copyfile(fio.pathjoin(tmp1, 'not_exists.txt'), tmp1)
+res, err = fio.copyfile(fio.pathjoin(tmp1, 'not_exists.txt'), tmp1)
+---
+...
+res
 ---
 - false
 ...
+err:match("failed to copy") ~= nil
+---
+- true
+...
 newdir = fio.pathjoin(tmpdir, "newdir")
 ---
 ...
@@ -646,3 +751,8 @@ fio.readlink(fio.pathjoin(newdir, "2", "3", "file.3")) == file1
 ---
 - true
 ...
+fio.copytree("/no/such/dir", "/some/where")
+---
+- false
+- Directory /no/such/dir does not exist
+...
diff --git a/test/app/fio.test.lua b/test/app/fio.test.lua
index 7826993131..d9d181fe69 100644
--- a/test/app/fio.test.lua
+++ b/test/app/fio.test.lua
@@ -7,14 +7,18 @@ type(fio.umask(0))
 fio.umask()
 
 -- pathjoin
-fio.basename(nil, nil)
+st, err = pcall(fio.basename, nil, nil)
+st
+err:match("basename") ~= nil
 fio.pathjoin('abc', 'cde')
 fio.pathjoin('/', 'abc')
 fio.pathjoin('abc/', '/cde')
 fio.pathjoin('/', '/cde')
 
 -- basename
-fio.basename(nil)
+st, err = pcall(fio.basename, nil)
+st
+err:match("basename") ~= nil
 fio.basename('/')
 fio.basename('abc')
 fio.basename('abc.cde', '.cde')
@@ -32,7 +36,9 @@ file3 = fio.pathjoin(tmpdir, 'file.3')
 file4 = fio.pathjoin(tmpdir, 'file.4')
 
 
-fio.open(nil)
+st, err = pcall(fio.open, nil)
+st
+err:match("open") ~= nil
 fh1 = fio.open(file1, { 'O_RDWR', 'O_TRUNC', 'O_CREAT' }, 0777)
 fh1 ~= nil
 f1s = fh1:stat()
@@ -113,7 +119,8 @@ string.format('%04o', bit.band(fio.stat(dir2).mode, 0x1FF))
 
 -- cleanup directories
 { fh1:close(), fh3:close() }
-{ fh1:close(), errno.strerror(), fh3:close(), errno.strerror() }
+fh1:close()
+fh3:close()
 
 fio.rmdir(nil)
 fio.rmdir(dir1)
@@ -122,20 +129,22 @@ fio.rmdir(dir2)
 { fio.unlink(file1), fio.unlink(file2), fio.unlink(file3), fio.unlink(file4) }
 { fio.unlink(file1), fio.unlink(file2), fio.unlink(file3), fio.unlink(file4) }
 fio.rmdir(tmpdir)
-{ fio.rmdir(tmpdir), errno.strerror() }
+fio.rmdir(tmpdir)
 
 fio.unlink()
 fio.unlink(nil)
 
 -- gh-1211 use 0777 if mode omitted in open
 fh4 = fio.open('newfile', {'O_RDWR','O_CREAT','O_EXCL'})
-string.format('%o', bit.band(fh4:stat().mode, 0x1FF))
+bit.band(fh4:stat().mode, 0x1FF) == bit.band(fio.umask(), 0x1ff)
 fh4:close()
 fio.unlink('newfile')
 
 -- dirname
 
-fio.dirname(nil)
+st, err = pcall(fio.dirname, nil)
+st
+err:match("dirname") ~= nil
 fio.dirname('abc')
 fio.dirname('/abc')
 fio.dirname('/abc/cde')
@@ -143,7 +152,9 @@ fio.dirname('/abc/cde/')
 fio.dirname('/')
 
 -- abspath
-fio.abspath(nil)
+st, err = pcall(fio.abspath, nil)
+st
+err:match("abspath") ~= nil
 fio.abspath("/")
 fio.abspath("/tmp")
 fio.abspath("/tmp/test/../")
@@ -156,8 +167,12 @@ type(string.find(fio.abspath("tmp"), "tmp"))
 
 -- chdir
 old_cwd = fio.cwd()
-fio.chdir(nil)
-fio.chdir(42)
+st, err = pcall(fio.chdir, nil)
+st
+err:match("chdir") ~= nil
+st, err = pcall(fio.chdir, 42)
+st
+err:match("chdir") ~= nil
 fio.chdir('/no/such/file/or/directory')
 fio.chdir('/')
 fio.cwd()
@@ -167,10 +182,14 @@ fio.cwd() == old_cwd
 -- listdir
 tmpdir = fio.tempdir()
 dir3 = fio.pathjoin(tmpdir, "dir3")
+st, err = pcall(fio.mkdir, nil)
+st
+err:match("mkdir") ~= nil
 fio.mkdir(dir3)
 fio.mkdir(fio.pathjoin(dir3, "1"))
 fio.mkdir(fio.pathjoin(dir3, "2"))
 fio.mkdir(fio.pathjoin(dir3, "3"))
+fio.listdir("/no/such/directory/")
 ls = fio.listdir(dir3)
 table.sort(ls, function(a, b) return tonumber(a) < tonumber(b) end)
 ls
@@ -179,15 +198,22 @@ ls
 fio.stat(dir3) ~= nil
 fio.rmtree(dir3)
 fio.stat(dir3) == nil
+st, err = fio.rmtree(dir3)
+st
+err:match("No such") ~= nil
 
 -- mktree
 tmp1 = fio.pathjoin(tmpdir, "1")
 tmp2 = fio.pathjoin(tmp1, "2")
 tree = fio.pathjoin(tmp2, "3")
+tree2 = fio.pathjoin(tmpdir, "4")
+st, err = pcall(fio.mktree, nil)
+st
+err:match("mktree") ~= nil
 fio.mktree(tree)
 fio.stat(tree) ~= nil
 fio.stat(tmp2) ~= nil
-
+fio.mktree(tree2, 1)
 
 -- copy and copytree
 file1 = fio.pathjoin(tmp1, 'file.1')
@@ -204,7 +230,9 @@ 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)
+res, err = fio.copyfile(fio.pathjoin(tmp1, 'not_exists.txt'), tmp1)
+res
+err:match("failed to copy") ~= nil
 
 newdir = fio.pathjoin(tmpdir, "newdir")
 fio.copytree(fio.pathjoin(tmpdir, "1"), newdir)
@@ -212,3 +240,4 @@ 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
+fio.copytree("/no/such/dir", "/some/where")
\ No newline at end of file
-- 
GitLab