mod_admin_telnet: Don't rely on getpeerchain returning an empty list
[prosody.git] / util / datamanager.lua
index b745cd390949932aa20d3ba8b00118152caf03ab..4a4d62b3c098519f361912162f9a73e30209c960 100644 (file)
@@ -25,28 +25,23 @@ local serialize = require "util.serialization".serialize;
 local path_separator = assert ( package.config:match ( "^([^\n]+)" ) , "package.config not in standard form" ) -- Extract directory seperator from package.config (an undocumented string that comes with lua)
 local lfs = require "lfs";
 local prosody = prosody;
-local raw_mkdir;
-local fallocate;
-
-if prosody.platform == "posix" then
-       raw_mkdir = require "util.pposix".mkdir; -- Doesn't trample on umask
-       fallocate = require "util.pposix".fallocate;
-else
-       raw_mkdir = lfs.mkdir;
-end
 
-if not fallocate then -- Fallback
-       function fallocate(f, offset, len)
-               -- This assumes that current position == offset
-               local fake_data = (" "):rep(len);
-               local ok, msg = f:write(fake_data);
-               if not ok then
-                       return ok, msg;
-               end
-               f:seek("set", offset);
-               return true;
+local raw_mkdir = lfs.mkdir;
+local function fallocate(f, offset, len)
+       -- This assumes that current position == offset
+       local fake_data = (" "):rep(len);
+       local ok, msg = f:write(fake_data);
+       if not ok then
+               return ok, msg;
        end
-end
+       f:seek("set", offset);
+       return true;
+end;
+pcall(function()
+       local pposix = require "util.pposix";
+       raw_mkdir = pposix.mkdir or raw_mkdir; -- Doesn't trample on umask
+       fallocate = pposix.fallocate or fallocate;
+end);
 
 module "datamanager"
 
@@ -118,12 +113,9 @@ function getpath(username, host, datastore, ext, create)
        if username then
                if create then mkdir(mkdir(mkdir(data_path).."/"..host).."/"..datastore); end
                return format("%s/%s/%s/%s.%s", data_path, host, datastore, username, ext);
-       elseif host then
+       else
                if create then mkdir(mkdir(data_path).."/"..host); end
                return format("%s/%s/%s.%s", data_path, host, datastore, ext);
-       else
-               if create then mkdir(data_path); end
-               return format("%s/%s.%s", data_path, datastore, ext);
        end
 end
 
@@ -195,17 +187,25 @@ function store(username, host, datastore, data)
 
        -- save the datastore
        local d = "return " .. serialize(data) .. ";\n";
-       local ok, msg = atomic_store(getpath(username, host, datastore, nil, true), d);
-       if not ok then
-               log("error", "Unable to write to %s storage ('%s') for user: %s@%s", datastore, msg, username or "nil", host or "nil");
-               return nil, "Error saving to storage";
-       end
-       if next(data) == nil then -- try to delete empty datastore
-               log("debug", "Removing empty %s datastore for user %s@%s", datastore, username or "nil", host or "nil");
-               os_remove(getpath(username, host, datastore));
-       end
-       -- we write data even when we are deleting because lua doesn't have a
-       -- platform independent way of checking for non-exisitng files
+       local mkdir_cache_cleared;
+       repeat
+               local ok, msg = atomic_store(getpath(username, host, datastore, nil, true), d);
+               if not ok then
+                       if not mkdir_cache_cleared then -- We may need to recreate a removed directory
+                               _mkdir = {};
+                               mkdir_cache_cleared = true;
+                       else
+                               log("error", "Unable to write to %s storage ('%s') for user: %s@%s", datastore, msg, username or "nil", host or "nil");
+                               return nil, "Error saving to storage";
+                       end
+               end
+               if next(data) == nil then -- try to delete empty datastore
+                       log("debug", "Removing empty %s datastore for user %s@%s", datastore, username or "nil", host or "nil");
+                       os_remove(getpath(username, host, datastore));
+               end
+               -- we write data even when we are deleting because lua doesn't have a
+               -- platform independent way of checking for non-exisitng files
+       until ok;
        return true;
 end
 
