13
15
-- require('profiler')
14
16
-- profiler.start()
17
19
-- A security measurement.
20
-- The core will exit if version ids mismatch.
19
22
if lsyncd_version then
20
-- checks if the runner is being loaded twice
21
lsyncd.log('Error', 'You cannot use the lsyncd runner as configuration file!')
22
lsyncd.terminate(-1) -- ERRNO
24
-- ensures the runner is not being loaded twice
27
'You cannot use the lsyncd runner as configuration file!'
30
lsyncd.terminate( -1 )
24
lsyncd_version = '2.0.7'
33
lsyncd_version = '2.1.5'
36
-- Hides the core interface from user scripts.
34
44
-- Shortcuts (which user is supposed to be able to use them as well)
37
47
terminate = lsyncd.terminate
39
49
readdir = lsyncd.readdir
52
-- Coping globals to ensure userscripts don't change this.
41
55
local terminate = terminate
50
64
-- Global: total number of processess running
51
66
local processCount = 0
69
-- Settings specified by command line.
71
local clSettings = { }
74
-- Settings specified by config scripts.
79
-- A copy of the settings function to see if the
80
-- user script replaced the settings() by a table
81
-- ( pre Lsyncd 2.1 style )
53
85
--============================================================================
54
86
-- Lsyncd Prototypes
55
87
--============================================================================
59
local Array = (function()
90
-- Array tables error if accessed with a non-number.
92
local Array = ( function( )
63
97
-- on accessing a nil index.
64
mt.__index = function(t, k)
98
mt.__index = function( t, k )
65
99
if type(k) ~= 'number' then
66
error('Key "'..k..'" invalid for Array', 2)
100
error( 'Key "'..k..'" invalid for Array', 2 )
102
return rawget( t, k )
71
105
-- on assigning a new index.
72
mt.__newindex = function(t, k, v)
73
if type(k) ~= 'number' then
74
error('Key "'..k..'" invalid for Array', 2)
106
mt.__newindex = function( t, k, v )
107
if type( k ) ~= 'number' then
108
error( 'Key "'..k..'" invalid for Array', 2 )
79
113
-- creates a new object
114
local function new( )
116
setmetatable( o, mt )
86
-- objects public interface
129
-- Count array tables error if accessed with a non-number.
131
-- Additionally they maintain their length as 'size' attribute,
132
-- since Lua's # operator does not work on tables whose key values are not
92
133
-- strictly linear.
94
local CountArray = (function()
135
local CountArray = ( function( )
99
-- key to native table
103
-- on accessing a nil index.
104
mt.__index = function(t, k)
105
if type(k) ~= 'number' then
106
error('Key "'..k..'" invalid for CountArray', 2)
143
-- Key to native table
148
-- On accessing a nil index.
150
mt.__index = function( t, k )
151
if type( k ) ~= 'number' then
152
error( 'Key "'..k..'" invalid for CountArray', 2 )
154
return t[ k_nt ][ k ]
112
-- on assigning a new index.
113
mt.__newindex = function(t, k, v)
158
-- On assigning a new index.
160
mt.__newindex = function( t, k, v )
114
162
if type(k) ~= 'number' then
115
error('Key "'..k..'" invalid for CountArray', 2)
163
error( 'Key "'..k..'" invalid for CountArray', 2 )
118
local vb = t[k_nt][k]
167
local vb = t[ k_nt ][ k ]
119
168
if v and not vb then
120
169
t._size = t._size + 1
121
170
elseif not v and vb then
122
171
t._size = t._size - 1
128
177
-- Walks through all entries in any order.
130
local function walk(self)
131
return pairs(self[k_nt])
179
local function walk( self )
180
return pairs( self[ k_nt ] )
137
local function size(self)
186
local function size( self )
138
187
return self._size
142
-- creates a new count array
191
-- Creates a new count array
193
local function new( )
145
195
-- k_nt is native table, private for this object.
146
local o = {_size = 0, walk = walk, size = size, [k_nt] = {} }
147
203
setmetatable(o, mt)
215
-- A queue is optimized for pushing on the right and poping on the left.
217
Queue = ( function( )
162
220
-- Creates a new queue.
165
return { first = 1, last = 0, size = 0};
222
local function new( )
169
231
-- Pushes a value on the queue.
170
232
-- Returns the last value
172
local function push(list, value)
234
local function push( list, value )
173
236
if not value then
174
237
error('Queue pushing nil value', 2)
176
240
local last = list.last + 1
179
243
list.size = list.size + 1
184
-- Removes item at pos from Queue.
186
local function remove(list, pos)
187
if list[pos] == nil then
248
-- Removes an item at pos from the Queue.
250
local function remove( list, pos )
252
if list[ pos ] == nil then
188
253
error('Removing nonexisting item in Queue', 2)
192
-- if removing first element, move list on.
258
-- if removing first or last element,
259
-- the queue limits are adjusted.
193
260
if pos == list.first then
194
262
local last = list.last
195
while list[pos] == nil and pos <= list.last do
264
while list[ pos ] == nil and pos <= list.last do
199
270
elseif pos == list.last then
200
while list[pos] == nil and pos >= list.first do
272
while list[ pos ] == nil and pos >= list.first do
206
-- reset indizies if list is empty
280
-- reset the indizies if the queue is empty
207
281
if list.last < list.first then
211
286
list.size = list.size - 1
215
290
-- Queue iterator (stateless)
217
local function iter(list, pos)
292
local function iter( list, pos )
219
while list[pos] == nil and pos <= list.last do
296
while list[ pos ] == nil and pos <= list.last do
222
300
if pos > list.last then
225
return pos, list[pos]
304
return pos, list[ pos ]
229
-- Reverse queue iterator. (stateless)
231
local function iterReverse(list, pos)
308
-- Reverse queue iterator (stateless)
310
local function iterReverse( list, pos )
233
314
while list[pos] == nil and pos >= list.first do
236
318
if pos < list.first then
239
return pos, list[pos]
322
return pos, list[ pos ]
243
326
-- Iteraters through the queue
244
-- returning all non-nil pos-value entries
327
-- returning all non-nil pos-value entries.
246
local function qpairs(list)
329
local function qpairs( list )
247
330
return iter, list, list.first - 1
251
334
-- Iteraters backwards through the queue
252
-- returning all non-nil pos-value entries
335
-- returning all non-nil pos-value entries.
254
local function qpairsReverse(list)
337
local function qpairsReverse( list )
255
338
return iterReverse, list, list.last + 1
262
qpairsReverse = qpairsReverse}
346
qpairsReverse = qpairsReverse
266
351
-- Locks globals,
352
-- No more globals can be created after this
268
local function lockGlobals()
354
local function lockGlobals( )
270
local mt = getmetatable(t) or {}
271
mt.__index = function(t, k)
272
if (k~='_' and string.sub(k, 1, 2) ~= '__') then
273
error('Access of non-existing global "'..k..'"', 2)
357
local mt = getmetatable( t ) or { }
359
-- TODO try to remove the underscore exceptions
360
mt.__index = function( t, k )
361
if k ~= '_' and string.sub(k, 1, 2) ~= '__' then
362
error( 'Access of non-existing global "'..k..'"', 2 )
278
mt.__newindex = function(t, k, v)
279
if (k~='_' and string.sub(k, 1, 2) ~= '__') then
368
mt.__newindex = function( t, k, v )
369
if k ~= '_' and string.sub( k, 1, 2 ) ~= '__' then
280
370
error('Lsyncd does not allow GLOBALS to be created on the fly. '..
281
371
'Declare "'..k..'" local or declare global on load.', 2)
377
setmetatable( t, mt )
291
local Delay = (function()
381
-- Holds the information about a delayed event for one Sync.
383
local Delay = ( function( )
293
386
-- Creates a new delay.
297
local function new(etype, sync, alarm, path, path2)
390
local function new( etype, sync, alarm, path, path2 )
300
394
-- Type of event.
301
395
-- Can be 'Create', 'Modify', 'Attrib', 'Delete' and 'Move'
305
-- Sync this delay belongs to
400
-- the Sync this delay belongs to
309
405
-- Latest point in time this should be catered for.
310
406
-- This value is in kernel ticks, return of the C's
311
407
-- times(NULL) call.
315
-- path and filename or dirname of the delay relative
411
-- Path and filename or dirname of the delay relative
316
412
-- to the syncs root.
317
414
-- for the directories it contains a trailing slash
322
-- only not nil for 'Move's.
323
-- path and file/dirname of a move destination.
419
-- Used only for Moves.
420
-- Path and file/dirname of a move destination.
327
425
-- Status of the event. Valid stati are:
328
427
-- 'wait' ... the event is ready to be handled.
329
429
-- 'active' ... there is process running catering for this event.
330
431
-- 'blocked' ... this event waits for another to be handled first.
331
433
-- 'done' ... event has been collected. This should never be
332
434
-- visible as all references should be droped on
333
-- collection, nevertheless seperat status for
435
-- collection, nevertheless the seperate status is
436
-- used as insurrance everything is running correctly.
338
440
-- Position in the queue
350
local Combiner = (function()
353
-- new delay absorbed by old
355
local function abso(d1, d2)
356
log('Delay',d2.etype,':',d2.path,' absorbed by ',d1.etype,':',d1.path)
459
local Combiner = ( function( )
462
-- The new delay is absorbed by an older one.
464
local function abso( d1, d2 )
468
d2.etype, ':',d2.path,
361
-- new delay replaces the old one if it is a file
363
local function refi(d1, d2)
364
if d2.path:byte(-1) == 47 then
365
log('Delay',d2.etype,':',d2.path,' blocked by ',d1.etype,':',d1.path)
478
-- The new delay replaces the old one if it's a file
480
local function refi( d1, d2 )
482
-- but a directory blocks
483
if d2.path:byte( -1 ) == 47 then
487
d2.etype,':',d2.path,
368
log('Delay',d2.etype,':',d2.path,' replaces ',d1.etype,':',d1.path)
373
-- new delay replaces the old one
375
local function repl(d1, d2)
376
log('Delay',d2.etype,':',d2.path,' replaces ',d1.etype,':',d1.path)
381
-- delays nullificate each other
383
local function null(d1, d2)
384
log('Delay',d2.etype,':',d2.path,' nullifies ',d1.etype,':',d1.path)
498
d2.etype, ':', d2.path,
500
d1.etype, ':', d1.path
508
-- The new delay replaces an older one.
510
local function repl( d1, d2 )
514
d2.etype, ':', d2.path,
516
d1.etype, ':', d1.path
524
-- Two delays nullificate each other.
526
local function null( d1, d2 )
530
d2.etype,':',d2.path,
389
-- Table how to combine events that dont involve a move.
540
-- Table on how to combine events that dont involve a move.
391
542
local combineNoMove = {
392
Attrib = {Attrib=abso, Modify=repl, Create=repl, Delete=repl },
393
Modify = {Attrib=abso, Modify=abso, Create=repl, Delete=repl },
394
Create = {Attrib=abso, Modify=abso, Create=abso, Delete=repl },
395
Delete = {Attrib=abso, Modify=abso, Create=refi, Delete=abso },
399
-- combines two delays
401
local function combine(d1, d2)
574
-- Combines two delays
576
local function combine( d1, d2 )
402
578
if d1.etype == 'Init' or d1.etype == 'Blanket' then
403
580
-- everything is blocked by init or blanket delays.
405
log('Delay',d2.etype,':',d2.path,'->',d2.path2,'blocked by',d1.etype,' event')
584
d2.etype,':',d2.path,'->',d2.path2,
407
log('Delay',d2.etype,':',d2.path,'blocked by',d1.etype,' event')
591
d2.etype,':',d2.path,
413
601
if d1.etype ~= 'Move' and d2.etype ~= 'Move' then
414
603
if d1.path == d2.path then
415
604
if d1.status == 'active' then
418
return combineNoMove[d1.etype][d2.etype](d1, d2)
608
return combineNoMove[ d1.etype ][ d2.etype ]( d1, d2 )
421
-- blocks events if one is a parent directory of another
611
-- if one is a parent directory of another, events are blocking
422
612
if d1.path:byte(-1) == 47 and string.starts(d2.path, d1.path) or
423
613
d2.path:byte(-1) == 47 and string.starts(d1.path, d2.path)
430
-- Normal upon a Move
622
-- non-move event on a move.
431
623
if d1.etype == 'Move' and d2.etype ~= 'Move' then
432
-- stacks the move if the from field could anyway be damaged
624
-- if the from field could be damaged the events are stacked
433
625
if d1.path == d2.path or
434
626
d2.path:byte(-1) == 47 and string.starts(d1.path, d2.path) or
435
627
d1.path:byte(-1) == 47 and string.starts(d2.path, d1.path)
437
log('Delay',d2.etype,':',d2.path,' blocked by','Move :',d1.path,'->',d1.path2)
631
d2.etype, ':', d2.path,
633
'Move :', d1.path,'->', d1.path2
441
-- Event does something with the move destination
639
-- the event does something with the move destination
442
641
if d1.path2 == d2.path then
443
643
if d2.etype == 'Delete' or d2.etype == 'Create' then
444
645
if d1.status == 'active' then
447
log('Delay',d2.etype,':',d2.path,' turns ',
448
'Move :',d1.path,'->',d1.path2,' into ','Delete:',d1.path)
651
d2.etype, ':', d2.path,
653
'Move :', d1.path, '->', d1.path2,
449
657
d1.etype = 'Delete'
453
-- on 'Attrib' or 'Modify' simply wait for the move first
663
-- on 'Attrib' or 'Modify' simply stack on moves
457
668
if d2.path :byte(-1) == 47 and string.starts(d1.path2, d2.path) or
458
669
d1.path2:byte(-1) == 47 and string.starts(d2.path, d1.path2)
460
log('Delay',d2.etype,':',d2.path,' blocked by ','Move:',d1.path,'->',d1.path2)
673
,d2.etype, ':', d2.path,
675
'Move:', d1.path, '->', d1.path2
466
-- Move upon a single event
684
-- a move upon a non-move event
467
685
if d1.etype ~= 'Move' and d2.etype == 'Move' then
468
686
if d1.path == d2.path or d1.path == d2.path2 or
469
687
d1.path :byte(-1) == 47 and string.starts(d2.path, d1.path) or
555
local function getPath(event)
785
-- Gets the path of an event.
787
local function getPath( event )
556
788
if event.move ~= 'To' then
557
return e2d[event].path
789
return e2d[ event ].path
559
return e2d[event].path2
791
return e2d[ event ].path2
564
796
-- Interface for user scripts to get event fields.
566
798
local eventFields = {
568
801
-- Returns a copy of the configuration as called by sync.
569
802
-- But including all inherited data and default values.
571
804
-- TODO give user a readonly version.
573
config = function(event)
574
return e2d[event].sync.config
806
config = function( event )
807
return e2d[ event ].sync.config
578
811
-- Returns the inlet belonging to an event.
580
inlet = function(event)
581
return e2d[event].sync.inlet
813
inlet = function( event )
814
return e2d[ event ].sync.inlet
585
818
-- Returns the type of the event.
586
820
-- Can be: 'Attrib', 'Create', 'Delete', 'Modify' or 'Move',
588
etype = function(event)
589
return e2d[event].etype
822
etype = function( event )
823
return e2d[ event ].etype
593
-- Tells this isn't a list.
827
-- Events are not lists.
600
-- Return the status of the event.
834
-- Returns the status of the event.
602
837
-- 'wait', 'active', 'block'.
604
status = function(event)
605
return e2d[event].status
609
-- Returns true if event relates to a directory.
611
isdir = function(event)
612
return string.byte(getPath(event), -1) == 47
839
status = function( event )
840
return e2d[ event ].status
844
-- Returns true if event relates to a directory
846
isdir = function( event )
847
return string.byte( getPath( event ), -1 ) == 47
616
851
-- Returns the name of the file/dir.
617
853
-- Includes a trailing slash for dirs.
619
name = function(event)
620
return string.match(getPath(event), '[^/]+/?$')
624
-- Returns the name of the file/dir.
625
-- Excludes a trailing slash for dirs.
627
basename = function(event)
628
return string.match(getPath(event), '([^/]+)/?$')
855
name = function( event )
856
return string.match( getPath( event ), '[^/]+/?$' )
860
-- Returns the name of the file/dir
861
-- excluding a trailing slash for dirs.
863
basename = function( event )
864
return string.match( getPath( event ), '([^/]+)/?$')
632
868
-- Returns the file/dir relative to watch root
633
-- Includes a trailing slash for dirs.
869
-- including a trailing slash for dirs.
635
path = function(event)
636
return getPath(event)
871
path = function( event )
872
return getPath( event )
640
876
-- Returns the directory of the file/dir relative to watch root
641
877
-- Always includes a trailing slash.
643
pathdir = function(event)
644
return string.match(getPath(event), '^(.*/)[^/]+/?') or ''
879
pathdir = function( event )
880
return string.match( getPath( event ), '^(.*/)[^/]+/?' ) or ''
648
884
-- Returns the file/dir relativ to watch root
649
-- Excludes a trailing slash for dirs.
885
-- excluding a trailing slash for dirs.
651
pathname = function(event)
652
return cutSlash(getPath(event))
887
pathname = function( event )
888
return cutSlash( getPath( event ) )
656
892
-- Returns the absolute path of the watch root.
657
-- All symlinks will have been resolved.
659
source = function(event)
660
return e2d[event].sync.source
664
-- Returns the absolute path of the file/dir.
665
-- Includes a trailing slash for dirs.
667
sourcePath = function(event)
668
return e2d[event].sync.source .. getPath(event)
672
-- Returns the absolute dir of the file/dir.
673
-- Includes a trailing slash.
675
sourcePathdir = function(event)
893
-- All symlinks are resolved.
895
source = function( event )
896
return e2d[ event ].sync.source
900
-- Returns the absolute path of the file/dir
901
-- including a trailing slash for dirs.
903
sourcePath = function( event )
904
return e2d[ event ].sync.source .. getPath( event )
908
-- Returns the absolute dir of the file/dir
909
-- including a trailing slash.
911
sourcePathdir = function( event )
676
912
return e2d[event].sync.source ..
677
(string.match(getPath(event), '^(.*/)[^/]+/?') or '')
681
-- Returns the absolute path of the file/dir.
682
-- Excludes a trailing slash for dirs.
684
sourcePathname = function(event)
685
return e2d[event].sync.source .. cutSlash(getPath(event))
689
-- Returns the target.
690
-- Just for user comfort
692
-- (except here, the lsyncd.runner does not care event about the
693
-- existance of 'target', this is up to the scripts.)
695
target = function(event)
696
return e2d[event].sync.config.target
700
-- Returns the relative dir/file appended to the target.
701
-- Includes a trailing slash for dirs.
703
targetPath = function(event)
704
return e2d[event].sync.config.target .. getPath(event)
708
-- Returns the dir of the dir/file appended to the target.
709
-- Includes a trailing slash.
711
targetPathdir = function(event)
712
return e2d[event].sync.config.target ..
713
(string.match(getPath(event), '^(.*/)[^/]+/?') or '')
717
-- Returns the relative dir/file appended to the target.
718
-- Excludes a trailing slash for dirs.
720
targetPathname = function(event)
721
return e2d[event].sync.config.target ..
722
cutSlash(getPath(event))
913
( string.match( getPath( event ), '^(.*/)[^/]+/?' ) or '' )
917
-- Returns the absolute path of the file/dir
918
-- excluding a trailing slash for dirs.
920
sourcePathname = function( event )
921
return e2d[ event ].sync.source .. cutSlash( getPath( event ) )
925
-- Returns the configured target
927
target = function( event )
928
return e2d[ event ].sync.config.target
932
-- Returns the relative dir/file appended to the target
933
-- including a trailing slash for dirs.
935
targetPath = function( event )
936
return e2d[ event ].sync.config.target .. getPath( event )
940
-- Returns the dir of the dir/file appended to the target
941
-- including a trailing slash.
943
targetPathdir = function( event )
944
return e2d[ event ].sync.config.target ..
945
( string.match( getPath( event ), '^(.*/)[^/]+/?' ) or '' )
949
-- Returns the relative dir/file appended to the target
950
-- excluding a trailing slash for dirs.
952
targetPathname = function( event )
953
return e2d[ event ].sync.config.target ..
954
cutSlash( getPath( event ) )
727
959
-- Retrievs event fields for the user script.
729
961
local eventMeta = {
730
__index = function(event, field)
731
local f = eventFields[field]
963
__index = function( event, field )
964
local f = eventFields[ field ]
733
966
if field == 'move' then
734
967
-- possibly undefined
737
error('event does not have field "'..field..'"', 2)
970
error( 'event does not have field "'..field..'"', 2 )
744
-- Interface for user scripts to get event fields.
978
-- Interface for user scripts to get list fields.
746
980
local eventListFuncs = {
748
983
-- Returns a list of paths of all events in list.
750
985
-- @param elist -- handle returned by getevents()
751
986
-- @param mutator -- if not nil called with (etype, path, path2)
752
987
-- returns one or two strings to add.
754
getPaths = function(elist, mutator)
989
getPaths = function( elist, mutator )
755
991
local dlist = e2d[elist]
756
993
if not dlist then
757
error('cannot find delay list from event list.')
994
error( 'cannot find delay list from event list.' )
760
998
local resultn = 1
761
for k, d in ipairs(dlist) do
1000
for k, d in ipairs( dlist ) do
764
s1, s2 = mutator(d.etype, d.path, d.path2)
1005
s1, s2 = mutator( d.etype, d.path, d.path2 )
766
1007
s1, s2 = d.path, d.path2
1010
result[ resultn ] = s1
769
1011
resultn = resultn + 1
1014
result[ resultn ] = s2
772
1015
resultn = resultn + 1
780
-- Retrievs event list fields for the user script.
1025
-- Retrievs event list fields for the user script
782
1027
local eventListMeta = {
783
__index = function(elist, func)
784
if func == 'isList' then return true end
786
if func == 'config' then return e2d[elist].sync.config end
788
local f = eventListFuncs[func]
1029
__index = function( elist, func )
1031
if func == 'isList' then
1035
if func == 'config' then
1036
return e2d[ elist ].sync.config
1039
local f = eventListFuncs[ func ]
790
error('event list does not have function "'..func..'"', 2)
1043
'event list does not have function "' .. func .. '"',
1048
return function( ... )
1049
return f( elist, ... )
800
-- table of all inlets with their syncs
805
-- allows the garbage collector to remove entries.
806
-- TODO check memory use
807
setmetatable(inlets, { __mode = 'v' })
1057
-- Table of all inlets with their syncs.
1062
-- Allows the garbage collector to remove entries.
1064
setmetatable( inlets, { __mode = 'v' } )
810
1067
-- Encapsulates a delay into an event for the user script.
812
local function d2e(delay)
1069
local function d2e( delay )
813
1071
-- already created?
814
1072
local eu = e2d2[delay]
816
1074
if delay.etype ~= 'Move' then
817
if eu then return eu end
820
setmetatable(event, eventMeta)
1081
setmetatable( event, eventMeta )
1082
e2d[ event ] = delay
1083
e2d2[ delay ] = event
825
1088
-- moves have 2 events - origin and destination
826
if eu then return eu[1], eu[2] end
828
1093
local event = { move = 'Fr' }
829
1094
local event2 = { move = 'To' }
830
setmetatable(event, eventMeta)
831
setmetatable(event2, eventMeta)
834
e2d2[delay] = { event, event2 }
1096
setmetatable( event, eventMeta )
1097
setmetatable( event2, eventMeta )
1099
e2d[ event ] = delay
1100
e2d[ event2 ] = delay
1102
e2d2[ delay ] = { event, event2 }
835
1104
-- move events have a field 'move'
836
1105
return event, event2
841
1111
-- Encapsulates a delay list into an event list for the user script.
843
local function dl2el(dlist)
844
local eu = e2d2[dlist]
845
if eu then return eu end
848
setmetatable(elist, eventListMeta)
1113
local function dl2el( dlist )
1115
local eu = e2d2[ dlist ]
1123
setmetatable( elist, eventListMeta )
1125
e2d [ elist ] = dlist
1126
e2d2[ dlist ] = elist
855
1133
-- The functions the inlet provides.
857
1135
local inletFuncs = {
861
addExclude = function(sync, pattern)
862
sync:addExclude(pattern)
866
-- removes an exclude.
868
rmExclude = function(sync, pattern)
869
sync:rmExclude(pattern)
873
-- gets the list of excludes in their original rsynlike patterns form.
875
getExcludes = function(sync)
1140
addExclude = function( sync, pattern )
1141
sync:addExclude( pattern )
1145
-- Removes an exclude.
1147
rmExclude = function( sync, pattern )
1148
sync:rmExclude( pattern )
1152
-- Gets the list of excludes in their original rsynlike patterns form.
1154
getExcludes = function( sync )
876
1156
-- creates a copy
879
for k, _ in pairs(sync.excludes.list) do
1160
for k, _ in pairs( sync.excludes.list ) do
887
1169
-- Creates a blanketEvent that blocks everything
888
1170
-- and is blocked by everything.
890
createBlanketEvent = function(sync)
891
return d2e(sync:addBlanketDelay())
1172
createBlanketEvent = function( sync )
1173
return d2e( sync:addBlanketDelay( ) )
895
1177
-- Discards a waiting event.
897
discardEvent = function(sync, event)
898
local delay = e2d[event]
1179
discardEvent = function( sync, event )
1180
local delay = e2d[ event ]
899
1181
if delay.status ~= 'wait' then
901
1184
'Ignored cancel of a non-waiting event of type ',
905
sync:removeDelay(delay)
1189
sync:removeDelay( delay )
909
1193
-- Gets the next not blocked event from queue.
911
getEvent = function(sync)
912
return d2e(sync:getNextDelay(now()))
1195
getEvent = function( sync )
1196
return d2e( sync:getNextDelay( now( ) ) )
916
1200
-- Gets all events that are not blocked by active events.
918
1202
-- @param if not nil a function to test each delay
920
getEvents = function(sync, test)
921
local dlist = sync:getDelays(test)
1204
getEvents = function( sync, test )
1205
local dlist = sync:getDelays( test )
1206
return dl2el( dlist )
926
1210
-- Returns the configuration table specified by sync{}
928
getConfig = function(sync)
1212
getConfig = function( sync )
929
1213
-- TODO gives a readonly handler only.
930
1214
return sync.config
935
1219
-- Forwards access to inlet functions.
937
1221
local inletMeta = {
938
__index = function(inlet, func)
939
local f = inletFuncs[func]
940
if not f then error('inlet does not have function "'..func..'"', 2) end
941
return function(...) return f(inlets[inlet], ...) end
1222
__index = function( inlet, func )
1223
local f = inletFuncs[ func ]
1226
'inlet does not have function "'..func..'"',
1231
return function( ... )
1232
return f( inlets[ inlet ], ... )
946
-- Creates a new inlet for Sync
947
local function newInlet(sync)
948
-- lua runner controlled variables
1238
-- Creates a new inlet for Sync.
1240
local function newInlet( sync )
1242
-- Lsyncd runner controlled variables
951
1245
-- sets use access methods
952
setmetatable(inlet, inletMeta)
1246
setmetatable( inlet, inletMeta )
1247
inlets[ inlet ] = sync
958
1252
-- Returns the delay from a event.
960
local function getDelayOrList(event)
1254
local function getDelayOrList( event )
965
1259
-- Returns the sync from an event or list
967
local function getSync(event)
968
return e2d[event].sync
1261
local function getSync( event )
1262
return e2d[ event ].sync
973
-- this one is split, one for user one for runner.
1266
-- Public interface.
975
1269
getDelayOrList = getDelayOrList,
978
1272
getSync = getSync,
979
1273
newInlet = newInlet,
985
1280
-- A set of exclude patterns
987
local Excludes = (function()
1282
local Excludes = ( function( )
990
1285
-- Turns a rsync like file pattern to a lua pattern.
1286
-- ( at best it can )
992
local function toLuaPattern(p)
1288
local function toLuaPattern( p )
994
p = string.gsub(p, '%%', '%%%%')
995
p = string.gsub(p, '%^', '%%^')
996
p = string.gsub(p, '%$', '%%$')
997
p = string.gsub(p, '%(', '%%(')
998
p = string.gsub(p, '%)', '%%)')
999
p = string.gsub(p, '%.', '%%.')
1000
p = string.gsub(p, '%[', '%%[')
1001
p = string.gsub(p, '%]', '%%]')
1002
p = string.gsub(p, '%+', '%%+')
1003
p = string.gsub(p, '%-', '%%-')
1004
p = string.gsub(p, '%?', '[^/]')
1005
p = string.gsub(p, '%*', '[^/]*')
1290
p = string.gsub( p, '%%', '%%%%' )
1291
p = string.gsub( p, '%^', '%%^' )
1292
p = string.gsub( p, '%$', '%%$' )
1293
p = string.gsub( p, '%(', '%%(' )
1294
p = string.gsub( p, '%)', '%%)' )
1295
p = string.gsub( p, '%.', '%%.' )
1296
p = string.gsub( p, '%[', '%%[' )
1297
p = string.gsub( p, '%]', '%%]' )
1298
p = string.gsub( p, '%+', '%%+' )
1299
p = string.gsub( p, '%-', '%%-' )
1300
p = string.gsub( p, '%?', '[^/]' )
1301
p = string.gsub( p, '%*', '[^/]*' )
1006
1302
-- this was a ** before
1007
p = string.gsub(p, '%[%^/%]%*%[%^/%]%*', '.*')
1008
p = string.gsub(p, '^/', '^/')
1009
if p:sub(1,2) ~= '^/' then -- does not begin with '^/'
1010
-- all matches should begin with '/'.
1303
p = string.gsub( p, '%[%^/%]%*%[%^/%]%*', '.*' )
1304
p = string.gsub( p, '^/', '^/' )
1306
if p:sub( 1, 2 ) ~= '^/' then
1307
-- if does not begin with '^/'
1308
-- then all matches should begin with '/'.
1013
log('Exclude', 'toLuaPattern "',o,'" = "',p,'"')
1018
-- Adds a pattern to exclude.
1020
local function add(self, pattern)
1021
if self.list[pattern] then
1322
-- Adds a pattern to exclude
1324
local function add( self, pattern )
1326
if self.list[ pattern ] then
1022
1327
-- already in the list
1025
local lp = toLuaPattern(pattern)
1026
self.list[pattern] = lp
1331
local lp = toLuaPattern( pattern )
1332
self.list[ pattern ] = lp
1030
1337
-- Removes a pattern to exclude.
1032
local function remove(self, pattern)
1033
if not self.list[pattern] then
1034
-- already in the list
1035
log('Normal', 'Removing not excluded exclude "'..pattern..'"')
1339
local function remove( self, pattern )
1341
if not self.list[ pattern ] then
1342
-- already in the list?
1346
'Removing not excluded exclude "' .. pattern .. '"'
1038
1352
self.list[pattern] = nil
1097
1441
remove = remove,
1103
1448
-- Public interface
1104
1450
return { new = new }
1108
1455
-- Holds information about one observed directory inclusively subdirs.
1110
local Sync = (function()
1457
local Sync = ( function( )
1112
1460
-- Syncs that have no name specified by the user script
1113
1461
-- get an incremental default name 'Sync[X]'
1115
1463
local nextDefaultName = 1
1118
1466
-- Adds an exclude.
1120
local function addExclude(self, pattern)
1121
return self.excludes:add(pattern)
1468
local function addExclude( self, pattern )
1470
return self.excludes:add( pattern )
1125
1475
-- Removes an exclude.
1127
local function rmExclude(self, pattern)
1128
return self.excludes:remove(pattern)
1477
local function rmExclude( self, pattern )
1479
return self.excludes:remove( pattern )
1132
1484
-- Removes a delay.
1134
local function removeDelay(self, delay)
1135
if self.delays[delay.dpos] ~= delay then
1136
error('Queue is broken, delay not a dpos')
1486
local function removeDelay( self, delay )
1487
if self.delays[ delay.dpos ] ~= delay then
1488
error( 'Queue is broken, delay not a dpos' )
1138
Queue.remove(self.delays, delay.dpos)
1491
Queue.remove( self.delays, delay.dpos )
1140
1493
-- free all delays blocked by this one.
1141
1494
if delay.blocks then
1142
for i, vd in pairs(delay.blocks) do
1495
for i, vd in pairs( delay.blocks ) do
1143
1496
vd.status = 'wait'
1149
1502
-- Returns true if this Sync concerns about 'path'
1151
local function concerns(self, path)
1504
local function concerns( self, path )
1152
1506
-- not concerned if watch rootdir doesnt match
1153
if not path:starts(self.source) then
1507
if not path:starts( self.source ) then
1157
1511
-- a sub dir and not concerned about subdirs
1158
1512
if self.config.subdirs == false and
1159
path:sub(#self.source, -1):match('[^/]+/?')
1513
path:sub( #self.source, -1 ):match( '[^/]+/?' )
1164
1518
-- concerned if not excluded
1165
return not self.excludes:test(path:sub(#self.source))
1519
return not self.excludes:test( path:sub( #self.source ) )
1169
1523
-- Collects a child process
1171
local function collect(self, pid, exitcode)
1172
local delay = self.processes[pid]
1525
local function collect( self, pid, exitcode )
1527
local delay = self.processes[ pid ]
1173
1529
if not delay then
1174
1530
-- not a child of this sync.
1178
1534
if delay.status then
1179
log('Delay', 'collected an event')
1536
log( 'Delay', 'collected an event' )
1180
1538
if delay.status ~= 'active' then
1181
1539
error('collecting a non-active process')
1183
1542
local rc = self.config.collect(
1184
InletFactory.d2e(delay),
1543
InletFactory.d2e( delay ),
1186
1547
if rc == 'die' then
1187
log('Error', 'Critical exitcode.');
1188
terminate(-1) --ERRNO
1548
log( 'Error', 'Critical exitcode.' );
1190
1552
if rc ~= 'again' then
1191
1553
-- if its active again the collecter restarted the event
1192
removeDelay(self, delay)
1193
log('Delay', 'Finish of ',delay.etype,' on ',self.source,delay.path,' = ',exitcode)
1554
removeDelay( self, delay )
1560
self.source,delay.path,
1195
1565
-- sets the delay on wait again
1196
1566
delay.status = 'wait'
1197
1568
local alarm = self.config.delay
1198
1570
-- delays at least 1 second
1199
1571
if alarm < 1 then
1202
delay.alarm = now() + alarm
1575
delay.alarm = now( ) + alarm
1205
log('Delay', 'collected a list')
1206
1583
local rc = self.config.collect(
1207
InletFactory.dl2el(delay),
1584
InletFactory.dl2el( delay ),
1209
1588
if rc == 'die' then
1210
log('Error', 'Critical exitcode.');
1211
terminate(-1) --ERRNO
1589
log( 'Error', 'Critical exitcode.' );
1213
1593
if rc == 'again' then
1214
1595
-- sets the delay on wait again
1215
1596
delay.status = 'wait'
1216
1597
local alarm = self.config.delay
1218
1599
if alarm < 1 then
1221
1603
alarm = now() + alarm
1222
for _, d in ipairs(delay) do
1605
for _, d in ipairs( delay ) do
1223
1606
d.alarm = alarm
1224
1607
d.status = 'wait'
1227
for _, d in ipairs(delay) do
1611
for _, d in ipairs( delay ) do
1228
1612
if rc ~= 'again' then
1229
removeDelay(self, d)
1613
removeDelay( self, d )
1231
1615
d.status = 'wait'
1234
log('Delay','Finished list = ',exitcode)
1619
log( 'Delay','Finished list = ',exitcode )
1236
self.processes[pid] = nil
1622
self.processes[ pid ] = nil
1240
1626
-- Stacks a newDelay on the oldDelay,
1241
1627
-- the oldDelay blocks the new Delay.
1243
1629
-- A delay can block 'n' other delays,
1244
1630
-- but is blocked at most by one, the latest delay.
1246
local function stack(oldDelay, newDelay)
1632
local function stack( oldDelay, newDelay )
1247
1634
newDelay.status = 'block'
1248
1636
if not oldDelay.blocks then
1249
oldDelay.blocks = {}
1637
oldDelay.blocks = { }
1251
table.insert(oldDelay.blocks, newDelay)
1640
table.insert( oldDelay.blocks, newDelay )
1255
1644
-- Puts an action on the delay stack.
1257
local function delay(self, etype, time, path, path2)
1258
log('Function', 'delay(',self.config.name,', ',etype,', ',path,', ',path2,')')
1646
local function delay( self, etype, time, path, path2 )
1651
self.config.name, ', ',
1261
local function recurse()
1262
if etype == 'Create' and path:byte(-1) == 47 then
1263
local entries = lsyncd.readdir(self.source .. path)
1659
local function recurse( )
1661
if etype == 'Create' and path:byte( -1 ) == 47 then
1662
local entries = lsyncd.readdir( self.source .. path )
1264
1664
if entries then
1265
1666
for dirname, isdir in pairs(entries) do
1266
1668
local pd = path .. dirname
1267
if isdir then pd = pd..'/' end
1268
log('Delay', 'Create creates Create on ',pd)
1269
delay(self, 'Create', time, pd, nil)
1676
'Create creates Create on ',
1679
delay( self, 'Create', time, pd, nil )
1275
1689
-- exclusion tests
1276
1690
if not path2 then
1277
1691
-- simple test for single path events
1278
1692
if self.excludes:test(path) then
1279
log('Exclude', 'excluded ',etype,' on "',path,'"')
1283
1704
-- for double paths (move) it might result into a split
1284
local ex1 = self.excludes:test(path)
1285
local ex2 = self.excludes:test(path2)
1705
local ex1 = self.excludes:test( path )
1706
local ex2 = self.excludes:test( path2 )
1286
1708
if ex1 and ex2 then
1287
log('Exclude', 'excluded "',etype,' on "',path,'" -> "',path2,'"')
1289
1723
elseif not ex1 and ex2 then
1290
1725
-- splits the move if only partly excluded
1291
log('Exclude', 'excluded destination transformed ',etype,' to Delete ',path)
1292
delay(self, 'Delete', time, path, nil)
1728
'excluded destination transformed ',
1294
1744
elseif ex1 and not ex2 then
1295
1745
-- splits the move if only partly excluded
1296
log('Exclude', 'excluded origin transformed ',etype,' to Create.',path2)
1297
delay(self, 'Create', time, path2, nil)
1748
'excluded origin transformed ',
1302
1766
if etype == 'Move' and not self.config.onMove then
1303
1768
-- if there is no move action defined,
1304
1769
-- split a move as delete/create
1305
1770
-- layer 1 scripts which want moves events have to
1306
1771
-- set onMove simply to 'true'
1307
log('Delay', 'splitting Move into Delete & Create')
1308
delay(self, 'Delete', time, path, nil)
1309
delay(self, 'Create', time, path2, nil)
1772
log( 'Delay', 'splitting Move into Delete & Create' )
1773
delay( self, 'Delete', time, path, nil )
1774
delay( self, 'Create', time, path2, nil )
1313
1779
-- creates the new action
1559
2085
rmExclude = rmExclude,
1560
2086
statusReport = statusReport,
1562
s.inlet = InletFactory.newInlet(s)
2089
s.inlet = InletFactory.newInlet( s )
1564
2091
-- provides a default name if needed
1565
2092
if not config.name then
1566
config.name = 'Sync'..nextDefaultName
2093
config.name = 'Sync' .. nextDefaultName
1568
-- increments default nevertheless to cause less confusion
1569
-- so name will be the n-th call to sync{}
2096
-- increments defaults if a config name was given or not
2097
-- so Sync{n} will be the n-th call to sync{}
1570
2098
nextDefaultName = nextDefaultName + 1
1572
2100
-- loads exclusions
1573
2101
if config.exclude then
1574
local te = type(config.exclude)
2103
local te = type( config.exclude )
1575
2105
if te == 'table' then
1576
s.excludes:addList(config.exclude)
2106
s.excludes:addList( config.exclude )
1577
2107
elseif te == 'string' then
1578
s.excludes:add(config.exclude)
2108
s.excludes:add( config.exclude )
1580
error('type for exclude must be table or string', 2)
2110
error( 'type for exclude must be table or string', 2 )
2115
config.delay ~= nil and
2117
type(config.delay) ~= 'number' or
2121
error( 'delay must be a number and >= 0', 2 )
1583
2124
if config.excludeFrom then
1584
s.excludes:loadFile(config.excludeFrom)
2125
s.excludes:loadFile( config.excludeFrom )
2134
return { new = new }
1598
2140
-- Syncs - a singleton
2142
-- Syncs maintains all configured syncs.
1601
local Syncs = (function()
2144
local Syncs = ( function( )
1603
2147
-- the list of all syncs
1605
local list = Array.new()
2149
local syncsList = Array.new( )
1608
2152
-- The round robin pointer. In case of global limited maxProcesses
1609
2153
-- gives every sync equal chances to spawn the next process.
1611
2155
local round = 1
1614
-- The cycle() sheduler goes into the next round of roundrobin.
1615
local function nextRound()
2158
-- The cycle( ) sheduler goes into the next round of roundrobin.
2160
local function nextRound( )
1616
2162
round = round + 1;
1617
if round > #list then
2164
if round > #syncsList then
1624
2172
-- Returns the round
1625
local function getRound()
2174
local function getRound( )
1630
2179
-- Returns sync at listpos i
1631
local function get(i)
1636
-- Inheritly copies all non integer keys from
1637
-- copy source (cs) to copy destination (cd).
1639
-- all entries with integer keys are treated as new sources to copy
1641
local function inherit(cd, cs)
1642
-- first copies from source all
1643
-- non-defined non-integer keyed values
1644
for k, v in pairs(cs) do
1645
if type(k) ~= 'number' and cd[k] == nil then
1649
-- first recurses into all integer keyed tables
1650
for i, v in ipairs(cs) do
1651
if type(v) == 'table' then
2181
local function get( i )
2182
return syncsList[ i ];
2186
-- Helper function for inherit
2192
-- Recurvely inherits a source table to a destionation table
2193
-- copying all keys from source.
2195
-- table copy source ( cs )
2196
-- table copy destination ( cd )
2198
-- All entries with integer keys are inherited as additional
2199
-- sources for non-verbatim tables
2201
local function inherit( cd, cs )
2204
-- First copies all entries with non-integer keys
2205
-- tables are merged, already present keys are not
2208
-- For verbatim tables integer keys are treated like
2211
for k, v in pairs( cs ) do
2214
type( k ) ~= 'number' or
2215
cs._verbatim == true
2219
type( cs._merge ) ~= 'table' or
2220
cs._merge[ k ] == true
2223
inheritKV( cd, k, v )
2228
-- recursevely inherits all integer keyed tables
2229
-- ( for non-verbatim tables )
2231
if cs._verbatim ~= true then
2234
for k, v in ipairs( cs ) do
2236
if type( v ) == 'table' then
2247
-- Helper to inherit. Inherits one key.
2249
inheritKV = function( cd, k, v )
2251
-- don't merge inheritance controls
2252
if k == '_merge' or k == '_verbatim' then
2256
local dtype = type( cd [ k ] )
2258
if type( v ) == 'table' then
2260
if dtype == 'nil' then
2262
inherit( cd[ k ], v )
2264
dtype == 'table' and
2267
inherit( cd[ k ], v )
2270
elseif dtype == 'nil' then
1658
2278
-- Adds a new sync (directory-tree to observe).
1660
local function add(config)
1661
-- Creates a new config table and inherit all keys/values
2280
local function add( config )
2282
-- workaround for backwards compatibility
2283
-- FIXME: remove when dropping that
2284
if settings ~= settingsSafe then
2287
'settings = { ... } is deprecated.\n'..
2288
' please use settings{ ... } (without the equal sign)'
2291
for k, v in pairs( settings ) do
2295
settings = settingsSafe
2298
-- Creates a new config table which inherits all keys/values
1662
2299
-- from integer keyed tables
1663
2300
local uconfig = config
1665
inherit(config, uconfig)
1667
-- Lets settings or commandline override delay values.
1669
config.delay = settings.delay or config.delay
1672
-- at very first lets the userscript 'prepare' function
1673
-- fill out more values.
1674
if type(config.prepare) == 'function' then
1675
-- explicitly gives a writeable copy of config.
1676
config.prepare(config)
1679
if not config['source'] then
1680
local info = debug.getinfo(3, 'Sl')
1681
log('Error', info.short_src,':',info.currentline,': source missing from sync.')
1682
terminate(-1) -- ERRNO
2304
inherit( config, uconfig )
2307
-- last and least defaults are inherited
2309
inherit( config, default )
2311
local inheritSettings = {
2317
-- Lets settings override these values.
2318
for _, v in ipairs( inheritSettings ) do
2319
if uSettings[ v ] then
2320
config[ v ] = uSettings[ v ]
2324
-- Lets commandline override these values.
2325
for _, v in ipairs( inheritSettings ) do
2326
if clSettings[ v ] then
2327
config[ v ] = clSettings[ v ]
2332
-- lets the userscript 'prepare' function
2333
-- check and complete the config
2335
if type( config.prepare ) == 'function' then
2337
-- prepare is given a writeable copy of config
2338
config.prepare( config, 4 )
2342
if not config[ 'source' ] then
2343
local info = debug.getinfo( 3, 'Sl' )
2347
info.currentline,': source missing from sync.'
1685
2353
-- absolute path of source
1686
local realsrc = lsyncd.realdir(config.source)
2355
local realsrc = lsyncd.realdir( config.source )
1687
2357
if not realsrc then
1688
log('Error', 'Cannot access source directory: ',config.source)
1689
terminate(-1) -- ERRNO
2360
'Cannot access source directory: ',
1691
2366
config._source = config.source
1692
2367
config.source = realsrc
1694
if not config.action and not config.onAttrib and
1695
not config.onCreate and not config.onModify and
1696
not config.onDelete and not config.onMove
2370
not config.action and
2371
not config.onAttrib and
2372
not config.onCreate and
2373
not config.onModify and
2374
not config.onDelete and
1698
local info = debug.getinfo(3, 'Sl')
1699
log('Error', info.short_src, ':', info.currentline,
1700
': no actions specified, use e.g. "config = default.rsync".')
1701
terminate(-1) -- ERRNO
2377
local info = debug.getinfo( 3, 'Sl' )
2380
info.short_src, ':',
2382
': no actions specified.'
1704
-- loads a default value for an option if not existent
1705
if not settings then
1708
local defaultValues = {
1715
for _, dn in pairs(defaultValues) do
1716
if config[dn] == nil then
1717
config[dn] = settings[dn] or default[dn]
1721
2388
-- the monitor to use
1722
2389
config.monitor =
1723
settings.monitor or config.monitor or Monitors.default()
1724
if config.monitor ~= 'inotify' and config.monitor ~= 'fsevents' then
1725
local info = debug.getinfo(3, 'Sl')
1726
log('Error',info.short_src,':',info.currentline,
1727
': event monitor "',config.monitor,'" unknown.')
1728
terminate(-1) -- ERRNO
2390
uSettings.monitor or
2395
config.monitor ~= 'inotify' and
2396
config.monitor ~= 'fsevents'
2398
local info = debug.getinfo( 3, 'Sl' )
2402
info.short_src, ':',
2404
': event monitor "',
1731
2412
--- creates the new sync
1732
local s = Sync.new(config)
1733
table.insert(list, s)
2413
local s = Sync.new( config )
2415
table.insert( syncsList, s )
1738
2421
-- Allows a for-loop to walk through all syncs.
1740
local function iwalk()
2423
local function iwalk( )
2424
return ipairs( syncsList )
1745
2428
-- Returns the number of syncs.
1747
local size = function()
2430
local size = function( )
1752
2435
-- Tests if any sync is interested in a path.
1754
local function concerns(path)
1755
for _, s in ipairs(list) do
1756
if s:concerns(path) then
2437
local function concerns( path )
2438
for _, s in ipairs( syncsList ) do
2439
if s:concerns( path ) then
1847
2550
-- registers the watch
1848
local inotifyMode = (settings and settings.inotifyMode) or '';
1849
local wd = lsyncd.inotify.addwatch(path, inotifyMode);
2551
local inotifyMode = ( uSettings and uSettings.inotifyMode ) or '';
2553
local wd = lsyncd.inotify.addwatch( path, inotifyMode) ;
1851
log('Inotify','Unable to add watch "',path,'"')
2556
log( 'Inotify','Unable to add watch "', path, '"' )
1856
2561
-- If this watch descriptor is registered already
1857
2562
-- the kernel reuses it since old dir is gone.
1858
local op = wdpaths[wd]
2563
local op = wdpaths[ wd ]
1859
2564
if op and op ~= path then
2569
pathwds[ path ] = wd
2570
wdpaths[ wd ] = path
1866
2572
-- registers and adds watches for all subdirectories
1867
local entries = lsyncd.readdir(path)
1868
if not entries then return end
1869
for dirname, isdir in pairs(entries) do
1870
if isdir then addWatch(path .. dirname .. '/') end
2573
local entries = lsyncd.readdir( path )
2579
for dirname, isdir in pairs( entries ) do
2581
addWatch( path .. dirname .. '/' )
1875
2587
-- Adds a Sync to receive events.
1877
2589
-- sync: Object to receive events
1878
2590
-- rootdir: root dir to watch
1880
local function addSync(sync, rootdir)
1881
if syncRoots[sync] then error('duplicate sync in Inotify.addSync()') end
1882
syncRoots[sync] = rootdir
2592
local function addSync( sync, rootdir )
2593
if syncRoots[ sync ] then
2594
error( 'duplicate sync in Inotify.addSync()' )
2596
syncRoots[ sync ] = rootdir
1887
2601
-- Called when an event has occured.
1889
-- etype: 'Attrib', 'Modify', 'Create', 'Delete', 'Move'
1890
-- wd: watch descriptor, matches lsyncd.inotifyadd()
1891
-- isdir: true if filename is a directory
1892
-- time: time of event
1893
-- filename: string filename without path
1894
-- wd2: watch descriptor for target if it's a Move
1895
-- filename2: string filename without path of Move target
1897
local function event(etype, wd, isdir, time, filename, wd2, filename2)
2603
local function event(
2604
etype, -- 'Attrib', 'Modify', 'Create', 'Delete', 'Move'
2605
wd, -- watch descriptor, matches lsyncd.inotifyadd()
2606
isdir, -- true if filename is a directory
2607
time, -- time of event
2608
filename, -- string filename without path
2609
wd2, -- watch descriptor for target if it's a Move
2610
filename2 -- string filename without path of Move target
1899
filename = filename..'/'
1900
if filename2 then filename2 = filename2..'/' end
2613
filename = filename .. '/'
2616
filename2 = filename2 .. '/'
1903
2620
if filename2 then
1904
log('Inotify','got event ',etype,' ',filename,'(',wd,') to ',filename2,'(',wd2,')')
1906
log('Inotify','got event ',etype,' ',filename,'(',wd,')')
1909
2642
-- looks up the watch descriptor id
1910
local path = wdpaths[wd]
1911
if path then path = path..filename end
1913
local path2 = wd2 and wdpaths[wd2]
1914
if path2 and filename2 then path2 = path2..filename2 end
2643
local path = wdpaths[ wd ]
2645
path = path..filename
2648
local path2 = wd2 and wdpaths[ wd2 ]
2650
if path2 and filename2 then
2651
path2 = path2..filename2
1916
2654
if not path and path2 and etype == 'Move' then
1917
log('Inotify', 'Move from deleted directory ',path2,' becomes Create.')
2657
'Move from deleted directory ',
1920
2663
etype = 'Create'
1923
2666
if not path then
1924
2667
-- this is normal in case of deleted subdirs
1925
log('Inotify', 'event belongs to unknown watch descriptor.')
2670
'event belongs to unknown watch descriptor.'
1929
for sync, root in pairs(syncRoots) do repeat
1930
local relative = splitPath(path, root)
2675
for sync, root in pairs( syncRoots ) do repeat
2677
local relative = splitPath( path, root )
1931
2678
local relative2 = nil
1933
relative2 = splitPath(path2, root)
2681
relative2 = splitPath( path2, root )
1935
2684
if not relative and not relative2 then
1936
2685
-- sync is not interested in this dir
1937
2686
break -- continue
1940
2689
-- makes a copy of etype to possibly change it
1941
2690
local etyped = etype
1942
2692
if etyped == 'Move' then
1943
2693
if not relative2 then
1944
log('Normal', 'Transformed Move to Delete for ', sync.config.name)
2696
'Transformed Move to Delete for ',
1945
2699
etyped = 'Delete'
1946
2700
elseif not relative then
1947
2701
relative = relative2
1948
2702
relative2 = nil
1949
log('Normal', 'Transformed Move to Create for ', sync.config.name)
2705
'Transformed Move to Create for ',
1950
2708
etyped = 'Create'
1955
2713
if etyped == 'Create' then
1957
2715
elseif etyped == 'Delete' then
1958
removeWatch(path, true)
2716
removeWatch( path, true )
1959
2717
elseif etyped == 'Move' then
1960
removeWatch(path, false)
2718
removeWatch( path, false )
1965
sync:delay(etyped, time, relative, relative2)
2723
sync:delay( etyped, time, relative, relative2 )
1970
-- Writes a status report about inotifies to a filedescriptor
1972
local function statusReport(f)
1973
f:write('Inotify watching ',wdpaths:size(),' directories\n')
1974
for wd, path in wdpaths:walk() do
1975
f:write(' ',wd,': ',path,'\n')
2729
-- Writes a status report about inotify to a file descriptor
2731
local function statusReport( f )
2733
f:write( 'Inotify watching ', wdpaths:size(), ' directories\n' )
2735
for wd, path in wdpaths:walk( ) do
2736
f:write( ' ', wd, ': ', path, '\n' )
2742
-- Public interface.
1981
2745
addSync = addSync,
1983
2747
statusReport = statusReport,
1990
local Fsevents = (function()
1993
-- A list indexed by sync's containing the root path this
1994
-- sync is interested in.
1996
local syncRoots = {}
1999
-- adds a Sync to receive events
2753
-- Interface to OSX /dev/fsevents
2755
-- This watches all the filesystems at once,
2756
-- but needs root access.
2758
-- All fsevents specific implementation are enclosed here.
2760
local Fsevents = ( function( )
2764
-- A list indexed by syncs yielding
2765
-- the root path the sync is interested in.
2767
local syncRoots = { }
2771
-- Adds a Sync to receive events.
2001
2773
-- @param sync Object to receive events
2002
2774
-- @param dir dir to watch
2004
local function addSync(sync, dir)
2005
if syncRoots[sync] then error('duplicate sync in Fanotify.addSync()') end
2006
syncRoots[sync] = dir
2776
local function addSync( sync, dir )
2778
if syncRoots[ sync ] then
2779
error( 'duplicate sync in Fanotify.addSync()' )
2782
syncRoots[ sync ] = dir
2010
-- Called when any event has occured.
2012
-- etype: 'Attrib', 'Modify', 'Create', 'Delete', 'Move')
2013
-- isdir: true if filename is a directory
2014
-- time: time of event
2015
-- path: path of file
2016
-- path2: path of target in case of 'Move'
2018
local function event(etype, isdir, time, path, path2)
2787
-- Called when an event has occured.
2789
local function event(
2790
etype, -- 'Attrib', 'Modify', 'Create', 'Delete', 'Move'
2791
isdir, -- true if filename is a directory
2792
time, -- time of event
2793
path, -- path of file
2794
path2 -- path of target in case of 'Move'
2021
if path2 then path2 = path2..'/' end
2800
path2 = path2 .. '/'
2024
log('Fsevents',etype,',',isdir,',',time,',',path,',',path2)
2026
2813
for _, sync in Syncs.iwalk() do repeat
2027
2815
local root = sync.source
2028
if not path:starts(root) then
2029
if not path2 or not path2:starts(root) then
2818
if not path:starts( root ) then
2819
if not path2 or not path2:starts( root ) then
2030
2820
break -- continue
2033
local relative = splitPath(path, root)
2824
local relative = splitPath( path, root )
2034
2826
local relative2
2036
relative2 = splitPath(path2, root)
2828
relative2 = splitPath( path2, root )
2039
2831
-- possibly change etype for this iteration only
2236
3064
ft = 'function(event, event2)\n'
2239
" log('Normal', 'Event ', event.etype, \n"..
2240
" ' spawns action \"".. str.."\"')\n"..
3068
" log('Normal', 'Event ', event.etype, \n" ..
3069
" ' spawns action \"".. str.."\"')\n" ..
2242
for _, v in ipairs(args) do
3072
for _, v in ipairs( args ) do
3073
ft = ft .. ',\n ' .. v
2250
3083
-- Translates a call using a shell to a lua function
2252
local function translateShell(str)
3085
local function translateShell( str )
2255
3089
local cmd = str
2257
3092
-- true if there is a second event
2258
3093
local haveEvent2 = false
2260
for _, v in ipairs(transVars) do
3095
for _, v in ipairs( transVars ) do
2261
3097
local occur = false
2262
cmd = string.gsub(cmd, v[1],
2265
return '"$'..argn..'"'
2267
lc = string.gsub(lc, v[1], ']]..'..v[2]..'..[[')
3104
return '"$' .. argn .. '"'
3111
']]..' .. v[2] .. '..[['
2269
3115
argn = argn + 1
2270
table.insert(args, v[2])
3116
table.insert( args, v[ 2 ] )
2272
3119
haveEvent2 = true
2277
3126
if not haveEvent2 then
2278
3127
ft = 'function(event)\n'
2280
3129
ft = 'function(event, event2)\n'
2282
3132
-- TODO do array joining instead
2284
3134
" log('Normal', 'Event ',event.etype,\n"..
2285
3135
" [[ spawns shell \""..lc.."\"]])\n"..
2286
3136
" spawnShell(event, [["..cmd.."]]"
2287
for _, v in ipairs(args) do
3138
for _, v in ipairs( args ) do
2288
3139
ft = ft..',\n '..v
2295
-- writes a lua function for a layer 3 user script.
2296
local function translate(str)
3149
-- Writes a lua function for a layer 3 user script.
3151
local function translate( str )
2298
str = string.match(str, '^%s*(.-)%s*$')
3153
str = string.match( str, '^%s*(.-)%s*$' )
2301
if string.byte(str, 1, 1) == 47 then
3156
if string.byte( str, 1, 1 ) == 47 then
2302
3157
-- starts with /
2303
ft = translateBinary(str)
2304
elseif string.byte(str, 1, 1) == 94 then
3158
ft = translateBinary( str )
3159
elseif string.byte( str, 1, 1 ) == 94 then
2305
3160
-- starts with ^
2306
ft = translateShell(str:sub(2, -1))
3161
ft = translateShell( str:sub( 2, -1 ) )
2308
ft = translateShell(str)
3163
ft = translateShell( str )
2310
log('FWrite','translated "',str,'" to \n',ft)
2317
return {translate = translate}
3179
-- Public interface.
3181
return { translate = translate }
2322
3189
-- Writes a status report file at most every [statusintervall] seconds.
2325
local StatusFile = (function()
3191
local StatusFile = ( function( )
2328
3195
-- Timestamp when the status file has been written.
2329
3197
local lastWritten = false
2332
-- Timestamp when a status file should be written
3201
-- Timestamp when a status file should be written.
2333
3203
local alarm = false
2336
-- Returns when the status file should be written
3207
-- Returns the alarm when the status file should be written-
2338
3209
local function getAlarm()
2343
3215
-- Called to check if to write a status file.
2345
local function write(timestamp)
2346
log('Function', 'write(', timestamp, ')')
2348
-- some logic to not write too often
2349
if settings.statusInterval > 0 then
3217
local function write( timestamp )
3227
-- takes care not write too often
3229
if uSettings.statusInterval > 0 then
2351
3232
if alarm and timestamp < alarm then
2352
log('Statusfile', 'waiting(',timestamp,' < ',alarm,')')
2355
3244
-- determines when a next write will be possible
2356
3245
if not alarm then
2357
3247
local nextWrite =
2358
lastWritten and timestamp + settings.statusInterval
3248
lastWritten and timestamp +
3249
uSettings.statusInterval
2359
3251
if nextWrite and timestamp < nextWrite then
2360
log('Statusfile', 'setting alarm: ', nextWrite)
2361
3257
alarm = nextWrite
2365
3263
lastWritten = timestamp
2369
log('Statusfile', 'writing now')
2370
local f, err = io.open(settings.statusFile, 'w')
3267
log( 'Statusfile', 'writing now' )
3269
local f, err = io.open( uSettings.statusFile, 'w' )
2372
log('Error', 'Cannot open status file "'..settings.statusFile.. '" :'..err)
3274
'Cannot open status file "' ..
3275
uSettings.statusFile ..
2375
f:write('Lsyncd status report at ',os.date(),'\n\n')
2376
for i, s in Syncs.iwalk() do
3282
f:write( 'Lsyncd status report at ', os.date( ), '\n\n' )
3284
for i, s in Syncs.iwalk( ) do
2381
Inotify.statusReport(f)
3289
Inotify.statusReport( f )
2386
return {write = write, getAlarm = getAlarm}
2391
local UserAlarms = (function()
3306
-- Lets userscripts make their own alarms.
3308
local UserAlarms = ( function( )
2395
3314
-- Calls the user function at timestamp.
2397
local function alarm(timestamp, func, extra)
3316
local function alarm( timestamp, func, extra )
2399
for k, v in ipairs(alarms) do
3319
for k, v in ipairs( alarms ) do
2400
3320
if timestamp < v.timestamp then
2405
local a = {timestamp = timestamp,
3327
timestamp = timestamp,
2409
table.insert(alarms, idx, a)
3333
table.insert( alarms, idx, a )
2411
table.insert(alarms, a)
3335
table.insert( alarms, a )
2416
-- Retrieves the nearest alarm.
2418
local function getAlarm()
3342
-- Retrieves the soonest alarm.
3344
local function getAlarm( )
2419
3346
if #alarms == 0 then
2422
3349
return alarms[1].timestamp
2427
3356
-- Calls user alarms.
2429
local function invoke(timestamp)
2430
while #alarms > 0 and alarms[1].timestamp <= timestamp do
2431
alarms[1].func(alarms[1].timestamp, alarms[1].extra)
2432
table.remove(alarms, 1)
3358
local function invoke( timestamp )
3361
alarms[ 1 ].timestamp <= timestamp
3363
alarms[ 1 ].func( alarms[ 1 ].timestamp, alarms[ 1 ].extra )
3364
table.remove( alarms, 1 )
2437
return { alarm = alarm,
2438
getAlarm = getAlarm,
2442
--============================================================================
2443
--============================================================================
3374
getAlarm = getAlarm,
3381
--============================================================================
3382
-- Lsyncd runner's plugs. These functions are called from core.
3383
--============================================================================
3386
-- Current status of Lsyncd.
2447
3388
-- 'init' ... on (re)init
2448
3389
-- 'run' ... normal operation
2584
3575
-- -monitor NAME Uses operating systems event montior NAME
2585
3576
-- (inotify/fanotify/fsevents)
2587
os.exit(-1) -- ERRNO
2593
local clSettings = {}
2596
3583
-- Called from core to parse the command line arguments
2598
function runner.configure(args, monitors)
2599
Monitors.initialize(monitors)
2601
-- a list of all valid --options
2602
-- first paramter is number of options
2603
-- if < 0 the function checks existance
2604
-- second paramter is function to call when in args
3585
-- returns a string as user script to load.
3586
-- or simply 'true' if running with rsync bevaiour
3588
-- terminates on invalid arguments.
3590
function runner.configure( args, monitors )
3592
Monitors.initialize( monitors )
3595
-- a list of all valid options
3597
-- first paramter is the number of parameters an option takes
3598
-- if < 0 the called function has to check the presence of
3599
-- optional arguments.
3601
-- second paramter is the function to call
2606
3603
local options = {
2607
3605
-- log is handled by core already.
2610
clSettings.delay = secs
2614
clSettings.insist = true
2620
clSettings.logfile = file
3611
clSettings.delay = secs + 0
3619
clSettings.insist = true
3633
clSettings.logfile = file
2623
{-1, function(monitor)
2625
io.stdout:write('This Lsyncd supports these monitors:\n')
2626
for _, v in ipairs(Monitors.list) do
2627
io.stdout:write(' ',v,'\n')
3642
io.stdout:write( 'This Lsyncd supports these monitors:\n' )
3643
for _, v in ipairs(Monitors.list) do
3644
io.stdout:write(' ',v,'\n')
3647
io.stdout:write('\n')
3649
lsyncd.terminate(-1)
3651
clSettings.monitor = monitor
2629
io.stdout:write('\n');
2630
lsyncd.terminate(-1); -- ERRNO
2632
clSettings.monitor=monitor
2637
clSettings.nodaemon = true
2641
clSettings.pidfile=file
3660
clSettings.nodaemon = true
3668
clSettings.pidfile=file
2644
{2, function(src, trg)
2645
clSettings.syncs = clSettings.syncs or {}
2646
table.insert(clSettings.syncs, {'rsync', src, trg})
3675
function( src, trg )
3676
clSettings.syncs = clSettings.syncs or { }
3679
{ 'rsync', src, trg }
2649
{3, function(src, host, tdir)
2650
clSettings.syncs = clSettings.syncs or {}
2651
table.insert(clSettings.syncs, {'rsyncssh', src, host, tdir})
3687
function( src, host, tdir )
3688
clSettings.syncs = clSettings.syncs or { }
3691
{ 'rsyncssh', src, host, tdir }
2654
{2, function(src, trg)
2655
clSettings.syncs = clSettings.syncs or {}
2656
table.insert(clSettings.syncs, {'direct', src, trg})
2660
io.stdout:write('Version: ',lsyncd_version,'\n')
3699
function( src, trg )
3700
clSettings.syncs = clSettings.syncs or { }
3703
{ 'direct', src, trg }
3712
io.stdout:write( 'Version: ', lsyncd_version, '\n' )
2664
-- nonopts is filled with all args that were no part dash options
3718
-- non-opts is filled with all args that were no part dash options
2667
3723
while i <= #args do
2669
if a:sub(1, 1) ~= '-' then
2670
table.insert(nonopts, args[i])
3727
if a:sub( 1, 1 ) ~= '-' then
3728
table.insert( nonopts, args[ i ] )
2672
if a:sub(1, 2) == '--' then
3730
if a:sub( 1, 2 ) == '--' then
2677
local o = options[a]
3736
local o = options[ a ]
2679
log('Error','unknown option command line option ', args[i])
2680
os.exit(-1) -- ERRNO
3741
'unknown option command line option ',
2682
if o[1] >= 0 and i + o[1] > #args then
2683
log('Error',a,' needs ',o[1],' arguments')
2684
os.exit(-1) -- ERRNO
3747
if o[ 1 ] >= 0 and i + o[ 1 ] > #args then
3748
log( 'Error', a ,' needs ', o[ 1 ],' arguments' )
2685
3750
elseif o[1] < 0 then
2691
elseif o[1] == 1 then
2693
elseif o[1] == 2 then
2694
o[2](args[i + 1], args[i + 2])
2695
elseif o[1] == 3 then
2696
o[2](args[i + 1], args[i + 2], args[i + 3])
3757
elseif o[ 1 ] == 1 then
3758
o[ 2 ]( args[i + 1] )
3759
elseif o[ 1 ] == 2 then
3760
o[ 2 ]( args[i + 1], args[i + 2] )
3761
elseif o[ 1 ] == 3 then
3762
o[ 2 ]( args[i + 1], args[i + 2], args[i + 3] )
2704
3771
if clSettings.syncs then
2705
3773
if #nonopts ~= 0 then
2706
log('Error', 'There cannot be command line default syncs with a config file.')
2707
os.exit(-1) -- ERRNO
3776
'There cannot be command line syncs and config file together.'
2710
3783
if #nonopts == 0 then
2711
runner.help(args[0])
3785
runner.help( args[ 0 ] )
2712
3787
elseif #nonopts == 1 then
2715
log('Error', 'There can only be one config file in command line.')
2716
os.exit(-1) -- ERRNO
3793
-- TODO make this possible
3796
'There can only be one config file in command line.'
2723
3807
-- Called from core on init or restart after user configuration.
3810
-- true when Lsyncd startups the first time,
3811
-- false on resets, due to HUP signal or monitor queue overflow.
2726
function runner.initialize(firstTime)
2727
-- creates settings if user didnt
2728
settings = settings or {}
3813
function runner.initialize( firstTime )
3815
if settings ~= settingsSafe then
3818
'settings = { ... } is deprecated.\n'..
3819
' please use settings{ ... } (without the equal sign)'
3822
for k, v in pairs( settings ) do
3828
lastReportedWaiting = false
2730
3831
-- From this point on, no globals may be created anymore
2733
-- copies simple settings with numeric keys to 'key=true' settings.
2734
for k, v in ipairs(settings) do
2736
log('Error', 'Double setting "'..v..'"')
2737
os.exit(-1) -- ERRNO
3836
-- copies simple settings with numeric keys to 'key = true' settings.
3838
-- FIXME this can be removed when
3839
-- Lsyncd 2.0.x backwards compatibility is dropped
3841
for k, v in ipairs( uSettings ) do
3843
if uSettings[ v ] then
3846
'Double setting "' .. v.. '"'
3851
uSettings[ v ]= true
2742
3856
-- all command line settings overwrite config file settings
2743
for k, v in pairs(clSettings) do
3858
for k, v in pairs( clSettings ) do
2744
3859
if k ~= 'syncs' then
2749
-- implicitly force insist to be true on Lsyncd resets.
3865
-- implicitly forces 'insist' on Lsyncd resets.
2750
3867
if not firstTime then
2751
settings.insist = true
3868
uSettings.insist = true
2754
3872
-- adds syncs specified by command line.
2755
3874
if clSettings.syncs then
2756
for _, s in ipairs(clSettings.syncs) do
2757
if s[1] == 'rsync' then
2758
sync{default.rsync, source=s[2], target=s[3]}
2759
elseif s[1] == 'rsyncssh' then
2760
sync{default.rsyncssh, source=s[2], host=s[3], targetdir=s[4]}
2761
elseif s[1] == 'direct' then
2762
sync{default.direct, source=s[2], target=s[3]}
3876
for _, s in ipairs( clSettings.syncs ) do
3878
if s[ 1 ] == 'rsync' then
3886
elseif s[ 1 ] == 'rsyncssh' then
3895
elseif s[ 1 ] == 'direct' then
2767
if settings.nodaemon then
2768
lsyncd.configure('nodaemon')
2770
if settings.logfile then
2771
lsyncd.configure('logfile', settings.logfile)
2773
if settings.logident then
2774
lsyncd.configure('logident', settings.logident)
2776
if settings.logfacility then
2777
lsyncd.configure('logfacility', settings.logfacility)
2779
if settings.pidfile then
2780
lsyncd.configure('pidfile', settings.pidfile)
2784
-- transfers some defaults to settings
2785
if settings.statusInterval == nil then
2786
settings.statusInterval = default.statusInterval
3908
if uSettings.nodaemon then
3909
lsyncd.configure( 'nodaemon' )
3912
if uSettings.logfile then
3913
lsyncd.configure( 'logfile', uSettings.logfile )
3916
if uSettings.logident then
3917
lsyncd.configure( 'logident', uSettings.logident )
3920
if uSettings.logfacility then
3921
lsyncd.configure( 'logfacility', uSettings.logfacility )
3924
if uSettings.pidfile then
3925
lsyncd.configure( 'pidfile', uSettings.pidfile )
3929
-- Transfers some defaults to uSettings
3931
if uSettings.statusInterval == nil then
3932
uSettings.statusInterval = default.statusInterval
2789
3935
-- makes sure the user gave Lsyncd anything to do
2790
3936
if Syncs.size() == 0 then
2791
log('Error', 'Nothing to watch!')
2792
os.exit(-1) -- ERRNO
2795
3946
-- from now on use logging as configured instead of stdout/err.
2796
3947
lsyncdStatus = 'run';
2797
lsyncd.configure('running');
3949
lsyncd.configure( 'running' );
2799
3951
local ufuncs = {
2800
'onAttrib', 'onCreate', 'onDelete',
2801
'onModify', 'onMove', 'onStartup',
2804
3960
-- translates layer 3 scripts
2805
3961
for _, s in Syncs.iwalk() do
2806
3963
-- checks if any user functions is a layer 3 string.
2807
3964
local config = s.config
2808
3966
for _, fn in ipairs(ufuncs) do
2809
3968
if type(config[fn]) == 'string' then
2810
3970
local ft = functionWriter.translate(config[fn])
2811
3971
config[fn] = assert(loadstring('return '..ft))()
2816
3978
-- runs through the Syncs created by users
2817
for _, s in Syncs.iwalk() do
3979
for _, s in Syncs.iwalk( ) do
2818
3981
if s.config.monitor == 'inotify' then
2819
Inotify.addSync(s, s.source)
3983
Inotify.addSync( s, s.source )
2820
3985
elseif s.config.monitor == 'fsevents' then
2821
Fsevents.addSync(s, s.source)
3987
Fsevents.addSync( s, s.source )
2823
error('sync '..s.config.name..' has no known event monitor interface.')
3994
' has no known event monitor interface.'
2825
-- if the sync has an init function, stacks an init delay
2826
-- that will cause the init function to be called.
3999
-- if the sync has an init function, the init delay
4000
-- is stacked which causes the init function to be called.
2827
4001
if s.config.init then
4011
-- Called by core to query the soonest alarm.
2835
4013
-- @return false ... no alarm, core can in untimed sleep, or
2836
4014
-- true ... immediate action
2837
4015
-- times ... the alarm time (only read if number is 1)
2839
function runner.getAlarm()
4017
function runner.getAlarm( )
2840
4019
if lsyncdStatus ~= 'run' then
2843
4023
local alarm = false
2845
-- checks if current nearest alarm or a is earlier
2847
local function checkAlarm(a)
4026
-- Checks if 'a' is sooner than the 'alarm' up-value.
4028
local function checkAlarm( a )
2848
4030
if a == nil then
2849
4031
error('got nil alarm')
2851
4034
if alarm == true or not a then
2852
-- already immediate or no new alarm
4035
-- 'alarm' is already immediate or
4036
-- a not a new alarm
2855
-- returns the ealier time
4040
-- sets 'alarm' to a if a is sooner
2856
4041
if not alarm or a < alarm then
2861
-- checks all syncs for their earliest alarm
4048
-- checks all syncs for their earliest alarm,
2862
4049
-- but only if the global process limit is not yet reached.
2863
if not settings.maxProcesses or processCount < settings.maxProcesses then
2864
for _, s in Syncs.iwalk() do
2865
checkAlarm(s:getAlarm())
4052
not uSettings.maxProcesses or
4053
processCount < uSettings.maxProcesses
4055
for _, s in Syncs.iwalk( ) do
4056
checkAlarm( s:getAlarm ( ))
2868
log('Alarm', 'at global process limit.')
4061
'at global process limit.'
2871
4065
-- checks if a statusfile write has been delayed
2872
checkAlarm(StatusFile.getAlarm())
4066
checkAlarm( StatusFile.getAlarm( ) )
2873
4068
-- checks for an userAlarm
2874
checkAlarm(UserAlarms.getAlarm())
2876
log('Alarm', 'runner.getAlarm returns: ',alarm)
4069
checkAlarm( UserAlarms.getAlarm( ) )
4073
'runner.getAlarm returns: ',
4083
-- Called when an file system monitor events arrive
2883
4085
runner.inotifyEvent = Inotify.event
2884
4086
runner.fsEventsEvent = Fsevents.event
2887
4089
-- Collector for every child process that finished in startup phase
2891
function runner.collector(pid, exitcode)
4091
function runner.collector(
4092
pid, -- pid of the child process
4093
exitcode -- exitcode of the child process
2892
4095
if exitcode ~= 0 then
2893
4096
log('Error', 'Startup process',pid,' failed')
2894
terminate(-1) -- ERRNO
2900
4104
-- Called by core when an overflow happened.
2902
function runner.overflow()
2903
log('Normal', '--- OVERFLOW in event queue ---')
4106
function runner.overflow( )
4110
'--- OVERFLOW in event queue ---'
2904
4113
lsyncdStatus = 'fade'
2908
4118
-- Called by core on a hup signal.
2910
function runner.hup()
2911
log('Normal', '--- HUP signal, resetting ---')
4120
function runner.hup( )
4124
'--- HUP signal, resetting ---'
2912
4127
lsyncdStatus = 'fade'
2916
4132
-- Called by core on a term signal.
2918
function runner.term()
2919
log('Normal', '--- TERM signal, fading ---')
4134
function runner.term( )
4138
'--- TERM signal, fading ---'
2920
4141
lsyncdStatus = 'fade'
2923
4145
--============================================================================
4146
-- Lsyncd runner's user interface
2924
4147
--============================================================================
2927
4150
-- Main utility to create new observations.
4152
-- Returns an Inlet to that sync.
4154
function sync( opts )
2930
4156
if lsyncdStatus ~= 'init' then
2931
error('Sync can only be created during initialization.', 2)
4158
'Sync can only be created during initialization.',
2933
return Syncs.add(opts).inlet
4163
return Syncs.add( opts ).inlet
2938
4169
-- Spawns a new child process.
2941
function spawn(agent, binary, ...)
2942
if agent == nil or type(agent) ~= 'table' then
2943
error('spawning with an invalid agent', 2)
4172
agent, -- the reason why a process is spawned.
4173
-- a delay or delay list for a sync
4174
-- it will mark the related files as blocked.
4175
binary, -- binary to call
4180
type( agent ) ~= 'table'
4183
'spawning with an invalid agent',
2946
4188
if lsyncdStatus == 'fade' then
2947
log('Normal', 'ignored process spawning while fading')
4191
'ignored process spawning while fading'
2951
if type(binary) ~= 'string' then
2952
error('calling spawn(agent, binary, ...), binary is not a string', 2)
2955
local dol = InletFactory.getDelayOrList(agent)
2956
if not dol then error('spawning with an unknown agent', 2) end
2958
-- checks if spawn is called on already active event
4196
if type( binary ) ~= 'string' then
4198
'calling spawn(agent, binary, ...): binary is not a string',
4203
local dol = InletFactory.getDelayOrList( agent )
4207
'spawning with an unknown agent',
4213
-- checks if a spawn is called on an already active event
2959
4215
if dol.status then
2960
4219
if dol.status ~= 'wait' then
2961
4220
error('spawn() called on an non-waiting event', 2)
2964
4226
for _, d in ipairs(dol) do
2965
4227
if d.status ~= 'wait' and d.status ~= 'block' then
2966
4228
error('spawn() called on an non-waiting event list', 2)
2971
local pid = lsyncd.exec(binary, ...)
4235
-- tries to spawn the process
4237
local pid = lsyncd.exec( binary, ... )
2973
4239
if pid and pid > 0 then
2974
4241
processCount = processCount + 1
2975
if settings.maxProcesses and processCount > settings.maxProcesses then
2976
error('Spawned too much processes!')
4243
uSettings.maxProcesses and
4244
processCount > uSettings.maxProcesses
4246
error( 'Spawned too much processes!' )
2978
local sync = InletFactory.getSync(agent)
4249
local sync = InletFactory.getSync( agent )
2979
4251
-- delay or list
2980
4252
if dol.status then
2982
4255
dol.status = 'active'
2983
sync.processes[pid] = dol
4256
sync.processes[ pid ] = dol
2986
for _, d in ipairs(dol) do
4261
for _, d in ipairs( dol ) do
2987
4262
d.status = 'active'
2989
sync.processes[pid] = dol
4264
sync.processes[ pid ] = dol
2995
4272
-- Spawns a child process using the default shell.
2997
function spawnShell(agent, command, ...)
2998
return spawn(agent, '/bin/sh', '-c', command, '/bin/sh', ...)
4274
function spawnShell(
4275
agent, -- the delay(list) to spawn the command for
4276
command, -- the shell command
4277
... -- additonal arguments
3002
4290
-- Observes a filedescriptor
3004
function observefd(fd, ready, writey)
3005
return lsyncd.observe_fd(fd, ready, writey)
3010
function nonobservefd(fd)
3011
return lsyncd.nonobserve_fd(fd)
4293
fd, -- file descriptor
4294
ready, -- called when fd is ready to be read
4295
writey -- called when fd is ready to be written
4297
return lsyncd.observe_fd(
4305
-- Stops observeing a filedescriptor
4307
function nonobservefd(
4308
fd -- file descriptor
4310
return lsyncd.nonobserve_fd( fd )
3015
4314
-- Calls func at timestamp.
3016
4316
-- Use now() to receive current timestamp
4317
-- add seconds with '+' to it
3018
4319
alarm = UserAlarms.alarm
3021
4322
-- Comfort routine also for user.
3022
4323
-- Returns true if 'String' starts with 'Start'
3024
function string.starts(String,Start)
3025
return string.sub(String,1,#Start)==Start
4325
function string.starts( String, Start )
4327
return string.sub( String, 1, #Start )==Start
3029
4332
-- Comfort routine also for user.
3030
4333
-- Returns true if 'String' ends with 'End'
3032
function string.ends(String,End)
3033
return End=='' or string.sub(String,-#End)==End
4335
function string.ends( String, End )
4337
return End == '' or string.sub( String, -#End ) == End
4342
-- The Lsyncd 2.1 settings call
4344
function settings( a1 )
4345
-- if a1 is a string this is a get operation
4346
if type( a1 ) == 'string' then
4347
return uSettings[ a1 ]
4350
-- if its a table it sets all the value of the bale
4351
for k, v in pairs( a1 ) do
4352
if type( k ) ~= 'number' then
4355
uSettings[ v ] = true
4359
settingsSafe = settings
3041
4362
-- Returns the core the runners function interface.