1--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2-- lsyncd.lua   Live (Mirror) Syncing Demon
3--
4-- This is the "runner" part of Lsyncd. It containts all its high-level logic.
5-- It works closely together with the Lsyncd core in lsyncd.c. This means it
6-- cannot be runned directly from the standard lua interpreter.
7--
8-- This code assumes your editor is at least 100 chars wide.
9--
10-- License: GPLv2 (see COPYING) or any later version
11-- Authors: Axel Kittenberger <axkibe@gmail.com>
12--
13--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
14
15
16--
17-- A security measurement.
18-- The core will exit if version ids mismatch.
19--
20if lsyncd_version
21then
22	-- ensures the runner is not being loaded twice
23	lsyncd.log( 'Error', 'You cannot use the lsyncd runner as configuration file!' )
24
25	lsyncd.terminate( -1 )
26end
27
28lsyncd_version = '2.2.3'
29
30
31--
32-- Hides the core interface from user scripts.
33--
34local _l = lsyncd
35lsyncd = nil
36
37local lsyncd = _l
38_l = nil
39
40
41--
42-- Shortcuts (which user is supposed to be able to use them as well)
43--
44log       = lsyncd.log
45terminate = lsyncd.terminate
46now       = lsyncd.now
47readdir   = lsyncd.readdir
48
49
50--
51-- Coping globals to ensure userscripts cannot change this.
52--
53local log       = log
54local terminate = terminate
55local now       = now
56local readdir   = readdir
57
58--
59-- Predeclarations.
60--
61local Monitors
62
63
64--
65-- Global: total number of processess running.
66--
67local processCount = 0
68
69
70--
71-- All valid entries in a settings{} call.
72--
73local settingsCheckgauge =
74{
75	logfile        = true,
76	pidfile        = true,
77	nodaemon	   = true,
78	statusFile     = true,
79	statusInterval = true,
80	logfacility    = true,
81	logident       = true,
82	insist         = true,
83	inotifyMode    = true,
84	maxProcesses   = true,
85	maxDelays      = true,
86}
87
88
89--
90-- Settings specified by command line.
91--
92local clSettings = { }
93
94
95--
96-- Settings specified by config scripts.
97--
98local uSettings = { }
99
100
101--
102-- A copy of the settings function to see if the
103-- user script replaced the settings() by a table
104-- ( pre Lsyncd 2.1 style )
105--
106local settingsSafe
107
108
109--============================================================================
110-- Lsyncd Prototypes
111--============================================================================
112
113
114--
115-- Array tables error if accessed with a non-number.
116--
117local Array = ( function
118( )
119	--
120	-- Metatable.
121	--
122	local mt = { }
123
124	--
125	-- On accessing a nil index.
126	--
127	mt.__index = function
128	(
129		t,  -- table accessed
130		k   -- key value accessed
131	)
132		if type(k) ~= 'number'
133		then
134			error( 'Key "'..k..'" invalid for Array', 2 )
135		end
136
137		return rawget( t, k )
138	end
139
140	--
141	-- On assigning a new index.
142	--
143	mt.__newindex = function
144	(
145		t,  -- table getting a new index assigned
146		k,  -- key value to assign to
147		v   -- value to assign
148	)
149		if type( k ) ~= 'number'
150		then
151			error( 'Key "'..k..'" invalid for Array', 2 )
152		end
153
154		rawset( t, k, v )
155	end
156
157	--
158	-- Creates a new object
159	--
160	local function new
161	( )
162		local o = { }
163
164		setmetatable( o, mt )
165
166		return o
167	end
168
169	--
170	-- Public interface
171	--
172	return { new = new }
173end )( )
174
175
176--
177-- Count array tables error if accessed with a non-number.
178--
179-- Additionally they maintain their length as 'size' attribute,
180-- since Lua's # operator does not work on tables whose key values are not
181-- strictly linear.
182--
183local CountArray = ( function
184( )
185	--
186	-- Metatable
187	--
188	local mt = { }
189
190	--
191	-- Key to native table
192	--
193	local k_nt = { }
194
195	--
196	-- On accessing a nil index.
197	--
198	mt.__index = function
199	(
200		t,  -- table being accessed
201		k   -- key used to access
202	)
203		if type( k ) ~= 'number'
204		then
205			error( 'Key "' .. k .. '" invalid for CountArray', 2 )
206		end
207
208		return t[ k_nt ][ k ]
209	end
210
211	--
212	-- On assigning a new index.
213	--
214	mt.__newindex = function
215	(
216		t,  -- table getting a new index assigned
217		k,  -- key value to assign to
218		v   -- value to assign
219	)
220		if type( k ) ~= 'number'
221		then
222			error( 'Key "'..k..'" invalid for CountArray', 2 )
223		end
224
225		-- value before
226		local vb = t[ k_nt ][ k ]
227
228		if v and not vb
229		then
230			t._size = t._size + 1
231		elseif not v and vb
232		then
233			t._size = t._size - 1
234		end
235
236		t[ k_nt ][ k ] = v
237	end
238
239	--
240	-- Walks through all entries in any order.
241	--
242	local function walk
243	(
244		self  -- the count array
245	)
246		return pairs( self[ k_nt ] )
247	end
248
249	--
250	-- Returns the count.
251	--
252	local function size
253	(
254		self  -- the count array
255	)
256		return self._size
257	end
258
259	--
260	-- Creates a new count array
261	--
262	local function new
263	( )
264		-- k_nt is a native table, private to this object.
265		local o =
266		{
267			_size = 0,
268			walk = walk,
269			size = size,
270			[ k_nt ] = { }
271		}
272
273		setmetatable( o, mt )
274
275		return o
276	end
277
278	--
279	-- Public interface
280	--
281	return { new = new }
282end )( )
283
284
285--
286-- A queue is optimized for pushing and poping.
287-- TODO: make this an object
288--
289Queue = ( function
290( )
291	--
292	-- Metatable
293	--
294	local mt = { }
295
296
297	--
298	-- Key to native table
299	--
300	local k_nt = { }
301
302
303	--
304	-- On accessing a nil index.
305	--
306	mt.__index = function
307	(
308		t,  -- table being accessed
309		k   -- key used to access
310	)
311		if type( k ) ~= 'number'
312		then
313			error( 'Key "' .. k .. '" invalid for Queue', 2 )
314		end
315
316		return t[ k_nt ][ k ]
317	end
318
319
320	--
321	-- On assigning a new index.
322	--
323	mt.__newindex = function
324	(
325		t,  -- table getting a new index assigned
326		k,  -- key value to assign to
327		v   -- value to assign
328	)
329		error( 'Queues are not directly assignable.', 2 )
330	end
331
332	--
333	-- Returns the first item of the Queue.
334	--
335	local function first
336	(
337		self
338	)
339		local nt = self[ k_nt ]
340
341		return nt[ nt.first ]
342	end
343
344	--
345	-- Returns the last item of the Queue.
346	--
347	local function last
348	(
349		self
350	)
351		local nt = self[ k_nt ]
352
353		return nt[ nt.last ]
354	end
355
356	--
357	-- Returns the size of the queue.
358	--
359	local function size
360	(
361		self
362	)
363		return self[ k_nt ].size
364	end
365
366
367	--
368	-- Pushes a value on the queue.
369	-- Returns the last value
370	--
371	local function push
372	(
373		self,   -- queue to push to
374		value   -- value to push
375	)
376		if not value
377		then
378			error( 'Queue pushing nil value', 2 )
379		end
380
381		local nt = self[ k_nt ]
382
383		local last = nt.last + 1
384
385		nt.last = last
386
387		nt[ last ] = value
388
389		nt.size = nt.size + 1
390
391		return last
392	end
393
394
395	--
396	-- Removes an item at pos from the Queue.
397	--
398	local function remove
399	(
400		self,  -- the queue
401		pos    -- position to remove
402	)
403		local nt = self[ k_nt ]
404
405		if nt[ pos ] == nil
406		then
407			error( 'Removing nonexisting item in Queue', 2 )
408		end
409
410		nt[ pos ] = nil
411
412		-- if removing first or last element,
413		-- the queue limits are adjusted.
414		if pos == nt.first
415		then
416			local last = nt.last
417
418			while nt[ pos ] == nil and pos <= last
419			do
420				pos = pos + 1
421			end
422
423			nt.first = pos
424
425		elseif pos == nt.last
426		then
427			local first = nt.first
428
429			while nt[ pos ] == nil and pos >= first
430			do
431				pos = pos - 1
432			end
433
434			nt.last = pos
435		end
436
437		-- reset the indizies if the queue is empty
438		if nt.last < nt.first
439		then
440			nt.first = 1
441
442			nt.last = 0
443		end
444
445		nt.size = nt.size - 1
446	end
447
448	--
449	-- Replaces a value.
450	--
451	local function replace
452	(
453		self,  -- the queue
454		pos,   -- position to replace
455		value  -- the new entry
456	)
457		local nt = self[ k_nt ]
458
459		if nt[ pos ] == nil
460		then
461			error( 'Trying to replace an unset Queue entry.' )
462		end
463
464		nt[ pos ] = value
465	end
466
467	--
468	-- Queue iterator ( stateless )
469	-- TODO rename next
470	--
471	local function iter
472	(
473		self,  -- queue to iterate
474		pos    -- current position
475	)
476		local nt = self[ k_nt ]
477
478		pos = pos + 1
479
480		while nt[ pos ] == nil and pos <= nt.last
481		do
482			pos = pos + 1
483		end
484
485		if pos > nt.last
486		then
487			return nil
488		end
489
490		return pos, nt[ pos ]
491	end
492
493
494	--
495	-- Reverse queue iterator (stateless)
496	-- TODO rename prev
497	--
498	local function iterReverse
499	(
500		self,  -- queue to iterate
501		pos    -- current position
502	)
503		local nt = self[ k_nt ]
504
505		pos = pos - 1
506
507		while nt[ pos ] == nil and pos >= nt.first
508		do
509			pos = pos - 1
510		end
511
512		if pos < nt.first
513		then
514			return nil
515		end
516
517		return pos, nt[ pos ]
518	end
519
520
521	--
522	-- Iteraters through the queue
523	-- returning all non-nil pos-value entries.
524	--
525	local function qpairs
526	(
527		self
528	)
529		return iter, self, self[ k_nt ].first - 1
530	end
531
532
533	--
534	-- Iteraters backwards through the queue
535	-- returning all non-nil pos-value entries.
536	--
537	local function qpairsReverse
538	(
539		self
540	)
541		return iterReverse, self, self[ k_nt ].last + 1
542	end
543
544	--
545	-- Creates a new queue.
546	--
547	local function new
548	( )
549		local q = {
550			first = first,
551			last = last,
552			push = push,
553			qpairs = qpairs,
554			qpairsReverse = qpairsReverse,
555			remove = remove,
556			replace = replace,
557			size = size,
558
559			[ k_nt ] =
560			{
561				first = 1,
562				last  = 0,
563				size  = 0
564			}
565		}
566
567		setmetatable( q, mt )
568
569		return q
570	end
571
572	--
573	-- Public interface
574	--
575	return { new = new }
576end )( )
577
578
579--
580-- Locks globals.
581--
582-- No more globals can be created after this!
583--
584local function lockGlobals
585( )
586	local t = _G
587
588	local mt = getmetatable( t ) or { }
589
590	-- TODO try to remove the underscore exceptions
591	mt.__index = function
592	(
593		t,  -- table being accessed
594		k   -- key used to access
595	)
596		if k ~= '_' and string.sub( k, 1, 2 ) ~= '__'
597		then
598			error( 'Access of non-existing global "' .. k ..'"', 2 )
599		else
600			rawget( t, k )
601		end
602	end
603
604	mt.__newindex = function
605	(
606		t,  -- table getting a new index assigned
607		k,  -- key value to assign to
608		v   -- value to assign
609	)
610		if k ~= '_' and string.sub( k, 1, 2 ) ~= '__'
611		then
612			error(
613				'Lsyncd does not allow GLOBALS to be created on the fly. '
614				.. 'Declare "' .. k.. '" local or declare global on load.',
615				2
616			)
617		else
618			rawset( t, k, v )
619		end
620	end
621
622	setmetatable( t, mt )
623end
624
625
626--
627-- Holds the information about a delayed event for one Sync.
628--
629-- Valid stati of an delay are:
630--   'wait'    ... the event is ready to be handled.
631--   'active'  ... there is process running catering for this event.
632--   'blocked' ... this event waits for another to be handled first.
633--
634local Delay = ( function
635( )
636	--
637	-- Metatable.
638	--
639	local mt = { }
640
641	--
642	-- Secret key to native table
643	--
644	local k_nt = { }
645
646	local assignAble =
647	{
648		dpos   = true,
649		etype  = true,
650		path   = true,
651		path2  = true,
652		status = true,
653	}
654
655	--
656	-- On accessing a nil index.
657	--
658	mt.__index = function
659	(
660		t,  -- table accessed
661		k   -- key value accessed
662	)
663		return t[ k_nt ][ k ]
664	end
665
666	--
667	-- On assigning a new index.
668	--
669	mt.__newindex = function
670	(
671		t,  -- table getting a new index assigned
672		k,  -- key value to assign to
673		v   -- value to assign
674	)
675		if not assignAble[ k ]
676		then
677			error( 'Cannot assign new key "' .. k .. '" to Delay' )
678		end
679
680		t[ k_nt ][ k ] = v
681	end
682
683	--
684	-- This delay is being blocked by another delay
685	--
686	local function blockedBy
687	(
688		self,  -- this delay
689		delay  -- the blocking delay
690	)
691		self[ k_nt ].status = 'block'
692
693		local blocks = delay[ k_nt ].blocks
694
695		if not blocks
696		then
697			blocks = { }
698
699			delay[ k_nt ].blocks = blocks
700		end
701
702		table.insert( blocks, self )
703	end
704
705
706	--
707	-- Sets the delay status to 'active'.
708	--
709	local function setActive
710	(
711		self
712	)
713		self[ k_nt ].status = 'active'
714	end
715
716	--
717	-- Sets the delay status to 'wait'
718	--
719	local function wait
720	(
721		self,   -- this delay
722		alarm   -- alarm for the delay
723	)
724		self[ k_nt ].status = 'wait'
725
726		self[ k_nt ].alarm = alarm
727	end
728
729	--
730	-- Creates a new delay.
731	--
732	local function new
733	(
734		etype,  -- type of event.
735		--         'Create', 'Modify', 'Attrib', 'Delete' or 'Move'
736		sync,   -- the Sync this delay belongs to
737		alarm,  -- latest point in time this should be catered for
738		path,   -- path and file-/dirname of the delay relative
739		--      -- to the syncs root.
740		path2   -- used only in moves, path and file-/dirname of
741		        -- move destination
742	)
743		local delay =
744			{
745				blockedBy = blockedBy,
746				setActive = setActive,
747				wait = wait,
748				[ k_nt ] =
749					{
750						etype = etype,
751						sync = sync,
752						alarm = alarm,
753						path = path,
754						path2  = path2,
755						status = 'wait'
756					},
757			}
758
759		setmetatable( delay, mt )
760
761		return delay
762	end
763
764	--
765	-- Public interface
766	--
767	return { new = new }
768end )( )
769
770
771--
772-- Combines delays.
773--
774local Combiner = ( function
775( )
776	--
777	-- The new delay replaces the old one if it's a file
778	--
779	local function refi
780	(
781		d1, -- old delay
782		d2  -- new delay
783	)
784		-- but a directory blocks
785		if d2.path:byte( -1 ) == 47
786		then
787			log(
788				'Delay',
789				d2.etype,': ',d2.path,
790				' blocked by ',
791				d1.etype,': ',d1.path
792			)
793
794			return 'stack'
795		end
796
797		log(
798			'Delay',
799			d2.etype, ': ', d2.path,
800			' replaces ',
801			d1.etype, ': ', d1.path
802		)
803
804		return 'replace'
805	end
806
807	--
808	-- Table on how to combine events that dont involve a move.
809	--
810	local combineNoMove = {
811
812		Attrib = {
813			Attrib = 'absorb',
814			Modify = 'replace',
815			Create = 'replace',
816			Delete = 'replace'
817		},
818
819		Modify = {
820			Attrib = 'absorb',
821			Modify = 'absorb',
822			Create = 'replace',
823			Delete = 'replace'
824		},
825
826		Create = {
827			Attrib = 'absorb',
828			Modify = 'absorb',
829			Create = 'absorb',
830			Delete = 'replace'
831		},
832
833		Delete = {
834			Attrib = 'absorb',
835			Modify = 'absorb',
836			Create = 'replace file,block dir',
837			Delete = 'absorb'
838		},
839	}
840
841	--
842	-- Returns the way two Delay should be combined.
843	--
844	-- Result:
845	--    nil               -- They don't affect each other.
846	--    'stack'           -- Old Delay blocks new Delay.
847	--    'replace'         -- Old Delay is replaced by new Delay.
848	--    'absorb'          -- Old Delay absorbs new Delay.
849	--    'toDelete,stack'  -- Old Delay is turned into a Delete
850	--                         and blocks the new Delay.
851	--    'split'           -- New Delay a Move is to be split
852	--                         into a Create and Delete.
853	--
854	local function combine
855	(
856		d1, -- old delay
857		d2  -- new delay
858	)
859		if d1.etype == 'Init' or d1.etype == 'Blanket'
860		then
861			return 'stack'
862		end
863
864		-- two normal events
865		if d1.etype ~= 'Move' and d2.etype ~= 'Move'
866		then
867			if d1.path == d2.path
868			then
869				-- lookups up the function in the combination matrix
870				-- and calls it
871				local result = combineNoMove[ d1.etype ][ d2.etype ]
872
873				if result == 'replace file,block dir'
874				then
875					if d2.path:byte( -1 ) == 47
876					then
877						return 'stack'
878					else
879						return 'replace'
880					end
881				end
882			end
883
884			-- if one is a parent directory of another, events are blocking
885			if d1.path:byte( -1 ) == 47 and string.starts( d2.path, d1.path )
886			or d2.path:byte( -1 ) == 47 and string.starts( d1.path, d2.path )
887			then
888				return 'stack'
889			end
890
891			return nil
892		end
893
894		-- non-move event on a move.
895		if d1.etype == 'Move' and d2.etype ~= 'Move'
896		then
897			-- if the move source could be damaged the events are stacked
898			if d1.path == d2.path
899			or d2.path:byte( -1 ) == 47 and string.starts( d1.path, d2.path )
900			or d1.path:byte( -1 ) == 47 and string.starts( d2.path, d1.path )
901			then
902				return 'stack'
903			end
904
905			--  the event does something with the move destination
906
907			if d1.path2 == d2.path
908			then
909				if d2.etype == 'Delete'
910				or d2.etype == 'Create'
911				then
912					return 'toDelete,stack'
913				end
914
915				-- on 'Attrib' or 'Modify' simply stack on moves
916				return 'stack'
917			end
918
919			if d2.path:byte( -1 ) == 47 and string.starts( d1.path2, d2.path )
920			or d1.path2:byte( -1 ) == 47 and string.starts( d2.path,  d1.path2 )
921			then
922				return 'stack'
923			end
924
925			return nil
926		end
927
928		-- a move upon a non-move event
929		if d1.etype ~= 'Move' and d2.etype == 'Move'
930		then
931			if d1.path == d2.path
932			or d1.path == d2.path2
933			or d1.path:byte( -1 ) == 47 and string.starts( d2.path, d1.path )
934			or d1.path:byte( -1 ) == 47 and string.starts( d2.path2, d1.path )
935			or d2.path:byte( -1 ) == 47 and string.starts( d1.path, d2.path )
936			or d2.path2:byte( -1 ) == 47 and string.starts( d1.path,  d2.path2 )
937			then
938				return 'split'
939			end
940
941			return nil
942		end
943
944		--
945		-- a move event upon a move event
946		--
947		if d1.etype == 'Move' and d2.etype == 'Move'
948		then
949			-- TODO combine moves,
950			if d1.path  == d2.path
951			or d1.path  == d2.path2
952			or d1.path2 == d2.path
953			or d2.path2 == d2.path
954			or d1.path:byte( -1 ) == 47 and string.starts( d2.path,  d1.path )
955			or d1.path:byte( -1 ) == 47 and string.starts( d2.path2, d1.path )
956			or d1.path2:byte( -1 ) == 47 and string.starts( d2.path,  d1.path2 )
957			or d1.path2:byte( -1 ) == 47 and string.starts( d2.path2, d1.path2 )
958			or d2.path:byte( -1 ) == 47 and string.starts( d1.path,  d2.path )
959			or d2.path:byte( -1 ) == 47 and string.starts( d1.path2, d2.path )
960			or d2.path2:byte( -1 ) == 47 and string.starts( d1.path,  d2.path2 )
961			or d2.path2:byte( -1 ) == 47 and string.starts( d1.path2, d2.path2 )
962			then
963				return 'split'
964			end
965
966			return nil
967		end
968
969		error( 'reached impossible state' )
970	end
971
972
973	--
974	-- The new delay is absorbed by an older one.
975	--
976	local function logAbsorb
977	(
978		d1, -- old delay
979		d2  -- new delay
980	)
981		log(
982			'Delay',
983			d2.etype, ': ',d2.path,
984			' absorbed by ',
985			d1.etype,': ',d1.path
986		)
987	end
988
989	--
990	-- The new delay replaces the old one if it's a file.
991	--
992	local function logReplace
993	(
994		d1, -- old delay
995		d2  -- new delay
996	)
997		log(
998			'Delay',
999			d2.etype, ': ', d2.path,
1000			' replaces ',
1001			d1.etype, ': ', d1.path
1002		)
1003	end
1004
1005
1006	--
1007	-- The new delay splits on the old one.
1008	--
1009	local function logSplit
1010	(
1011		d1, -- old delay
1012		d2  -- new delay
1013	)
1014		log(
1015			'Delay',
1016			d2.etype, ': ',
1017			d2.path, ' -> ', d2.path2,
1018			' splits on ',
1019			d1.etype, ': ', d1.path
1020		)
1021	end
1022
1023	--
1024	-- The new delay is blocked by the old delay.
1025	--
1026	local function logStack
1027	(
1028		d1, -- old delay
1029		d2  -- new delay
1030	)
1031		local active = ''
1032
1033		if d1.active
1034		then
1035			active = 'active '
1036		end
1037
1038		if d2.path2
1039		then
1040			log(
1041				'Delay',
1042				d2.etype, ': ',
1043				d2.path, '->', d2.path2,
1044				' blocked by ',
1045				active,
1046				d1.etype, ': ', d1.path
1047			)
1048		else
1049			log(
1050				'Delay',
1051				d2.etype, ': ', d2.path,
1052				' blocked by ',
1053				active,
1054				d1.etype, ': ', d1.path
1055			)
1056		end
1057	end
1058
1059
1060	--
1061	-- The new delay turns the old one (a move) into a delete and is blocked.
1062	--
1063	local function logToDeleteStack
1064	(
1065		d1, -- old delay
1066		d2  -- new delay
1067	)
1068		if d1.path2
1069		then
1070			log(
1071				'Delay',
1072				d2.etype, ': ', d2.path,
1073				' turns ',
1074			    d1.etype, ': ', d1.path, ' -> ', d1.path2,
1075				' into Delete: ', d1.path
1076			)
1077		else
1078			log(
1079				'Delay',
1080				d2.etype, ': ', d2.path,
1081				' turns ',
1082			    d1.etype, ': ', d1.path,
1083				' into Delete: ', d1.path
1084			)
1085		end
1086	end
1087
1088
1089	local logFuncs =
1090	{
1091		absorb               = logAbsorb,
1092		replace              = logReplace,
1093		split                = logSplit,
1094		stack                = logStack,
1095		[ 'toDelete,stack' ] = logToDeleteStack
1096	}
1097
1098
1099	--
1100	-- Prints the log message for a combination result
1101	--
1102	local function log
1103	(
1104		result, -- the combination result
1105		d1,     -- old delay
1106		d2      -- new delay
1107	)
1108		local lf = logFuncs[ result ]
1109
1110		if not lf
1111		then
1112			error( 'unknown combination result: ' .. result )
1113		end
1114
1115		lf( d1, d2 )
1116	end
1117
1118	--
1119	-- Public interface
1120	--
1121	return
1122	{
1123		combine = combine,
1124		log = log
1125	}
1126
1127end )( )
1128
1129
1130--
1131-- Creates inlets for syncs: the user interface for events.
1132--
1133local InletFactory = ( function
1134( )
1135	--
1136	-- Table to receive the delay of an event
1137	-- or the delay list of an event list.
1138	--
1139	-- Keys are events and values are delays.
1140	--
1141	local e2d = { }
1142
1143	--
1144	-- Table to ensure the uniqueness of every event
1145	-- related to a delay.
1146	--
1147	-- Keys are delay and values are events.
1148	--
1149	local e2d2 = { }
1150
1151	--
1152	-- Allows the garbage collector to remove not refrenced
1153	-- events.
1154	--
1155	setmetatable( e2d,  { __mode = 'k' } )
1156	setmetatable( e2d2, { __mode = 'v' } )
1157
1158	--
1159	-- Removes the trailing slash from a path.
1160	--
1161	local function cutSlash
1162	(
1163		path -- path to cut
1164	)
1165		if string.byte( path, -1 ) == 47
1166		then
1167			return string.sub( path, 1, -2 )
1168		else
1169			return path
1170		end
1171	end
1172
1173	--
1174	-- Gets the path of an event.
1175	--
1176	local function getPath
1177	(
1178		event
1179	)
1180		if event.move ~= 'To'
1181		then
1182			return e2d[ event ].path
1183		else
1184			return e2d[ event ].path2
1185		end
1186	end
1187
1188	--
1189	-- Interface for user scripts to get event fields.
1190	--
1191	local eventFields = {
1192
1193		--
1194		-- Returns a copy of the configuration as called by sync.
1195		-- But including all inherited data and default values.
1196		--
1197		-- TODO give user a readonly version.
1198		--
1199		config = function
1200		(
1201			event
1202		)
1203			return e2d[ event ].sync.config
1204		end,
1205
1206		--
1207		-- Returns the inlet belonging to an event.
1208		--
1209		inlet = function
1210		(
1211			event
1212		)
1213			return e2d[ event ].sync.inlet
1214		end,
1215
1216		--
1217		-- Returns the type of the event.
1218		--
1219		-- Can be: 'Attrib', 'Create', 'Delete', 'Modify' or 'Move',
1220		--
1221		etype = function
1222		(
1223			event
1224		)
1225			return e2d[ event ].etype
1226		end,
1227
1228		--
1229		-- Events are not lists.
1230		--
1231		isList = function
1232		( )
1233			return false
1234		end,
1235
1236		--
1237		-- Returns the status of the event.
1238		--
1239		-- Can be:
1240		--    'wait', 'active', 'block'.
1241		--
1242		status = function
1243		(
1244			event
1245		)
1246			return e2d[ event ].status
1247		end,
1248
1249		--
1250		-- Returns true if event relates to a directory
1251		--
1252		isdir = function
1253		(
1254			event
1255		)
1256			return string.byte( getPath( event ), -1 ) == 47
1257		end,
1258
1259		--
1260		-- Returns the name of the file/dir.
1261		--
1262		-- Includes a trailing slash for dirs.
1263		--
1264		name = function
1265		(
1266			event
1267		)
1268			return string.match( getPath( event ), '[^/]+/?$' )
1269		end,
1270
1271		--
1272		-- Returns the name of the file/dir
1273		-- excluding a trailing slash for dirs.
1274		--
1275		basename = function
1276		(
1277			event
1278		)
1279			return string.match( getPath( event ), '([^/]+)/?$')
1280		end,
1281
1282		--
1283		-- Returns the file/dir relative to watch root
1284		-- including a trailing slash for dirs.
1285		--
1286		path = function
1287		(
1288			event
1289		)
1290			local p = getPath( event )
1291
1292			if string.byte( p, 1 ) == 47
1293			then
1294				p = string.sub( p, 2, -1 )
1295			end
1296
1297			return p
1298		end,
1299
1300		--
1301		-- Returns the directory of the file/dir relative to watch root
1302		-- Always includes a trailing slash.
1303		--
1304		pathdir = function
1305		(
1306			event
1307		)
1308			local p = getPath( event )
1309
1310			if string.byte( p, 1 ) == 47
1311			then
1312				p = string.sub( p, 2, -1 )
1313			end
1314
1315			return string.match( p, '^(.*/)[^/]+/?' ) or ''
1316		end,
1317
1318		--
1319		-- Returns the file/dir relativ to watch root
1320		-- excluding a trailing slash for dirs.
1321		--
1322		pathname = function
1323		(
1324			event
1325		)
1326			local p = getPath( event )
1327
1328			if string.byte( p, 1 ) == 47
1329			then
1330				p = string.sub( p, 2, -1 )
1331			end
1332
1333			return cutSlash( p )
1334		end,
1335
1336		--
1337		-- Returns the absolute path of the watch root.
1338		-- All symlinks are resolved.
1339		--
1340		source = function
1341		(
1342			event
1343		)
1344			return e2d[ event ].sync.source
1345		end,
1346
1347		--
1348		-- Returns the absolute path of the file/dir
1349		-- including a trailing slash for dirs.
1350		--
1351		sourcePath = function
1352		(
1353			event
1354		)
1355			return e2d[ event ].sync.source .. getPath( event )
1356		end,
1357
1358		--
1359		-- Returns the absolute dir of the file/dir
1360		-- including a trailing slash.
1361		--
1362		sourcePathdir = function
1363		(
1364			event
1365		)
1366			return(
1367				e2d[event].sync.source
1368				.. (
1369					string.match( getPath( event ), '^(.*/)[^/]+/?' )
1370					or ''
1371				)
1372			)
1373		end,
1374
1375		--
1376		-- Returns the absolute path of the file/dir
1377		-- excluding a trailing slash for dirs.
1378		--
1379		sourcePathname = function
1380		(
1381			event
1382		)
1383			return e2d[ event ].sync.source .. cutSlash( getPath( event ) )
1384		end,
1385
1386		--
1387		-- Returns the configured target.
1388		--
1389		target = function
1390		(
1391			event
1392		)
1393			return e2d[ event ].sync.config.target
1394		end,
1395
1396		--
1397		-- Returns the relative dir/file appended to the target
1398		-- including a trailing slash for dirs.
1399		--
1400		targetPath = function
1401		(
1402			event
1403		)
1404			return e2d[ event ].sync.config.target .. getPath( event )
1405		end,
1406
1407		--
1408		-- Returns the dir of the dir/file appended to the target
1409		-- including a trailing slash.
1410		--
1411		targetPathdir = function
1412		(
1413			event
1414		)
1415			return(
1416				e2d[ event ].sync.config.target
1417				.. (
1418					string.match( getPath( event ), '^(.*/)[^/]+/?' )
1419					or ''
1420				)
1421			)
1422		end,
1423
1424		--
1425		-- Returns the relative dir/file appended to the target
1426		-- excluding a trailing slash for dirs.
1427		--
1428		targetPathname = function( event )
1429			return(
1430				e2d[ event ].sync.config.target
1431				.. cutSlash( getPath( event ) )
1432			)
1433		end,
1434	}
1435
1436	--
1437	-- Retrievs event fields for the user script.
1438	--
1439	local eventMeta =
1440	{
1441		__index = function
1442		(
1443			event,
1444			field
1445		)
1446			local f = eventFields[ field ]
1447
1448			if not f
1449			then
1450				if field == 'move'
1451				then
1452					-- possibly undefined
1453					return nil
1454				end
1455
1456				error( 'event does not have field "' .. field .. '"', 2 )
1457			end
1458
1459			return f( event )
1460		end
1461	}
1462
1463	--
1464	-- Interface for user scripts to get list fields.
1465	--
1466	local eventListFuncs =
1467	{
1468		--
1469		-- Returns a list of paths of all events in list.
1470		--
1471		--
1472		getPaths = function
1473		(
1474			elist,   -- handle returned by getevents( )
1475			mutator  -- if not nil called with ( etype, path, path2 )
1476			--          returns one or two strings to add.
1477		)
1478			local dlist = e2d[ elist ]
1479
1480			if not dlist
1481			then
1482				error( 'cannot find delay list from event list.' )
1483			end
1484
1485			local result  = { }
1486			local resultn = 1
1487
1488			for k, d in ipairs( dlist )
1489			do
1490				local s1, s2
1491
1492				if mutator
1493				then
1494					s1, s2 = mutator( d.etype, d.path, d.path2 )
1495				else
1496					s1, s2 = d.path, d.path2
1497				end
1498
1499				result[ resultn ] = s1
1500
1501				resultn = resultn + 1
1502
1503				if s2
1504				then
1505					result[ resultn ] = s2
1506
1507					resultn = resultn + 1
1508				end
1509			end
1510
1511			return result
1512
1513		end
1514	}
1515
1516	--
1517	-- Retrievs event list fields for the user script
1518	--
1519	local eventListMeta =
1520	{
1521		__index = function
1522		(
1523			elist,
1524			func
1525		)
1526			if func == 'isList'
1527			then
1528				return true
1529			end
1530
1531			if func == 'config'
1532			then
1533				return e2d[ elist ].sync.config
1534			end
1535
1536			local f = eventListFuncs[ func ]
1537
1538			if not f
1539			then
1540				error(
1541					'event list does not have function "' .. func .. '"',
1542					2
1543				)
1544			end
1545
1546			return function
1547			( ... )
1548				return f( elist, ... )
1549			end
1550		end
1551	}
1552
1553	--
1554	-- Table of all inlets with their syncs.
1555	--
1556	local inlets = { }
1557
1558	--
1559	-- Allows the garbage collector to remove entries.
1560	--
1561	setmetatable( inlets, { __mode = 'v' } )
1562
1563	--
1564	-- Encapsulates a delay into an event for the user script.
1565	--
1566	local function d2e
1567	(
1568		delay  -- delay to encapsulate
1569	)
1570		-- already created?
1571		local eu = e2d2[ delay ]
1572
1573		if delay.etype ~= 'Move'
1574		then
1575			if eu then return eu end
1576
1577			local event = { }
1578
1579			setmetatable( event, eventMeta )
1580
1581			e2d[ event ]  = delay
1582
1583			e2d2[ delay ] = event
1584
1585			return event
1586		else
1587			-- moves have 2 events - origin and destination
1588			if eu then return eu[1], eu[2] end
1589
1590			local event  = { move = 'Fr' }
1591			local event2 = { move = 'To' }
1592
1593			setmetatable( event, eventMeta )
1594			setmetatable( event2, eventMeta )
1595
1596			e2d[ event ]  = delay
1597			e2d[ event2 ] = delay
1598
1599			e2d2[ delay ] = { event, event2 }
1600
1601			-- move events have a field 'move'
1602			return event, event2
1603		end
1604	end
1605
1606	--
1607	-- Encapsulates a delay list into an event list for the user script.
1608	--
1609	local function dl2el
1610	(
1611		dlist
1612	)
1613		local eu = e2d2[ dlist ]
1614
1615		if eu then return eu end
1616
1617		local elist = { }
1618
1619		setmetatable( elist, eventListMeta )
1620
1621		e2d [ elist ] = dlist
1622
1623		e2d2[ dlist ] = elist
1624
1625		return elist
1626	end
1627
1628	--
1629	-- The functions the inlet provides.
1630	--
1631	local inletFuncs =
1632	{
1633		--
1634		-- Adds an exclude.
1635		--
1636		addExclude = function
1637		(
1638			sync,   -- the sync of the inlet
1639			pattern -- exlusion pattern to add
1640		)
1641			sync:addExclude( pattern )
1642		end,
1643
1644
1645		--
1646		-- Appens a filter.
1647		--
1648		appendFilter = function
1649		(
1650			sync,   -- the sync of the inlet
1651			rule,   -- '+' or '-'
1652			pattern -- exlusion pattern to add
1653		)
1654			sync:appendFilter( rule, pattern )
1655		end,
1656
1657		--
1658		-- Removes an exclude.
1659		--
1660		rmExclude = function
1661		(
1662			sync,   -- the sync of the inlet
1663			pattern -- exlusion pattern to remove
1664		)
1665			sync:rmExclude( pattern )
1666		end,
1667
1668		--
1669		-- Gets the list of excludes in their
1670		-- rsync-like patterns form.
1671		--
1672		getExcludes = function
1673		(
1674			sync -- the sync of the inlet
1675		)
1676			-- creates a copy
1677			local e = { }
1678			local en = 1;
1679
1680			for k, _ in pairs( sync.excludes.list )
1681			do
1682				e[ en ] = k;
1683				en = en + 1;
1684			end
1685
1686			return e;
1687		end,
1688
1689		--
1690		-- Gets the list of filters and excldues
1691		-- as rsync-like filter/patterns form.
1692		--
1693		getFilters = function
1694		(
1695			sync -- the sync of the inlet
1696		)
1697			-- creates a copy
1698			local e = { }
1699			local en = 1;
1700
1701			-- first takes the filters
1702			if sync.filters
1703			then
1704				for _, entry in ipairs( sync.filters.list )
1705				do
1706					e[ en ] = entry.rule .. ' ' .. entry.pattern;
1707					en = en + 1;
1708				end
1709			end
1710
1711			-- then the excludes
1712			for k, _ in pairs( sync.excludes.list )
1713			do
1714				e[ en ] = '- ' .. k;
1715				en = en + 1;
1716			end
1717
1718			return e;
1719		end,
1720
1721		--
1722		-- Returns true if the sync has filters
1723		--
1724		hasFilters = function
1725		(
1726			sync -- the sync of the inlet
1727		)
1728			return not not sync.filters
1729		end,
1730
1731		--
1732		-- Creates a blanketEvent that blocks everything
1733		-- and is blocked by everything.
1734		--
1735		createBlanketEvent = function
1736		(
1737			sync -- the sync of the inlet
1738		)
1739			return d2e( sync:addBlanketDelay( ) )
1740		end,
1741
1742		--
1743		-- Discards a waiting event.
1744		--
1745		discardEvent = function
1746		(
1747			sync,
1748			event
1749		)
1750			local delay = e2d[ event ]
1751
1752			if delay.status ~= 'wait'
1753			then
1754				log(
1755					'Error',
1756					'Ignored cancel of a non-waiting event of type ',
1757					event.etype
1758				)
1759
1760				return
1761			end
1762
1763			sync:removeDelay( delay )
1764		end,
1765
1766		--
1767		-- Gets the next not blocked event from queue.
1768		--
1769		getEvent = function
1770		(
1771			sync
1772		)
1773			return d2e( sync:getNextDelay( now( ) ) )
1774		end,
1775
1776		--
1777		-- Gets all events that are not blocked by active events.
1778		--
1779		getEvents = function
1780		(
1781			sync, -- the sync of the inlet
1782			test  -- if not nil use this function to test if to include an event
1783		)
1784			local dlist = sync:getDelays( test )
1785
1786			return dl2el( dlist )
1787		end,
1788
1789		--
1790		-- Returns the configuration table specified by sync{ }
1791		--
1792		getConfig = function( sync )
1793			-- TODO give a readonly handler only.
1794			return sync.config
1795		end,
1796	}
1797
1798	--
1799	-- Forwards access to inlet functions.
1800	--
1801	local inletMeta =
1802	{
1803		__index = function
1804		(
1805			inlet,
1806			func
1807		)
1808			local f = inletFuncs[ func ]
1809
1810			if not f
1811			then
1812				error( 'inlet does not have function "'..func..'"', 2 )
1813			end
1814
1815			return function( ... )
1816				return f( inlets[ inlet ], ... )
1817			end
1818		end,
1819	}
1820
1821	--
1822	-- Creates a new inlet for a sync.
1823	--
1824	local function newInlet
1825	(
1826		sync  -- the sync to create the inlet for
1827	)
1828		-- Lsyncd runner controlled variables
1829		local inlet = { }
1830
1831		-- sets use access methods
1832		setmetatable( inlet, inletMeta )
1833
1834		inlets[ inlet ] = sync
1835
1836		return inlet
1837	end
1838
1839	--
1840	-- Returns the delay from a event.
1841	--
1842	local function getDelayOrList
1843	(
1844		event
1845	)
1846		return e2d[ event ]
1847	end
1848
1849	--
1850	-- Returns the sync from an event or list
1851	--
1852	local function getSync
1853	(
1854		event
1855	)
1856		return e2d[ event ].sync
1857	end
1858
1859	--
1860	-- Public interface.
1861	--
1862	return {
1863		getDelayOrList = getDelayOrList,
1864		d2e            = d2e,
1865		dl2el          = dl2el,
1866		getSync        = getSync,
1867		newInlet       = newInlet,
1868	}
1869end )( )
1870
1871
1872--
1873-- A set of exclude patterns.
1874--
1875local Excludes = ( function
1876( )
1877	--
1878	-- Turns a rsync like file pattern to a lua pattern.
1879	-- ( at best it can )
1880	--
1881	local function toLuaPattern
1882	(
1883		p  --  the rsync like pattern
1884	)
1885		local o = p
1886
1887		p = string.gsub( p, '%%', '%%%%'  )
1888		p = string.gsub( p, '%^', '%%^'   )
1889		p = string.gsub( p, '%$', '%%$'   )
1890		p = string.gsub( p, '%(', '%%('   )
1891		p = string.gsub( p, '%)', '%%)'   )
1892		p = string.gsub( p, '%.', '%%.'   )
1893		p = string.gsub( p, '%[', '%%['   )
1894		p = string.gsub( p, '%]', '%%]'   )
1895		p = string.gsub( p, '%+', '%%+'   )
1896		p = string.gsub( p, '%-', '%%-'   )
1897		p = string.gsub( p, '%?', '[^/]'  )
1898		p = string.gsub( p, '%*', '[^/]*' )
1899		-- this was a ** before
1900		p = string.gsub( p, '%[%^/%]%*%[%^/%]%*', '.*' )
1901		p = string.gsub( p, '^/', '^/'    )
1902
1903		if p:sub( 1, 2 ) ~= '^/'
1904		then
1905			-- if does not begin with '^/'
1906			-- then all matches should begin with '/'.
1907			p = '/' .. p;
1908		end
1909
1910		log( 'Exclude', 'toLuaPattern "', o, '" = "', p, '"' )
1911
1912		return p
1913	end
1914
1915	--
1916	-- Adds a pattern to exclude.
1917	--
1918	local function add
1919	(
1920		self,
1921		pattern  -- the pattern to exclude
1922	)
1923		if self.list[ pattern ]
1924		then -- already in the list
1925			return
1926		end
1927
1928		local lp = toLuaPattern( pattern )
1929
1930		self.list[ pattern ] = lp
1931	end
1932
1933	--
1934	-- Removes a pattern to exclude.
1935	--
1936	local function remove
1937	(
1938		self,    -- self
1939		pattern  -- the pattern to remove
1940	)
1941		-- already in the list?
1942		if not self.list[ pattern ]
1943		then
1944			log(
1945				'Normal',
1946				'Removing not excluded exclude "' .. pattern .. '"'
1947			)
1948
1949			return
1950		end
1951
1952		self.list[ pattern ] = nil
1953	end
1954
1955	--
1956	-- Adds a list of patterns to exclude.
1957	--
1958	local function addList
1959	(
1960		self,
1961		plist
1962	)
1963		for _, v in ipairs( plist )
1964		do
1965			add( self, v )
1966		end
1967	end
1968
1969	--
1970	-- Loads the excludes from a file.
1971	--
1972	local function loadFile
1973	(
1974		self,  -- self
1975		file   -- filename to load from
1976	)
1977		f, err = io.open( file )
1978
1979		if not f
1980		then
1981			log( 'Error', 'Cannot open exclude file "', file,'": ', err )
1982
1983			terminate( -1 )
1984		end
1985
1986	    for line in f:lines()
1987		do
1988			-- lsyncd 2.0 does not support includes
1989
1990			if not string.match( line, '^%s*%+' )
1991			and not string.match( line, '^%s*#' )
1992			and not string.match( line, '^%s*$' )
1993			then
1994				local p = string.match( line, '%s*-?%s*(.*)' )
1995
1996				if p
1997				then
1998					add( self, p )
1999				end
2000			end
2001		end
2002
2003		f:close( )
2004	end
2005
2006	--
2007	-- Tests if 'path' is excluded.
2008	--
2009	local function test
2010	(
2011		self,  -- self
2012		path   -- the path to test
2013	)
2014		if path:byte( 1 ) ~= 47
2015		then
2016			error( 'Paths for exlusion tests must start with \'/\'' )
2017		end
2018
2019		for _, p in pairs( self.list )
2020		do
2021			if p:byte( -1 ) == 36
2022			then
2023				-- ends with $
2024				if path:match( p )
2025				then
2026					return true
2027				end
2028			else
2029				-- ends either end with / or $
2030				if path:match( p .. '/' )
2031				or path:match( p .. '$' )
2032				then
2033					return true
2034				end
2035			end
2036		end
2037
2038		return false
2039	end
2040
2041	--
2042	-- Cretes a new exclude set.
2043	--
2044	local function new
2045	( )
2046		return {
2047			list = { },
2048
2049			-- functions
2050			add      = add,
2051			addList  = addList,
2052			loadFile = loadFile,
2053			remove   = remove,
2054			test     = test,
2055		}
2056	end
2057
2058	--
2059	-- Public interface.
2060	--
2061	return { new = new }
2062end )( )
2063
2064
2065--
2066-- A set of filter patterns.
2067--
2068-- Filters allow excludes and includes
2069--
2070local Filters = ( function
2071( )
2072	--
2073	-- Turns a rsync like file pattern to a lua pattern.
2074	-- ( at best it can )
2075	--
2076	local function toLuaPattern
2077	(
2078		p  --  the rsync like pattern
2079	)
2080		local o = p
2081
2082		p = string.gsub( p, '%%', '%%%%'  )
2083		p = string.gsub( p, '%^', '%%^'   )
2084		p = string.gsub( p, '%$', '%%$'   )
2085		p = string.gsub( p, '%(', '%%('   )
2086		p = string.gsub( p, '%)', '%%)'   )
2087		p = string.gsub( p, '%.', '%%.'   )
2088		p = string.gsub( p, '%[', '%%['   )
2089		p = string.gsub( p, '%]', '%%]'   )
2090		p = string.gsub( p, '%+', '%%+'   )
2091		p = string.gsub( p, '%-', '%%-'   )
2092		p = string.gsub( p, '%?', '[^/]'  )
2093		p = string.gsub( p, '%*', '[^/]*' )
2094		-- this was a ** before
2095		p = string.gsub( p, '%[%^/%]%*%[%^/%]%*', '.*' )
2096		p = string.gsub( p, '^/', '^/'    )
2097
2098		if p:sub( 1, 2 ) ~= '^/'
2099		then
2100			-- if does not begin with '^/'
2101			-- then all matches should begin with '/'.
2102			p = '/' .. p;
2103		end
2104
2105		log( 'Filter', 'toLuaPattern "', o, '" = "', p, '"' )
2106
2107		return p
2108	end
2109
2110	--
2111	-- Appends a filter pattern
2112	--
2113	local function append
2114	(
2115		self,    -- the filters object
2116		line     -- filter line
2117	)
2118		local rule, pattern = string.match( line, '%s*([+|-])%s*(.*)' )
2119
2120		if not rule or not pattern
2121		then
2122			log( 'Error', 'Unknown filter rule: "', line, '"' )
2123			terminate( -1 )
2124		end
2125
2126		local lp = toLuaPattern( pattern )
2127
2128		table.insert( self. list, { rule = rule, pattern = pattern, lp = lp } )
2129	end
2130
2131	--
2132	-- Adds a list of patterns to exclude.
2133	--
2134	local function appendList
2135	(
2136		self,
2137		plist
2138	)
2139		for _, v in ipairs( plist )
2140		do
2141			append( self, v )
2142		end
2143	end
2144
2145	--
2146	-- Loads the filters from a file.
2147	--
2148	local function loadFile
2149	(
2150		self,  -- self
2151		file   -- filename to load from
2152	)
2153		f, err = io.open( file )
2154
2155		if not f
2156		then
2157			log( 'Error', 'Cannot open filter file "', file, '": ', err )
2158
2159			terminate( -1 )
2160		end
2161
2162	    for line in f:lines( )
2163		do
2164			if string.match( line, '^%s*#' )
2165			or string.match( line, '^%s*$' )
2166			then
2167				-- a comment or empty line: ignore
2168			else
2169				append( self, line )
2170			end
2171		end
2172
2173		f:close( )
2174	end
2175
2176	--
2177	-- Tests if 'path' is filtered.
2178	--
2179	local function test
2180	(
2181		self,  -- self
2182		path   -- the path to test
2183	)
2184		if path:byte( 1 ) ~= 47
2185		then
2186			error( 'Paths for filter tests must start with \'/\'' )
2187		end
2188
2189		for _, entry in ipairs( self.list )
2190		do
2191			local rule = entry.rule
2192			local lp = entry.lp -- lua pattern
2193
2194			if lp:byte( -1 ) == 36
2195			then
2196				-- ends with $
2197				if path:match( lp )
2198				then
2199					return rule == '-'
2200				end
2201			else
2202				-- ends either end with / or $
2203				if path:match( lp .. '/' )
2204				or path:match( lp .. '$' )
2205				then
2206					return rule == '-'
2207				end
2208			end
2209		end
2210
2211		-- nil means neither a positivie
2212		-- or negative hit, thus excludes have to
2213		-- be queried
2214		return nil
2215	end
2216
2217	--
2218	-- Cretes a new filter set.
2219	--
2220	local function new
2221	( )
2222		return {
2223			list = { },
2224			-- functions
2225			append     = append,
2226			appendList = appendList,
2227			loadFile   = loadFile,
2228			test       = test,
2229		}
2230	end
2231
2232
2233	--
2234	-- Public interface.
2235	--
2236	return { new = new }
2237
2238end )( )
2239
2240
2241
2242--
2243-- Holds information about one observed directory including subdirs.
2244--
2245local Sync = ( function
2246( )
2247	--
2248	-- Syncs that have no name specified by the user script
2249	-- get an incremental default name 'Sync[X]'
2250	--
2251	local nextDefaultName = 1
2252
2253	--
2254	-- Adds an exclude.
2255	--
2256	local function addExclude
2257	(
2258		self,
2259		pattern
2260	)
2261		return self.excludes:add( pattern )
2262	end
2263
2264	local function appendFilter
2265	(
2266		self,
2267		rule,
2268		pattern
2269	)
2270		if not self.filters then self.filters = Filters.new( ) end
2271
2272		return self.filters:append( rule, pattern )
2273	end
2274
2275	--
2276	-- Removes an exclude.
2277	--
2278	local function rmExclude
2279	(
2280		self,
2281		pattern
2282	)
2283		return self.excludes:remove( pattern )
2284	end
2285
2286	--
2287	-- Removes a delay.
2288	--
2289	local function removeDelay
2290	(
2291		self,
2292		delay
2293	)
2294		if self.delays[ delay.dpos ] ~= delay
2295		then
2296			error( 'Queue is broken, delay not at dpos' )
2297		end
2298
2299		self.delays:remove( delay.dpos )
2300
2301		-- frees all delays blocked by this one.
2302		if delay.blocks
2303		then
2304			for _, vd in pairs( delay.blocks )
2305			do
2306				vd.status = 'wait'
2307			end
2308		end
2309	end
2310
2311
2312	--
2313	-- Returns true if the relative path is excluded or filtered
2314	--
2315	local function testFilter
2316	(
2317		self,   -- the Sync
2318		path    -- the relative path
2319	)
2320		-- never filter the relative root itself
2321		-- ( that would make zero sense )
2322		if path == '/' then return false end
2323
2324		local filter = self.filters and self.filters:test( path )
2325
2326		if filter ~= nil then return filter end
2327
2328		-- otherwise check excludes if concerned
2329		return self.excludes:test( path )
2330	end
2331
2332	--
2333	-- Returns true if this Sync concerns about 'path'.
2334	--
2335	local function concerns
2336	(
2337		self,    -- the Sync
2338		path     -- the absolute path
2339	)
2340		-- not concerned if watch rootdir doesn't match
2341		if not path:starts( self.source )
2342		then
2343			return false
2344		end
2345
2346		-- a sub dir and not concerned about subdirs
2347		if self.config.subdirs == false
2348		and path:sub( #self.source, -1 ):match( '[^/]+/?' )
2349		then
2350			return false
2351		end
2352
2353		return not testFilter( self, path:sub( #self.source ) )
2354	end
2355
2356	--
2357	-- Collects a child process.
2358	--
2359	local function collect
2360	(
2361		self,     -- the sync
2362		pid,      -- process id of collected child process
2363		exitcode  -- exitcode of child process
2364	)
2365		local delay = self.processes[ pid ]
2366
2367		-- not a child of this sync?
2368		if not delay then return end
2369
2370		if delay.status
2371		then
2372			log( 'Delay', 'collected an event' )
2373
2374			if delay.status ~= 'active'
2375			then
2376				error( 'collecting a non-active process' )
2377			end
2378
2379			local rc = self.config.collect(
2380				InletFactory.d2e( delay ),
2381				exitcode
2382			)
2383
2384			if rc == 'die'
2385			then
2386				log( 'Error', 'Critical exitcode.' )
2387
2388				terminate( -1 )
2389			elseif rc ~= 'again'
2390			then
2391				-- if its active again the collecter restarted the event
2392				removeDelay( self, delay )
2393
2394				log(
2395					'Delay',
2396					'Finish of ',
2397					delay.etype,
2398					' on ',
2399					self.source,delay.path,
2400					' = ',
2401					exitcode
2402				)
2403			else
2404				-- sets the delay on wait again
2405				local alarm = self.config.delay
2406
2407				-- delays at least 1 second
2408				if alarm < 1 then alarm = 1 end
2409
2410				delay:wait( now( ) + alarm )
2411			end
2412		else
2413			log( 'Delay', 'collected a list' )
2414
2415			local rc = self.config.collect(
2416				InletFactory.dl2el( delay ),
2417				exitcode
2418			)
2419
2420			if rc == 'die'
2421			then
2422				log( 'Error', 'Critical exitcode.' );
2423
2424				terminate( -1 )
2425			elseif rc == 'again'
2426			then
2427				-- sets the delay on wait again
2428				local alarm = self.config.delay
2429
2430				-- delays are at least 1 second
2431				if alarm < 1 then alarm = 1 end
2432
2433				alarm = now() + alarm
2434
2435				for _, d in ipairs( delay )
2436				do
2437					d:wait( alarm )
2438				end
2439			else
2440				for _, d in ipairs( delay )
2441				do
2442					removeDelay( self, d )
2443				end
2444			end
2445
2446			log( 'Delay','Finished list = ',exitcode )
2447		end
2448
2449		self.processes[ pid ] = nil
2450	end
2451
2452	--
2453	-- Stacks a newDelay on the oldDelay,
2454	-- the oldDelay blocks the new Delay.
2455	--
2456	-- A delay can block 'n' other delays,
2457	-- but is blocked at most by one, the latest delay.
2458	--
2459	local function stack
2460	(
2461		oldDelay,
2462		newDelay
2463	)
2464		newDelay:blockedBy( oldDelay )
2465	end
2466
2467	--
2468	-- Puts an action on the delay stack.
2469	--
2470	local function delay
2471	(
2472		self,   -- the sync
2473		etype,  -- the event type
2474		time,   -- time of the event
2475		path,   -- path of the event
2476		path2   -- desitination path of move events
2477	)
2478		log(
2479			'Function',
2480			'delay( ',
2481				self.config.name, ', ',
2482				etype, ', ',
2483				path, ', ',
2484				path2,
2485			' )'
2486		)
2487
2488		--
2489		-- In case new directories were created
2490		-- looks through this directories and makes create events for
2491		-- new stuff found in there.
2492		--
2493		local function recurse
2494		( )
2495			if etype == 'Create' and path:byte( -1 ) == 47
2496			then
2497				local entries = lsyncd.readdir( self.source .. path )
2498
2499				if entries
2500				then
2501					for dirname, isdir in pairs( entries )
2502					do
2503						local pd = path .. dirname
2504
2505						if isdir then pd = pd..'/' end
2506
2507						log( 'Delay', 'Create creates Create on ', pd )
2508
2509						delay( self, 'Create', time, pd, nil )
2510					end
2511				end
2512			end
2513		end
2514
2515		-- exclusion tests
2516		if not path2
2517		then
2518			-- simple test for single path events
2519			if testFilter( self, path )
2520			then
2521				log( 'Filter', 'filtered ', etype, ' on "', path, '"' )
2522
2523				return
2524			end
2525		else
2526			-- for double paths ( move ) it might result into a split
2527			local ex1 = testFilter( self, path )
2528
2529			local ex2 = testFilter( self, path2 )
2530
2531			if ex1 and ex2
2532			then
2533				log(
2534					'Filter',
2535					'filtered "', etype, ' on "', path,
2536					'" -> "', path2, '"'
2537				)
2538
2539				return
2540			elseif not ex1 and ex2
2541			then
2542				-- splits the move if only partly excluded
2543				log(
2544					'Filter',
2545					'filtered destination transformed ',
2546					etype,
2547					' to Delete ',
2548					path
2549				)
2550
2551				 delay( self, 'Delete', time, path, nil )
2552
2553				return
2554			elseif ex1 and not ex2
2555			then
2556				-- splits the move if only partly excluded
2557				log(
2558					'Filter',
2559					'filtered origin transformed ',
2560					etype,
2561					' to Create.',
2562					path2
2563				)
2564
2565				delay( self, 'Create', time, path2, nil )
2566
2567				return
2568			end
2569		end
2570
2571		if etype == 'Move'
2572		and not self.config.onMove
2573		then
2574			-- if there is no move action defined,
2575			-- split a move as delete/create
2576			-- layer 1 scripts which want moves events have to
2577			-- set onMove simply to 'true'
2578			log( 'Delay', 'splitting Move into Delete & Create' )
2579
2580			delay( self, 'Delete', time, path,  nil )
2581
2582			delay( self, 'Create', time, path2, nil )
2583
2584			return
2585		end
2586
2587		-- creates the new action
2588		local alarm
2589
2590		if time and self.config.delay
2591		then
2592			alarm = time + self.config.delay
2593		else
2594			alarm = now( )
2595		end
2596
2597		-- new delay
2598		local nd = Delay.new( etype, self, alarm, path, path2 )
2599
2600		if nd.etype == 'Init' or nd.etype == 'Blanket'
2601		then
2602			-- always stack init or blanket events on the last event
2603			log(
2604				'Delay',
2605				'Stacking ',
2606				nd.etype,
2607				' event.'
2608			)
2609
2610			if self.delays:size( ) > 0
2611			then
2612				stack( self.delays:last( ), nd )
2613			end
2614
2615			nd.dpos = self.delays:push( nd )
2616
2617			recurse( )
2618
2619			return
2620		end
2621
2622		-- detects blocks and combos by working from back until
2623		-- front through the fifo
2624		for il, od in self.delays:qpairsReverse( )
2625		do
2626			-- asks Combiner what to do
2627			local ac = Combiner.combine( od, nd )
2628
2629			if ac
2630			then
2631				Combiner.log( ac, od, nd )
2632
2633				if ac == 'remove'
2634				then
2635					self.delays:remove( il )
2636				elseif ac == 'stack'
2637				then
2638					stack( od, nd )
2639
2640					nd.dpos = self.delays:push( nd )
2641				elseif ac == 'toDelete,stack'
2642				then
2643					if od.status ~= 'active'
2644					then
2645						-- turns olddelay into a delete
2646						local rd = Delay.new( 'Delete', self, od.alarm, od.path )
2647
2648						self.delays:replace( il, rd )
2649
2650						rd.dpos = il
2651
2652						-- and stacks delay2
2653						stack( rd, nd )
2654					else
2655						-- and stacks delay2
2656						stack( od, nd )
2657					end
2658
2659					nd.dpos = self.delays:push( nd )
2660				elseif ac == 'absorb'
2661				then
2662					-- nada
2663				elseif ac == 'replace'
2664				then
2665					if od.status ~= 'active'
2666					then
2667						self.delays:replace( il, nd )
2668
2669						nd.dpos = il
2670					else
2671						stack( od, nd )
2672
2673						nd.dpos = self.delays:push( nd )
2674					end
2675				elseif ac == 'split'
2676				then
2677					delay( self, 'Delete', time, path,  nil )
2678
2679					delay( self, 'Create', time, path2, nil )
2680				else
2681					error( 'unknown result of combine()' )
2682				end
2683
2684				recurse( )
2685
2686				return
2687			end
2688
2689			il = il - 1
2690		end
2691
2692		if nd.path2
2693		then
2694			log( 'Delay', 'New ', nd.etype, ': ', nd.path, ' -> ', nd.path2 )
2695		else
2696			log( 'Delay', 'New ', nd.etype, ': ', nd.path )
2697		end
2698
2699		-- no block or combo
2700		nd.dpos = self.delays:push( nd )
2701
2702		recurse( )
2703	end
2704
2705	--
2706	-- Returns the soonest alarm for this Sync.
2707	--
2708	local function getAlarm
2709	(
2710		self
2711	)
2712		if self.processes:size( ) >= self.config.maxProcesses
2713		then
2714			return false
2715		end
2716
2717		-- first checks if more processes could be spawned
2718		if self.processes:size( ) < self.config.maxProcesses
2719		then
2720			-- finds the nearest delay waiting to be spawned
2721			for _, d in self.delays:qpairs( )
2722			do
2723				if d.status == 'wait'
2724				then
2725					return d.alarm
2726				end
2727			end
2728		end
2729
2730		-- nothing to spawn
2731		return false
2732	end
2733
2734	--
2735	-- Gets all delays that are not blocked by active delays.
2736	--
2737	local function getDelays
2738	(
2739		self,  -- the sync
2740		test   -- function to test each delay
2741	)
2742		local dlist  = { sync = self }
2743
2744		local dlistn = 1
2745
2746		local blocks = { }
2747
2748		--
2749		-- inheritly transfers all blocks from delay
2750		--
2751		local function getBlocks
2752		(
2753			delay
2754		)
2755			blocks[ delay ] = true
2756
2757			if delay.blocks
2758			then
2759				for _, d in ipairs( delay.blocks )
2760				do
2761					getBlocks( d )
2762				end
2763			end
2764		end
2765
2766		for _, d in self.delays:qpairs( )
2767		do
2768			local tr = true
2769
2770			if test
2771			then
2772				tr = test( InletFactory.d2e( d ) )
2773			end
2774
2775			if tr == 'break' then break end
2776
2777			if d.status == 'active' or not tr
2778			then
2779				getBlocks( d )
2780			elseif not blocks[ d ]
2781			then
2782				dlist[ dlistn ] = d
2783
2784				dlistn = dlistn + 1
2785			end
2786		end
2787
2788		return dlist
2789	end
2790
2791	--
2792	-- Creates new actions
2793	--
2794	local function invokeActions
2795	(
2796		self,
2797		timestamp
2798	)
2799		log(
2800			'Function',
2801			'invokeActions( "',
2802				self.config.name, '", ',
2803				timestamp,
2804			' )'
2805		)
2806
2807		if self.processes:size( ) >= self.config.maxProcesses
2808		then
2809			-- no new processes
2810			return
2811		end
2812
2813		for _, d in self.delays:qpairs( )
2814		do
2815			-- if reached the global limit return
2816			if uSettings.maxProcesses
2817			and processCount >= uSettings.maxProcesses
2818			then
2819				log('Alarm', 'at global process limit.')
2820
2821				return
2822			end
2823
2824			if self.delays:size( ) < self.config.maxDelays
2825			then
2826				-- time constrains are only concerned if not maxed
2827				-- the delay FIFO already.
2828				if d.alarm ~= true and timestamp < d.alarm
2829				then
2830					-- reached point in stack where delays are in future
2831					return
2832				end
2833			end
2834
2835			if d.status == 'wait'
2836			then
2837				-- found a waiting delay
2838				if d.etype ~= 'Init'
2839				then
2840					self.config.action( self.inlet )
2841				else
2842					self.config.init( InletFactory.d2e( d ) )
2843				end
2844
2845				if self.processes:size( ) >= self.config.maxProcesses
2846				then
2847					-- no further processes
2848					return
2849				end
2850			end
2851		end
2852	end
2853
2854	--
2855	-- Gets the next event to be processed.
2856	--
2857	local function getNextDelay
2858	(
2859		self,
2860		timestamp
2861	)
2862		for i, d in self.delays:qpairs( )
2863		do
2864			if self.delays:size( ) < self.config.maxDelays
2865			then
2866				-- time constrains are only concerned if not maxed
2867				-- the delay FIFO already.
2868				if d.alarm ~= true and timestamp < d.alarm
2869				then
2870					-- reached point in stack where delays are in future
2871					return nil
2872				end
2873			end
2874
2875			if d.status == 'wait'
2876			then
2877				-- found a waiting delay
2878				return d
2879			end
2880		end
2881	end
2882
2883	--
2884	-- Adds and returns a blanket delay thats blocks all.
2885	-- Used as custom marker.
2886	--
2887	local function addBlanketDelay
2888	(
2889		self
2890	)
2891		local newd = Delay.new( 'Blanket', self, true, '' )
2892
2893		newd.dpos = self.delays:push( newd )
2894
2895		return newd
2896	end
2897
2898	--
2899	-- Adds and returns a blanket delay thats blocks all.
2900	-- Used as startup marker to call init asap.
2901	--
2902	local function addInitDelay
2903	(
2904		self
2905	)
2906		local newd = Delay.new( 'Init', self, true, '' )
2907
2908		newd.dpos = self.delays:push( newd )
2909
2910		return newd
2911	end
2912
2913	--
2914	-- Writes a status report about delays in this sync.
2915	--
2916	local function statusReport
2917	(
2918		self,
2919		f
2920	)
2921		local spaces = '                    '
2922
2923		f:write( self.config.name, ' source=', self.source, '\n' )
2924
2925		f:write( 'There are ', self.delays:size( ), ' delays\n')
2926
2927		for i, vd in self.delays:qpairs( )
2928		do
2929			local st = vd.status
2930
2931			f:write( st, string.sub( spaces, 1, 7 - #st ) )
2932			f:write( vd.etype, ' ' )
2933			f:write( vd.path )
2934
2935			if vd.path2
2936			then
2937				f:write( ' -> ',vd.path2 )
2938			end
2939
2940			f:write('\n')
2941
2942		end
2943
2944		f:write( 'Filtering:\n' )
2945
2946		local nothing = true
2947
2948		if self.filters
2949		then
2950			for _, e in pairs( self.filters.list )
2951			do
2952				nothing = false
2953
2954				f:write( e.rule, ' ', e.pattern,'\n' )
2955			end
2956		end
2957
2958		if #self.excludes.list > 0
2959		then
2960			f:write( 'From excludes:\n' )
2961
2962			for t, p in pairs( self.excludes.list )
2963			do
2964				nothing = false
2965
2966				f:write( '- ', t,'\n' )
2967			end
2968		end
2969
2970		if nothing
2971		then
2972			f:write('  nothing.\n')
2973		end
2974
2975		f:write( '\n' )
2976	end
2977
2978	--
2979	-- Creates a new Sync.
2980	--
2981	local function new
2982	(
2983		config
2984	)
2985		local s =
2986		{
2987			-- fields
2988			config = config,
2989			delays = Queue.new( ),
2990			source = config.source,
2991			processes = CountArray.new( ),
2992			excludes = Excludes.new( ),
2993			filters = nil,
2994
2995			-- functions
2996			addBlanketDelay = addBlanketDelay,
2997			addExclude      = addExclude,
2998			addInitDelay    = addInitDelay,
2999			appendFilter    = appendFilter,
3000			collect         = collect,
3001			concerns        = concerns,
3002			delay           = delay,
3003			getAlarm        = getAlarm,
3004			getDelays       = getDelays,
3005			getNextDelay    = getNextDelay,
3006			invokeActions   = invokeActions,
3007			removeDelay     = removeDelay,
3008			rmExclude       = rmExclude,
3009			statusReport    = statusReport,
3010		}
3011
3012		s.inlet = InletFactory.newInlet( s )
3013
3014		-- provides a default name if needed
3015		if not config.name
3016		then
3017			config.name = 'Sync' .. nextDefaultName
3018		end
3019
3020		-- increments defaults if a config name was given or not
3021		-- so Sync{n} will be the n-th call to sync{}
3022		nextDefaultName = nextDefaultName + 1
3023
3024		-- loads filters
3025		if config.filter
3026		then
3027			local te = type( config.filter )
3028
3029			s.filters = Filters.new( )
3030
3031			if te == 'table'
3032			then
3033				s.filters:appendList( config.filter )
3034			elseif te == 'string'
3035			then
3036				s.filters:append( config.filter )
3037			else
3038				error( 'type for filter must be table or string', 2 )
3039			end
3040
3041		end
3042
3043		-- loads exclusions
3044		if config.exclude
3045		then
3046			local te = type( config.exclude )
3047
3048			if te == 'table'
3049			then
3050				s.excludes:addList( config.exclude )
3051			elseif te == 'string'
3052			then
3053				s.excludes:add( config.exclude )
3054			else
3055				error( 'type for exclude must be table or string', 2 )
3056			end
3057
3058		end
3059
3060		if config.delay ~= nil
3061		and ( type( config.delay ) ~= 'number' or config.delay < 0 )
3062		then
3063			error( 'delay must be a number and >= 0', 2 )
3064		end
3065
3066		if config.filterFrom
3067		then
3068			if not s.filters then s.filters = Filters.new( ) end
3069
3070			s.filters:loadFile( config.filterFrom )
3071		end
3072
3073		if config.excludeFrom
3074		then
3075			s.excludes:loadFile( config.excludeFrom )
3076		end
3077
3078		return s
3079	end
3080
3081	--
3082	-- Public interface
3083	--
3084	return { new = new }
3085end )( )
3086
3087
3088--
3089-- Syncs - a singleton
3090--
3091-- Syncs maintains all configured syncs.
3092--
3093local Syncs = ( function
3094( )
3095	--
3096	-- the list of all syncs
3097	--
3098	local syncsList = Array.new( )
3099
3100	--
3101	-- The round robin pointer. In case of global limited maxProcesses
3102	-- gives every sync equal chances to spawn the next process.
3103	--
3104	local round = 1
3105
3106	--
3107	-- The cycle( ) sheduler goes into the next round of roundrobin.
3108	--
3109	local function nextRound
3110	( )
3111		round = round + 1;
3112
3113		if round > #syncsList
3114		then
3115			round = 1
3116		end
3117
3118		return round
3119	end
3120
3121	--
3122	-- Returns the round
3123	--
3124	local function getRound
3125	( )
3126		return round
3127	end
3128
3129	--
3130	-- Returns sync at listpos i
3131	--
3132	local function get
3133	( i )
3134		return syncsList[ i ];
3135	end
3136
3137	--
3138	-- Helper function for inherit
3139	-- defined below
3140	--
3141	local inheritKV
3142
3143	--
3144	-- Recurvely inherits a source table to a destionation table
3145	-- copying all keys from source.
3146	--
3147	-- All entries with integer keys are inherited as additional
3148	-- sources for non-verbatim tables
3149	--
3150	local function inherit
3151	(
3152		cd,       -- table copy destination
3153		cs,       -- table copy source
3154		verbatim  -- forced verbatim ( for e.g. 'exitcodes' )
3155	)
3156		-- First copies all entries with non-integer keys.
3157		--
3158		-- Tables are merged; already present keys are not
3159		-- overwritten
3160		--
3161		-- For verbatim tables integer keys are treated like
3162		-- non-integer keys
3163		for k, v in pairs( cs )
3164		do
3165			if
3166				(
3167					type( k ) ~= 'number'
3168					or verbatim
3169					or cs._verbatim == true
3170				)
3171				and
3172				(
3173					type( cs._merge ) ~= 'table'
3174					or cs._merge[ k ] == true
3175				)
3176			then
3177				inheritKV( cd, k, v )
3178			end
3179		end
3180
3181		-- recursevely inherits all integer keyed tables
3182		-- ( for non-verbatim tables )
3183		if cs._verbatim ~= true
3184		then
3185			for k, v in ipairs( cs )
3186			do
3187				if type( v ) == 'table'
3188				then
3189					inherit( cd, v )
3190				else
3191					cd[ #cd + 1 ] = v
3192				end
3193			end
3194
3195		end
3196	end
3197
3198	--
3199	-- Helper to inherit. Inherits one key.
3200	--
3201	inheritKV =
3202		function(
3203			cd,  -- table copy destination
3204			k,   -- key
3205			v    -- value
3206		)
3207
3208		-- don't merge inheritance controls
3209		if k == '_merge' or k == '_verbatim' then return end
3210
3211		local dtype = type( cd [ k ] )
3212
3213		if type( v ) == 'table'
3214		then
3215			if dtype == 'nil'
3216			then
3217				cd[ k ] = { }
3218				inherit( cd[ k ], v, k == 'exitcodes' )
3219			elseif
3220				dtype == 'table' and
3221				v._merge ~= false
3222			then
3223				inherit( cd[ k ], v, k == 'exitcodes' )
3224			end
3225		elseif dtype == 'nil'
3226		then
3227			cd[ k ] = v
3228		end
3229	end
3230
3231
3232	--
3233	-- Adds a new sync.
3234	--
3235	local function add
3236	(
3237		config
3238	)
3239		-- Checks if user overwrote the settings function.
3240		-- ( was Lsyncd <2.1 style )
3241		if settings ~= settingsSafe
3242		then
3243			log(
3244				'Error',
3245				'Do not use settings = { ... }\n'..
3246				'      please use settings{ ... } (without the equal sign)'
3247			)
3248
3249			os.exit( -1 )
3250		end
3251
3252		-- Creates a new config table which inherits all keys/values
3253		-- from integer keyed tables
3254		local uconfig = config
3255
3256		config = { }
3257
3258		inherit( config, uconfig )
3259
3260		--
3261		-- last and least defaults are inherited
3262		--
3263		inherit( config, default )
3264
3265		local inheritSettings = {
3266			'delay',
3267			'maxDelays',
3268			'maxProcesses'
3269		}
3270
3271		-- Lets settings override these values.
3272		for _, v in ipairs( inheritSettings )
3273		do
3274			if uSettings[ v ]
3275			then
3276				config[ v ] = uSettings[ v ]
3277			end
3278		end
3279
3280		-- Lets commandline override these values.
3281		for _, v in ipairs( inheritSettings )
3282		do
3283			if clSettings[ v ]
3284			then
3285				config[ v ] = clSettings[ v ]
3286			end
3287		end
3288
3289		--
3290		-- lets the userscript 'prepare' function
3291		-- check and complete the config
3292		--
3293		if type( config.prepare ) == 'function'
3294		then
3295			-- prepare is given a writeable copy of config
3296			config.prepare( config, 4 )
3297		end
3298
3299		if not config[ 'source' ]
3300		then
3301			local info = debug.getinfo( 3, 'Sl' )
3302
3303			log(
3304				'Error',
3305				info.short_src,':',
3306				info.currentline,': source missing from sync.'
3307			)
3308
3309			terminate( -1 )
3310		end
3311
3312		--
3313		-- absolute path of source
3314		--
3315		local realsrc = lsyncd.realdir( config.source )
3316
3317		if not realsrc
3318		then
3319			log(
3320				'Error',
3321				'Cannot access source directory: ',
3322				config.source
3323			)
3324
3325			terminate( -1 )
3326		end
3327
3328		config._source = config.source
3329		config.source = realsrc
3330
3331		if not config.action
3332		and not config.onAttrib
3333		and not config.onCreate
3334		and not config.onModify
3335		and not config.onDelete
3336		and not config.onMove
3337		then
3338			local info = debug.getinfo( 3, 'Sl' )
3339
3340			log(
3341				'Error',
3342				info.short_src, ':',
3343				info.currentline,
3344				': no actions specified.'
3345			)
3346
3347			terminate( -1 )
3348		end
3349
3350		-- the monitor to use
3351		config.monitor =
3352			uSettings.monitor or
3353			config.monitor or
3354			Monitors.default( )
3355
3356		if config.monitor ~= 'inotify'
3357		and config.monitor ~= 'fsevents'
3358		then
3359			local info = debug.getinfo( 3, 'Sl' )
3360
3361			log(
3362				'Error',
3363				info.short_src, ':',
3364				info.currentline,
3365				': event monitor "',
3366				config.monitor,
3367				'" unknown.'
3368			)
3369
3370			terminate( -1 )
3371		end
3372
3373		-- creates the new sync
3374		local s = Sync.new( config )
3375
3376		table.insert( syncsList, s )
3377
3378		return s
3379	end
3380
3381	--
3382	-- Allows a for-loop to walk through all syncs.
3383	--
3384	local function iwalk
3385	( )
3386		return ipairs( syncsList )
3387	end
3388
3389	--
3390	-- Returns the number of syncs.
3391	--
3392	local size = function
3393	( )
3394		return #syncsList
3395	end
3396
3397	--
3398	-- Tests if any sync is interested in a path.
3399	--
3400	local function concerns
3401	(
3402		path
3403	)
3404		for _, s in ipairs( syncsList )
3405		do
3406			if s:concerns( path )
3407			then
3408				return true
3409			end
3410		end
3411
3412		return false
3413	end
3414
3415	--
3416	-- Public interface
3417	--
3418	return {
3419		add = add,
3420		get = get,
3421		getRound = getRound,
3422		concerns = concerns,
3423		iwalk = iwalk,
3424		nextRound = nextRound,
3425		size = size
3426	}
3427end )( )
3428
3429
3430--
3431-- Utility function,
3432-- Returns the relative part of absolute path if it
3433-- begins with root
3434--
3435local function splitPath
3436(
3437	path,
3438	root
3439)
3440	local rlen = #root
3441
3442	local sp = string.sub( path, 1, rlen )
3443
3444	if sp == root
3445	then
3446		return string.sub( path, rlen, -1 )
3447	else
3448		return nil
3449	end
3450end
3451
3452--
3453-- Interface to inotify.
3454--
3455-- watches recursively subdirs and sends events.
3456--
3457-- All inotify specific implementation is enclosed here.
3458--
3459local Inotify = ( function
3460( )
3461	--
3462	-- A list indexed by inotify watch descriptors yielding
3463	-- the directories absolute paths.
3464	--
3465	local wdpaths = CountArray.new( )
3466
3467	--
3468	-- The same vice versa,
3469	-- all watch descriptors by their absolute paths.
3470	--
3471	local pathwds = { }
3472
3473	--
3474	-- A list indexed by syncs containing yielding
3475	-- the root paths the syncs are interested in.
3476	--
3477	local syncRoots = { }
3478
3479	--
3480	-- Stops watching a directory
3481	--
3482	local function removeWatch
3483	(
3484		path,  -- absolute path to unwatch
3485		core   -- if false not actually send the unwatch to the kernel
3486		--        ( used in moves which reuse the watch )
3487	)
3488		local wd = pathwds[ path ]
3489
3490		if not wd
3491		then
3492			return
3493		end
3494
3495		if core
3496		then
3497			lsyncd.inotify.rmwatch( wd )
3498		end
3499
3500		wdpaths[ wd   ] = nil
3501		pathwds[ path ] = nil
3502	end
3503
3504
3505	--
3506	-- Adds watches for a directory (optionally) including all subdirectories.
3507	--
3508	--
3509	local function addWatch
3510	(
3511		path  -- absolute path of directory to observe
3512	)
3513		log( 'Function', 'Inotify.addWatch( ', path, ' )' )
3514
3515		if not Syncs.concerns( path )
3516		then
3517			log('Inotify', 'not concerning "', path, '"')
3518
3519			return
3520		end
3521
3522		-- registers the watch
3523		local inotifyMode = ( uSettings and uSettings.inotifyMode ) or '';
3524
3525		local wd = lsyncd.inotify.addwatch( path, inotifyMode ) ;
3526
3527		if wd < 0
3528		then
3529			log( 'Inotify','Unable to add watch "', path, '"' )
3530
3531			return
3532		end
3533
3534		do
3535			-- If this watch descriptor is registered already
3536			-- the kernel reuses it since the old dir is gone.
3537			local op = wdpaths[ wd ]
3538
3539			if op and op ~= path
3540			then
3541				pathwds[ op ] = nil
3542			end
3543		end
3544
3545		pathwds[ path ] = wd
3546
3547		wdpaths[ wd   ] = path
3548
3549		-- registers and adds watches for all subdirectories
3550		local entries = lsyncd.readdir( path )
3551
3552		if not entries
3553		then
3554			return
3555		end
3556
3557		for dirname, isdir in pairs( entries )
3558		do
3559			if isdir
3560			then
3561				addWatch( path .. dirname .. '/' )
3562			end
3563		end
3564	end
3565
3566	--
3567	-- Adds a Sync to receive events.
3568	--
3569	local function addSync
3570	(
3571		sync,     -- object to receive events.
3572		rootdir   -- root dir to watch
3573	)
3574		if syncRoots[ sync ]
3575		then
3576			error( 'duplicate sync in Inotify.addSync()' )
3577		end
3578
3579		syncRoots[ sync ] = rootdir
3580
3581		addWatch( rootdir )
3582	end
3583
3584	--
3585	-- Called when an event has occured.
3586	--
3587	local function event
3588	(
3589		etype,     -- 'Attrib', 'Modify', 'Create', 'Delete', 'Move'
3590		wd,        --  watch descriptor, matches lsyncd.inotifyadd()
3591		isdir,     --  true if filename is a directory
3592		time,      --  time of event
3593		filename,  --  string filename without path
3594		wd2,       --  watch descriptor for target if it's a Move
3595		filename2  --  string filename without path of Move target
3596	)
3597		if isdir
3598		then
3599			filename = filename .. '/'
3600
3601			if filename2
3602			then
3603				filename2 = filename2 .. '/'
3604			end
3605		end
3606
3607		if filename2
3608		then
3609			log(
3610				'Inotify',
3611				'got event ',
3612				etype,
3613				' ',
3614				filename,
3615				'(', wd, ') to ',
3616				filename2,
3617				'(', wd2 ,')'
3618			)
3619		else
3620			log(
3621				'Inotify',
3622				'got event ',
3623				etype,
3624				' ',
3625				filename,
3626				'(', wd, ')'
3627			)
3628		end
3629
3630		-- looks up the watch descriptor id
3631		local path = wdpaths[ wd ]
3632
3633		if path
3634		then
3635			path = path..filename
3636		end
3637
3638		local path2 = wd2 and wdpaths[ wd2 ]
3639
3640		if path2 and filename2
3641		then
3642			path2 = path2..filename2
3643		end
3644
3645		if not path and path2 and etype == 'Move'
3646		then
3647			log(
3648				'Inotify',
3649				'Move from deleted directory ',
3650				path2,
3651				' becomes Create.'
3652			)
3653
3654			path  = path2
3655
3656			path2 = nil
3657
3658			etype = 'Create'
3659		end
3660
3661		if not path
3662		then
3663			-- this is normal in case of deleted subdirs
3664			log(
3665				'Inotify',
3666				'event belongs to unknown watch descriptor.'
3667			)
3668
3669			return
3670		end
3671
3672		for sync, root in pairs( syncRoots )
3673		do repeat
3674			local relative  = splitPath( path, root )
3675
3676			local relative2 = nil
3677
3678			if path2
3679			then
3680				relative2 = splitPath( path2, root )
3681			end
3682
3683			if not relative and not relative2
3684			then
3685				-- sync is not interested in this dir
3686				break -- continue
3687			end
3688
3689			-- makes a copy of etype to possibly change it
3690			local etyped = etype
3691
3692			if etyped == 'Move'
3693			then
3694				if not relative2
3695				then
3696					log(
3697						'Normal',
3698						'Transformed Move to Delete for ',
3699						sync.config.name
3700					)
3701
3702					etyped = 'Delete'
3703				elseif not relative
3704				then
3705					relative = relative2
3706
3707					relative2 = nil
3708
3709					log(
3710						'Normal',
3711						'Transformed Move to Create for ',
3712						sync.config.name
3713					)
3714
3715					etyped = 'Create'
3716				end
3717			end
3718
3719			if isdir
3720			then
3721				if etyped == 'Create'
3722				then
3723					addWatch( path )
3724				elseif etyped == 'Delete'
3725				then
3726					removeWatch( path, true )
3727				elseif etyped == 'Move'
3728				then
3729					removeWatch( path, false )
3730					addWatch( path2 )
3731				end
3732			end
3733
3734			sync:delay( etyped, time, relative, relative2 )
3735
3736		until true end
3737	end
3738
3739	--
3740	-- Writes a status report about inotify to a file descriptor
3741	--
3742	local function statusReport( f )
3743
3744		f:write( 'Inotify watching ', wdpaths:size(), ' directories\n' )
3745
3746		for wd, path in wdpaths:walk( )
3747		do
3748			f:write( '  ', wd, ': ', path, '\n' )
3749		end
3750	end
3751
3752
3753	--
3754	-- Public interface.
3755	--
3756	return {
3757		addSync = addSync,
3758		event = event,
3759		statusReport = statusReport,
3760	}
3761
3762end)( )
3763
3764
3765--
3766-- Interface to OSX /dev/fsevents
3767--
3768-- This watches all the filesystems at once,
3769-- but needs root access.
3770--
3771-- All fsevents specific implementation are enclosed here.
3772--
3773local Fsevents = ( function
3774( )
3775	--
3776	-- A list indexed by syncs yielding
3777	-- the root path the sync is interested in.
3778	--
3779	local syncRoots = { }
3780
3781	--
3782	-- Adds a Sync to receive events.
3783	--
3784	local function addSync
3785	(
3786		sync,  -- object to receive events
3787		dir    -- dir to watch
3788	)
3789		if syncRoots[ sync ]
3790		then
3791			error( 'duplicate sync in Fanotify.addSync()' )
3792		end
3793
3794		syncRoots[ sync ] = dir
3795
3796	end
3797
3798	--
3799	-- Called when an event has occured.
3800	--
3801	local function event
3802	(
3803		etype,  --  'Attrib', 'Modify', 'Create', 'Delete', 'Move'
3804		isdir,  --  true if filename is a directory
3805		time,   --  time of event
3806		path,   --  path of file
3807		path2   --  path of target in case of 'Move'
3808	)
3809		if isdir
3810		then
3811			path = path .. '/'
3812
3813			if path2 then path2 = path2 .. '/' end
3814		end
3815
3816		log( 'Fsevents', etype, ',', isdir, ',', time,  ',', path, ',', path2 )
3817
3818		for _, sync in Syncs.iwalk()
3819		do repeat
3820
3821			local root = sync.source
3822
3823			-- TODO combine ifs
3824			if not path:starts( root )
3825			then
3826				if not path2 or not path2:starts( root )
3827				then
3828					break  -- continue
3829				end
3830			end
3831
3832			local relative = splitPath( path, root )
3833
3834			local relative2
3835
3836			if path2
3837			then
3838				relative2 = splitPath( path2, root )
3839			end
3840
3841			-- possibly change etype for this iteration only
3842			local etyped = etype
3843
3844			if etyped == 'Move'
3845			then
3846				if not relative2
3847				then
3848					log( 'Normal', 'Transformed Move to Delete for ', sync.config.name )
3849
3850					etyped = 'Delete'
3851
3852				elseif not relative
3853				then
3854					relative = relative2
3855
3856					relative2 = nil
3857
3858					log( 'Normal', 'Transformed Move to Create for ', sync.config.name )
3859
3860					etyped = 'Create'
3861				end
3862			end
3863
3864			sync:delay( etyped, time, relative, relative2 )
3865
3866		until true end
3867
3868	end
3869
3870
3871	--
3872	-- Writes a status report about fsevents to a filedescriptor.
3873	--
3874	local function statusReport
3875	(
3876		f
3877	)
3878		-- TODO
3879	end
3880
3881	--
3882	-- Public interface
3883	--
3884	return {
3885		addSync      = addSync,
3886		event        = event,
3887		statusReport = statusReport
3888	}
3889end )( )
3890
3891
3892--
3893-- Holds information about the event monitor capabilities
3894-- of the core.
3895--
3896Monitors = ( function
3897( )
3898	--
3899	-- The cores monitor list
3900	--
3901	local list = { }
3902
3903
3904	--
3905	-- The default event monitor.
3906	--
3907	local function default
3908	( )
3909		return list[ 1 ]
3910	end
3911
3912
3913	--
3914	-- Initializes with info received from core
3915	--
3916	local function initialize( clist )
3917		for k, v in ipairs( clist )
3918		do
3919			list[ k ] = v
3920		end
3921	end
3922
3923
3924	--
3925	-- Public interface
3926	--
3927	return {
3928		default = default,
3929		list = list,
3930		initialize = initialize
3931	}
3932
3933end)( )
3934
3935--
3936-- Writes functions for the user for layer 3 configurations.
3937--
3938local functionWriter = ( function( )
3939
3940	--
3941	-- All variables known to layer 3 configs.
3942	--
3943	transVars = {
3944		{ '%^pathname',          'event.pathname',        1 },
3945		{ '%^pathdir',           'event.pathdir',         1 },
3946		{ '%^path',              'event.path',            1 },
3947		{ '%^sourcePathname',    'event.sourcePathname',  1 },
3948		{ '%^sourcePathdir',     'event.sourcePathdir',   1 },
3949		{ '%^sourcePath',        'event.sourcePath',      1 },
3950		{ '%^source',            'event.source',          1 },
3951		{ '%^targetPathname',    'event.targetPathname',  1 },
3952		{ '%^targetPathdir',     'event.targetPathdir',   1 },
3953		{ '%^targetPath',        'event.targetPath',      1 },
3954		{ '%^target',            'event.target',          1 },
3955		{ '%^o%.pathname',       'event.pathname',        1 },
3956		{ '%^o%.path',           'event.path',            1 },
3957		{ '%^o%.sourcePathname', 'event.sourcePathname',  1 },
3958		{ '%^o%.sourcePathdir',  'event.sourcePathdir',   1 },
3959		{ '%^o%.sourcePath',     'event.sourcePath',      1 },
3960		{ '%^o%.targetPathname', 'event.targetPathname',  1 },
3961		{ '%^o%.targetPathdir',  'event.targetPathdir',   1 },
3962		{ '%^o%.targetPath',     'event.targetPath',      1 },
3963		{ '%^d%.pathname',       'event2.pathname',       2 },
3964		{ '%^d%.path',           'event2.path',           2 },
3965		{ '%^d%.sourcePathname', 'event2.sourcePathname', 2 },
3966		{ '%^d%.sourcePathdir',  'event2.sourcePathdir',  2 },
3967		{ '%^d%.sourcePath',     'event2.sourcePath',     2 },
3968		{ '%^d%.targetPathname', 'event2.targetPathname', 2 },
3969		{ '%^d%.targetPathdir',  'event2.targetPathdir',  2 },
3970		{ '%^d%.targetPath',     'event2.targetPath',     2 },
3971	}
3972
3973	--
3974	-- Splits a user string into its arguments.
3975	-- Returns a table of arguments
3976	--
3977	local function splitStr(
3978		str -- a string where parameters are seperated by spaces.
3979	)
3980		local args = { }
3981
3982		while str ~= ''
3983		do
3984			-- break where argument stops
3985			local bp = #str
3986
3987			-- in a quote
3988			local inQuote = false
3989
3990			-- tests characters to be space and not within quotes
3991			for i = 1, #str
3992			do
3993				local c = string.sub( str, i, i )
3994
3995				if c == '"'
3996				then
3997					inQuote = not inQuote
3998				elseif c == ' ' and not inQuote
3999				then
4000					bp = i - 1
4001
4002					break
4003				end
4004			end
4005
4006			local arg = string.sub( str, 1, bp )
4007			arg = string.gsub( arg, '"', '\\"' )
4008			table.insert( args, arg )
4009			str = string.sub( str, bp + 1, -1 )
4010			str = string.match( str, '^%s*(.-)%s*$' )
4011
4012		end
4013
4014		return args
4015	end
4016
4017
4018	--
4019	-- Translates a call to a binary to a lua function.
4020	-- TODO this has a little too blocking.
4021	--
4022	local function translateBinary
4023	(
4024		str
4025	)
4026		-- splits the string
4027		local args = splitStr( str )
4028
4029		-- true if there is a second event
4030		local haveEvent2 = false
4031
4032		for ia, iv in ipairs( args )
4033		do
4034			-- a list of arguments this arg is being split into
4035			local a = { { true, iv } }
4036
4037			-- goes through all translates
4038			for _, v in ipairs( transVars )
4039			do
4040				local ai = 1
4041				while ai <= #a
4042				do
4043					if a[ ai ][ 1 ]
4044					then
4045						local pre, post =
4046							string.match( a[ ai ][ 2 ], '(.*)'..v[1]..'(.*)' )
4047
4048						if pre
4049						then
4050							if v[3] > 1
4051							then
4052								haveEvent2 = true
4053							end
4054
4055							if pre ~= ''
4056							then
4057								table.insert( a, ai, { true, pre } )
4058								ai = ai + 1
4059							end
4060
4061							a[ ai ] = { false, v[ 2 ] }
4062
4063							if post ~= ''
4064							then
4065								table.insert( a, ai + 1, { true, post } )
4066							end
4067						end
4068					end
4069					ai = ai + 1
4070				end
4071			end
4072
4073			-- concats the argument pieces into a string.
4074			local as = ''
4075			local first = true
4076
4077			for _, v in ipairs( a )
4078			do
4079				if not first then as = as..' .. ' end
4080
4081				if v[ 1 ]
4082				then
4083					as = as .. '"' .. v[ 2 ] .. '"'
4084				else
4085					as = as .. v[ 2 ]
4086				end
4087
4088				first = false
4089			end
4090
4091			args[ ia ] = as
4092		end
4093
4094		local ft
4095
4096		if not haveEvent2
4097		then
4098			ft = 'function( event )\n'
4099		else
4100			ft = 'function( event, event2 )\n'
4101		end
4102
4103		ft = ft ..
4104			"    log('Normal', 'Event ', event.etype, \n" ..
4105			"        ' spawns action \"".. str.."\"')\n" ..
4106			"    spawn( event"
4107
4108		for _, v in ipairs( args )
4109		do
4110			ft = ft .. ',\n         ' .. v
4111		end
4112
4113		ft = ft .. ')\nend'
4114		return ft
4115
4116	end
4117
4118
4119	--
4120	-- Translates a call using a shell to a lua function
4121	--
4122	local function translateShell
4123	(
4124		str
4125	)
4126		local argn = 1
4127
4128		local args = { }
4129
4130		local cmd = str
4131
4132		local lc = str
4133
4134		-- true if there is a second event
4135		local haveEvent2 = false
4136
4137		for _, v in ipairs( transVars )
4138		do
4139			local occur = false
4140
4141			cmd = string.gsub(
4142				cmd,
4143				v[ 1 ],
4144				function
4145				( )
4146					occur = true
4147					return '"$' .. argn .. '"'
4148				end
4149			)
4150
4151			lc = string.gsub( lc, v[1], ']]..' .. v[2] .. '..[[' )
4152
4153			if occur
4154			then
4155				argn = argn + 1
4156
4157				table.insert( args, v[ 2 ] )
4158
4159				if v[ 3 ] > 1
4160				then
4161					haveEvent2 = true
4162				end
4163			end
4164
4165		end
4166
4167		local ft
4168
4169		if not haveEvent2
4170		then
4171			ft = 'function( event )\n'
4172		else
4173			ft = 'function( event, event2 )\n'
4174		end
4175
4176		-- TODO do array joining instead
4177		ft = ft..
4178			"    log('Normal', 'Event ',event.etype,\n"..
4179			"        [[ spawns shell \""..lc.."\"]])\n"..
4180			"    spawnShell(event, [["..cmd.."]]"
4181
4182		for _, v in ipairs( args )
4183		do
4184			ft = ft..',\n         '..v
4185		end
4186
4187		ft = ft .. ')\nend'
4188
4189		return ft
4190
4191	end
4192
4193	--
4194	-- Writes a lua function for a layer 3 user script.
4195	--
4196	local function translate
4197	(
4198		str
4199	)
4200		-- trims spaces
4201		str = string.match( str, '^%s*(.-)%s*$' )
4202
4203		local ft
4204
4205		if string.byte( str, 1, 1 ) == 47
4206		then
4207			-- starts with /
4208			 ft = translateBinary( str )
4209		elseif string.byte( str, 1, 1 ) == 94
4210		then
4211			-- starts with ^
4212			 ft = translateShell( str:sub( 2, -1 ) )
4213		else
4214			 ft = translateShell( str )
4215		end
4216
4217		log( 'FWrite', 'translated "', str, '" to \n', ft )
4218
4219		return ft
4220	end
4221
4222
4223	--
4224	-- Public interface.
4225	--
4226	return { translate = translate }
4227
4228end )( )
4229
4230
4231
4232--
4233-- Writes a status report file at most every 'statusintervall' seconds.
4234--
4235local StatusFile = ( function
4236( )
4237	--
4238	-- Timestamp when the status file has been written.
4239	--
4240	local lastWritten = false
4241
4242
4243	--
4244	-- Timestamp when a status file should be written.
4245	--
4246	local alarm = false
4247
4248
4249	--
4250	-- Returns the alarm when the status file should be written-
4251	--
4252	local function getAlarm
4253	( )
4254		return alarm
4255	end
4256
4257
4258	--
4259	-- Called to check if to write a status file.
4260	--
4261	local function write
4262	(
4263		timestamp
4264	)
4265		log( 'Function', 'write( ', timestamp, ' )' )
4266
4267		--
4268		-- takes care not write too often
4269		--
4270		if uSettings.statusInterval > 0
4271		then
4272			-- already waiting?
4273			if alarm and timestamp < alarm
4274			then
4275				log( 'Statusfile', 'waiting(', timestamp, ' < ', alarm, ')' )
4276
4277				return
4278			end
4279
4280			-- determines when a next write will be possible
4281			if not alarm
4282			then
4283				local nextWrite = lastWritten and timestamp + uSettings.statusInterval
4284
4285				if nextWrite and timestamp < nextWrite
4286				then
4287					log( 'Statusfile', 'setting alarm: ', nextWrite )
4288					alarm = nextWrite
4289
4290					return
4291				end
4292			end
4293
4294			lastWritten = timestamp
4295			alarm = false
4296		end
4297
4298		log( 'Statusfile', 'writing now' )
4299
4300		local f, err = io.open( uSettings.statusFile, 'w' )
4301
4302		if not f
4303		then
4304			log(
4305				'Error',
4306				'Cannot open status file "' ..
4307					uSettings.statusFile ..
4308					'" :' ..
4309					err
4310			)
4311			return
4312		end
4313
4314		f:write( 'Lsyncd status report at ', os.date( ), '\n\n' )
4315
4316		for i, s in Syncs.iwalk( )
4317		do
4318			s:statusReport( f )
4319
4320			f:write( '\n' )
4321		end
4322
4323		Inotify.statusReport( f )
4324
4325		f:close( )
4326	end
4327
4328
4329	--
4330	-- Public interface
4331	--
4332	return {
4333		write = write,
4334		getAlarm = getAlarm
4335	}
4336
4337end )( )
4338
4339
4340--
4341-- Lets userscripts make their own alarms.
4342--
4343local UserAlarms = ( function
4344( )
4345	local alarms = { }
4346
4347	--
4348	-- Calls the user function at timestamp.
4349	--
4350	local function alarm
4351	(
4352		timestamp,
4353		func,
4354		extra
4355	)
4356		local idx
4357
4358		for k, v in ipairs( alarms )
4359		do
4360			if timestamp < v.timestamp
4361			then
4362				idx = k
4363
4364				break
4365			end
4366		end
4367
4368		local a =
4369		{
4370			timestamp = timestamp,
4371			func = func,
4372			extra = extra
4373		}
4374
4375		if idx
4376		then
4377			table.insert( alarms, idx, a )
4378		else
4379			table.insert( alarms, a )
4380		end
4381	end
4382
4383
4384	--
4385	-- Retrieves the soonest alarm.
4386	--
4387	local function getAlarm
4388	( )
4389		if #alarms == 0
4390		then
4391			return false
4392		else
4393			return alarms[1].timestamp
4394		end
4395	end
4396
4397
4398	--
4399	-- Calls user alarms.
4400	--
4401	local function invoke
4402	(
4403		timestamp
4404	)
4405		while #alarms > 0
4406		and alarms[ 1 ].timestamp <= timestamp
4407		do
4408			alarms[ 1 ].func( alarms[ 1 ].timestamp, alarms[ 1 ].extra )
4409			table.remove( alarms, 1 )
4410		end
4411	end
4412
4413
4414	--
4415	-- Public interface
4416	--
4417	return {
4418		alarm    = alarm,
4419		getAlarm = getAlarm,
4420		invoke   = invoke
4421	}
4422
4423end )( )
4424
4425--============================================================================
4426-- Lsyncd runner's plugs. These functions are called from core.
4427--============================================================================
4428
4429--
4430-- Current status of Lsyncd.
4431--
4432-- 'init'  ... on (re)init
4433-- 'run'   ... normal operation
4434-- 'fade'  ... waits for remaining processes
4435--
4436local lsyncdStatus = 'init'
4437
4438--
4439-- The cores interface to the runner.
4440--
4441local runner = { }
4442
4443--
4444-- Last time said to be waiting for more child processes
4445--
4446local lastReportedWaiting = false
4447
4448--
4449-- Called from core whenever Lua code failed.
4450--
4451-- Logs a backtrace
4452--
4453function runner.callError
4454(
4455	message
4456)
4457	log( 'Error', 'in Lua: ', message )
4458
4459	-- prints backtrace
4460	local level = 2
4461
4462	while true
4463	do
4464		local info = debug.getinfo( level, 'Sl' )
4465
4466		if not info
4467		then
4468			terminate( -1 )
4469		end
4470
4471		log(
4472			'Error',
4473			'Backtrace ',
4474			level - 1, ' :',
4475			info.short_src, ':',
4476			info.currentline
4477		)
4478
4479		level = level + 1
4480	end
4481end
4482
4483
4484--
4485-- Called from core whenever a child process has finished and
4486-- the zombie process was collected by core.
4487--
4488function runner.collectProcess
4489(
4490	pid,       -- process id
4491	exitcode   -- exitcode
4492)
4493	processCount = processCount - 1
4494
4495	if processCount < 0
4496	then
4497		error( 'negative number of processes!' )
4498	end
4499
4500	for _, s in Syncs.iwalk( )
4501	do
4502		if s:collect( pid, exitcode ) then return end
4503	end
4504end
4505
4506--
4507-- Called from core everytime a masterloop cycle runs through.
4508--
4509-- This happens in case of
4510--   * an expired alarm.
4511--   * a returned child process.
4512--   * received filesystem events.
4513--   * received a HUP, TERM or INT signal.
4514--
4515function runner.cycle(
4516	timestamp   -- the current kernel time (in jiffies)
4517)
4518	log( 'Function', 'cycle( ', timestamp, ' )' )
4519
4520	if lsyncdStatus == 'fade'
4521	then
4522		if processCount > 0
4523		then
4524			if
4525				lastReportedWaiting == false or
4526				timestamp >= lastReportedWaiting + 60
4527			then
4528				lastReportedWaiting = timestamp
4529
4530				log(
4531					'Normal',
4532					'waiting for ',
4533					processCount,
4534					' more child processes.'
4535				)
4536			end
4537
4538			return true
4539		else
4540			return false
4541		end
4542	end
4543
4544	if lsyncdStatus ~= 'run'
4545	then
4546		error( 'runner.cycle() called while not running!' )
4547	end
4548
4549	--
4550	-- goes through all syncs and spawns more actions
4551	-- if possibly. But only let Syncs invoke actions if
4552	-- not at global limit
4553	--
4554	if not uSettings.maxProcesses
4555	or processCount < uSettings.maxProcesses
4556	then
4557		local start = Syncs.getRound( )
4558
4559		local ir = start
4560
4561		repeat
4562			local s = Syncs.get( ir )
4563
4564			s:invokeActions( timestamp )
4565
4566			ir = ir + 1
4567
4568			if ir > Syncs.size( )
4569			then
4570				ir = 1
4571			end
4572		until ir == start
4573
4574		Syncs.nextRound( )
4575	end
4576
4577	UserAlarms.invoke( timestamp )
4578
4579	if uSettings.statusFile
4580	then
4581		StatusFile.write( timestamp )
4582	end
4583
4584	return true
4585end
4586
4587--
4588-- Called by core if '-help' or '--help' is in
4589-- the arguments.
4590--
4591function runner.help( )
4592	io.stdout:write(
4593[[
4594
4595USAGE:
4596  runs a config file:
4597    lsyncd [OPTIONS] [CONFIG-FILE]
4598
4599  default rsync behaviour:
4600    lsyncd [OPTIONS] -rsync [SOURCE] [TARGET]
4601
4602  default rsync with mv's through ssh:
4603    lsyncd [OPTIONS] -rsyncssh [SOURCE] [HOST] [TARGETDIR]
4604
4605  default local copying mechanisms (cp|mv|rm):
4606    lsyncd [OPTIONS] -direct [SOURCE] [TARGETDIR]
4607
4608OPTIONS:
4609  -delay SECS         Overrides default delay times
4610  -help               Shows this
4611  -insist             Continues startup even if it cannot connect
4612  -log    all         Logs everything (debug)
4613  -log    scarce      Logs errors only
4614  -log    [Category]  Turns on logging for a debug category
4615  -logfile FILE       Writes log to FILE (DEFAULT: uses syslog)
4616  -nodaemon           Does not detach and logs to stdout/stderr
4617  -pidfile FILE       Writes Lsyncds PID into FILE
4618  -runner FILE        Loads Lsyncds lua part from FILE
4619  -version            Prints versions and exits
4620
4621LICENSE:
4622  GPLv2 or any later version.
4623
4624SEE:
4625  `man lsyncd` for further information.
4626
4627]])
4628
4629--
4630--  -monitor NAME       Uses operating systems event montior NAME
4631--                      (inotify/fanotify/fsevents)
4632
4633	os.exit( -1 )
4634end
4635
4636
4637--
4638-- Called from core to parse the command line arguments
4639--
4640-- returns a string as user script to load.
4641--    or simply 'true' if running with rsync bevaiour
4642--
4643-- terminates on invalid arguments.
4644--
4645function runner.configure( args, monitors )
4646
4647	Monitors.initialize( monitors )
4648
4649	--
4650	-- a list of all valid options
4651	--
4652	-- first paramter is the number of parameters an option takes
4653	-- if < 0 the called function has to check the presence of
4654	-- optional arguments.
4655	--
4656	-- second paramter is the function to call
4657	--
4658	local options =
4659	{
4660		-- log is handled by core already.
4661
4662		delay =
4663		{
4664			1,
4665			function
4666			(
4667				secs
4668			)
4669				clSettings.delay = secs + 0
4670			end
4671		},
4672
4673		insist =
4674		{
4675			0,
4676			function
4677			( )
4678				clSettings.insist = true
4679			end
4680		},
4681
4682		log =
4683		{
4684			1,
4685			nil
4686		},
4687
4688		logfile =
4689		{
4690			1,
4691			function
4692			(
4693				file
4694			)
4695				clSettings.logfile = file
4696			end
4697		},
4698
4699		monitor =
4700		{
4701			-1,
4702			function
4703			(
4704				monitor
4705			)
4706				if not monitor
4707				then
4708					io.stdout:write( 'This Lsyncd supports these monitors:\n' )
4709					for _, v in ipairs( Monitors.list )
4710					do
4711						io.stdout:write( '   ', v, '\n' )
4712					end
4713
4714					io.stdout:write('\n')
4715
4716					lsyncd.terminate( -1 )
4717				else
4718					clSettings.monitor = monitor
4719				end
4720			end
4721		},
4722
4723		nodaemon =
4724		{
4725			0,
4726			function
4727			( )
4728				clSettings.nodaemon = true
4729			end
4730		},
4731
4732		pidfile =
4733		{
4734			1,
4735			function
4736			(
4737				file
4738			)
4739				clSettings.pidfile=file
4740			end
4741		},
4742
4743		rsync =
4744		{
4745			2,
4746			function
4747			(
4748				src,
4749				trg
4750			)
4751				clSettings.syncs = clSettings.syncs or { }
4752				table.insert( clSettings.syncs, { 'rsync', src, trg } )
4753			end
4754		},
4755
4756		rsyncssh =
4757		{
4758			3,
4759			function
4760			(
4761				src,
4762				host,
4763				tdir
4764			)
4765				clSettings.syncs = clSettings.syncs or { }
4766
4767				table.insert( clSettings.syncs, { 'rsyncssh', src, host, tdir } )
4768			end
4769		},
4770
4771		direct =
4772		{
4773			2,
4774			function
4775			(
4776				src,
4777				trg
4778			)
4779				clSettings.syncs = clSettings.syncs or { }
4780
4781				table.insert( clSettings.syncs, { 'direct', src, trg } )
4782			end
4783		},
4784
4785		version =
4786		{
4787			0,
4788			function
4789			( )
4790				io.stdout:write( 'Version: ', lsyncd_version, '\n' )
4791
4792				os.exit( 0 )
4793			end
4794		}
4795	}
4796
4797	-- non-opts is filled with all args that were no part dash options
4798
4799	local nonopts = { }
4800
4801	local i = 1
4802
4803	while i <= #args
4804	do
4805		local a = args[ i ]
4806
4807		if a:sub( 1, 1 ) ~= '-'
4808		then
4809			table.insert( nonopts, args[ i ] )
4810		else
4811			if a:sub( 1, 2 ) == '--'
4812			then
4813				a = a:sub( 3 )
4814			else
4815				a = a:sub( 2 )
4816			end
4817
4818			local o = options[ a ]
4819
4820			if not o
4821			then
4822				log( 'Error', 'unknown option command line option ', args[ i ] )
4823
4824				os.exit( -1 )
4825			end
4826
4827			if o[ 1 ] >= 0 and i + o[ 1 ] > #args
4828			then
4829				log( 'Error', a ,' needs ', o[ 1 ],' arguments' )
4830
4831				os.exit( -1 )
4832			elseif o[1] < 0
4833			then
4834				o[ 1 ] = -o[ 1 ]
4835			end
4836
4837			if o[ 2 ]
4838			then
4839				if o[ 1 ] == 0
4840				then
4841					o[ 2 ]( )
4842				elseif o[ 1 ] == 1
4843				then
4844					o[ 2 ]( args[ i + 1] )
4845				elseif o[ 1 ] == 2
4846				then
4847					o[ 2 ]( args[ i + 1], args[ i + 2] )
4848				elseif o[ 1 ] == 3
4849				then
4850					o[ 2 ]( args[ i + 1], args[ i + 2], args[ i + 3] )
4851				end
4852			end
4853
4854			i = i + o[1]
4855		end
4856
4857		i = i + 1
4858	end
4859
4860	if clSettings.syncs
4861	then
4862		if #nonopts ~= 0
4863		then
4864			log( 'Error', 'There cannot be command line syncs and a config file together.' )
4865
4866			os.exit( -1 )
4867		end
4868	else
4869		if #nonopts == 0
4870		then
4871			runner.help( args[ 0 ] )
4872		elseif #nonopts == 1
4873		then
4874			return nonopts[ 1 ]
4875		else
4876			-- TODO make this possible
4877			log( 'Error', 'There can only be one config file in the command line.' )
4878
4879			os.exit( -1 )
4880		end
4881	end
4882end
4883
4884
4885--
4886-- Called from core on init or restart after user configuration.
4887--
4888-- firstTime:
4889--    true when Lsyncd startups the first time,
4890--    false on resets, due to HUP signal or monitor queue overflow.
4891--
4892function runner.initialize( firstTime )
4893
4894	-- Checks if user overwrote the settings function.
4895	-- ( was Lsyncd <2.1 style )
4896	if settings ~= settingsSafe
4897	then
4898		log(
4899			'Error',
4900			'Do not use settings = { ... }\n'..
4901			'      please use settings{ ... } ( without the equal sign )'
4902		)
4903
4904		os.exit( -1 )
4905	end
4906
4907	lastReportedWaiting = false
4908
4909	--
4910	-- From this point on, no globals may be created anymore
4911	--
4912	lockGlobals( )
4913
4914	--
4915	-- all command line settings overwrite config file settings
4916	--
4917	for k, v in pairs( clSettings )
4918	do
4919		if k ~= 'syncs'
4920		then
4921			uSettings[ k ] = v
4922		end
4923	end
4924
4925	--
4926	-- implicitly forces 'insist' on Lsyncd resets.
4927	--
4928	if not firstTime
4929	then
4930		uSettings.insist = true
4931	end
4932
4933	--
4934	-- adds syncs specified by command line.
4935	--
4936	if clSettings.syncs
4937	then
4938		for _, s in ipairs( clSettings.syncs )
4939		do
4940			if s[ 1 ] == 'rsync'
4941			then
4942				sync{
4943					default.rsync,
4944					source = s[ 2 ],
4945					target = s[ 3 ]
4946				}
4947			elseif s[ 1 ] == 'rsyncssh'
4948			then
4949				sync{
4950					default.rsyncssh,
4951					source = s[ 2 ],
4952					host   = s[ 3 ],
4953					targetdir=s[ 4 ]
4954				}
4955			elseif s[ 1 ] == 'direct'
4956			then
4957				sync{
4958					default.direct,
4959					source=s[ 2 ],
4960					target=s[ 3 ]
4961				}
4962			end
4963		end
4964	end
4965
4966	if uSettings.nodaemon
4967	then
4968		lsyncd.configure( 'nodaemon' )
4969	end
4970
4971	if uSettings.logfile
4972	then
4973		lsyncd.configure( 'logfile', uSettings.logfile )
4974	end
4975
4976	if uSettings.logident
4977	then
4978		lsyncd.configure( 'logident', uSettings.logident )
4979	end
4980
4981	if uSettings.logfacility
4982	then
4983		lsyncd.configure( 'logfacility', uSettings.logfacility )
4984	end
4985
4986	if uSettings.pidfile
4987	then
4988		lsyncd.configure( 'pidfile', uSettings.pidfile )
4989	end
4990
4991	--
4992	-- Transfers some defaults to uSettings
4993	--
4994	if uSettings.statusInterval == nil
4995	then
4996		uSettings.statusInterval = default.statusInterval
4997	end
4998
4999	-- makes sure the user gave Lsyncd anything to do
5000	if Syncs.size() == 0
5001	then
5002		log( 'Error', 'Nothing to watch!' )
5003
5004		os.exit( -1 )
5005	end
5006
5007	-- from now on use logging as configured instead of stdout/err.
5008	lsyncdStatus = 'run';
5009
5010	lsyncd.configure( 'running' );
5011
5012	local ufuncs =
5013	{
5014		'onAttrib',
5015		'onCreate',
5016		'onDelete',
5017		'onModify',
5018		'onMove',
5019		'onStartup',
5020	}
5021
5022	-- translates layer 3 scripts
5023	for _, s in Syncs.iwalk()
5024	do
5025		-- checks if any user functions is a layer 3 string.
5026		local config = s.config
5027
5028		for _, fn in ipairs( ufuncs )
5029		do
5030			if type(config[fn]) == 'string'
5031			then
5032				local ft = functionWriter.translate( config[ fn ] )
5033
5034				config[ fn ] = assert( load( 'return '..ft ) )( )
5035			end
5036		end
5037	end
5038
5039	-- runs through the Syncs created by users
5040	for _, s in Syncs.iwalk( )
5041	do
5042		if s.config.monitor == 'inotify'
5043		then
5044			Inotify.addSync( s, s.source )
5045		elseif s.config.monitor == 'fsevents'
5046		then
5047			Fsevents.addSync( s, s.source )
5048		else
5049			error(
5050				'sync ' ..
5051				s.config.name ..
5052				' has no known event monitor interface.'
5053			)
5054		end
5055
5056		-- if the sync has an init function, the init delay
5057		-- is stacked which causes the init function to be called.
5058		if s.config.init
5059		then
5060			s:addInitDelay( )
5061		end
5062	end
5063end
5064
5065--
5066-- Called by core to query the soonest alarm.
5067--
5068-- @return false ... no alarm, core can go in untimed sleep
5069--         true  ... immediate action
5070--         times ... the alarm time (only read if number is 1)
5071--
5072function runner.getAlarm
5073( )
5074	log( 'Function', 'getAlarm( )' )
5075
5076	if lsyncdStatus ~= 'run' then return false end
5077
5078	local alarm = false
5079
5080	--
5081	-- Checks if 'a' is sooner than the 'alarm' up-value.
5082	--
5083	local function checkAlarm
5084	(
5085		a  -- alarm time
5086	)
5087		if a == nil then error( 'got nil alarm' ) end
5088
5089		if alarm == true or not a
5090		then
5091			-- 'alarm' is already immediate or
5092			-- a not a new alarm
5093			return
5094		end
5095
5096		-- sets 'alarm' to a if a is sooner
5097		if not alarm or a < alarm
5098		then
5099			alarm = a
5100		end
5101	end
5102
5103	--
5104	-- checks all syncs for their earliest alarm,
5105	-- but only if the global process limit is not yet reached.
5106	--
5107	if not uSettings.maxProcesses
5108	or processCount < uSettings.maxProcesses
5109	then
5110		for _, s in Syncs.iwalk( )
5111		do
5112			checkAlarm( s:getAlarm( ) )
5113		end
5114	else
5115		log(
5116			'Alarm',
5117			'at global process limit.'
5118		)
5119	end
5120
5121	-- checks if a statusfile write has been delayed
5122	checkAlarm( StatusFile.getAlarm( ) )
5123
5124	-- checks for an userAlarm
5125	checkAlarm( UserAlarms.getAlarm( ) )
5126
5127	log( 'Alarm', 'runner.getAlarm returns: ', alarm )
5128
5129	return alarm
5130end
5131
5132
5133--
5134-- Called when an file system monitor events arrive
5135--
5136runner.inotifyEvent = Inotify.event
5137runner.fsEventsEvent = Fsevents.event
5138
5139--
5140-- Collector for every child process that finished in startup phase
5141--
5142function runner.collector
5143(
5144	pid,       -- pid of the child process
5145	exitcode   -- exitcode of the child process
5146)
5147	if exitcode ~= 0
5148	then
5149		log( 'Error', 'Startup process', pid, ' failed' )
5150
5151		terminate( -1 )
5152	end
5153
5154	return 0
5155end
5156
5157--
5158-- Called by core when an overflow happened.
5159--
5160function runner.overflow
5161( )
5162	log( 'Normal', '--- OVERFLOW in event queue ---' )
5163
5164	lsyncdStatus = 'fade'
5165end
5166
5167--
5168-- Called by core on a hup signal.
5169--
5170function runner.hup
5171( )
5172	log( 'Normal', '--- HUP signal, resetting ---' )
5173
5174	lsyncdStatus = 'fade'
5175end
5176
5177--
5178-- Called by core on a term signal.
5179--
5180function runner.term
5181(
5182	sigcode  -- signal code
5183)
5184	local sigtexts =
5185	{
5186		[ 2 ] = 'INT',
5187		[ 15 ] = 'TERM'
5188	};
5189
5190	local sigtext = sigtexts[ sigcode ];
5191
5192	if not sigtext then sigtext = 'UNKNOWN' end
5193
5194	log( 'Normal', '--- ', sigtext, ' signal, fading ---' )
5195
5196	lsyncdStatus = 'fade'
5197
5198end
5199
5200--============================================================================
5201-- Lsyncd runner's user interface
5202--============================================================================
5203
5204--
5205-- Main utility to create new observations.
5206--
5207-- Returns an Inlet to that sync.
5208--
5209function sync
5210(
5211	opts
5212)
5213	if lsyncdStatus ~= 'init'
5214	then
5215		error( 'Sync can only be created during initialization.', 2 )
5216	end
5217
5218	return Syncs.add( opts ).inlet
5219end
5220
5221
5222--
5223-- Spawns a new child process.
5224--
5225function spawn(
5226	agent,  -- the reason why a process is spawned.
5227	        -- a delay or delay list for a sync
5228	        -- it will mark the related files as blocked.
5229	binary, -- binary to call
5230	...     -- arguments
5231)
5232	if agent == nil
5233	or type( agent ) ~= 'table'
5234	then
5235		error( 'spawning with an invalid agent', 2 )
5236	end
5237
5238	if lsyncdStatus == 'fade'
5239	then
5240		log( 'Normal', 'ignored process spawning while fading' )
5241		return
5242	end
5243
5244	if type( binary ) ~= 'string'
5245	then
5246		error( 'calling spawn(agent, binary, ...): binary is not a string', 2 )
5247	end
5248
5249	local dol = InletFactory.getDelayOrList( agent )
5250
5251	if not dol
5252	then
5253		error( 'spawning with an unknown agent', 2 )
5254	end
5255
5256	--
5257	-- checks if a spawn is called on an already active event
5258	--
5259	if dol.status
5260	then
5261		-- is an event
5262
5263		if dol.status ~= 'wait'
5264		then
5265			error('spawn() called on an non-waiting event', 2)
5266		end
5267	else
5268		-- is a list
5269		for _, d in ipairs( dol )
5270		do
5271			if d.status ~= 'wait'
5272			and d.status ~= 'block'
5273			then
5274				error( 'spawn() called on an non-waiting event list', 2 )
5275			end
5276		end
5277	end
5278
5279	--
5280	-- tries to spawn the process
5281	--
5282	local pid = lsyncd.exec( binary, ... )
5283
5284	if pid and pid > 0
5285	then
5286		processCount = processCount + 1
5287
5288		if uSettings.maxProcesses
5289		and processCount > uSettings.maxProcesses
5290		then
5291			error( 'Spawned too much processes!' )
5292		end
5293
5294		local sync = InletFactory.getSync( agent )
5295
5296		-- delay or list
5297		if dol.status
5298		then
5299			-- is a delay
5300			dol:setActive( )
5301
5302			sync.processes[ pid ] = dol
5303		else
5304			-- is a list
5305			for _, d in ipairs( dol )
5306			do
5307				d:setActive( )
5308			end
5309
5310			sync.processes[ pid ] = dol
5311		end
5312	end
5313end
5314
5315--
5316-- Spawns a child process using the default shell.
5317--
5318function spawnShell
5319(
5320	agent,     -- the delay(list) to spawn the command for
5321	command,   -- the shell command
5322	...        -- additonal arguments
5323)
5324	return spawn( agent, '/bin/sh', '-c', command, '/bin/sh', ... )
5325end
5326
5327
5328--
5329-- Observes a filedescriptor.
5330--
5331function observefd
5332(
5333	fd,     -- file descriptor
5334	ready,  -- called when fd is ready to be read
5335	writey  -- called when fd is ready to be written
5336)
5337	return lsyncd.observe_fd( fd, ready, writey )
5338end
5339
5340
5341--
5342-- Stops observeing a filedescriptor.
5343--
5344function nonobservefd
5345(
5346	fd      -- file descriptor
5347)
5348	return lsyncd.nonobserve_fd( fd )
5349end
5350
5351
5352--
5353-- Calls func at timestamp.
5354--
5355-- Use now() to receive current timestamp
5356-- add seconds with '+' to it
5357--
5358alarm = UserAlarms.alarm
5359
5360
5361--
5362-- Comfort routine also for user.
5363-- Returns true if 'String' starts with 'Start'
5364--
5365function string.starts
5366(
5367	String,
5368	Start
5369)
5370	return string.sub( String, 1, #Start )==Start
5371end
5372
5373
5374--
5375-- Comfort routine also for user.
5376-- Returns true if 'String' ends with 'End'
5377--
5378function string.ends
5379(
5380	String,
5381	End
5382)
5383	return End == '' or string.sub( String, -#End ) == End
5384end
5385
5386
5387--
5388-- The settings call
5389--
5390function settings
5391(
5392	a1  -- a string for getting a setting
5393	--     or a table of key/value pairs to set these settings
5394)
5395
5396	-- if a1 is a string this is a get operation
5397	if type( a1 ) == 'string'
5398	then
5399		return uSettings[ a1 ]
5400	end
5401
5402	-- if its a table it sets all the value of the bale
5403	for k, v in pairs( a1 )
5404	do
5405		if type( k ) ~= 'number'
5406		then
5407			if not settingsCheckgauge[ k ]
5408			then
5409				error( 'setting "'..k..'" unknown.', 2 )
5410			end
5411
5412			uSettings[ k ] = v
5413		else
5414			if not settingsCheckgauge[ v ]
5415			then
5416				error( 'setting "'..v..'" unknown.', 2 )
5417			end
5418
5419			uSettings[ v ] = true
5420		end
5421	end
5422end
5423
5424settingsSafe = settings
5425
5426--
5427-- Returns the core the runners function interface.
5428--
5429return runner
5430