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