diff --git a/default/proto.lua b/default/proto.lua new file mode 100644 index 0000000..b6c0ec8 --- /dev/null +++ b/default/proto.lua @@ -0,0 +1,300 @@ +--============================================================================ +-- proto.lua Live (Mirror) Syncing Demon +-- +-- The default behavior of all syncs. +-- +-- This default layer 1 functions provide the higher layer functionality. +-- +-- License: GPLv2 (see COPYING) or any later version +-- Authors: Axel Kittenberger +--============================================================================ + + +if not default then error( 'default not loaded' ) end + +if default.proto then error( 'default-proto already loaded' ) end + +proto = { } + +default.proto = proto + + +-- +-- used to ensure there aren't typos in the keys +-- +proto.checkgauge = +{ + action = true, + checkgauge = true, + collect = true, + delay = true, + exitcodes = true, + init = true, + maxDelays = true, + maxProcesses = true, + onAttrib = true, + onCreate = true, + onModify = true, + onDelete = true, + onStartup = true, + onMove = true, + prepare = true, + source = true, + target = true, +} + +-- +-- On default action the user's on*** scripts are called. +-- +proto.action = function +( + inlet -- the inlet of the active sync. +) + -- 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 type( func ) == 'function' + 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 collector. +-- +-- Called when collecting a finished child process +-- +proto.collect = function +( + agent, -- event or event list being collected + exitcode -- the exitcode of the spawned process +) + local config = agent.config + + local rc + + if agent.syncStopped + then + log( 'Normal', 'Sync stopped, ignoring exitcode of finished child' ) + return 'ok' + end + + 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, ' -> ',agent.target,' finished.' + ) + + return 'ok' + elseif rc == 'again' + then + if settings( 'insist' ) + then + log( + 'Normal', + 'Retrying startup of ', + agent.source, ' -> ', agent.target, + ': ', exitcode + ) + + return 'again' + end + elseif rc == 'die' + then + log( + 'Error', + 'Failure on startup of ', + agent.source, ' -> ', agent.target + ) + + terminate( -1 ) + else + log( + 'Error', + 'Unknown exitcode "', exitcode, + '" on startup of ', + agent.source, ' -> ', agent.target + ) + return 'die' + end + 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 with exitcode = ', exitcode ) + else + log( 'Error', 'Unknown exitcode "', exitcode, '" with a list' ) + + rc = 'die' + end + else + if rc == 'ok' + then + log( + 'Normal', + 'Finished ', agent.etype, + ' on ', agent.sourcePath, ' = ', exitcode + ) + elseif rc == 'again' + then + log( + 'Normal', + 'Retrying ', 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 + + return rc +end + + +-- +-- Called on the Init event sent +-- on (re)initialization of Lsyncd for every sync +-- +proto.init = function +( + event -- the precreated init 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 + 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 collapsor tries not to have more than these delays. +-- So the delay queue does not grow too large +-- since calculation for stacking events is n*log( n ) (or so) +-- +proto.maxDelays = 1000 + + +-- +-- The maximum number of processes Lsyncd will +-- simultanously spawn for this sync. +-- +proto.maxProcesses = 1 + + +-- +-- Checks all keys to be in the checkgauge. +-- +local function check +( + config, + gauge, + subtable, + level +) + for k, v in pairs( config ) + do + if not gauge[ k ] + then + error( + 'Parameter "' .. subtable .. 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 "' .. subtable .. k .. '" must be a table.', + level + ) + end + + check( + config[ k ], + gauge[ k ], + subtable .. k .. '.', + level + 1 + ) + end + end +end + + +proto.prepare = function +( + config, -- the config to prepare for + level -- current callback level for error reporting +) + + local gauge = config.checkgauge + + if not gauge then return end + + check( config, gauge, '', level + 1 ) +end + diff --git a/default/rsync.lua b/default/rsync.lua index 5f72c68..9d47524 100644 --- a/default/rsync.lua +++ b/default/rsync.lua @@ -31,7 +31,6 @@ default.rsync = rsync -- used to ensure there aren't typos in the keys -- rsync.checkgauge = { - -- unsets default user action handlers onCreate = false, onModify = false, @@ -628,14 +627,9 @@ rsync.delete = true -- rsync.exitcodes = { - -- -- 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 - -- - _verbatim = true, + -- this will not be merged into that one + _merge = false, [ 0 ] = 'ok', [ 1 ] = 'die', diff --git a/default/rsyncssh.lua b/default/rsyncssh.lua index d9746d9..abf1cdd 100644 --- a/default/rsyncssh.lua +++ b/default/rsyncssh.lua @@ -41,6 +41,8 @@ default.rsyncssh = rsyncssh -- used to ensure there aren't typos in the keys -- rsyncssh.checkgauge = { + -- inherits the rsync checkgauge + default.rsync.checkgauge, -- unsets the inherited value of from default.rsync target = false, @@ -577,13 +579,8 @@ rsyncssh.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 - -- + -- this will not be merged into that one _merge = false, - _verbatim = true, [ 0 ] = 'ok', [ 255 ] = 'again', diff --git a/mantle/syncmaster.lua b/mantle/syncmaster.lua index f30e904..3dfbd15 100644 --- a/mantle/syncmaster.lua +++ b/mantle/syncmaster.lua @@ -80,11 +80,40 @@ local function get return syncList[ i ] end + +local inherit + + -- --- Helper function for inherit --- defined below +-- Inherits the contents of all tables with array keys +-- Returns the table with flattened inhertance, +-- also returns array size -- -local inheritKV +-- +local function flattenInheritance +( + t +) + local tf = { } + + inherit( tf, t ) + + for k, v in ipairs( t ) + do + -- numbers as key and table as value + -- means recursive inherit + if type( v ) == 'table' + then + local vv = flattenInheritance( v ) + inherit( tf, vv ) + else + if tf[ k ] == nil then tf[ k ] = v end + end + end + + return tf +end + -- -- Recursevly inherits a source table to a destionation table @@ -93,75 +122,52 @@ local inheritKV -- All entries with integer keys are inherited as additional -- sources for non-verbatim tables -- -local function inherit +inherit = function ( - cd, -- table copy destination - cs, -- table copy source - verbatim -- forced verbatim ( for e.g. 'exitcodes' ) + cd, -- table copy destination + cs -- table copy source ) - -- First copies all entries with non-integer keys. - -- - -- Tables are merged; already present keys are not - -- overwritten - -- - -- For verbatim tables integer keys are treated like - -- non-integer keys + local imax = 0 + + for k, _ in ipairs( cs ) do imax = k end + for k, v in pairs( cs ) do - if type( k ) ~= 'number' - or verbatim - or cs._verbatim == true + if type( k ) == 'number' then - inheritKV( cd, k, v ) - end - end + if( k < 1 or k > imax or math.floor( k ) ~= k ) + then + -- not an array integer + if type( v ) == 'table' + then + error( 'non sequence numeric key used as inheritance', 2 ) + end - -- recursevely inherits all integer keyed tables - -- ( for non-verbatim tables ) - if cs._verbatim ~= true - then - for k, v in ipairs( cs ) - do + if cd[ k ] == nil then cd[ k ] = v end + end + else if type( v ) == 'table' then - inherit( cd, v ) - else - cd[ #cd + 1 ] = v + v = flattenInheritance( v ) + end + + local dv = cd[ k ] + + if dv == nil + then + cd[ k ] = v + elseif type( dv ) == 'table' + and type( v ) == 'table' + and v._merge ~= false + then + dv = inherit( { }, dv ) + dv = inherit( dv, v ) + cd[ k ] = dv end end - end -end --- --- Helper to inherit. Inherits one key. --- -inheritKV = - function( - cd, -- table copy destination - k, -- key - v -- value - ) - - -- don't merge inheritance controls - if k == '_verbatim' then return end - - local dtype = type( cd [ k ] ) - - if type( v ) == 'table' - then - if dtype == 'nil' - then - cd[ k ] = { } - inherit( cd[ k ], v, k == 'exitcodes' ) - elseif dtype == 'table' - then - inherit( cd[ k ], v, k == 'exitcodes' ) - end - elseif dtype == 'nil' - then - cd[ k ] = v - end + return cd end @@ -174,11 +180,7 @@ local function add ) -- Creates a new config table which inherits all keys/values -- from integer keyed tables - local uconfig = config - - config = { } - - inherit( config, uconfig ) + config = flattenInheritance( config ) -- last and least default prototype is inherited inherit( config, userenv.default.proto )