@@ -282,47 +282,86 @@ function list_load(username, host, datastore)
        return items;
 end
 
-function list_stores(username, host)
-       if not host then
-               return nil, "bad argument #2 to 'list_stores' (string expected, got nothing)";
+local type_map = {
+       keyval = "dat";
+       list = "list";
+}
+
+function users(host, store, typ)
+       typ = type_map[typ or "keyval"];
+       local store_dir = format("%s/%s/%s", data_path, encode(host), store);
+
+       local mode, err = lfs.attributes(store_dir, "mode");
+       if not mode then
+               return function() log("debug", err or (store_dir .. " does not exist")) end
        end
-       local list = {};
-       local host_dir = format("%s/%s/", data_path, encode(host));
-       for node in lfs.dir(host_dir) do
-               if not node:match"^%." then -- dots should be encoded, this is probably . or ..
-                       local store = decode(node);
-                       local path = host_dir..node;
-                       if username == true then
-                               if lfs.attributes(path, "mode") == "directory" then
-                                       list[#list+1] = store;
-                               end
-                       elseif username then
-                               if lfs.attributes(getpath(username, host, store), "mode")
-                                       or lfs.attributes(getpath(username, host, store, "list"), "mode") then
-                                       list[#list+1] = store;
+       local next, state = lfs.dir(store_dir);
+       return function(state)
+               for node in next, state do
+                       local file, ext = node:match("^(.*)%.([dalist]+)$");
+                       if file and ext == typ then
+                               return decode(file);
+                       end
+               end
+       end, state;
+end
+
+function stores(username, host, typ)
+       typ = type_map[typ or "keyval"];
+       local store_dir = format("%s/%s/", data_path, encode(host));
+
+       local mode, err = lfs.attributes(store_dir, "mode");
+       if not mode then
+               return function() log("debug", err or (store_dir .. " does not exist")) end
+       end
+       local next, state = lfs.dir(store_dir);
+       return function(state)
+               for node in next, state do
+                       if not node:match"^%." then
+                               if username == true then
+                                       if lfs.attributes(store_dir..node, "mode") == "directory" then
+                                               return decode(node);
+                                       end
+                               elseif username then
+                                       local store = decode(node)
+                                       if lfs.attributes(getpath(username, host, store, typ), "mode") then
+                                               return store;
+                                       end
+                               elseif lfs.attributes(node, "mode") == "file" then
+                                       local file, ext = node:match("^(.*)%.([dalist]+)$");
+                                       if ext == typ then
+                                               return decode(file)
+                                       end
                                end
-                       elseif lfs.attributes(path, "mode") == "file" then
-                               list[#list+1] = store:gsub("%.[dalist]+$","");
                        end
                end
+       end, state;
+end
+
+local function do_remove(path)
+       local ok, err = os_remove(path);
+       if not ok and lfs.attributes(path, "mode") then
+               return ok, err;
        end
-       return list;
+       return true
 end
 
 function purge(username, host)
        local host_dir = format("%s/%s/", data_path, encode(host));
-       local deleted = 0;
        local errs = {};
        for file in lfs.dir(host_dir) do
                if lfs.attributes(host_dir..file, "mode") == "directory" then
                        local store = decode(file);
-                       local ok, err = os_remove(getpath(username, host, store));
-                       if not ok and not err:lower():match("no such") then errs[#errs+1] = err; end
-                       local ok, err = os_remove(getpath(username, host, store, "list"));
-                       if not ok and not err:lower():match("no such") then errs[#errs+1] = err; end
+                       local ok, err = do_remove(getpath(username, host, store));
+                       if not ok then errs[#errs+1] = err; end
+
+                       local ok, err = do_remove(getpath(username, host, store, "list"));
+                       if not ok then errs[#errs+1] = err; end
                end
        end
        return #errs == 0, t_concat(errs, ", ");
 end
 
+_M.path_decode = decode;
+_M.path_encode = encode;
 return _M;