diff --git a/default-rsync.lua b/default-rsync.lua index 73a3c4e..7234641 100644 --- a/default-rsync.lua +++ b/default-rsync.lua @@ -28,6 +28,64 @@ end default.rsync = { } local rsync = default.rsync +-- uses default collect + +-- +-- used to ensure there aren't typos in the keys +-- +rsync.checkgauge = { + + default.checkgauge, + + -- unsets default user action handlers + onCreate = false, + onModify = false, + onDelete = false, + onStartup = false, + onMove = false, + + delete = true, + exclude = true, + source = true, + target = true, + + rsync = { + -- rsync binary + binary = true, + + -- rsync shortflags + verbose = true, + quiet = true, + checksum = true, + update = true, + links = true, + copy_links = true, + hard_links = true, + perms = true, + executability = true, + acls = true, + xattrs = true, + owner = true, + group = true, + times = true, + sparse = true, + dry_run = true, + whole_file = true, + one_file_system = true, + prune_empty_dirs = true, + ignore_times = true, + compress = true, + cvs_exclude = true, + protect_args = true, + ipv4 = true, + ipv6 = true, + + -- further rsync options + rsh = true, + rsync_path = true, + }, +} + -- -- Spawns rsync for a list of events @@ -163,10 +221,19 @@ end -- rsync.init = function(event) - local config = event.config - local inlet = event.inlet + local config = event.config + local inlet = event.inlet local excludes = inlet.getExcludes( ) - local delete = nil + local delete = nil + local target = config.target + + if not target then + if not config.host then + error('Internal fail, Neither target nor host is configured') + end + + target = config.host .. ':' .. config.targetdir + end if config.delete then delete = { '--delete', '--ignore-errors' } @@ -179,7 +246,7 @@ rsync.init = function(event) 'recursive startup rsync: ', config.source, ' -> ', - config.target + target ) spawn( @@ -189,7 +256,7 @@ rsync.init = function(event) config.rsync._computed, '-r', config.source, - config.target + target ) else @@ -202,7 +269,7 @@ rsync.init = function(event) 'recursive startup rsync: ', config.source, ' -> ', - config.target, + target, ' excluding\n', exS ) @@ -216,7 +283,7 @@ rsync.init = function(event) config.rsync._computed, '-r', config.source, - config.target + target ) end end @@ -225,19 +292,30 @@ end -- -- Prepares and checks a syncs configuration on startup. -- -rsync.prepare = function( config ) +rsync.prepare = function( + config, -- the configuration + level, -- additional error level for inherited use ( by rsyncssh ) + skipTarget -- used by rsyncssh, do not check for target +) - if not config.target then + level = level or 4 + + -- + -- First let default.prepare test the checkgauge + -- + default.prepare( config, level + 6 ) + + if not skipTarget and not config.target then error( 'default.rsync needs "target" configured', - 4 + level ) end if config.rsyncOps then error( '"rsyncOps" is outdated please use the new rsync = { ... } syntax.', - 4 + level ) end @@ -246,7 +324,7 @@ rsync.prepare = function( config ) '"rsyncOpts" is outdated in favor of the new rsync = { ... } syntax\n"' + 'for which you provided the _extra attribute as well.\n"' + 'Please remove rsyncOpts from your config.', - 4 + level ) end @@ -265,7 +343,7 @@ rsync.prepare = function( config ) '"rsyncBinary is outdated in favor of the new rsync = { ... } syntax\n"'+ 'for which you provided the binary attribute as well.\n"' + "Please remove rsyncBinary from your config.'", - 4 + level ) end @@ -283,75 +361,108 @@ rsync.prepare = function( config ) if config.rsync._computed then error( 'please do not use the internal rsync._computed parameter', - 4 + level ) end -- computes the rsync arguments into one list local rsync = config.rsync; + rsync._computed = { true } local computed = rsync._computed + local computedN = 1 + + local shortFlags = { + verbose = 'v', + quiet = 'q', + checksum = 'c', + update = 'u', + links = 'l', + copy_links = 'L', + hard_links = 'H', + perms = 'p', + executability = 'E', + acls = 'A', + xattrs = 'X', + owner = 'o', + group = 'g', + times = 't', + sparse = 'S', + dry_run = 'n', + whole_file = 'W', + one_file_system = 'x', + prune_empty_dirs = 'm', + ignore_times = 'I', + compress = 'z', + cvs_exclude = 'C', + protect_args = 's', + ipv4 = '4', + ipv6 = '6' + } + local shorts = { '-' } + local shortsN = 2 if config.rsync._extra then for k, v in ipairs( config.rsync._extra ) do - computed[ k + 1 ] = v + computed[ computedN ] = v + computedN = computedN + 1 end end - if rsync.links then - shorts[ #shorts + 1 ] = 'l' + for k, flag in pairs( shortFlags ) do + if config.rsync[k] then + shorts[ shortsN ] = flag + shortsN = shortsN + 1 + end end - if rsync.times then - shorts[ #shorts + 1 ] = 't' + if config.rsync.rsh then + computed[ computedN ] = '--rsh=' + config.rsync.rsh + computedN = computedN + 1 end - if rsync.protectArgs then - shorts[ #shorts + 1 ] = 's' + if config.rsync.rsync_path then + computed[ computedN ] = '--rsync-path=' + config.rsync.rsync_path + computedN = computedN + 1 end - if #shorts ~= 1 then + if shortsN ~= 2 then computed[ 1 ] = table.concat( shorts, '' ) else computed[ 1 ] = { } end -- appends a / to target if not present - if string.sub(config.target, -1) ~= '/' then + if not skipTarget and string.sub(config.target, -1) ~= '/' then config.target = config.target..'/' end + end --- --- rsync uses default collect --- - - -- -- By default do deletes. -- rsync.delete = true +-- +-- Rsyncd exitcodes +-- +rsync.exitcodes = default.rsyncExitCodes -- -- Calls rsync with this default options -- rsync.rsync = { -- The rsync binary to be called. - binary = '/usr/bin/rsync', - links = true, - times = true, - protectArgs = true + binary = '/usr/bin/rsync', + links = true, + times = true, + protect_args = true } --- --- Exit codes for rsync. --- -rsync.exitcodes = default.rsyncExitCodes - -- -- Default delay -- diff --git a/default-rsyncssh.lua b/default-rsyncssh.lua index 625d08e..9ea744c 100644 --- a/default-rsyncssh.lua +++ b/default-rsyncssh.lua @@ -16,344 +16,340 @@ -- if not default then - error('default not loaded'); + error( 'default not loaded' ); +end + +if not default.rsync then + error( 'default.rsync not loaded' ); end if default.rsyncssh then - error('default-rsyncssh already loaded'); + error( 'default-rsyncssh already loaded' ); end -default.rsyncssh = { +-- +-- rsyncssh extends default.rsync +-- +local rsyncssh = { default.rsync } +default.rsyncssh = rsyncssh - -- - -- Spawns rsync for a list of events - -- - action = function(inlet) +-- +-- used to ensure there aren't typos in the keys +-- +rsyncssh.checkgauge = { - local event, event2 = inlet.getEvent() - local config = inlet.getConfig() + -- unsets the inherited value of from default.rsync + target = false, + onMove = true, - -- makes move local on host - -- if fails deletes the source... - if event.etype == 'Move' then - log('Normal', 'Moving ',event.path,' -> ',event2.path) - spawn(event, '/usr/bin/ssh', - config.host, - 'mv', - '\"' .. config.targetdir .. event.path .. '\"', - '\"' .. config.targetdir .. event2.path .. '\"', - '||', 'rm', '-rf', - '\"' .. config.targetdir .. event.path .. '\"') + -- rsyncssh users host and targetdir + host = true, + targetdir = true, + + -- ssh settings + ssh = { + binary = true, + port = true, + _extra = true + }, + + -- xargs settings + xargs = { + binary = true, + delimiter = true, + _extra = true + } +} + +-- +-- Spawns rsync for a list of events +-- +rsyncssh.action = function( inlet ) + + local event, event2 = inlet.getEvent() + local config = inlet.getConfig() + + -- makes move local on target host + -- if the move fails, it deletes the source + if event.etype == 'Move' then + log('Normal', 'Moving ',event.path,' -> ',event2.path) + + spawn( + event, + config.ssh.binary, +-- config.ssh._computed, TODO XXX + config.host, + 'mv', + '\"' .. config.targetdir .. event.path .. '\"', + '\"' .. config.targetdir .. event2.path .. '\"', + '||', 'rm', '-rf', + '\"' .. config.targetdir .. event.path .. '\"') + return + end + + -- uses ssh to delete files on remote host + -- instead of constructing rsync filters + + if event.etype == 'Delete' then + if not config.delete then + inlet.discardEvent(event) return end - -- uses ssh to delete files on remote host - -- instead of constructing rsync filters - - if event.etype == 'Delete' then - - if not config.delete then - inlet.discardEvent(event) - return - end - - local elist = inlet.getEvents( - function(e) - return e.etype == 'Delete' - end - ) - - local paths = elist.getPaths( - function(etype, path1, path2) - if path2 then - return config.targetdir..path1, config.targetdir..path2 - else - return config.targetdir..path1 - end - end - ) - - for _, v in pairs(paths) do - if string.match(v, '^%s*/+%s*$') then - log('Error', 'refusing to `rm -rf /` the target!') - terminate(-1) -- ERRNO - end - end - - log('Normal', 'Deleting list\n', table.concat(paths, '\n')) - - local params = {} - - if config.port then - params[#params + 1] = 'p' - params[#params + 1] = config.port - end - - spawn( - elist, - config.ssh.binary, - '<', table.concat(paths, config.xargs.delimiter), - params, - config.ssh._extra, - config.host, - config.xargs.binary, - config.xargs._extra - ) - - return - end - - -- for everything else spawn a rsync + -- gets all other deletes ready to be + -- executed local elist = inlet.getEvents( - function(e) - -- TODO use a table - return e.etype ~= 'Move' and - e.etype ~= 'Delete' and - e.etype ~= 'Init' and - e.etype ~= 'Blanket' + function( e ) + return e.etype == 'Delete' end ) - local paths = elist.getPaths() + -- returns the paths of the delete list + local paths = elist.getPaths( + function( etype, path1, path2 ) + if path2 then + return config.targetdir..path1, config.targetdir..path2 + else + return config.targetdir..path1 + end + end + ) - -- removes trailing slashes from dirs. - for k, v in ipairs(paths) do - if string.byte(v, -1) == 47 then - paths[k] = string.sub(v, 1, -2) + -- ensures none of the paths is '/' + for _, v in pairs( paths ) do + if string.match(v, '^%s*/+%s*$') then + log('Error', 'refusing to `rm -rf /` the target!') + terminate(-1) -- ERRNO end end - local sPaths = table.concat(paths, '\n') - local zPaths = table.concat(paths, '\000') - log('Normal', 'Rsyncing list\n', sPaths) + log( + 'Normal', + 'Deleting list\n', + table.concat( paths, '\n' ) + ) + + local params = { } spawn( elist, - config.rsyncBinary, - '<', zPaths, - config.rsyncOpts, - '--from0', - '--files-from=-', - config.source, - config.host .. ':' .. config.targetdir + config.ssh.binary, + '<', table.concat(paths, config.xargs.delimiter), + params, + -- config.ssh._computed, TODO XXX + config.host, + config.xargs.binary, + config.xargs._extra ) - end, - ----- - -- Called when collecting a finished child process + return + end + -- - collect = function(agent, exitcode) + -- for everything else a rsync is spawned + -- + local elist = inlet.getEvents( + function(e) + -- TODO use a table + return e.etype ~= 'Move' and + e.etype ~= 'Delete' and + e.etype ~= 'Init' and + e.etype ~= 'Blanket' + end + ) - local config = agent.config - - if not agent.isList and agent.etype == 'Init' then - - local rc = config.rsyncExitCodes[exitcode] - - if rc == 'ok' then - log('Normal', 'Startup of "',agent.source,'" finished: ', exitcode) - elseif rc == 'again' then - if settings.insist then - log('Normal', 'Retrying startup of "',agent.source,'": ', exitcode) - else - log('Error', 'Temporary or permanent failure on startup of "', - agent.source, '". Terminating since "insist" is not set.'); - terminate(-1) -- ERRNO - end - elseif rc == 'die' then - log('Error', 'Failure on startup of "',agent.source,'": ', exitcode) - else - log('Error', 'Unknown exitcode on startup of "', agent.source,': "',exitcode) - rc = 'die' - end - - return rc + local paths = elist.getPaths( ) + -- + -- removes trailing slashes from dirs. + -- + for k, v in ipairs( paths ) do + if string.byte(v, -1) == 47 then + paths[k] = string.sub(v, 1, -2) end - if agent.isList then + end - local rc = config.rsyncExitCodes[exitcode] + local sPaths = table.concat(paths, '\n') + local zPaths = table.concat(paths, '\000') - if rc == 'ok' then - log('Normal', 'Finished (list): ',exitcode) - elseif rc == 'again' then - log('Normal', 'Retrying (list): ',exitcode) - elseif rc == 'die' then - log('Error', 'Failure (list): ', exitcode) + log('Normal', 'Rsyncing list\n', sPaths) + + spawn( + elist, + config.rsync.binary, + '<', zPaths, + config.rsync._computed, + '--from0', + '--files-from=-', + config.source, + config.host .. ':' .. config.targetdir + ) +end + +----- +-- Called when collecting a finished child process +-- +rsyncssh.collect = function( agent, exitcode ) + + local config = agent.config + + if not agent.isList and agent.etype == 'Init' then + local rc = config.rsyncExitCodes[exitcode] + + if rc == 'ok' then + log('Normal', 'Startup of "',agent.source,'" finished: ', exitcode) + elseif rc == 'again' then + if settings.insist then + log('Normal', 'Retrying startup of "',agent.source,'": ', exitcode) else - log('Error', 'Unknown exitcode (list): ',exitcode) - rc = 'die' + log('Error', 'Temporary or permanent failure on startup of "', + agent.source, '". Terminating since "insist" is not set.'); + terminate(-1) -- ERRNO end - return rc - + elseif rc == 'die' then + log('Error', 'Failure on startup of "',agent.source,'": ', exitcode) else - - local rc = config.sshExitCodes[exitcode] - - if rc == 'ok' then - log('Normal', 'Finished ',agent.etype,' ',agent.sourcePath,': ',exitcode) - elseif rc == 'again' then - log('Normal', 'Retrying ',agent.etype,' ',agent.sourcePath,': ',exitcode) - elseif rc == 'die' then - log('Normal', 'Failure ',agent.etype,' ',agent.sourcePath,': ',exitcode) - else - log('Error', 'Unknown exitcode ',agent.etype,' ',agent.sourcePath,': ',exitcode) - rc = 'die' - end - - return rc - + log('Error', 'Unknown exitcode on startup of "', agent.source,': "',exitcode) + rc = 'die' end - end, + return rc - -- - -- spawns the recursive startup sync - -- - init = function(event) + end - local config = event.config - local inlet = event.inlet - local excludes = inlet.getExcludes() - local target = config.host .. ':' .. config.targetdir - local delete = nil - - if config.delete then - delete = { '--delete', '--ignore-errors' }; - end - - if #excludes == 0 then - log('Normal', 'Recursive startup rsync: ',config.source,' -> ',target) - spawn( - event, config.rsyncBinary, - delete, - '-r', - config.rsyncOpts, - config.source, - target - ) + if agent.isList then + local rc = config.rsyncExitCodes[exitcode] + if rc == 'ok' then + log('Normal', 'Finished (list): ',exitcode) + elseif rc == 'again' then + log('Normal', 'Retrying (list): ',exitcode) + elseif rc == 'die' then + log('Error', 'Failure (list): ', exitcode) else - local exS = table.concat(excludes, '\n') - log('Normal', 'Recursive startup rsync: ',config.source, - ' -> ',target,' with excludes.') - spawn( - event, config.rsyncBinary, - '<', exS, - '--exclude-from=-', - delete, - '-r', - config.rsyncOpts, - config.source, - target - ) + log('Error', 'Unknown exitcode (list): ',exitcode) + rc = 'die' end - end, + return rc + else + local rc = config.sshExitCodes[exitcode] - -- - -- checks the configuration. - -- - prepare = function(config) - - if not config.host then - error('default.rsyncssh needs "host" configured', 4) + if rc == 'ok' then + log('Normal', 'Finished ',agent.etype,' ',agent.sourcePath,': ',exitcode) + elseif rc == 'again' then + log('Normal', 'Retrying ',agent.etype,' ',agent.sourcePath,': ',exitcode) + elseif rc == 'die' then + log('Normal', 'Failure ',agent.etype,' ',agent.sourcePath,': ',exitcode) + else + log('Error', 'Unknown exitcode ',agent.etype,' ',agent.sourcePath,': ',exitcode) + rc = 'die' end - if not config.targetdir then - error('default.rsyncssh needs "targetdir" configured', 4) - end + return rc + end - if config.rsyncOps then - error('did you mean rsyncOpts with "t"?', 4) - end +end - -- appends a slash to the targetdir if missing - if string.sub(config.targetdir, -1) ~= '/' then - config.targetdir = config.targetdir .. '/' - end - end, +-- +-- checks the configuration. +-- +rsyncssh.prepare = function( config ) - -- - -- the rsync binary called - -- - rsyncBinary = '/usr/bin/rsync', + default.rsync.prepare( config, 5, true ) - -- - -- calls rsync with this default short opts - -- - rsyncOpts = '-lts', + if not config.host then + error('default.rsyncssh needs "host" configured', 4) + end - -- - -- allow processes - -- - maxProcesses = 1, + if not config.targetdir then + error('default.rsyncssh needs "targetdir" configured', 4) + end - -- - -- The core should not split move events - -- - onMove = true, + if config.rsyncOps then + error('did you mean rsyncOpts with "t"?', 4) + end - -- - -- default delay - -- - delay = 15, + -- appends a slash to the targetdir if missing + if string.sub(config.targetdir, -1) ~= '/' then + config.targetdir = config.targetdir .. '/' + end + +end + +-- +-- allow processes +-- +rsyncssh.maxProcesses = 1 + +-- +-- The core should not split move events +-- +rsyncssh.onMove = true + +-- +-- default delay +-- +rsyncssh.delay = 15 - -- - -- by default do deletes - -- - delete = true, +-- +-- no default exit codes +-- +rsyncssh.exitcodes = false + +-- +-- rsync exit codes +-- +rsyncssh.rsyncExitCodes = default.rsyncExitCodes + +-- +-- ssh exit codes +-- +rsyncssh.sshExitCodes = default.sshExitCodes + +-- +-- xargs calls configuration +-- +-- xargs is used to delete multiple remote files, when ssh access is +-- available this is simpler than to build filters for rsync for this. +-- +rsyncssh.xargs = { -- - -- rsync exit codes - -- - rsyncExitCodes = default.rsyncExitCodes, + -- the binary called (on target host) + binary = '/usr/bin/xargs', -- - -- ssh exit codes - -- - sshExitCodes = default.sshExitCodes, + -- delimiter, uses null by default, you might want to override this for older + -- by for example '\n' + delimiter = '\000', -- - -- xargs calls configuration - -- - -- xargs is used to delete multiple remote files, when ssh access is - -- available this is simpler than to build filters for rsync for this. - -- - xargs = { - - -- - -- the binary called (on target host) - binary = '/usr/bin/xargs', - - -- - -- delimiter, uses null by default, you might want to override this for older - -- by for example '\n' - delimiter = '\000', - - -- - -- extra parameters - _extra = { '-0', 'rm -rf' } - }, - - -- - -- ssh calls configuration - -- - -- ssh is used to move and delete files on the target host - -- - ssh = { - -- - -- the binary called - binary = '/usr/bin/ssh', - - -- - -- if set connect to this port - port = nil, - - -- - -- extra parameters - _extra = { } - } - + -- extra parameters + _extra = { '-0', 'rm -rf' } } + +-- +-- ssh calls configuration +-- +-- ssh is used to move and delete files on the target host +-- +rsyncssh.ssh = { + + -- + -- the binary called + -- + binary = '/usr/bin/ssh', + + -- + -- if set connect to this port + -- + port = nil, + + -- + -- extra parameters + -- + _extra = { } +} + diff --git a/default.lua b/default.lua index 7ec7e05..a1f23d4 100644 --- a/default.lua +++ b/default.lua @@ -9,237 +9,332 @@ --============================================================================ if default then - error('default already loaded') + error( 'default already loaded' ) end -default = { - ----- - -- Default action calls user scripts on**** functions. - -- - action = function(inlet) - -- in case of moves getEvent returns the origin and dest of the move - local event, event2 = inlet.getEvent() - local config = inlet.getConfig() - local func = config['on'.. event.etype] - if func then - func(event, event2) - end - -- if function didnt change the wait status its not interested - -- in this event -> drop it. - if event.status == 'wait' then - inlet.discardEvent(event) - end - end, +default = { } - ----- - -- Default collector. - -- - -- Called when collecting a finished child process - -- - collect = function(agent, exitcode) +-- +-- used to ensure there aren't typos in the keys +-- +default.checkgauge = { + action = true, + checkgauge = true, + collect = true, + delay = true, + exitcodes = true, + init = true, + maxDelays = true, + maxProcesses = true, + onCreate = true, + onModify = true, + onDelete = true, + onStartup = true, + onMove = true, + prepare = true, + rsyncExitCodes = true, -- TODO + sshExitCodes = true -- TODO +} - local config = agent.config - local rc +-- +-- On default action the user's on*** scripts are called. +-- +default.action = function( inlet ) - if config.exitcodes then - rc = config.exitcodes[exitcode] - elseif exitcode == 0 then - rc = 'ok' - else - rc = 'die' - end + -- in case of moves getEvent returns the origin and dest of the move + local event, event2 = inlet.getEvent( ) + local config = inlet.getConfig( ) - -- TODO synchronize with similar code before - if not agent.isList and agent.etype == 'Init' then - if rc == 'ok' then - log('Normal', 'Startup of "',agent.source,'" finished.') - return 'ok' - elseif rc == 'again' then - if settings.insist then - log( - 'Normal', - 'Retrying startup of "', - agent.source, - '": ', - exitcode - ) + local func = config[ 'on'.. event.etype ] - return 'again' - else - log( - 'Error', - 'Temporary or permanent failure on startup of "', - agent.source, - '". Terminating since "insist" is not set.' - ) + if func then + func( event, event2 ) + end - terminate( -1 ) - end - elseif rc == 'die' then + -- if function didnt change the wait status its not interested + -- in this event -> drop it. + if event.status == 'wait' then + inlet.discardEvent( event ) + end + +end + + +-- +-- Default collector. +-- +-- Called when collecting a finished child process +-- +default.collect = function( agent, exitcode ) + + local config = agent.config + local rc + + if config.exitcodes then + rc = config.exitcodes[exitcode] + elseif exitcode == 0 then + rc = 'ok' + else + rc = 'die' + end + + -- TODO synchronize with similar code before + if not agent.isList and agent.etype == 'Init' then + if rc == 'ok' then + log('Normal', 'Startup of "',agent.source,'" finished.') + return 'ok' + elseif rc == 'again' then + if settings.insist then + log( + 'Normal', + 'Retrying startup of "', + agent.source, + '": ', + exitcode + ) + + return 'again' + else log( 'Error', - 'Failure on startup of "', + 'Temporary or permanent failure on startup of "', agent.source, - '".' + '". Terminating since "insist" is not set.' ) terminate( -1 ) - else - log( - 'Error', - 'Unknown exitcode "', - exitcode, - '" on startup of "', - agent.source, - '".' - ) - return 'die' end - end + elseif rc == 'die' then + log( + 'Error', + 'Failure on startup of "', + agent.source, + '".' + ) - if agent.isList then - if rc == 'ok' then - log( - 'Normal', - 'Finished a list after exitcode: ', - exitcode - ) - elseif rc == 'again' then - log( - 'Normal', - 'Retrying a list after exitcode = ', - exitcode - ) - elseif rc == 'die' then - log( - 'Error', - 'Failure with a list width exitcode = ', - exitcode - ) - else - log( - 'Error', - 'Unknown exitcode "',exitcode,'" with a list' - ) - - rc = 'die' - end + terminate( -1 ) else - if rc == 'ok' then - log('Normal', 'Retrying ',agent.etype,' on ',agent.sourcePath,' = ',exitcode) - elseif rc == 'again' then - log('Normal', 'Finished ',agent.etype,' on ',agent.sourcePath,' = ',exitcode) - elseif rc == 'die' then - log('Error', 'Failure with ',agent.etype,' on ',agent.sourcePath,' = ',exitcode) - else - log('Normal', 'Unknown exitcode "',exitcode,'" with ', agent.etype, - ' on ',agent.sourcePath,' = ',exitcode) - rc = 'die' - end + log( + 'Error', + 'Unknown exitcode "', + exitcode, + '" on startup of "', + agent.source, + '".' + ) + return 'die' end + end - return rc - end, + if agent.isList then + if rc == 'ok' then + log( + 'Normal', + 'Finished a list after exitcode: ', + exitcode + ) + elseif rc == 'again' then + log( + 'Normal', + 'Retrying a list after exitcode = ', + exitcode + ) + elseif rc == 'die' then + log( + 'Error', + 'Failure with a list width exitcode = ', + exitcode + ) + else + log( + 'Error', + 'Unknown exitcode "',exitcode,'" with a list' + ) - ----- - -- called on (re)initialization of Lsyncd. - -- - init = function(event) - local config = event.config - local inlet = event.inlet - -- user functions - -- calls a startup if given by user script. - if type(config.onStartup) == 'function' then - local startup = config.onStartup(event) - -- TODO honor some return codes of startup like "warmstart". + rc = 'die' end - - if event.status == 'wait' then - -- user script did not spawn anything - -- thus the blanket event is deleted again. - inlet.discardEvent(event) + else + if rc == 'ok' then + log('Normal', 'Retrying ',agent.etype,' on ',agent.sourcePath,' = ',exitcode) + elseif rc == 'again' then + log('Normal', 'Finished ',agent.etype,' on ',agent.sourcePath,' = ',exitcode) + elseif rc == 'die' then + log('Error', 'Failure with ',agent.etype,' on ',agent.sourcePath,' = ',exitcode) + else + log('Normal', 'Unknown exitcode "',exitcode,'" with ', agent.etype, + ' on ',agent.sourcePath,' = ',exitcode) + rc = 'die' end - end, + end + + return rc +end + + +-- +-- Called on the Init event sent +-- on (re)initialization of Lsyncd for every sync +-- +default.init = function(event) + local config = event.config + local inlet = event.inlet + + -- user functions + -- calls a startup if given by user script. + if type(config.onStartup) == 'function' then + local startup = config.onStartup(event) + -- TODO honor some return codes of startup like "warmstart". + end + + if event.status == 'wait' then + -- user script did not spawn anything + -- thus the blanket event is deleted again. + inlet.discardEvent(event) + end +end + + +-- +-- The maximum number of processes Lsyncd will +-- simultanously spawn for this sync. +-- +default.maxProcesses = 1 + + +-- +-- The collapsor tries not to have more than these delays. +-- So it dealy stack does not grow too large, +-- since calculation for stacking events is n*log(n) (or so) +-- +default.maxDelays = 1000 + + +--- +-- a default configuration using /bin/cp|rm|mv. +-- TODO huh? +-- +default.direct = default_direct + + +-- +-- Exitcodes of rsync and what to do. +-- TODO move to rsync +-- +default.rsyncExitCodes = { - ----- - -- The maximum number of processes Lsyncd will spawn simultanously for - -- one sync. -- - maxProcesses = 1, - - ----- - -- Try not to have more than these delays. - -- not too large, since total calculation for stacking - -- events is n*log(n) or so.. + -- if another config provides the same table + -- this will not be inherited (merged) into that one -- - maxDelays = 1000, - - ----- - -- a default configuration using /bin/cp|rm|mv. + -- if it does not, integer keys are to be copied + -- verbatim -- - direct = default_direct, + _merge = false, + _verbatim = true, - ------ - -- Exitcodes of rsync and what to do. - -- - rsyncExitCodes = { + [ 0 ] = 'ok', + [ 1 ] = 'die', + [ 2 ] = 'die', + [ 3 ] = 'again', + [ 4 ] = 'die', + [ 5 ] = 'again', + [ 6 ] = 'again', + [ 10 ] = 'again', + [ 11 ] = 'again', + [ 12 ] = 'again', + [ 14 ] = 'again', + [ 20 ] = 'again', + [ 21 ] = 'again', + [ 22 ] = 'again', - -- - -- if another config provides the same table - -- this will not be inherited (merged) into that one - -- - -- if it does not, integer keys are to be copied - -- verbatim - -- - _merge = false, - _verbatim = true, + -- partial transfers are ok, since Lsyncd has registered the event that + -- caused the transfer to be partial and will recall rsync. + [ 23 ] = 'ok', + [ 24 ] = 'ok', - [ 0 ] = 'ok', - [ 1 ] = 'die', - [ 2 ] = 'die', - [ 3 ] = 'again', - [ 4 ] = 'die', - [ 5 ] = 'again', - [ 6 ] = 'again', - [ 10 ] = 'again', - [ 11 ] = 'again', - [ 12 ] = 'again', - [ 14 ] = 'again', - [ 20 ] = 'again', - [ 21 ] = 'again', - [ 22 ] = 'again', - -- partial transfers are ok, since Lsyncd has registered the event that - -- caused the transfer to be partial and will recall rsync. - [ 23 ] = 'ok', - [ 24 ] = 'ok', - [ 25 ] = 'die', - [ 30 ] = 'again', - [ 35 ] = 'again', - [ 255 ] = 'again', - }, + [ 25 ] = 'die', + [ 30 ] = 'again', + [ 35 ] = 'again', - ----- - -- Exitcodes of ssh and what to do. - -- - sshExitCodes = { - - -- - -- if another config provides the same table - -- this will not be inherited (merged) into that one - -- - -- if it does not, integer keys are to be copied - -- verbatim - -- - _merge = false, - _verbatim = true, - - [ 0 ] = 'ok', - [ 255 ] = 'again', - }, - - ----- - -- Minimum seconds between two writes of a status file. - -- - statusInterval = 10, + [ 255 ] = 'again', } + + +-- +-- Exitcodes of ssh and what to do. +-- +default.sshExitCodes = { + + -- + -- if another config provides the same table + -- this will not be inherited (merged) into that one + -- + -- if it does not, integer keys are to be copied + -- verbatim + -- + _merge = false, + _verbatim = true, + + [ 0 ] = 'ok', + [ 255 ] = 'again', +} + + +-- +-- Minimum seconds between two writes of a status file. +-- +default.statusInterval = 10 + + +-- +-- checks all keys to be in the checkgauge +-- + +local function check( + config, + gauge, + level +) + for k, v in pairs( config ) do + + if not gauge[k] then + error( + 'Parameter "' + .. k + .. '" unknown.' + .. ' (if this is not a typo add it to checkgauge)', + level + ); + end + + if type( gauge [ k ] ) == 'table' then + + if type( v ) ~= 'table' then + + error( + 'Parameter "' + .. k + .. '" must be a table.', + level + ) + + end + + check( config[ k ], gauge[ k ], level + 1 ) + + end + end +end + +default.prepare = function( config, level ) + + local gauge = config.checkgauge + + if not gauge then + return + end + + check( config, gauge, level or 2 ) +end +