mirror of https://github.com/octoleo/lsyncd.git
Compare commits
374 Commits
release-2.
...
master
Author | SHA1 | Date |
---|---|---|
Daniel Poelzleithner | e1e13503b2 | |
Daniel Poelzleithner | 95a2c2fca2 | |
Daniel Poelzleithner | 5d3e0fe417 | |
Jan Vorwerk | 77f5a61bab | |
Daniel Poelzleithner | 3872ca77ac | |
Daniel Poelzleithner | a4556b835f | |
Daniel Poelzleithner | d4fc88ba10 | |
Daniel Poelzleithner | dd48284cd0 | |
Daniel Poelzleithner | 6d59f16140 | |
Daniel Poelzleithner | e831ad6945 | |
Daniel Poelzleithner | d779eb434f | |
Daniel Poelzleithner | 818fd4115f | |
Daniel Poelzleithner | f4c15496dc | |
Daniel Poelzleithner | 96a6276440 | |
Daniel Poelzleithner | 5096f27bbd | |
Daniel Poelzleithner | 945b57d8fb | |
Daniel Poelzleithner | 787b2b0015 | |
Daniel Poelzleithner | e09b58b721 | |
Daniel Poelzleithner | 9b81bb1785 | |
Daniel Poelzleithner | cda98d6ba9 | |
Daniel Poelzleithner | daa8abb4cf | |
Daniel Poelzleithner | 7604dd0e16 | |
Daniel Poelzleithner | e371078222 | |
Daniel Poelzleithner | a2f7df504c | |
Daniel Poelzleithner | e08685cfd6 | |
Daniel Poelzleithner | 1eb8a83500 | |
Daniel Poelzleithner | e6f3427c5f | |
Daniel Poelzleithner | f02bf700c1 | |
Daniel Poelzleithner | 6c8a3c2490 | |
Daniel Poelzleithner | 82f9f27172 | |
Daniel Poelzleithner | 63f506582a | |
Daniel Poelzleithner | 18008e1d15 | |
Daniel Poelzleithner | dd84b2e6b6 | |
Daniel Poelzleithner | 8c71941f9c | |
Daniel Poelzleithner | c84e3d0e05 | |
Daniel Poelzleithner | c5514e0451 | |
Daniel Poelzleithner | e94edac51e | |
Daniel Poelzleithner | f244405bce | |
Daniel Poelzleithner | 5b8fa2deda | |
Daniel Poelzleithner | f2272f1aa7 | |
Daniel Poelzleithner | 867a3cec8e | |
Daniel Poelzleithner | 22259eee49 | |
Daniel Poelzleithner | a16d714d6e | |
Daniel Poelzleithner | 1b7bf7577f | |
Daniel Poelzleithner | 075d64a069 | |
Daniel Poelzleithner | 7f0127548b | |
Daniel Poelzleithner | 32e2cac8cc | |
Daniel Poelzleithner | 4a0896f58f | |
Daniel Poelzleithner | 6d9164a29a | |
Daniel Poelzleithner | ff84a35d4d | |
Daniel Poelzleithner | 9ec4f42888 | |
Daniel Poelzleithner | 95f8ea67dd | |
Daniel Poelzleithner | c8fbe955fe | |
Daniel Poelzleithner | 58f1991e0b | |
Daniel Poelzleithner | d6c49b6858 | |
Daniel Poelzleithner | 46188f9f60 | |
Daniel Poelzleithner | 6e60f6b89e | |
Daniel Poelzleithner | 2b9de7d4c3 | |
Daniel Poelzleithner | fb86124f30 | |
Daniel Poelzleithner | eda846deed | |
Daniel Poelzleithner | f66b7147fd | |
Daniel Poelzleithner | c543a21162 | |
Daniel Poelzleithner | 3d2288dccf | |
Daniel Poelzleithner | f603d41c6c | |
Daniel Poelzleithner | 274b2b0416 | |
Daniel Poelzleithner | 24ef43f5fd | |
Daniel Poelzleithner | 1796fdb71b | |
Daniel Poelzleithner | 3fae375201 | |
Daniel Poelzleithner | b9c459b94d | |
Daniel Poelzleithner | 96725fa494 | |
Daniel Poelzleithner | 0de678ab3c | |
Daniel Poelzleithner | 2841ef8528 | |
Daniel Poelzleithner | a410ddebb8 | |
Daniel Poelzleithner | 8e002d64b5 | |
Marko Oldenburg | b7d11f6b00 | |
Marko Oldenburg | efa8155b68 | |
Daniel Poelzleithner | e2a27af6e7 | |
Daniel Poelzleithner | f65cdd282d | |
Daniel Poelzleithner | a609f34971 | |
Daniel Poelzleithner | 012b0cdd3e | |
Daniel Poelzleithner | 0b59ae7652 | |
Daniel Poelzleithner | b7aa3d9a75 | |
Daniel Poelzleithner | 39f2f3a373 | |
Daniel Poelzleithner | d737c03c03 | |
Daniel Poelzleithner | f812cd4cd5 | |
Daniel Poelzleithner | 938c702525 | |
Daniel Poelzleithner | e5f71ea3ae | |
Daniel Poelzleithner | dcebacb554 | |
Daniel Poelzleithner | 92af746a73 | |
Daniel Poelzleithner | 5212fd4794 | |
Daniel Poelzleithner | aea80964f3 | |
Daniel Poelzleithner | 99a682f4cd | |
Daniel Poelzleithner | 75bb0fb084 | |
Daniel Poelzleithner | 3e8aad3b2e | |
Daniel Poelzleithner | deb425a075 | |
Daniel Poelzleithner | 5b0d266669 | |
Daniel Poelzleithner | bb247e0267 | |
Daniel Poelzleithner | 185e317119 | |
Daniel Poelzleithner | 4b1b4d0104 | |
Daniel Poelzleithner | 960c357045 | |
Daniel Poelzleithner | 4c3cf2e6af | |
Daniel Poelzleithner | 564d54be76 | |
Daniel Poelzleithner | 9b2b3ad6e6 | |
A. Jesse Jiryu Davis | 7bb8715bfd | |
Thomas Nixon | c4b5465622 | |
Taylor Dean | cd9672699e | |
Torben Jaeger | c02ee3b6d7 | |
Torben Jaeger | 5e08ea2cef | |
Dan Rose | 390de3dbaa | |
Bernard Gray | a57b80f9c8 | |
Bernard Gray | 188b691bea | |
Lucas Gabriel Schneider | d32957d5a2 | |
Axel Kittenberger | 42413cabbe | |
Axel Kittenberger | 13bdd9075f | |
yokogawa-k | 0af99d8d5b | |
Axel Kittenberger | 9b47a1f8fe | |
Axel Kittenberger | a26eb6f94a | |
Axel Kittenberger | f77df7f3e2 | |
Axel Kittenberger | 465deb1549 | |
Axel Kittenberger | 1094759975 | |
Axel Kittenberger | ff0f4fbd7a | |
Axel Kittenberger | c0a31c215d | |
Axel Kittenberger | 99b6b18ac2 | |
Axel Kittenberger | a23426d8a4 | |
Axel Kittenberger | f3d65770cd | |
Axel Kittenberger | 4bc456b544 | |
Axel Kittenberger | 2e5f4462b4 | |
Axel Kittenberger | 2dc8dc665b | |
Axel Kittenberger | 8904710acb | |
Axel Kittenberger | ebd2600914 | |
Axel Kittenberger | faa582e259 | |
Axel Kittenberger | ba52ee1a6f | |
Axel Kittenberger | a78f239fa2 | |
Axel Kittenberger | 465e173983 | |
Axel Kittenberger | 1e0d867f80 | |
Axel Kittenberger | c50aa7c9c1 | |
Axel Kittenberger | d72a59e70f | |
Axel Kittenberger | bbe9c5f38c | |
Daniel Miranda | 62093f609f | |
Daniel Miranda | 1dfff8d13a | |
Axel Kittenberger | fb4301d515 | |
Soufiane Ghzal | ee51e3edd9 | |
Soufiane Ghzal | 1460b5795c | |
Axel Kittenberger | ea068b4014 | |
Axel Kittenberger | 24d58f7741 | |
Axel Kittenberger | 785a7dd503 | |
Axel Kittenberger | 544f6066b7 | |
Axel Kittenberger | 3f78514273 | |
Axel Kittenberger | 288f09ad01 | |
Axel Kittenberger | a0ab850ec0 | |
Axel Kittenberger | e21a2184ba | |
Axel Kittenberger | 4c83a9b263 | |
Axel Kittenberger | 0c5e2c707e | |
Axel Kittenberger | 1b09a56320 | |
Axel Kittenberger | 29f3e63bfa | |
Axel Kittenberger | b7c4fe437c | |
Axel Kittenberger | 467a69eba2 | |
Axel Kittenberger | d1ef5f3d51 | |
Axel Kittenberger | 0a0fef20ac | |
Axel Kittenberger | ccae4ac142 | |
Axel Kittenberger | 620304c92f | |
Axel Kittenberger | fed5cdd2bd | |
Axel Kittenberger | f6f360b9dc | |
Axel Kittenberger | ac5ef0474e | |
Axel Kittenberger | ac82ea4708 | |
Axel Kittenberger | dbe9935328 | |
Axel Kittenberger | a0bdc8778d | |
Axel Kittenberger | 12d4f43d0f | |
Axel Kittenberger | ff891441c8 | |
Axel Kittenberger | c08719ce00 | |
Axel Kittenberger | 4909dd3b2c | |
Axel Kittenberger | 7bae036f03 | |
Axel Kittenberger | 83c6436e84 | |
Axel Kittenberger | 16919a893e | |
Axel Kittenberger | 62923459f4 | |
Axel Kittenberger | 98c83a7fbf | |
Axel Kittenberger | 8e1a85a12d | |
Axel Kittenberger | ee50768743 | |
Axel Kittenberger | 837a3cd927 | |
Axel Kittenberger | 9e647f8113 | |
Axel Kittenberger | d8b565ae02 | |
Axel Kittenberger | 4005c2899b | |
Axel Kittenberger | 20edbe5f6e | |
Axel Kittenberger | 155dbf155c | |
Axel Kittenberger | 8927426be2 | |
Axel Kittenberger | 1bec061109 | |
Axel Kittenberger | 982552e6ec | |
Axel Kittenberger | b15f1d39d9 | |
Axel Kittenberger | 40a46b6048 | |
Axel Kittenberger | 4c77666281 | |
Axel Kittenberger | 815d2c4779 | |
Axel Kittenberger | ccc492cac3 | |
Axel Kittenberger | a4481c98e6 | |
Axel Kittenberger | 19d0478bd7 | |
Axel Kittenberger | db488a86ac | |
Axel Kittenberger | 2ff9ae72be | |
Axel Kittenberger | d444ccb2d2 | |
Axel Kittenberger | b79defadb3 | |
Axel Kittenberger | c58658e642 | |
Axel Kittenberger | b71089b12e | |
Axel Kittenberger | dbbe5dfdf7 | |
Axel Kittenberger | 2f4dd3aa7e | |
Axel Kittenberger | 319681e7eb | |
Axel Kittenberger | dc93af01aa | |
Axel Kittenberger | de2d47f7e6 | |
Axel Kittenberger | d5957200a2 | |
Axel Kittenberger | aaae8f4083 | |
Axel Kittenberger | 6b6d718a48 | |
Axel Kittenberger | b3f46e2462 | |
Axel Kittenberger | 25ce41a136 | |
Axel Kittenberger | 4d2c793b01 | |
Axel Kittenberger | 8367ae89a0 | |
Axel Kittenberger | b2da0ea6d2 | |
Alex Juarez | f81e5d64d2 | |
Axel Kittenberger | 5d2126a472 | |
Axel Kittenberger | fa3028321b | |
Axel Kittenberger | 545eb341cf | |
Axel Kittenberger | 315379072c | |
Axel Kittenberger | 60d84c3ea1 | |
Axel Kittenberger | e1c9e325d0 | |
Axel Kittenberger | 8e361d1f77 | |
Axel Kittenberger | 2e572a1391 | |
Axel Kittenberger | 2a3e4aeb05 | |
Axel Kittenberger | 7ec63e1eba | |
Axel Kittenberger | ee5d693846 | |
Marc Abramowitz | 16c854df8d | |
Marc Abramowitz | 47bb377efc | |
Marc Abramowitz | 86e7a5ab2a | |
Axel Kittenberger | abc2a509f6 | |
Axel Kittenberger | 0b9297b790 | |
Axel Kittenberger | 4740a2ca8f | |
Matt Wells | a0389d6f08 | |
Marcin Szewczyk | c4f4ac3e01 | |
Axel Kittenberger | cce7db5ebd | |
Axel Kittenberger | 4bdde8a672 | |
Axel Kittenberger | 8fb9fc4bbb | |
Axel Kittenberger | bddc9a7fde | |
Axel Kittenberger | ea31ea92a7 | |
Axel Kittenberger | 5f74865f68 | |
Axel Kittenberger | a18ba20365 | |
Axel Kittenberger | b301838eb3 | |
Axel Kittenberger | 2493476f64 | |
Axel Kittenberger | 1f95925304 | |
Timo Teräs | abbf307b97 | |
Timo Teräs | 5b76dc1cb6 | |
Timo Teräs | 19b08adf35 | |
Axel Kittenberger | e9ffda07f0 | |
Ángel González | e6016b3748 | |
Sven Schwedas | 18f02ad013 | |
Roland Walker | aea57a5a11 | |
Axel Kittenberger | 4da2257758 | |
Axel Kittenberger | f199fd1866 | |
Axel Kittenberger | 8572a6dacc | |
Axel Kittenberger | 23dfeb6a05 | |
Axel Kittenberger | b6228f442d | |
Axel Kittenberger | 0a1cab6609 | |
Axel Kittenberger | 6d69134971 | |
Axel Kittenberger | 110a525392 | |
Axel Kittenberger | f8fea06944 | |
Axel Kittenberger | 85e95ef150 | |
Michael Ploujnikov | 786f37d22d | |
Kazunori SAKAMOTO | 7addc7060b | |
Axel Kittenberger | 178f315907 | |
Andrew Fenn | b2383227cd | |
Jun SAITO | 1bfd98d64d | |
Axel Kittenberger | c23e9841ee | |
Axel Kittenberger | b6f4c6f990 | |
Rob Hunter | 54e721e94b | |
Axel Kittenberger | 25d2405906 | |
Axel Kittenberger | 6f4613c53a | |
Axel Kittenberger | 1dacb68745 | |
David Reiss | d772fcba0f | |
David Reiss | 716b88909a | |
David Reiss | 502e2e0eed | |
Axel Kittenberger | e880c607be | |
Axel Kittenberger | d0c9a60213 | |
Axel Kittenberger | c785f0a2ad | |
Axel Kittenberger | 1c299c14dd | |
Axel Kittenberger | 3aa8ed1182 | |
Axel Kittenberger | 7fe13abab8 | |
Axel Kittenberger | 72fa0e8865 | |
Axel Kittenberger | 6290bd6ea7 | |
Axel Kittenberger | 25a2274d83 | |
Axel Kittenberger | eb4370db9c | |
Axel Kittenberger | ec15abd244 | |
Axel Kittenberger | d0e56565a8 | |
flygoast | 367d5e940a | |
Axel Kittenberger | 3c9f88330b | |
Axel Kittenberger | 46d23d59b3 | |
Axel Kittenberger | 6c6ab9adf0 | |
Axel Kittenberger | 9619062764 | |
Daniel Black | 99ea89ad0d | |
Axel Kittenberger | dd4a1134a5 | |
Kashyap Paidimarri | 769fb3c26b | |
Axel Kittenberger | c13af5df7e | |
Kenyon Ralph | 8c36e20877 | |
Axel Kittenberger | 02cc23909a | |
Kenyon Ralph | 50b6e879a6 | |
Kenyon Ralph | 9dafacbf5e | |
Axel Kittenberger | 7bfb535da3 | |
Axel Kittenberger | d25eb11a3f | |
Axel Kittenberger | 5b7057bfd7 | |
Axel Kittenberger | 824797645b | |
Axel Kittenberger | ca3d3eca59 | |
Axel Kittenberger | e7b757bbc6 | |
Axel Kittenberger | 9ff62e15c6 | |
Axel Kittenberger | 78fa6208bf | |
Axel Kittenberger | fe41e18f7b | |
Axel Kittenberger | 9c6e703ff4 | |
David Wittman | 2c2570e314 | |
Axel Kittenberger | 3616dc64fe | |
Axel Kittenberger | b1257b0d48 | |
Axel Kittenberger | 856186168f | |
Birger Schmidt | 33c8f88bd3 | |
Birger Schmidt | e116ce0a6e | |
Birger Schmidt | 848f6e1f06 | |
Axel Kittenberger | beaa258ad0 | |
Axel Kittenberger | 4a7bf07f4f | |
Axel Kittenberger | d3b31bda37 | |
Birger Schmidt | 3b015f8c05 | |
axel | 9dbea196c5 | |
Axel Kittenberger | 19094a9fd4 | |
Axel Kittenberger | f09ac240a7 | |
Axel Kittenberger | 5f01a04335 | |
Axel Kittenberger | a82f4da7cd | |
Axel Kittenberger | f9231a11b2 | |
Axel Kittenberger | 23dbbe5ecd | |
Axel Kittenberger | 998f9ab3d0 | |
Axel Kittenberger | 1217fef9ba | |
Axel Kittenberger | 1b6d9bb65a | |
Axel Kittenberger | a6b49c8650 | |
Axel Kittenberger | 6f90c19196 | |
Axel Kittenberger | 6a862d6b8f | |
Axel Kittenberger | 1bf1d13eaa | |
Axel Kittenberger | 2e9c103f55 | |
Axel Kittenberger | 899077ccd7 | |
Axel Kittenberger | 698c5c3e4d | |
Axel Kittenberger | 887b8a004f | |
Axel Kittenberger | e0c1b25a1b | |
Axel Kittenberger | 5e34adc29b | |
Axel Kittenberger | 5aa4d497cb | |
Dennis Schridde | 91a7a78b4f | |
Dennis Schridde | 023f0efa3c | |
Axel Kittenberger | 91edb62083 | |
Axel Kittenberger | c8238f0209 | |
Dennis Schridde | 6034750db3 | |
Dennis Schridde | 30280bcc50 | |
Dennis Schridde | ab41f0159f | |
Axel Kittenberger | cccf5d6a73 | |
Axel Kittenberger | e2b0cdb359 | |
Axel Kittenberger | a694e0c55c | |
Axel Kittenberger | f9ef52ab34 | |
Axel Kittenberger | 5ec68314fe | |
Axel Kittenberger | c77298cc74 | |
Axel Kittenberger | 604493029a | |
Dennis Schridde | 6a5eb24a90 | |
Dennis Schridde | 88bc95fd67 | |
Dennis Schridde | dea7e87b40 | |
Dennis Schridde | a82f43ab3f | |
Dennis Schridde | 68982e6e5b | |
Dennis Schridde | 702f6d3d4e | |
Dennis Schridde | 3d065fccba | |
Axel Kittenberger | ddbf2c2a19 | |
Axel Kittenberger | ab36ab7823 | |
Axel Kittenberger | b6fa608787 | |
Axel Kittenberger | 6d4149acdc | |
Axel Kittenberger | 9106f81c4a | |
Axel Kittenberger | f50be8e51b | |
Axel Kittenberger | 7fbf97cd21 | |
Axel Kittenberger | 993ef08eb2 | |
Roman Ovchinnikov | 6ae28d8350 | |
Axel Kittenberger | 3efd6888a0 | |
Axel Kittenberger | a28999ddde | |
Axel Kittenberger | ebd2ad1312 |
|
@ -0,0 +1,8 @@
|
|||
# 4 tab indentation
|
||||
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
|
||||
[*.nix]
|
||||
indent_style = space
|
||||
indent_size = 2
|
|
@ -0,0 +1,20 @@
|
|||
name: "Build"
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
jobs:
|
||||
tests:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
version:
|
||||
- lsyncd_lua5_3
|
||||
# broken with lua 5.4.4. luac segfault
|
||||
# - lsyncd_lua5_4
|
||||
- lsyncd_lua5_1
|
||||
steps:
|
||||
- uses: actions/checkout@v2.4.0
|
||||
- uses: cachix/install-nix-action@v15
|
||||
- run: chmod og-rw ~
|
||||
- run: nix develop .#${{ matrix.version }} --command ./tests/ci-run.sh
|
|
@ -1,38 +1,28 @@
|
|||
#~~~~~
|
||||
# Compiled output
|
||||
#
|
||||
# compiled stuff
|
||||
|
||||
*.o
|
||||
*.out
|
||||
lsyncd
|
||||
|
||||
#~~~~~
|
||||
# Autoconf/Automake
|
||||
#
|
||||
aclocal.m4
|
||||
|
||||
# cmake
|
||||
|
||||
AdditionalInfo.txt
|
||||
config.h
|
||||
config.h.in
|
||||
config.log
|
||||
config.status
|
||||
configure
|
||||
install-sh
|
||||
Makefile
|
||||
depcomp
|
||||
Makefile.in
|
||||
missing
|
||||
stamp-h1
|
||||
build*/
|
||||
CMakeCache.txt
|
||||
CMakeFiles/
|
||||
cmake_install.cmake
|
||||
install_manifest.txt
|
||||
|
||||
.deps/
|
||||
autom4te.cache/
|
||||
|
||||
#~~~~~
|
||||
# Manpages
|
||||
#
|
||||
doc/lsyncd.1.xml
|
||||
doc/lsyncd.1
|
||||
# generated C code
|
||||
|
||||
#####
|
||||
# Generated C code
|
||||
#
|
||||
defaults.c
|
||||
runner.c
|
||||
result
|
||||
tests/ssh
|
||||
|
||||
# docs cache
|
||||
.jekyll-cache
|
||||
|
|
|
@ -0,0 +1,142 @@
|
|||
# preamble
|
||||
project( Lsyncd )
|
||||
cmake_minimum_required( VERSION 3.5 )
|
||||
|
||||
# extract version
|
||||
file(STRINGS "lsyncd.lua" LSYNCD_VERSION_RAW REGEX "lsyncd_version = '.*'")
|
||||
string(REGEX REPLACE "lsyncd_version = \'(.*)\'"
|
||||
"\\1" LSYNCD_VERSION
|
||||
${LSYNCD_VERSION_RAW})
|
||||
|
||||
set( CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/" )
|
||||
|
||||
|
||||
# finding Lua
|
||||
find_package( Lua REQUIRED )
|
||||
include_directories ( ${LUA_INCLUDE_DIR} )
|
||||
|
||||
|
||||
# setting Lsyncd sources
|
||||
set( LSYNCD_SRC lsyncd.c runner.c defaults.c )
|
||||
|
||||
|
||||
# selecting the file notification mechanisms to compile against
|
||||
option( WITH_INOTIFY "Compile with inotify file notifications (Linux)" ON )
|
||||
option( WITH_FSEVENTS "Compile with inotify file notifications (OSX)" OFF )
|
||||
|
||||
if( WITH_INOTIFY )
|
||||
set( LSYNCD_SRC ${LSYNCD_SRC} inotify.c )
|
||||
endif( WITH_INOTIFY )
|
||||
|
||||
if( WITH_FSEVENTS )
|
||||
set( LSYNCD_SRC ${LSYNCD_SRC} fsevents.c )
|
||||
|
||||
option( XNU_DIR "Path to the xnu sources" )
|
||||
|
||||
# if( NOT XNU_DIR/bsd/sys/fsevents.h )
|
||||
# message( SEND_ERROR "Cannot find bsd/sys/fsevents.h in XNU_DIR" )
|
||||
# endif( )
|
||||
|
||||
include_directories( ${XNU_DIR} )
|
||||
endif( WITH_FSEVENTS )
|
||||
|
||||
if ( APPLE )
|
||||
set( LSYNCD_TARGET_APPLE 1 )
|
||||
endif ( APPLE )
|
||||
|
||||
# generating the config.h file
|
||||
configure_file (
|
||||
"${PROJECT_SOURCE_DIR}/config.h.in"
|
||||
"${PROJECT_BINARY_DIR}/config.h"
|
||||
)
|
||||
include_directories("${PROJECT_BINARY_DIR}")
|
||||
|
||||
|
||||
# building and compiling the part of lsyncd written in Lua
|
||||
# also called "runner"
|
||||
add_custom_command( OUTPUT runner.c
|
||||
COMMAND ${CMAKE_COMMAND} -E echo "Generating built-in runner linkable"
|
||||
COMMAND ${LUA_EXECUTABLE} ${PROJECT_SOURCE_DIR}/bin2carray.lua runner.out runner runner.c
|
||||
DEPENDS runner.out
|
||||
)
|
||||
|
||||
# this supposes the Lua compiler 'luac' is sitting right next to the Lua interpreter 'lua'
|
||||
add_custom_command( OUTPUT runner.out
|
||||
COMMAND ${CMAKE_COMMAND} -E echo "Compiling built-in runner"
|
||||
COMMAND ${LUA_COMPILER} -o runner.out ${PROJECT_SOURCE_DIR}/lsyncd.lua
|
||||
DEPENDS ${PROJECT_SOURCE_DIR}/lsyncd.lua
|
||||
)
|
||||
|
||||
# building and compiling the built-in default configs:
|
||||
# rsync rysnc-ssh and direct
|
||||
add_custom_command( OUTPUT defaults.c
|
||||
COMMAND ${CMAKE_COMMAND} -E echo "Generating built-in default configs"
|
||||
COMMAND ${LUA_EXECUTABLE} ${PROJECT_SOURCE_DIR}/bin2carray.lua defaults.out defaults defaults.c
|
||||
DEPENDS defaults.out
|
||||
)
|
||||
|
||||
set( DEFAULT_CONFIGS
|
||||
${PROJECT_SOURCE_DIR}/default.lua
|
||||
${PROJECT_SOURCE_DIR}/default-rsync.lua
|
||||
${PROJECT_SOURCE_DIR}/default-rsyncssh.lua
|
||||
${PROJECT_SOURCE_DIR}/default-direct.lua
|
||||
)
|
||||
|
||||
add_custom_command( OUTPUT defaults.out
|
||||
COMMAND ${CMAKE_COMMAND} -E echo "Compiling built-in default configs"
|
||||
COMMAND ${LUA_COMPILER} -o defaults.out ${DEFAULT_CONFIGS}
|
||||
DEPENDS ${DEFAULT_CONFIGS}
|
||||
)
|
||||
|
||||
# the manpage
|
||||
add_custom_target( manpage
|
||||
COMMAND ${CMAKE_COMMAND} -E echo "Updating the manpage"
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/man
|
||||
COMMAND a2x --format=manpage ${PROJECT_SOURCE_DIR}/docs/manpage/lsyncd.1.txt -D ${CMAKE_CURRENT_BINARY_DIR}/man
|
||||
DEPENDS docs/manpage/lsyncd.1.txt
|
||||
)
|
||||
|
||||
# the html documention
|
||||
add_custom_target( docs-html
|
||||
COMMAND ${CMAKE_COMMAND} -E echo "Generate html documentation"
|
||||
COMMAND env JEKYLL_ENV=local jekyll build -d ${CMAKE_CURRENT_BINARY_DIR}/html
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/docs
|
||||
DEPENDS ${CMAKE_SOURCE_DIR}/docs
|
||||
)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT tests
|
||||
COMMAND ${CMAKE_COMMAND} -E create_symlink ${CMAKE_SOURCE_DIR}/tests tests)
|
||||
|
||||
ADD_CUSTOM_TARGET(prepare_tests ALL
|
||||
DEPENDS tests/
|
||||
)
|
||||
|
||||
add_custom_target( run-tests
|
||||
COMMAND echo "Running the tests"
|
||||
COMMAND echo "Note you are expected to:"
|
||||
COMMAND echo " * have lua-posix installed"
|
||||
COMMAND ${LUA_EXECUTABLE} ${CMAKE_SOURCE_DIR}/tests/setup.lua
|
||||
COMMAND ${CMAKE_BINARY_DIR}/lsyncd -log all -script ${CMAKE_SOURCE_DIR}/tests/utils_test.lua
|
||||
COMMAND ${LUA_EXECUTABLE} ${CMAKE_SOURCE_DIR}/tests/cron-rsync.lua
|
||||
COMMAND ${LUA_EXECUTABLE} ${CMAKE_SOURCE_DIR}/tests/schedule.lua
|
||||
COMMAND ${LUA_EXECUTABLE} ${CMAKE_SOURCE_DIR}/tests/l4rsyncdata.lua
|
||||
COMMAND ${LUA_EXECUTABLE} ${CMAKE_SOURCE_DIR}/tests/filter-rsync.lua
|
||||
COMMAND ${LUA_EXECUTABLE} ${CMAKE_SOURCE_DIR}/tests/exclude-rsync.lua
|
||||
COMMAND ${LUA_EXECUTABLE} ${CMAKE_SOURCE_DIR}/tests/exclude-rsyncssh.lua
|
||||
COMMAND ${LUA_EXECUTABLE} ${CMAKE_SOURCE_DIR}/tests/churn-rsync.lua
|
||||
COMMAND ${LUA_EXECUTABLE} ${CMAKE_SOURCE_DIR}/tests/churn-rsyncssh.lua
|
||||
COMMAND ${LUA_EXECUTABLE} ${CMAKE_SOURCE_DIR}/tests/churn-direct.lua
|
||||
COMMAND ${LUA_EXECUTABLE} ${CMAKE_SOURCE_DIR}/tests/teardown.lua
|
||||
COMMAND echo "Finished all successfull!"
|
||||
DEPENDS prepare_tests
|
||||
)
|
||||
|
||||
# compiling and linking it all together
|
||||
add_executable( lsyncd ${LSYNCD_SRC} )
|
||||
target_link_libraries( lsyncd ${LUA_LIBRARIES} )
|
||||
|
||||
install( TARGETS lsyncd RUNTIME DESTINATION bin )
|
||||
install( FILES ${CMAKE_CURRENT_BINARY_DIR}/man/lsyncd.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1 COMPONENT man )
|
||||
install( DIRECTORY examples DESTINATION doc )
|
||||
install( DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html DESTINATION doc OPTIONAL)
|
6
COPYING
6
COPYING
|
@ -313,8 +313,8 @@ Also add information on how to contact you by electronic and paper mail.
|
|||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
Lsyncd version 2.X, Copyright (C) 2013 Axel Kittenberger
|
||||
Lsyncd comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
|
@ -328,7 +328,7 @@ school, if any, to sign a "copyright disclaimer" for the program, if
|
|||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
`Lsyncd' (which makes synchronises directories) written by Axel Kittenberger.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
|
169
ChangeLog
169
ChangeLog
|
@ -1,4 +1,141 @@
|
|||
??-??-????:
|
||||
2022-11-17: 2.3.1
|
||||
change: multiple bugfixes, style fixes
|
||||
|
||||
2022-03-09: 2.3.0
|
||||
add: nix flake support
|
||||
add: support for tunnel commands
|
||||
add: support for batchSizeLimit
|
||||
add: -onepass option
|
||||
add: crontab support
|
||||
change: support relative executable paths
|
||||
|
||||
2018-03-09: 2.2.3
|
||||
enhaencement: supporting includes with new filter and filterFrom options
|
||||
change: needing now at least Lua 5.2 (Lua 5.1 no longer supported, Lua5.3 supported)
|
||||
change: if the target/targetdir ends with a ':' do not append
|
||||
a trailing '/' to it, since that would change it from homedir to rootdir!
|
||||
add: example for Amazon S3 Bucket (Daniel Miranda)
|
||||
fix: setting stdout/stderr to linebuffer mode.
|
||||
fix: Lua5.3 compatiblity, using load() instead of loadstring()
|
||||
fix: cmake lua detection, will resort to "lua" and "luac" binaries only if
|
||||
more specific suffixes (e.g. luac5.3) are not available
|
||||
fix: test suit, Lua5.3 compatibility (table.unpack)
|
||||
|
||||
2017-02-16: 2.2.2
|
||||
fix: checkgauge 'insist'
|
||||
fix: no partial path exlusion tests
|
||||
fix: write pid of forked process in pidfile
|
||||
fix: crash on not reachable target
|
||||
workaround:
|
||||
changed back to filter style rsync calling
|
||||
until https://bugzilla.samba.org/show_bug.cgi?id=12569
|
||||
is fixed and released.
|
||||
|
||||
2017-01-05: 2.2.1
|
||||
enhancement: now always using filter lists with rysnc
|
||||
instead of include/exclude lists taking advantage of the new --delete-missing-args
|
||||
parameter to delete files on target.
|
||||
>>> Thus Lsyncd 2.2.1 needs rsync >= 3.1.0
|
||||
change: added "tests" make target to run all the tests.
|
||||
fix: crash due to typo in changed ^path, ^pathdir, ^pathname
|
||||
|
||||
2017-01-04: 2.2.0
|
||||
enhancement: add rsync options:
|
||||
"append",
|
||||
"append_verify",
|
||||
"backup",
|
||||
"backup_dir",
|
||||
"chmod",
|
||||
"chown",
|
||||
"copy_dirlinks",
|
||||
"existing",
|
||||
"groupmap",
|
||||
"omit_dir_times",
|
||||
"omit_link_times",
|
||||
"suffix,"
|
||||
"usermap",
|
||||
enhancement: settings{ } now checks for unknown entries and errors if so.
|
||||
change: Level3 scripts ^path,^pathdir and ^pathname now don't start with a slash.
|
||||
change: Lsyncd now writes a startup log message before daemonizing
|
||||
does in case logging fails, it is recognized before it cannot
|
||||
message anything about it, since it deamonized
|
||||
change: compatible with Lua5.3 (along with 5.1 and 5.2)
|
||||
change: _verbatim forced for 'exitcodes' entry.
|
||||
change: manpage is not rebuild by default.
|
||||
it is provided precompiled.
|
||||
change: faulty/deprecated config files that use settings = { ... }, with equal sign
|
||||
are no longer worked around.
|
||||
change: default.direct now calls copy with -p
|
||||
fix: potential race conditions:
|
||||
default.rsyncssh will now channel deletes also through rsync and treats moves
|
||||
as blocking events.
|
||||
fix: ']' is not escaped for rsync rules, since rsync only applies
|
||||
doesn't applie pattern matching if no other pattern chars
|
||||
are found.
|
||||
fix: Shell injection hole close for default.direct on mv commands. (Marcin Szewczyk)
|
||||
fix: Crash of default-direct when source doesn't exit (Michael Ploujnikov)
|
||||
fix: fixed faulty event replacement,
|
||||
a race condition noticed by extensive default.rsyncssh testing
|
||||
changed Delays were not reflected in Events
|
||||
|
||||
2015-10-15: 2.1.6
|
||||
enhancement: Lsyncd now locks its pidfile
|
||||
enhancement: added ssh.identifyFile and ssh.options options
|
||||
enhancement: added rsync inplace option
|
||||
fix: ignore blank lines and rsync commenits in exclude files (David Reiss)
|
||||
fix: don't tread exclude lines with embedded "+" chars as inclusions (David Reiss)
|
||||
fix: crash when debugging inotify (Michael Ploujnikov)
|
||||
fix: fixed Finished/Retrying error messages being swapped around (Jun Saito)
|
||||
fix: properly encapsulate filenames on ssh mv commands to avoid shell command injections.
|
||||
fix: postcmd example (Timo Teräs)
|
||||
change: closes also on INT signals
|
||||
change: now removes its pidfile on INT and TERM signals
|
||||
change: changed build system from autotools to cmake
|
||||
|
||||
2013-06-07: 2.1.5
|
||||
enhancement: Added rsync options: bwlimit, timeout
|
||||
fix: Specifying ssh port no longer overwrites the last rsync option
|
||||
fix: rsync option password_file is now accepted
|
||||
fix: onAttrib is accepted again
|
||||
fix: -log Exec now prints now fully all arguments
|
||||
fix: configure script lua detection now includes math lib to workaround
|
||||
wrongly created "needs COMPAT_ALL" messages.
|
||||
fix: repaired variable replacement for layer 3 scripts
|
||||
fix: config.delay is now checked to a number >= 0
|
||||
change: a2x is no longer checked by configure script.
|
||||
should not be needed when building from tarball
|
||||
|
||||
2012-11-24: 2.1.4
|
||||
fix: making ssh custom port changes work with ssh and rsync
|
||||
|
||||
2012-11-23: 2.1.3
|
||||
fix: fixed 2 crash conditions due to failure to read 'uSettings'
|
||||
|
||||
2012-11-03: 2.1.2
|
||||
fix: added excludeFrom to checkgauge (thx to DavidWittman)
|
||||
fix: fixed rsync option computation
|
||||
enhancement: added password_file file option to rsync
|
||||
|
||||
2012-10-27: 2.1.1
|
||||
fix: fix rsync.rsh, rsync.rsync_path, rsync.tmp_dir, rsync._extra parameters
|
||||
thanks go to Birger Schmidt for this fix.
|
||||
|
||||
2012-10-23: 2.1.0
|
||||
fix: fail startup if settings.inist is false and one of the target hosts fails
|
||||
fix: in case of waiting for processes during restart only logs this state now once a minute
|
||||
rather than filling the log crazy about it
|
||||
enhancement: rsyncOpts has been replaced by rsync = {...} parameter lists
|
||||
enhancement: default.rsyncssh has now a ssh = {...} parameter similar to default.rsync to
|
||||
add option to ssh calls. Ditto for xargs = {...}
|
||||
enhancement: the default.* implementations have a checkgauge erroring on any unknown
|
||||
parameters to the sync{} call
|
||||
enhancement: the delete parameter now takes: true, false, 'running' and 'startup'
|
||||
improvement: Dennis Schridde provided various improvements for Lsyncd's autoconf building
|
||||
change: Lsyncd is now Lua 5.2 compatible
|
||||
change: Lsyncd now exits with exitcode 143 on TERM signal
|
||||
change: settings is now be used as call like settings{...} instead of settings = {...}
|
||||
|
||||
2012-04-04: 2.0.7
|
||||
fix: closed a memory leak due to not correct configured weak tables
|
||||
fix: default.direct, do not use on OSX unrecognized option -t on modify
|
||||
fix: default.direct, typo leading to compile error
|
||||
|
@ -8,7 +145,7 @@
|
|||
change: removed --with-default-runner since it was broken, and will be replaced by something
|
||||
more generic in future
|
||||
|
||||
16-02-2011: 2.0.6
|
||||
2012-02-16: 2.0.6
|
||||
fix: no longer stops syslogging on HUP signals
|
||||
fix: OSX event watcher no longer misses moves into and out of the watch tree
|
||||
fix: not refinding a relative path to the config file in case of HUP.
|
||||
|
@ -39,7 +176,7 @@
|
|||
default.rsyncssh: does not add --delete to rsync, and does not use rm via ssh tunnel
|
||||
default.direct: does not add --delete to startup rsync and does not use rm
|
||||
|
||||
25-08-2011: 2.0.5
|
||||
2011-08-25: 2.0.5
|
||||
fix: Lsyncd will now terminate if it inotify watching exceeds
|
||||
its preset limit.
|
||||
fix: rsync error exit code 12 now results in retries.
|
||||
|
@ -68,14 +205,14 @@
|
|||
enhancement: readdir(path) is available to userscripts, reads the contents
|
||||
of a directory.
|
||||
|
||||
27-03-2011: 2.0.4
|
||||
2011-03-27: 2.0.4
|
||||
enhancement: new setting options logident, logfacility
|
||||
fix: moving filenames with spaces through ssh
|
||||
fix: excludes containing chars % $ ( ) . [ ] + -
|
||||
fix: various typos
|
||||
change: api, settings.statusInterval instead of settings.statusIntervall
|
||||
|
||||
25-02-2011: 2.0.3
|
||||
2011-02-25: 2.0.3
|
||||
enhancement: new default target --direct using /bin/ binaries
|
||||
to keep to local dirs in sync (and by default
|
||||
not preserving ownership)
|
||||
|
@ -90,7 +227,7 @@
|
|||
change: leave lua apichecking enabled by default.
|
||||
|
||||
|
||||
20-01-2011: 2.0.2
|
||||
2011-01-20: 2.0.2
|
||||
fix: exclude rules not terminated with '/' now match a file
|
||||
or dir named exactly the same not starting with.
|
||||
fix: pass exclude rules to the startup sync
|
||||
|
@ -98,7 +235,7 @@
|
|||
partial path than on syncs
|
||||
fix: properly close pipes that needed more than one write.
|
||||
|
||||
11-01-2011: 2.0.1
|
||||
2011-01-11: 2.0.1
|
||||
fix: write pidfile after daemonize()
|
||||
fix: fixed weak tables that allowed garbage collector to collect
|
||||
event lists too eraly.
|
||||
|
@ -106,7 +243,7 @@
|
|||
change: added OSX fsevents interface, disabled in autoconf by default
|
||||
since still very experimental and limited to OSX 10.5 only.
|
||||
|
||||
02-12-2010: 2.0.0
|
||||
2010-12-02: 2.0.0
|
||||
a complete recoding!
|
||||
|
||||
change: format of command line arguments changed completly.
|
||||
|
@ -128,7 +265,7 @@
|
|||
change: manpage is now written in asciidoc
|
||||
change: most more complex logic of Lsyncd is now written in Lua.
|
||||
|
||||
04-10-2010: 1.39
|
||||
2010-10-04: 1.39
|
||||
enhancement: call action for multiple targets simultanously
|
||||
fix: correctly accept <file-filter/> from config xml
|
||||
fix: correctly close and free the inotify file descriptor in case of restart
|
||||
|
@ -136,7 +273,7 @@
|
|||
fix: when delay=0 a bug always called rsync file filter even when in
|
||||
directory mode
|
||||
|
||||
01-09-2010: 1.38
|
||||
2010-09-01: 1.38
|
||||
enhancement: implemented file filters for singular operations
|
||||
enhancement: added --singular parameter for single file calls
|
||||
fix: fixed --dryrun messages
|
||||
|
@ -144,7 +281,7 @@
|
|||
being kill -HUPed
|
||||
internal: printout the actual binary called when --debug specified
|
||||
|
||||
05-08-2010: 1.37
|
||||
2010-08-05: 1.37
|
||||
enhancement: react on HUP signals (interpreted as complete restart)
|
||||
enhancement: inotifies are configureable
|
||||
enhancement: --no-startup skips the startup calls
|
||||
|
@ -155,22 +292,22 @@
|
|||
internal: removed the need of the "tosync" stack
|
||||
internal: use more pointers instead of indexes
|
||||
|
||||
11-07-2010: 1.34
|
||||
2010-07-11: 1.34
|
||||
fix: logging segfault on 64bit systems
|
||||
changed: man page location, spellings
|
||||
|
||||
05-06-2010: 1.33
|
||||
2010-06-05: 1.33
|
||||
fix: exlude file argument passing to rsync
|
||||
fix: allow exlude files specified for individual sources
|
||||
fix/enhancement: exlusions will be compared with extended
|
||||
path files allowing sub dirs to be excluded.
|
||||
enhancement: allow delays and call aggregation
|
||||
|
||||
05-01-2009: Release of lsyncd 1.26
|
||||
2009-01-05: Release of lsyncd 1.26
|
||||
fix: segfault on multitargets
|
||||
changed meaning of "version" tag in lsyncd.conf.xml
|
||||
|
||||
14-12-2008: Release of lsyncd 1.25
|
||||
2008-12-14: Release of lsyncd 1.25
|
||||
fix: mv dir and cp -r working
|
||||
fix: working with reiserfs
|
||||
enhancement: config files
|
||||
|
@ -181,5 +318,5 @@
|
|||
lots of smaller stuff here and there ...
|
||||
Thanks to all contributers!
|
||||
|
||||
05-12-2007: Release of lsyncd 1.0
|
||||
2007-12-05: Release of lsyncd 1.0
|
||||
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
INSTALLING
|
||||
==========
|
||||
|
||||
Prerequisites
|
||||
-------------
|
||||
|
||||
CMake
|
||||
|
||||
Lsyncd now uses CMake as configuration tool
|
||||
|
||||
Common compiler stuff
|
||||
|
||||
The C compiler, make, binutils, etc.
|
||||
|
||||
Lua
|
||||
|
||||
For building Lsyncd the Lua interpreter 'lua'
|
||||
and the Lua compiler 'luac' are needed.
|
||||
They aren't needed in the deployed binary though.
|
||||
|
||||
Use Lua 5.2 or later.
|
||||
|
||||
Liblua
|
||||
|
||||
The lua library.
|
||||
|
||||
Note that you likely need the package "liblua-dev"
|
||||
or something like that.
|
||||
|
||||
Use Lua 5.2 or later.
|
||||
|
||||
Note, this has to be exactly the same Version as the
|
||||
lua compiler used above!
|
||||
|
||||
|
||||
Building
|
||||
--------
|
||||
|
||||
Building with a seperate build directory:
|
||||
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
make
|
||||
sudo make install
|
||||
|
||||
Building intree:
|
||||
cmake .
|
||||
make
|
||||
|
||||
On OSX you yet need to get the xnu sources.
|
||||
For example:
|
||||
cmake -DWITH_INOTIFY=OFF -DWITH_FSEVENTS=ON -DXNU_DIR=/path/to/xnu-VERSION
|
||||
make
|
||||
|
||||
FIXME make install not yet done
|
59
Makefile.am
59
Makefile.am
|
@ -1,59 +0,0 @@
|
|||
AUTOMAKE_OPTIONS = foreign
|
||||
CFLAGS += -Wall $(LUA_CFLAGS)
|
||||
bin_PROGRAMS = lsyncd
|
||||
lsyncd_SOURCES = lsyncd.h lsyncd.c lsyncd.lua default-rsync.lua
|
||||
|
||||
if INOTIFY
|
||||
lsyncd_SOURCES += inotify.c
|
||||
endif
|
||||
|
||||
if FSEVENTS
|
||||
lsyncd_SOURCES += fsevents.c
|
||||
endif
|
||||
|
||||
lsyncd_LDADD = $(LUA_LIBS)
|
||||
exampledir = $(docdir)/
|
||||
dist_example_DATA = \
|
||||
examples/lbash.lua \
|
||||
examples/lecho.lua \
|
||||
examples/lgforce.lua \
|
||||
examples/limagemagic.lua \
|
||||
examples/lpostcmd.lua \
|
||||
examples/lrsync.lua \
|
||||
examples/lrsyncssh.lua
|
||||
TESTS = \
|
||||
tests/churn-rsync.lua \
|
||||
tests/churn-rsyncssh.lua \
|
||||
tests/churn-direct.lua \
|
||||
tests/exclude-rsync.lua \
|
||||
tests/exclude-rsyncssh.lua \
|
||||
tests/schedule.lua \
|
||||
tests/l4rsyncdata.lua
|
||||
|
||||
dist_man1_MANS = doc/lsyncd.1
|
||||
EXTRA_DIST = doc/lsyncd.1.txt inotify.c fsevents.c bin2carray.lua \
|
||||
default.lua default-rsync.lua default-rsyncssh.lua default-direct.lua
|
||||
|
||||
doc/lsyncd.1: doc/lsyncd.1.txt
|
||||
a2x --format=manpage $<
|
||||
|
||||
CLEANFILES = runner.out defaults.out runner.c defaults.c
|
||||
|
||||
# compiles the runner and the defaults into the binary
|
||||
lsyncd_LDADD += runner.o defaults.o
|
||||
|
||||
runner.o: runner.c
|
||||
defaults.o: defaults.c
|
||||
|
||||
runner.c: runner.out bin2carray.lua
|
||||
lua ./bin2carray.lua $< runner $@
|
||||
|
||||
defaults.c: defaults.out bin2carray.lua
|
||||
lua ./bin2carray.lua $< defaults $@
|
||||
|
||||
runner.out: lsyncd.lua
|
||||
luac -o $@ $<
|
||||
|
||||
defaults.out: default.lua default-rsync.lua default-rsyncssh.lua default-direct.lua
|
||||
luac -o $@ $^
|
||||
|
35
README.md
35
README.md
|
@ -2,11 +2,13 @@ Lsyncd -- Live Syncing (Mirror) Daemon
|
|||
======================================
|
||||
Description
|
||||
-----------
|
||||
Lsyncd watches a local directory trees event monitor interface (inotify or fsevents). It aggregates and combines events for a few seconds and then spawns one (or more) process(es) to synchronize the changes. By default this is [rsync](http://rsync.samba.org/). Lsyncd is thus a light-weight live mirror solution that is comparatively easy to install not requiring new filesystems or blockdevices and does not hamper local filesystem performance.
|
||||
Lsyncd watches a local directory trees event monitor interface (inotify or fsevents). It aggregates and combines events for a few seconds and then spawns one (or more) process(es) to synchronize the changes. By default this is [rsync](http://rsync.samba.org/). Lsyncd is thus a light-weight live mirror solution that is comparatively easy to install not requiring new filesystems or block devices and does not hamper local filesystem performance.
|
||||
|
||||
Rsync+ssh is an advanced action configuration that uses a SSH to act file and directory moves directly on the target instead of retransmitting the move destination over the wire.
|
||||
Rsync+ssh is an advanced action configuration that uses a SSH to act file and directory moves directly on the target instead of re-transmitting the move destination over the wire.
|
||||
|
||||
Fine-grained customizaton can be achieved through the config file. Custom action configs can even be written from scratch in cascading layers ranging from shell scripts to code written in the [Lua language](http://www.lua.org/) This way simplicity can be balanced with powerfulness. See the manual for details [Lsyncd20Manual](http://code.google.com/p/lsyncd/wiki/Lsyncd20Manual)
|
||||
Fine-grained customization can be achieved through the config file. Custom action configs can even be written from scratch in cascading layers ranging from shell scripts to code written in the [Lua language](http://www.lua.org/). This way simple, powerful and flexible configurations can be achieved. See [the manual](https://lsyncd.github.io/lsyncd/) for details.
|
||||
|
||||
Lsyncd 2.2.1 requires rsync >= 3.1 on all source and target machines.
|
||||
|
||||
License: [GPLv2](http://www.fsf.org/licensing/licenses/info/GPLv2.html) or any later GPL version.
|
||||
|
||||
|
@ -14,26 +16,41 @@ When to use
|
|||
-----------
|
||||
Lsyncd is designed to synchronize a local directory tree with low profile of expected changes to a remote mirror. Lsyncd is especially useful to sync data from a secure area to a not-so-secure area.
|
||||
|
||||
2-way/bidirection synchronization
|
||||
---------------------------------
|
||||
It is not possible to use lsyncd to synchronize for example `folder1` with `folder2` and vice versa. Only one source to one target. Two way synchronization is a very hard problem that needs specialized tools.
|
||||
Imagine you start writing a very large file to `folder1`, lsyncd will start synchronizing this file to `folder2`, which might be on a different machine. The lsyncd on that machine will see a new file, and try to synchronize it back to `folder1`. If at the same time, you change bytes in this file, those changes will be overwritten with old data. Using lsyncd in such a way might work in practice, but data corruption is easily possible if you write into files afterwards.
|
||||
`git-annex` is a good way to do this, if you don't mind working with git repositories. It stores each change as a revision that can be rolled back.
|
||||
|
||||
|
||||
Other synchronization tools
|
||||
------------------------
|
||||
[DRBD](http://www.drbd.org) operates on block device level. This makes it useful for synchronizing systems that are under heavy load. Lsyncd on the other hand does not require you to change block devices and/or mount points, allows you to change uid/gid of the transferred files, separates the receiver through the one-way nature of rsync. DRBD is likely the better option if you are syncing Databases.
|
||||
[DRBD](http://www.drbd.org) operates on block device level. This makes it useful for synchronizing systems that are under heavy load. Lsyncd on the other hand does not require you to change block devices and/or mount points, allows you to change uid/gid of the transferred files, separates the receiver through the one-way nature of rsync. DRBD is likely the better option if you are syncing databases.
|
||||
|
||||
[GlusterFS](http://www.gluster.org) and [BindFS](http://www.cs.helsinki.fi/u/partel/bindfs/) use a FUSE-Filesystem to interject kernel/userspace filesystem events.
|
||||
[GlusterFS](http://www.gluster.org) and [BindFS](http://bindfs.org/) use a FUSE-Filesystem to interject kernel/userspace filesystem events.
|
||||
|
||||
[Mirror](https://github.com/stephenh/mirror) is an asynchronous synchronisation tool that takes use of the inotify notifications much like Lsyncd. The main differences are: it is developed specifically for master-master use, thus running on a daemon on both systems, uses its own transportation layer instead of rsync and is Java instead of Lsyncd's C core with Lua scripting.
|
||||
|
||||
[git-annex](https://git-annex.branchable.com/) allows managing large files with git, without storing the file contents in git. It can sync, backup, and archive your data, offline and online. Checksums and encryption keep your data safe and secure. Bring the power and distributed nature of git to bear on your large files with git-annex.
|
||||
|
||||
git-annex is designed for git users who love the command line. For everyone else, the git-annex assistant turns git-annex into an easy to use folder synchroniser.
|
||||
|
||||
[Wikipedia](https://en.wikipedia.org/wiki/Comparison_of_file_synchronization_software) Comparison of file synchronization software
|
||||
|
||||
Lsyncd usage examples
|
||||
---------------------
|
||||
```lsyncd -rsync /home remotehost.org::share/```
|
||||
|
||||
This watches and rsycn´s the local directory /home with all subdirectories and
|
||||
This watches and rsyncs the local directory /home with all sub-directories and
|
||||
transfers them to 'remotehost' using the rsync-share 'share'.
|
||||
|
||||
```lsyncd -rsyncssh /home remotehost.org backup-home/```
|
||||
|
||||
This will also rsync/watch '/home', but it uses a ssh connection to make moves local on the remotehost instead of retransmitting the moved file over the wire.
|
||||
This will also rsync/watch '/home', but it uses a ssh connection to make moves local on the remotehost instead of re-transmitting the moved file over the wire.
|
||||
|
||||
Some more complicated examples, tips and tricks you can find in the [Lsyncd20Manual](http://code.google.com/p/lsyncd/wiki/Lsyncd20Manual).
|
||||
Some more complicated examples, tips and tricks you can find in the [manual](https://lsyncd.github.io/lsyncd/).
|
||||
|
||||
Disclaimer
|
||||
----------
|
||||
Besides the usual disclaimer in the license, we want to specifically emphasize that neither the authors nor any organization the authors are associated with can and will hold responsible for data-loss caused by possible malfunctions of Lsyncd.
|
||||
Besides the usual disclaimer in the license, we want to specifically emphasize that the authors, and any organizations the authors are associated with, can not be held responsible for data-loss caused by possible malfunctions of Lsyncd.
|
||||
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
echo "Generating configure files... may take a while."
|
||||
|
||||
autoreconf --install --force && \
|
||||
echo "Preparing was successful if there was no error messages above." && \
|
||||
echo "Now type:" && \
|
||||
echo " ./configure && make" && \
|
||||
echo "Run './configure --help' for more information"
|
|
@ -0,0 +1,129 @@
|
|||
# Locate Lua library
|
||||
# This module defines
|
||||
# LUA_EXECUTABLE, if found
|
||||
# LUA_FOUND, if false, do not try to link to Lua
|
||||
# LUA_LIBRARIES
|
||||
# LUA_INCLUDE_DIR, where to find lua.h
|
||||
# LUA_VERSION_STRING, the version of Lua found (since CMake 2.8.8)
|
||||
#
|
||||
# Note that the expected include convention is
|
||||
# #include "lua.h"
|
||||
# and not
|
||||
# #include <lua/lua.h>
|
||||
# This is because, the lua location is not standardized and may exist
|
||||
# in locations other than lua/
|
||||
|
||||
#=============================================================================
|
||||
# Copyright 2007-2009 Kitware, Inc.
|
||||
# Modified to support Lua 5.2 by LuaDist 2012
|
||||
# Modified to support Lua 5.4 by LuaDist 2022
|
||||
#
|
||||
# Distributed under the OSI-approved BSD License (the "License");
|
||||
# see accompanying file Copyright.txt for details.
|
||||
#
|
||||
# This software is distributed WITHOUT ANY WARRANTY; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the License for more information.
|
||||
#=============================================================================
|
||||
# (To distribute this file outside of CMake, substitute the full
|
||||
# License text for the above reference.)
|
||||
#
|
||||
# This module will try to find the newest Lua version down to 5.4
|
||||
|
||||
# Always search for non-versioned lua first (recommended)
|
||||
SET(_POSSIBLE_LUA_INCLUDE include include/lua)
|
||||
#SET(_POSSIBLE_LUA_EXECUTABLE lua)
|
||||
#SET(_POSSIBLE_LUA_COMPILER luac)
|
||||
#SET(_POSSIBLE_LUA_LIBRARY lua)
|
||||
|
||||
# Determine possible naming suffixes (there is no standard for this)
|
||||
SET(_POSSIBLE_SUFFIXES "54" "5.4" "-5.4" "53" "5.3" "-5.3" "52" "5.2" "-5.2" "")
|
||||
|
||||
# Set up possible search names and locations
|
||||
FOREACH(_SUFFIX IN LISTS _POSSIBLE_SUFFIXES)
|
||||
LIST(APPEND _POSSIBLE_LUA_INCLUDE "include/lua${_SUFFIX}")
|
||||
LIST(APPEND _POSSIBLE_LUA_EXECUTABLE "lua${_SUFFIX}")
|
||||
LIST(APPEND _POSSIBLE_LUA_COMPILER "luac${_SUFFIX}")
|
||||
LIST(APPEND _POSSIBLE_LUA_LIBRARY "lua${_SUFFIX}")
|
||||
ENDFOREACH(_SUFFIX)
|
||||
|
||||
# Find the lua executable
|
||||
FIND_PROGRAM(LUA_EXECUTABLE
|
||||
NAMES ${_POSSIBLE_LUA_EXECUTABLE}
|
||||
)
|
||||
|
||||
# Find the lua executable
|
||||
FIND_PROGRAM(LUA_COMPILER
|
||||
NAMES luac5.3 ${_POSSIBLE_LUA_COMPILER}
|
||||
)
|
||||
|
||||
# Find the lua header
|
||||
FIND_PATH(LUA_INCLUDE_DIR lua.h
|
||||
HINTS
|
||||
$ENV{LUA_DIR}
|
||||
PATH_SUFFIXES ${_POSSIBLE_LUA_INCLUDE}
|
||||
PATHS
|
||||
~/Library/Frameworks
|
||||
/Library/Frameworks
|
||||
/usr/local
|
||||
/usr
|
||||
/sw # Fink
|
||||
/opt/local # DarwinPorts
|
||||
/opt/csw # Blastwave
|
||||
/opt
|
||||
)
|
||||
|
||||
# Find the lua library
|
||||
FIND_LIBRARY(LUA_LIBRARY
|
||||
NAMES ${_POSSIBLE_LUA_LIBRARY}
|
||||
HINTS
|
||||
$ENV{LUA_DIR}
|
||||
PATH_SUFFIXES lib64 lib
|
||||
PATHS
|
||||
~/Library/Frameworks
|
||||
/Library/Frameworks
|
||||
/usr/local
|
||||
/usr
|
||||
/sw
|
||||
/opt/local
|
||||
/opt/csw
|
||||
/opt
|
||||
)
|
||||
|
||||
IF(LUA_LIBRARY)
|
||||
# include the math library for Unix
|
||||
IF(UNIX AND NOT APPLE)
|
||||
FIND_LIBRARY(LUA_MATH_LIBRARY m)
|
||||
SET( LUA_LIBRARIES "${LUA_LIBRARY};${LUA_MATH_LIBRARY}" CACHE STRING "Lua Libraries")
|
||||
# For Windows and Mac, don't need to explicitly include the math library
|
||||
ELSE(UNIX AND NOT APPLE)
|
||||
SET( LUA_LIBRARIES "${LUA_LIBRARY}" CACHE STRING "Lua Libraries")
|
||||
ENDIF(UNIX AND NOT APPLE)
|
||||
ENDIF(LUA_LIBRARY)
|
||||
|
||||
# Determine Lua version
|
||||
IF(LUA_INCLUDE_DIR AND EXISTS "${LUA_INCLUDE_DIR}/lua.h")
|
||||
FILE(STRINGS "${LUA_INCLUDE_DIR}/lua.h" lua_version_major_str REGEX "^#define[ \t]+LUA_VERSION_MAJOR[ \t]+\".+\"")
|
||||
FILE(STRINGS "${LUA_INCLUDE_DIR}/lua.h" lua_version_minor_str REGEX "^#define[ \t]+LUA_VERSION_MINOR[ \t]+\".+\"")
|
||||
FILE(STRINGS "${LUA_INCLUDE_DIR}/lua.h" lua_version_release_str REGEX "^#define[ \t]+LUA_VERSION_RELEASE[ \t]+\".+\"")
|
||||
|
||||
STRING(REGEX REPLACE "^#define[ \t]+LUA_VERSION_MAJOR[ \t]+\"([^\"]+)\".*" "\\1" LUA_VERSION_MAJOR "${lua_version_major_str}")
|
||||
STRING(REGEX REPLACE "^#define[ \t]+LUA_VERSION_MINOR[ \t]+\"([^\"]+)\".*" "\\1" LUA_VERSION_MINOR "${lua_version_minor_str}")
|
||||
STRING(REGEX REPLACE "^#define[ \t]+LUA_VERSION_RELEASE[ \t]+\"([^\"]+)\".*" "\\1" LUA_VERSION_RELEASE "${lua_version_release_str}")
|
||||
|
||||
STRING(CONCAT LUA_VERSION_STRING ${LUA_VERSION_MAJOR} "." ${LUA_VERSION_MINOR} "." ${LUA_VERSION_RELEASE})
|
||||
|
||||
UNSET(lua_version_major_str)
|
||||
UNSET(lua_version_minor_str)
|
||||
UNSET(lua_version_release_str)
|
||||
ENDIF()
|
||||
|
||||
INCLUDE(FindPackageHandleStandardArgs)
|
||||
# handle the QUIETLY and REQUIRED arguments and set LUA_FOUND to TRUE if
|
||||
# all listed variables are TRUE
|
||||
FIND_PACKAGE_HANDLE_STANDARD_ARGS(Lua
|
||||
REQUIRED_VARS LUA_LIBRARIES LUA_INCLUDE_DIR
|
||||
VERSION_VAR LUA_VERSION_STRING)
|
||||
|
||||
MARK_AS_ADVANCED(LUA_INCLUDE_DIR LUA_LIBRARIES LUA_LIBRARY LUA_MATH_LIBRARY LUA_EXECUTABLE LUA_COMPILER)
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
/* Lsyncd Version */
|
||||
#define PACKAGE_VERSION "@LSYNCD_VERSION@"
|
||||
|
||||
/* File event notification mechanims available */
|
||||
#cmakedefine WITH_INOTIFY 1
|
||||
#cmakedefine WITH_FSEVENTS 1
|
||||
|
||||
/* OS */
|
||||
#cmakedefine LSYNCD_TARGET_APPLE 1
|
58
configure.ac
58
configure.ac
|
@ -1,58 +0,0 @@
|
|||
# -*- Autoconf -*-
|
||||
# Process this file with autoconf to produce a configure script.
|
||||
#AC_PREREQ(2.60)
|
||||
AC_INIT(lsyncd, 2.0.7, axkibe@gmail.com)
|
||||
AC_CONFIG_SRCDIR([lsyncd.c])
|
||||
AC_CONFIG_HEADER([config.h])
|
||||
AM_INIT_AUTOMAKE(AC_PACKAGE_NAME, AC_PACKAGE_VERSION)
|
||||
# Checks for programs.
|
||||
AC_PROG_CC
|
||||
AC_PROG_INSTALL
|
||||
AC_PROG_MAKE_SET
|
||||
|
||||
###
|
||||
# Checks for lua
|
||||
PKG_CHECK_MODULES([LUA], [lua5.1 >= 5.1.3],,[
|
||||
PKG_CHECK_MODULES([LUA], [lua51 >= 5.1.3],,[
|
||||
PKG_CHECK_MODULES([LUA], [lua-5.1 >= 5.1.3],,[
|
||||
PKG_CHECK_MODULES([LUA], [lua >= 5.1.3])
|
||||
])
|
||||
])
|
||||
])
|
||||
|
||||
###
|
||||
# Checks for header files.
|
||||
AC_CHECK_HEADERS([sys/inotify.h])
|
||||
|
||||
###
|
||||
# --without-inotify option
|
||||
AC_ARG_WITH([inotify],
|
||||
[ --without-inotify Do not use Linux inotify event interface. On by default.],
|
||||
[],[with_inotify=yes])
|
||||
if test "x${with_inotify}" == xyes; then
|
||||
echo "compiling with inotify"
|
||||
AC_DEFINE(LSYNCD_WITH_INOTIFY,,"descr")
|
||||
else
|
||||
echo "compiling without inotify"
|
||||
fi
|
||||
AM_CONDITIONAL([INOTIFY], [test x${with_inotify} != xno])
|
||||
|
||||
###
|
||||
# --with-fsevents
|
||||
# disabled per default, experimental, works only with OS X 10.5/10.6
|
||||
AC_ARG_WITH([fsevents],
|
||||
[ --with-fsevents Uses MacOS (10.5) /dev/fsevents. EXPERIMENTAL!
|
||||
Off by default.])
|
||||
if test "x${with_fsevents}" == xyes; then
|
||||
echo "compiling with fsevents. WARNING experimental!"
|
||||
AC_DEFINE(LSYNCD_WITH_FSEVENTS,,"descr")
|
||||
fi
|
||||
AM_CONDITIONAL([FSEVENTS],
|
||||
[test x${with_fsevents} != x -a xno${with_fsevents} != xno])
|
||||
|
||||
# Checks for typedefs, structures, and compiler characteristics.
|
||||
# Checks for library functions.
|
||||
AC_CONFIG_FILES([Makefile])
|
||||
AC_OUTPUT
|
||||
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
-- default-direct.lua
|
||||
--
|
||||
-- Keeps two directories with /bin/cp, /bin/rm and /bin/mv in sync.
|
||||
-- Keeps two directories with cp, rm and mv in sync.
|
||||
-- Startup still uses rsync tough.
|
||||
--
|
||||
-- A (Layer 1) configuration.
|
||||
|
@ -9,6 +9,7 @@
|
|||
-- Note:
|
||||
-- this is infact just a configuration using Layer 1 configuration
|
||||
-- like any other. It only gets compiled into the binary by default.
|
||||
--
|
||||
-- You can simply use a modified one, by copying everything into a
|
||||
-- config file of yours and name it differently.
|
||||
--
|
||||
|
@ -17,158 +18,212 @@
|
|||
--
|
||||
--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
if not default then error('default not loaded'); end
|
||||
if not default.rsync then error('default-direct (currently) needs default.rsync loaded'); end
|
||||
if default.direct then error('default-direct already loaded'); end
|
||||
if not default then
|
||||
error('default not loaded')
|
||||
end
|
||||
|
||||
default.direct = {
|
||||
-----
|
||||
-- Spawns rsync for a list of events
|
||||
if not default.rsync then
|
||||
error('default-direct (currently) needs default.rsync loaded')
|
||||
end
|
||||
|
||||
if default.direct then
|
||||
error('default-direct already loaded')
|
||||
end
|
||||
|
||||
local direct = { }
|
||||
|
||||
default.direct = direct
|
||||
|
||||
|
||||
--
|
||||
-- known configuration parameters
|
||||
--
|
||||
direct.checkgauge = {
|
||||
--
|
||||
action = function(inlet)
|
||||
-- gets all events ready for syncing
|
||||
local event, event2 = inlet.getEvent()
|
||||
local config = inlet.getConfig()
|
||||
-- inherits rsync config params
|
||||
--
|
||||
default.rsync.checkgauge,
|
||||
|
||||
if event.etype == 'Create' then
|
||||
if event.isdir then
|
||||
spawn(
|
||||
event,
|
||||
'/bin/mkdir',
|
||||
event.targetPath
|
||||
)
|
||||
else
|
||||
-- 'cp -t', not supported on OSX
|
||||
spawn(
|
||||
event,
|
||||
'/bin/cp',
|
||||
event.sourcePath,
|
||||
event.targetPathdir
|
||||
)
|
||||
end
|
||||
elseif event.etype == 'Modify' then
|
||||
if event.isdir then
|
||||
error("Do not know how to handle 'Modify' on dirs")
|
||||
end
|
||||
spawn(event,
|
||||
'/bin/cp',
|
||||
rsyncExitCodes = true,
|
||||
onMove = true,
|
||||
}
|
||||
|
||||
|
||||
--
|
||||
-- Spawns rsync for a list of events
|
||||
--
|
||||
direct.action = function(inlet)
|
||||
-- gets all events ready for syncing
|
||||
local event, event2 = inlet.getEvent()
|
||||
local config = inlet.getConfig()
|
||||
|
||||
if event.etype == 'Create' then
|
||||
if event.isdir then
|
||||
spawn(
|
||||
event,
|
||||
'mkdir',
|
||||
'--',
|
||||
event.targetPath
|
||||
)
|
||||
else
|
||||
-- 'cp -t', not supported on OSX
|
||||
spawn(
|
||||
event,
|
||||
'cp',
|
||||
'-p',
|
||||
'--',
|
||||
event.sourcePath,
|
||||
event.targetPathdir
|
||||
)
|
||||
elseif event.etype == 'Delete' then
|
||||
if not config.delete then
|
||||
inlet.discardEvent(event)
|
||||
end
|
||||
end
|
||||
elseif event.etype == 'Modify' then
|
||||
if event.isdir then
|
||||
error("Do not know how to handle 'Modify' on dirs")
|
||||
end
|
||||
spawn(event,
|
||||
'cp',
|
||||
'-p',
|
||||
'--',
|
||||
event.sourcePath,
|
||||
event.targetPathdir
|
||||
)
|
||||
elseif event.etype == 'Delete' then
|
||||
|
||||
local tp = event.targetPath
|
||||
-- extra security check
|
||||
if tp == '' or tp == '/' or not tp then
|
||||
error('Refusing to erase your harddisk!')
|
||||
end
|
||||
spawn(event, '/bin/rm', '-rf', tp)
|
||||
elseif event.etype == 'Move' then
|
||||
local tp = event.targetPath
|
||||
-- extra security check
|
||||
if tp == '' or tp == '/' or not tp then
|
||||
error('Refusing to erase your harddisk!')
|
||||
end
|
||||
local command = '/bin/mv $1 $2 || /bin/rm -rf $1'
|
||||
if not config.delete then command = '/bin/mv $1 $2'; end
|
||||
spawnShell(
|
||||
event,
|
||||
command,
|
||||
event.targetPath,
|
||||
event2.targetPath)
|
||||
else
|
||||
log('Warn', 'ignored an event of type "',event.etype, '"')
|
||||
if
|
||||
config.delete ~= true and
|
||||
config.delete ~= 'running'
|
||||
then
|
||||
inlet.discardEvent(event)
|
||||
return
|
||||
end
|
||||
end,
|
||||
|
||||
-----
|
||||
-- Called when collecting a finished child process
|
||||
--
|
||||
collect = function(agent, exitcode)
|
||||
local config = agent.config
|
||||
local tp = event.targetPath
|
||||
|
||||
if not agent.isList and agent.etype == 'Init' then
|
||||
local rc = config.rsyncExitCodes[exitcode]
|
||||
if rc == 'ok' then
|
||||
log('Normal', 'Startup of "',agent.source,'" finished: ', exitcode)
|
||||
elseif rc == 'again' then
|
||||
if settings.insist then
|
||||
log('Normal', 'Retrying startup of "',agent.source,'": ', exitcode)
|
||||
else
|
||||
log('Error', 'Temporary or permanent failure on startup of "',
|
||||
agent.source, '". Terminating since "insist" is not set.');
|
||||
terminate(-1) -- ERRNO
|
||||
end
|
||||
elseif rc == 'die' then
|
||||
log('Error', 'Failure on startup of "',agent.source,'": ', exitcode)
|
||||
-- extra security check
|
||||
if tp == '' or tp == '/' or not tp then
|
||||
error('Refusing to erase your harddisk!')
|
||||
end
|
||||
|
||||
spawn(event, 'rm', '-rf', '--', tp)
|
||||
|
||||
elseif event.etype == 'Move' then
|
||||
local tp = event.targetPath
|
||||
|
||||
-- extra security check
|
||||
if tp == '' or tp == '/' or not tp then
|
||||
error('Refusing to erase your harddisk!')
|
||||
end
|
||||
|
||||
local command = 'mv -- "$1" "$2" || rm -rf -- "$1"'
|
||||
|
||||
if
|
||||
config.delete ~= true and
|
||||
config.delete ~= 'running'
|
||||
then
|
||||
command = 'mv -- "$1" "$2"'
|
||||
end
|
||||
|
||||
spawnShell(
|
||||
event,
|
||||
command,
|
||||
event.targetPath,
|
||||
event2.targetPath
|
||||
)
|
||||
elseif event.etype == 'Full' then
|
||||
local tp = event.targetPath
|
||||
|
||||
-- extra security check
|
||||
if tp == '' or tp == '/' or not tp then
|
||||
error('Refusing to erase your harddisk!')
|
||||
end
|
||||
|
||||
-- trigger full sync function
|
||||
direct.full(event)
|
||||
else
|
||||
log('Warn', 'ignored an event of type "',event.etype, '"')
|
||||
inlet.discardEvent(event)
|
||||
end
|
||||
end
|
||||
|
||||
--
|
||||
-- Called when collecting a finished child process
|
||||
--
|
||||
direct.collect = function(agent, exitcode)
|
||||
|
||||
local config = agent.config
|
||||
|
||||
if not agent.isList and agent.etype == 'Init' then
|
||||
local rc = config.rsyncExitCodes[exitcode]
|
||||
if rc == 'ok' then
|
||||
log('Normal', 'Startup of "',agent.source,'" finished: ', exitcode)
|
||||
elseif rc == 'again'
|
||||
then
|
||||
if settings( 'insist' )
|
||||
then
|
||||
log('Normal', 'Retrying startup of "',agent.source,'": ', exitcode)
|
||||
else
|
||||
log('Error', 'Unknown exitcode on startup of "', agent.source,': "',exitcode)
|
||||
rc = 'die'
|
||||
log('Error', 'Temporary or permanent failure on startup of "',
|
||||
agent.source, '". Terminating since "insist" is not set.');
|
||||
terminate(-1) -- ERRNO
|
||||
end
|
||||
return rc
|
||||
elseif rc == 'die' then
|
||||
log('Error', 'Failure on startup of "',agent.source,'": ', exitcode)
|
||||
else
|
||||
log('Error', 'Unknown exitcode on startup of "', agent.source,': "',exitcode)
|
||||
rc = 'die'
|
||||
end
|
||||
return rc
|
||||
end
|
||||
|
||||
-- everything else is just as it is,
|
||||
-- there is no network to retry something.
|
||||
return
|
||||
end,
|
||||
-- everything else is just as it is,
|
||||
-- there is no network to retry something.
|
||||
return
|
||||
end
|
||||
|
||||
-----
|
||||
-- Spawns the recursive startup sync
|
||||
-- (currently) identical to default rsync.
|
||||
--
|
||||
init = default.rsync.init,
|
||||
--
|
||||
-- Spawns the recursive startup sync
|
||||
-- (currently) identical to default rsync.
|
||||
--
|
||||
direct.init = default.rsync.init
|
||||
|
||||
-----
|
||||
-- Checks the configuration.
|
||||
--
|
||||
prepare = function(config)
|
||||
if not config.target then
|
||||
error('default.direct needs "target".', 4)
|
||||
end
|
||||
--
|
||||
-- Spawns the recursive startup sync
|
||||
-- (currently) identical to default rsync.
|
||||
--
|
||||
direct.full = default.rsync.full
|
||||
|
||||
if config.rsyncOps then
|
||||
error('did you mean rsyncOpts with "t"?', 4)
|
||||
end
|
||||
end,
|
||||
--
|
||||
-- Checks the configuration.
|
||||
--
|
||||
direct.prepare = function( config, level )
|
||||
|
||||
-----
|
||||
-- Default delay is very short.
|
||||
--
|
||||
delay = 1,
|
||||
default.rsync.prepare( config, level + 1 )
|
||||
|
||||
------
|
||||
-- Let the core not split move events.
|
||||
--
|
||||
onMove = true,
|
||||
end
|
||||
|
||||
-----
|
||||
-- The rsync binary called.
|
||||
--
|
||||
rsyncBinary = '/usr/bin/rsync',
|
||||
--
|
||||
-- Default delay is very short.
|
||||
--
|
||||
direct.delay = 1
|
||||
|
||||
-----
|
||||
-- For startup sync
|
||||
--
|
||||
rsyncOpts = '-lts',
|
||||
--
|
||||
-- Let the core not split move events.
|
||||
--
|
||||
direct.onMove = true
|
||||
|
||||
-----
|
||||
-- By default do deletes.
|
||||
--
|
||||
delete = true,
|
||||
--
|
||||
-- Rsync configuration for startup.
|
||||
--
|
||||
direct.rsync = default.rsync.rsync
|
||||
direct.rsyncExitCodes = default.rsyncExitCodes
|
||||
|
||||
-----
|
||||
-- rsync exit codes
|
||||
--
|
||||
rsyncExitCodes = default.rsyncExitCodes,
|
||||
--
|
||||
-- By default do deletes.
|
||||
--
|
||||
direct.delete = true
|
||||
|
||||
-----
|
||||
-- On many system multiple disk operations just rather slow down
|
||||
-- than speed up.
|
||||
--
|
||||
-- On many system multiple disk operations just rather slow down
|
||||
-- than speed up.
|
||||
|
||||
maxProcesses = 1,
|
||||
}
|
||||
direct.maxProcesses = 1
|
||||
|
|
|
@ -15,171 +15,804 @@
|
|||
--
|
||||
--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
if not default then error('default not loaded'); end
|
||||
if default.rsync then error('default-rsync already loaded'); end
|
||||
|
||||
default.rsync = {
|
||||
-----
|
||||
-- Spawns rsync for a list of events
|
||||
if not default then error( 'default not loaded' ) end
|
||||
|
||||
if default.rsync then error( 'default-rsync already loaded' ) end
|
||||
|
||||
|
||||
local rsync = { }
|
||||
|
||||
default.rsync = rsync
|
||||
|
||||
-- uses default collect
|
||||
|
||||
--
|
||||
-- used to ensure there aren't typos in the keys
|
||||
--
|
||||
rsync.checkgauge = {
|
||||
|
||||
-- unsets default user action handlers
|
||||
onCreate = false,
|
||||
onModify = false,
|
||||
onDelete = false,
|
||||
onStartup = false,
|
||||
onMove = false,
|
||||
|
||||
delete = true,
|
||||
exclude = true,
|
||||
excludeFrom = true,
|
||||
filter = true,
|
||||
filterFrom = true,
|
||||
target = true,
|
||||
batchSizeLimit = true,
|
||||
|
||||
rsync = {
|
||||
acls = true,
|
||||
append = true,
|
||||
append_verify = true,
|
||||
archive = true,
|
||||
backup = true,
|
||||
backup_dir = true,
|
||||
binary = true,
|
||||
bwlimit = true,
|
||||
checksum = true,
|
||||
chown = true,
|
||||
chmod = true,
|
||||
compress = true,
|
||||
copy_dirlinks = true,
|
||||
copy_links = true,
|
||||
copy_unsafe_links = true,
|
||||
cvs_exclude = true,
|
||||
delete_excluded = true,
|
||||
dry_run = true,
|
||||
executability = true,
|
||||
existing = true,
|
||||
group = true,
|
||||
groupmap = true,
|
||||
hard_links = true,
|
||||
ignore_times = true,
|
||||
inplace = true,
|
||||
ipv4 = true,
|
||||
ipv6 = true,
|
||||
keep_dirlinks = true,
|
||||
links = true,
|
||||
one_file_system = true,
|
||||
omit_dir_times = true,
|
||||
omit_link_times = true,
|
||||
owner = true,
|
||||
password_file = true,
|
||||
perms = true,
|
||||
protect_args = true,
|
||||
prune_empty_dirs = true,
|
||||
quiet = true,
|
||||
rsh = true,
|
||||
rsync_path = true,
|
||||
sparse = true,
|
||||
suffix = true,
|
||||
temp_dir = true,
|
||||
timeout = true,
|
||||
times = true,
|
||||
update = true,
|
||||
usermap = true,
|
||||
verbose = true,
|
||||
whole_file = true,
|
||||
xattrs = true,
|
||||
_extra = true,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
-- internal function to actually do the transfer
|
||||
local run_action = function
|
||||
(
|
||||
inlet,
|
||||
elist
|
||||
)
|
||||
local config = inlet.getConfig( )
|
||||
|
||||
local substitudes = inlet.getSubstitutionData(elist, {})
|
||||
local target = substitudeCommands(config.target, substitudes)
|
||||
|
||||
--
|
||||
action = function(inlet)
|
||||
-- gets all events ready for syncing
|
||||
local elist = inlet.getEvents(
|
||||
function(event)
|
||||
return event.etype ~= 'Init' and event.etype ~= 'Blanket'
|
||||
-- Replaces what rsync would consider filter rules by literals
|
||||
--
|
||||
local function sub
|
||||
(
|
||||
p -- pattern
|
||||
)
|
||||
if not p then return end
|
||||
|
||||
return p:
|
||||
gsub( '%?', '\\?' ):
|
||||
gsub( '%*', '\\*' ):
|
||||
gsub( '%[', '\\[' ):
|
||||
gsub( '%]', '\\]' )
|
||||
end
|
||||
|
||||
--
|
||||
-- Gets the list of paths for the event list
|
||||
--
|
||||
-- Deletes create multi match patterns
|
||||
--
|
||||
local paths = elist.getPaths(
|
||||
function
|
||||
(
|
||||
etype, -- event type
|
||||
path1, -- path
|
||||
path2 -- path to for move events
|
||||
)
|
||||
if string.byte( path1, -1 ) == 47 and etype == 'Delete'
|
||||
then
|
||||
return sub( path1 )..'***', sub( path2 )
|
||||
else
|
||||
return sub( path1 ), sub( path2 )
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
-- stores all filters by integer index
|
||||
local filterI = { }
|
||||
|
||||
-- stores all filters with path index
|
||||
local filterP = { }
|
||||
|
||||
-- adds one path to the filter
|
||||
local function addToFilter
|
||||
(
|
||||
path
|
||||
)
|
||||
if filterP[ path ] then return end
|
||||
|
||||
filterP[ path ] = true
|
||||
|
||||
table.insert( filterI, path )
|
||||
end
|
||||
|
||||
-- adds a path to the filter.
|
||||
--
|
||||
-- rsync needs to have entries for all steps in the path,
|
||||
-- so the file for example d1/d2/d3/f1 needs following filters:
|
||||
-- 'd1/', 'd1/d2/', 'd1/d2/d3/' and 'd1/d2/d3/f1'
|
||||
for _, path in ipairs( paths )
|
||||
do
|
||||
if path and path ~= ''
|
||||
then
|
||||
addToFilter( path )
|
||||
|
||||
local pp = string.match( path, '^(.*/)[^/]+/?' )
|
||||
|
||||
while pp
|
||||
do
|
||||
addToFilter( pp )
|
||||
|
||||
pp = string.match( pp, '^(.*/)[^/]+/?' )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
log(
|
||||
'Normal',
|
||||
'Calling rsync with filter-list of new/modified files/dirs\n',
|
||||
table.concat( filterI, '\n' )
|
||||
)
|
||||
|
||||
local config = inlet.getConfig( )
|
||||
|
||||
local delete = nil
|
||||
|
||||
if config.delete == true or config.delete == 'running'
|
||||
then
|
||||
delete = { '--delete', '--ignore-errors' }
|
||||
end
|
||||
|
||||
spawn(
|
||||
elist,
|
||||
config.rsync.binary,
|
||||
'<', table.concat( filterI, '\000' ),
|
||||
config.rsync._computed,
|
||||
'-r',
|
||||
delete,
|
||||
'--force',
|
||||
'--from0',
|
||||
'--include-from=-',
|
||||
'--exclude=*',
|
||||
config.source,
|
||||
target
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
--
|
||||
-- Returns true for non Init and Blanket events.
|
||||
--
|
||||
local eventNotInitBlank =
|
||||
function
|
||||
(
|
||||
event
|
||||
)
|
||||
return event.etype ~= 'Init' and event.etype ~= 'Blanket'
|
||||
end
|
||||
|
||||
--
|
||||
-- Returns size or true if the event is for batch processing
|
||||
--
|
||||
local getBatchSize =
|
||||
function
|
||||
(
|
||||
event
|
||||
)
|
||||
-- print("getBatchSize", event, event.status, event.etype, event.pathname)
|
||||
if event.status == 'active' then
|
||||
return false
|
||||
end
|
||||
if event.etype == 'Init' or event.etype == 'Blanket' or event.etype == 'Full' then
|
||||
return false
|
||||
end
|
||||
-- moves and deletes go always into batch
|
||||
if event.etype == 'Move' or event.etype == 'Delete' then
|
||||
return true
|
||||
end
|
||||
return lsyncd.get_file_size(event.sourcePath)
|
||||
end
|
||||
|
||||
--
|
||||
-- Spawns rsync for a list of events
|
||||
--
|
||||
-- Exclusions are already handled by not having
|
||||
-- events for them.
|
||||
--
|
||||
rsync.action = function
|
||||
(
|
||||
inlet
|
||||
)
|
||||
local sizeLimit = inlet.getConfig().batchSizeLimit
|
||||
|
||||
if sizeLimit == nil then
|
||||
-- gets all events ready for syncing
|
||||
return run_action(inlet, inlet.getEvents(eventNotInitBlank))
|
||||
else
|
||||
-- spawn all files under the size limit/deletes/moves in batch mode
|
||||
local eventInBatch = function(event)
|
||||
if event.etype == "Full" then
|
||||
return false
|
||||
end
|
||||
local size = getBatchSize(event)
|
||||
if type(size) == "boolean" then
|
||||
return size
|
||||
elseif size == nil then
|
||||
return true
|
||||
end
|
||||
if size <= sizeLimit then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- indicator for grabbing one element of the queue
|
||||
local single_returned = false
|
||||
-- grab all events for seperate transfers
|
||||
local eventNoBatch = function(event)
|
||||
if event.etype == "Full" then
|
||||
return false
|
||||
end
|
||||
local size = getBatchSize(event)
|
||||
if type(size) ~= "number" or size == nil then
|
||||
return false
|
||||
end
|
||||
if single_returned then
|
||||
return 'break'
|
||||
end
|
||||
if size > sizeLimit then
|
||||
single_returned = true
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
local extralist = inlet.getEvents(eventInBatch)
|
||||
|
||||
-- get all batched events
|
||||
if extralist.size() > 0 then
|
||||
run_action(inlet, extralist)
|
||||
end
|
||||
|
||||
while true do
|
||||
local cnt, maxcnt = lsyncd.get_process_info()
|
||||
if inlet.getSync().processes:size( ) >= inlet.getConfig().maxProcesses then
|
||||
log('Normal',
|
||||
'Maximum processes for sync reached. Delaying large transfer for sync: '..inlet.getConfig().name)
|
||||
break
|
||||
elseif maxcnt and cnt >= maxcnt then
|
||||
log('Normal',
|
||||
'Maximum process count reached. Delaying large transfer for sync: '..inlet.getConfig().name)
|
||||
break
|
||||
end
|
||||
local extralist = inlet.getEvents(eventNoBatch)
|
||||
|
||||
-- no more single size events
|
||||
if extralist.size() == 0 then break end
|
||||
run_action(inlet, extralist)
|
||||
-- get next result
|
||||
single_returned = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
----
|
||||
---- NOTE: This optimized version can be used once
|
||||
---- https://bugzilla.samba.org/show_bug.cgi?id=12569
|
||||
---- is fixed.
|
||||
----
|
||||
---- Spawns rsync for a list of events
|
||||
----
|
||||
---- Exclusions are already handled by not having
|
||||
---- events for them.
|
||||
----
|
||||
--rsync.action = function
|
||||
--(
|
||||
-- inlet
|
||||
--)
|
||||
-- local config = inlet.getConfig( )
|
||||
--
|
||||
-- -- gets all events ready for syncing
|
||||
-- local elist = inlet.getEvents( eventNotInitBlank )
|
||||
--
|
||||
-- -- gets the list of paths for the event list
|
||||
-- -- deletes create multi match patterns
|
||||
-- local paths = elist.getPaths( )
|
||||
--
|
||||
-- -- removes trailing slashes from dirs.
|
||||
-- for k, v in ipairs( paths )
|
||||
-- do
|
||||
-- if string.byte( v, -1 ) == 47
|
||||
-- then
|
||||
-- paths[ k ] = string.sub( v, 1, -2 )
|
||||
-- end
|
||||
-- end
|
||||
--
|
||||
-- log(
|
||||
-- 'Normal',
|
||||
-- 'Calling rsync with filter-list of new/modified files/dirs\n',
|
||||
-- table.concat( paths, '\n' )
|
||||
-- )
|
||||
--
|
||||
-- local delete = nil
|
||||
--
|
||||
-- if config.delete == true
|
||||
-- or config.delete == 'running'
|
||||
-- then
|
||||
-- delete = { '--delete-missing-args', '--ignore-errors' }
|
||||
-- end
|
||||
--
|
||||
-- spawn(
|
||||
-- elist,
|
||||
-- config.rsync.binary,
|
||||
-- '<', table.concat( paths, '\000' ),
|
||||
-- config.rsync._computed,
|
||||
-- delete,
|
||||
-- '--force',
|
||||
-- '--from0',
|
||||
-- '--files-from=-',
|
||||
-- config.source,
|
||||
-- config.target
|
||||
-- )
|
||||
--end
|
||||
|
||||
|
||||
--
|
||||
-- Spawns the recursive startup sync.
|
||||
--
|
||||
rsync.init = function
|
||||
(
|
||||
event
|
||||
)
|
||||
return rsync.full(event)
|
||||
end
|
||||
|
||||
--
|
||||
-- Triggers a full sync event
|
||||
--
|
||||
rsync.full = function
|
||||
(
|
||||
event
|
||||
)
|
||||
local config = event.config
|
||||
|
||||
local inlet = event.inlet
|
||||
|
||||
local excludes = inlet.getExcludes( )
|
||||
|
||||
local filters = inlet.hasFilters( ) and inlet.getFilters( )
|
||||
|
||||
local delete = {}
|
||||
|
||||
local target = config.target
|
||||
|
||||
if not target
|
||||
then
|
||||
if not config.host
|
||||
then
|
||||
error('Internal fail, Neither target nor host is configured')
|
||||
end
|
||||
|
||||
target = config.host .. ':' .. config.targetdir
|
||||
end
|
||||
|
||||
local substitudes = inlet.getSubstitutionData(event, {})
|
||||
target = substitudeCommands(target, substitudes)
|
||||
|
||||
if config.delete == true
|
||||
or config.delete == 'startup'
|
||||
then
|
||||
delete = { '--delete', '--ignore-errors' }
|
||||
end
|
||||
|
||||
if config.rsync.delete_excluded == true
|
||||
then
|
||||
table.insert( delete, '--delete-excluded' )
|
||||
end
|
||||
|
||||
if not filters and #excludes == 0
|
||||
then
|
||||
-- starts rsync without any filters or excludes
|
||||
log(
|
||||
'Normal',
|
||||
'recursive full rsync: ',
|
||||
config.source,
|
||||
' -> ',
|
||||
target
|
||||
)
|
||||
|
||||
-----
|
||||
-- replaces filter rule by literals
|
||||
--
|
||||
local function sub(p)
|
||||
if not p then
|
||||
return
|
||||
end
|
||||
return p:gsub('%?', '\\?'):
|
||||
gsub('%*', '\\*'):
|
||||
gsub('%[', '\\['):
|
||||
gsub('%]', '\\]')
|
||||
end
|
||||
|
||||
local paths = elist.getPaths(
|
||||
function(etype, path1, path2)
|
||||
if string.byte(path1, -1) == 47 and etype == 'Delete' then
|
||||
return sub(path1)..'***', sub(path2)
|
||||
else
|
||||
return sub(path1), sub(path2)
|
||||
end
|
||||
end)
|
||||
-- stores all filters with integer index
|
||||
-- local filterI = inlet.getExcludes();
|
||||
local filterI = {}
|
||||
-- stores all filters with path index
|
||||
local filterP = {}
|
||||
|
||||
-- adds one entry into the filter
|
||||
-- @param path ... path to add
|
||||
-- @param leaf ... true if this the original path
|
||||
-- false if its a parent
|
||||
local function addToFilter(path)
|
||||
if filterP[path] then
|
||||
return
|
||||
end
|
||||
filterP[path]=true
|
||||
table.insert(filterI, path)
|
||||
end
|
||||
|
||||
-- adds a path to the filter, for rsync this needs
|
||||
-- to have entries for all steps in the path, so the file
|
||||
-- d1/d2/d3/f1 needs filters
|
||||
-- 'd1/', 'd1/d2/', 'd1/d2/d3/' and 'd1/d2/d3/f1'
|
||||
for _, path in ipairs(paths) do
|
||||
if path and path ~= '' then
|
||||
addToFilter(path)
|
||||
local pp = string.match(path, '^(.*/)[^/]+/?')
|
||||
while pp do
|
||||
addToFilter(pp)
|
||||
pp = string.match(pp, '^(.*/)[^/]+/?')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local filterS = table.concat(filterI, '\n')
|
||||
local filter0 = table.concat(filterI, '\000')
|
||||
log('Normal', 'Calling rsync with filter-list of new/modified files/dirs\n', filterS)
|
||||
local config = inlet.getConfig()
|
||||
local delete = nil
|
||||
if config.delete then delete = { '--delete', '--ignore-errors' }; end
|
||||
spawn(elist, config.rsyncBinary,
|
||||
'<', filter0,
|
||||
config.rsyncOpts,
|
||||
'-r',
|
||||
spawn(
|
||||
event,
|
||||
config.rsync.binary,
|
||||
delete,
|
||||
'--force',
|
||||
'--from0',
|
||||
'--include-from=-',
|
||||
'--exclude=*',
|
||||
config.rsync._computed,
|
||||
'-r',
|
||||
config.source,
|
||||
config.target)
|
||||
end,
|
||||
target
|
||||
)
|
||||
|
||||
-----
|
||||
-- Spawns the recursive startup sync
|
||||
--
|
||||
init = function(event)
|
||||
local config = event.config
|
||||
local inlet = event.inlet
|
||||
local excludes = inlet.getExcludes()
|
||||
local delete = nil
|
||||
if config.delete then delete = { '--delete', '--ignore-errors' }; end
|
||||
elseif not filters
|
||||
then
|
||||
-- starts rsync providing an exclusion list
|
||||
-- on stdin
|
||||
local exS = table.concat( excludes, '\n' )
|
||||
|
||||
if #excludes == 0 then
|
||||
log('Normal', 'recursive startup rsync: ', config.source, ' -> ', config.target)
|
||||
spawn(event, config.rsyncBinary,
|
||||
delete,
|
||||
config.rsyncOpts,
|
||||
'-r',
|
||||
config.source,
|
||||
config.target)
|
||||
else
|
||||
local exS = table.concat(excludes, '\n')
|
||||
log('Normal', 'recursive startup rsync: ',config.source,
|
||||
' -> ',config.target,' excluding\n',exS)
|
||||
spawn(event, config.rsyncBinary,
|
||||
'<', exS,
|
||||
'--exclude-from=-',
|
||||
delete,
|
||||
config.rsyncOpts,
|
||||
'-r',
|
||||
config.source,
|
||||
config.target)
|
||||
log(
|
||||
'Normal',
|
||||
'recursive full rsync: ',
|
||||
config.source,
|
||||
' -> ',
|
||||
target,
|
||||
' excluding\n',
|
||||
exS
|
||||
)
|
||||
|
||||
spawn(
|
||||
event,
|
||||
config.rsync.binary,
|
||||
'<', exS,
|
||||
'--exclude-from=-',
|
||||
delete,
|
||||
config.rsync._computed,
|
||||
'-r',
|
||||
config.source,
|
||||
target
|
||||
)
|
||||
else
|
||||
-- starts rsync providing a filter list
|
||||
-- on stdin
|
||||
local fS = table.concat( filters, '\n' )
|
||||
|
||||
log(
|
||||
'Normal',
|
||||
'recursive full rsync: ',
|
||||
config.source,
|
||||
' -> ',
|
||||
target,
|
||||
' filtering\n',
|
||||
fS
|
||||
)
|
||||
|
||||
spawn(
|
||||
event,
|
||||
config.rsync.binary,
|
||||
'<', fS,
|
||||
'--filter=. -',
|
||||
delete,
|
||||
config.rsync._computed,
|
||||
'-r',
|
||||
config.source,
|
||||
target
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--
|
||||
-- Prepares and checks a syncs configuration on startup.
|
||||
--
|
||||
rsync.prepare = function
|
||||
(
|
||||
config, -- the configuration
|
||||
level, -- additional error level for inherited use ( by rsyncssh )
|
||||
skipTarget -- used by rsyncssh, do not check for target
|
||||
)
|
||||
|
||||
-- First let default.prepare test the checkgauge
|
||||
default.prepare( config, level + 6 )
|
||||
|
||||
if not skipTarget and not config.target
|
||||
then
|
||||
error(
|
||||
'default.rsync needs "target" configured',
|
||||
level
|
||||
)
|
||||
end
|
||||
|
||||
-- checks if the _computed argument exists already
|
||||
if config.rsync._computed
|
||||
then
|
||||
error(
|
||||
'please do not use the internal rsync._computed parameter',
|
||||
level
|
||||
)
|
||||
end
|
||||
|
||||
-- computes the rsync arguments into one list
|
||||
local crsync = config.rsync;
|
||||
|
||||
-- everything implied by archive = true
|
||||
local archiveFlags = {
|
||||
recursive = true,
|
||||
links = true,
|
||||
perms = true,
|
||||
times = true,
|
||||
group = true,
|
||||
owner = true,
|
||||
devices = true,
|
||||
specials = true,
|
||||
hard_links = false,
|
||||
acls = false,
|
||||
xattrs = false,
|
||||
}
|
||||
|
||||
-- if archive is given the implications are filled in
|
||||
if crsync.archive
|
||||
then
|
||||
for k, v in pairs( archiveFlags )
|
||||
do
|
||||
if crsync[ k ] == nil
|
||||
then
|
||||
crsync[ k ] = v
|
||||
end
|
||||
end
|
||||
end,
|
||||
end
|
||||
|
||||
-----
|
||||
-- Checks the configuration.
|
||||
--
|
||||
prepare = function(config)
|
||||
if not config.target then
|
||||
error('default.rsync needs "target" configured', 4)
|
||||
crsync._computed = { true }
|
||||
|
||||
--- @type any
|
||||
local computed = crsync._computed
|
||||
|
||||
local computedN = 2
|
||||
|
||||
local shortFlags = {
|
||||
acls = 'A',
|
||||
backup = 'b',
|
||||
checksum = 'c',
|
||||
compress = 'z',
|
||||
copy_dirlinks = 'k',
|
||||
copy_links = 'L',
|
||||
cvs_exclude = 'C',
|
||||
dry_run = 'n',
|
||||
executability = 'E',
|
||||
group = 'g',
|
||||
hard_links = 'H',
|
||||
ignore_times = 'I',
|
||||
ipv4 = '4',
|
||||
ipv6 = '6',
|
||||
keep_dirlinks = 'K',
|
||||
links = 'l',
|
||||
one_file_system = 'x',
|
||||
omit_dir_times = 'O',
|
||||
omit_link_times = 'J',
|
||||
owner = 'o',
|
||||
perms = 'p',
|
||||
protect_args = 's',
|
||||
prune_empty_dirs = 'm',
|
||||
quiet = 'q',
|
||||
sparse = 'S',
|
||||
times = 't',
|
||||
update = 'u',
|
||||
verbose = 'v',
|
||||
whole_file = 'W',
|
||||
xattrs = 'X',
|
||||
}
|
||||
|
||||
local shorts = { '-' }
|
||||
local shortsN = 2
|
||||
|
||||
if crsync._extra
|
||||
then
|
||||
for k, v in ipairs( crsync._extra )
|
||||
do
|
||||
computed[ computedN ] = v
|
||||
computedN = computedN + 1
|
||||
end
|
||||
end
|
||||
|
||||
for k, flag in pairs( shortFlags )
|
||||
do
|
||||
if crsync[ k ]
|
||||
then
|
||||
shorts[ shortsN ] = flag
|
||||
shortsN = shortsN + 1
|
||||
end
|
||||
end
|
||||
|
||||
if crsync.devices and crsync.specials
|
||||
then
|
||||
shorts[ shortsN ] = 'D'
|
||||
shortsN = shortsN + 1
|
||||
else
|
||||
if crsync.devices
|
||||
then
|
||||
computed[ computedN ] = '--devices'
|
||||
computedN = computedN + 1
|
||||
end
|
||||
|
||||
if config.rsyncOps then
|
||||
error('did you mean rsyncOpts with "t"?', 4)
|
||||
if crsync.specials
|
||||
then
|
||||
computed[ computedN ] = '--specials'
|
||||
computedN = computedN + 1
|
||||
end
|
||||
end
|
||||
|
||||
-- appends a / to target if not present
|
||||
if string.sub(config.target, -1) ~= '/' then
|
||||
config.target = config.target..'/'
|
||||
end
|
||||
end,
|
||||
if crsync.append
|
||||
then
|
||||
computed[ computedN ] = '--append'
|
||||
computedN = computedN + 1
|
||||
end
|
||||
|
||||
-----
|
||||
-- rsync uses default collect
|
||||
----
|
||||
if crsync.append_verify
|
||||
then
|
||||
computed[ computedN ] = '--append-verify'
|
||||
computedN = computedN + 1
|
||||
end
|
||||
|
||||
-----
|
||||
-- By default do deletes.
|
||||
--
|
||||
delete = true,
|
||||
if crsync.backup_dir
|
||||
then
|
||||
computed[ computedN ] = '--backup-dir=' .. crsync.backup_dir
|
||||
computedN = computedN + 1
|
||||
end
|
||||
|
||||
-----
|
||||
if crsync.bwlimit
|
||||
then
|
||||
computed[ computedN ] = '--bwlimit=' .. crsync.bwlimit
|
||||
computedN = computedN + 1
|
||||
end
|
||||
|
||||
if crsync.chmod
|
||||
then
|
||||
computed[ computedN ] = '--chmod=' .. crsync.chmod
|
||||
computedN = computedN + 1
|
||||
end
|
||||
|
||||
if crsync.chown
|
||||
then
|
||||
computed[ computedN ] = '--chown=' .. crsync.chown
|
||||
computedN = computedN + 1
|
||||
end
|
||||
|
||||
if crsync.copy_unsafe_links
|
||||
then
|
||||
computed[ computedN ] = '--copy-unsafe-links'
|
||||
computedN = computedN + 1
|
||||
end
|
||||
|
||||
if crsync.groupmap
|
||||
then
|
||||
computed[ computedN ] = '--groupmap=' .. crsync.groupmap
|
||||
computedN = computedN + 1
|
||||
end
|
||||
|
||||
if crsync.existing
|
||||
then
|
||||
computed[ computedN ] = '--existing'
|
||||
computedN = computedN + 1
|
||||
end
|
||||
|
||||
if crsync.inplace
|
||||
then
|
||||
computed[ computedN ] = '--inplace'
|
||||
computedN = computedN + 1
|
||||
end
|
||||
|
||||
if crsync.password_file
|
||||
then
|
||||
computed[ computedN ] = '--password-file=' .. crsync.password_file
|
||||
computedN = computedN + 1
|
||||
end
|
||||
|
||||
if crsync.rsh
|
||||
then
|
||||
computed[ computedN ] = '--rsh=' .. crsync.rsh
|
||||
computedN = computedN + 1
|
||||
end
|
||||
|
||||
if crsync.rsync_path
|
||||
then
|
||||
computed[ computedN ] = '--rsync-path=' .. crsync.rsync_path
|
||||
computedN = computedN + 1
|
||||
end
|
||||
|
||||
if crsync.suffix
|
||||
then
|
||||
computed[ computedN ] = '--suffix=' .. crsync.suffix
|
||||
computedN = computedN + 1
|
||||
end
|
||||
|
||||
if crsync.temp_dir
|
||||
then
|
||||
computed[ computedN ] = '--temp-dir=' .. crsync.temp_dir
|
||||
computedN = computedN + 1
|
||||
end
|
||||
|
||||
if crsync.timeout
|
||||
then
|
||||
computed[ computedN ] = '--timeout=' .. crsync.timeout
|
||||
computedN = computedN + 1
|
||||
end
|
||||
|
||||
if crsync.usermap
|
||||
then
|
||||
computed[ computedN ] = '--usermap=' .. crsync.usermap
|
||||
computedN = computedN + 1
|
||||
end
|
||||
|
||||
if shortsN ~= 2
|
||||
then
|
||||
computed[ 1 ] = table.concat( shorts, '' )
|
||||
else
|
||||
computed[ 1 ] = { }
|
||||
end
|
||||
|
||||
-- appends a / to target if not present
|
||||
-- and not a ':' for home dir.
|
||||
if not skipTarget
|
||||
and string.sub( config.target, -1 ) ~= '/'
|
||||
and string.sub( config.target, -1 ) ~= ':'
|
||||
then
|
||||
config.target = config.target..'/'
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--
|
||||
-- By default do deletes.
|
||||
--
|
||||
rsync.delete = true
|
||||
|
||||
--
|
||||
-- Rsyncd exitcodes
|
||||
--
|
||||
rsync.exitcodes = default.rsyncExitCodes
|
||||
|
||||
--
|
||||
-- Calls rsync with this default options
|
||||
--
|
||||
rsync.rsync =
|
||||
{
|
||||
-- The rsync binary to be called.
|
||||
--
|
||||
rsyncBinary = '/usr/bin/rsync',
|
||||
|
||||
-----
|
||||
-- Calls rsync with this default short opts.
|
||||
--
|
||||
rsyncOpts = '-lts',
|
||||
|
||||
-----
|
||||
-- Exit codes for rsync.
|
||||
--
|
||||
exitcodes = default.rsyncExitCodes,
|
||||
|
||||
-----
|
||||
-- Default delay
|
||||
--
|
||||
delay = 15,
|
||||
binary = 'rsync',
|
||||
links = true,
|
||||
times = true,
|
||||
protect_args = true
|
||||
}
|
||||
|
||||
|
||||
--
|
||||
-- Default delay
|
||||
--
|
||||
rsync.delay = 15
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
--
|
||||
-- default-rsyncssh.lua
|
||||
--
|
||||
-- Improved rsync - sync with rsync, but moves and deletes executed over ssh.
|
||||
|
@ -13,261 +13,670 @@
|
|||
-- License: GPLv2 (see COPYING) or any later version
|
||||
-- Authors: Axel Kittenberger <axkibe@gmail.com>
|
||||
--
|
||||
--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
--
|
||||
|
||||
if not default then error('default not loaded'); end
|
||||
if default.rsyncssh then error('default-rsyncssh already loaded'); end
|
||||
if not default
|
||||
then
|
||||
error( 'default not loaded' );
|
||||
end
|
||||
|
||||
default.rsyncssh = {
|
||||
-----
|
||||
-- Spawns rsync for a list of events
|
||||
--
|
||||
action = function(inlet)
|
||||
local event, event2 = inlet.getEvent()
|
||||
local config = inlet.getConfig()
|
||||
if not default.rsync
|
||||
then
|
||||
error( 'default.rsync not loaded' );
|
||||
end
|
||||
|
||||
-- makes move local on host
|
||||
-- if fails deletes the source...
|
||||
if event.etype == 'Move' then
|
||||
log('Normal', 'Moving ',event.path,' -> ',event2.path)
|
||||
spawn(event, '/usr/bin/ssh',
|
||||
config.host,
|
||||
'mv',
|
||||
'\"' .. config.targetdir .. event.path .. '\"',
|
||||
'\"' .. config.targetdir .. event2.path .. '\"',
|
||||
'||', 'rm', '-rf',
|
||||
'\"' .. config.targetdir .. event.path .. '\"')
|
||||
return
|
||||
end
|
||||
if default.rsyncssh
|
||||
then
|
||||
error( 'default-rsyncssh already loaded' );
|
||||
end
|
||||
|
||||
-- uses ssh to delete files on remote host
|
||||
-- instead of constructing rsync filters
|
||||
if event.etype == 'Delete' then
|
||||
if not config.delete then
|
||||
inlet.discardEvent(event)
|
||||
return
|
||||
end
|
||||
--
|
||||
-- rsyncssh extends default.rsync
|
||||
--
|
||||
local rsyncssh = { default.rsync }
|
||||
|
||||
local elist = inlet.getEvents(
|
||||
function(e)
|
||||
return e.etype == 'Delete'
|
||||
end)
|
||||
default.rsyncssh = rsyncssh
|
||||
|
||||
local paths = elist.getPaths(
|
||||
function(etype, path1, path2)
|
||||
if path2 then
|
||||
return config.targetdir..path1, config.targetdir..path2
|
||||
else
|
||||
return config.targetdir..path1
|
||||
end
|
||||
end)
|
||||
--
|
||||
-- used to ensure there aren't typos in the keys
|
||||
--
|
||||
rsyncssh.checkgauge = {
|
||||
|
||||
for _, v in pairs(paths) do
|
||||
if string.match(v, '^%s*/+%s*$') then
|
||||
log('Error', 'refusing to `rm -rf /` the target!')
|
||||
terminate(-1) -- ERRNO
|
||||
end
|
||||
end
|
||||
-- unsets the inherited value of from default.rsync
|
||||
target = false,
|
||||
onMove = true,
|
||||
|
||||
local sPaths = table.concat(paths, '\n')
|
||||
local zPaths = table.concat(paths, config.xargs.delimiter)
|
||||
log('Normal', 'Deleting list\n', sPaths)
|
||||
spawn(elist, '/usr/bin/ssh',
|
||||
'<', zPaths,
|
||||
config.host,
|
||||
config.xargs.binary, config.xargs.xparams)
|
||||
return
|
||||
end
|
||||
-- rsyncssh users host and targetdir
|
||||
host = true,
|
||||
targetdir = true,
|
||||
sshExitCodes = true,
|
||||
rsyncExitCodes = true,
|
||||
|
||||
-- for everything else spawn a rsync
|
||||
local elist = inlet.getEvents(
|
||||
function(e)
|
||||
-- TODO use a table
|
||||
return e.etype ~= 'Move' and
|
||||
e.etype ~= 'Delete' and
|
||||
e.etype ~= 'Init' and
|
||||
e.etype ~= 'Blanket'
|
||||
end)
|
||||
local paths = elist.getPaths()
|
||||
|
||||
-- removes trailing slashes from dirs.
|
||||
for k, v in ipairs(paths) do
|
||||
if string.byte(v, -1) == 47 then
|
||||
paths[k] = string.sub(v, 1, -2)
|
||||
end
|
||||
end
|
||||
local sPaths = table.concat(paths, '\n')
|
||||
local zPaths = table.concat(paths, '\000')
|
||||
log('Normal', 'Rsyncing list\n', sPaths)
|
||||
spawn(
|
||||
elist, config.rsyncBinary,
|
||||
'<', zPaths,
|
||||
config.rsyncOpts,
|
||||
'--from0',
|
||||
'--files-from=-',
|
||||
config.source,
|
||||
config.host .. ':' .. config.targetdir
|
||||
)
|
||||
end,
|
||||
|
||||
-----
|
||||
-- Called when collecting a finished child process
|
||||
--
|
||||
collect = function(agent, exitcode)
|
||||
local config = agent.config
|
||||
|
||||
if not agent.isList and agent.etype == 'Init' then
|
||||
local rc = config.rsyncExitCodes[exitcode]
|
||||
if rc == 'ok' then
|
||||
log('Normal', 'Startup of "',agent.source,'" finished: ', exitcode)
|
||||
elseif rc == 'again' then
|
||||
if settings.insist then
|
||||
log('Normal', 'Retrying startup of "',agent.source,'": ', exitcode)
|
||||
else
|
||||
log('Error', 'Temporary or permanent failure on startup of "',
|
||||
agent.source, '". Terminating since "insist" is not set.');
|
||||
terminate(-1) -- ERRNO
|
||||
end
|
||||
elseif rc == 'die' then
|
||||
log('Error', 'Failure on startup of "',agent.source,'": ', exitcode)
|
||||
else
|
||||
log('Error', 'Unknown exitcode on startup of "', agent.source,': "',exitcode)
|
||||
rc = 'die'
|
||||
end
|
||||
return rc
|
||||
end
|
||||
|
||||
if agent.isList then
|
||||
local rc = config.rsyncExitCodes[exitcode]
|
||||
if rc == 'ok' then log('Normal', 'Finished (list): ',exitcode)
|
||||
elseif rc == 'again' then log('Normal', 'Retrying (list): ',exitcode)
|
||||
elseif rc == 'die' then log('Error', 'Failure (list): ', exitcode)
|
||||
else
|
||||
log('Error', 'Unknown exitcode (list): ',exitcode)
|
||||
rc = 'die'
|
||||
end
|
||||
return rc
|
||||
else
|
||||
local rc = config.sshExitCodes[exitcode]
|
||||
if rc == 'ok' then
|
||||
log('Normal', 'Finished ',agent.etype,' ',agent.sourcePath,': ',exitcode)
|
||||
elseif rc == 'again' then
|
||||
log('Normal', 'Retrying ',agent.etype,' ',agent.sourcePath,': ',exitcode)
|
||||
elseif rc == 'die' then
|
||||
log('Normal', 'Failure ',agent.etype,' ',agent.sourcePath,': ',exitcode)
|
||||
else
|
||||
log('Error', 'Unknown exitcode ',agent.etype,' ',agent.sourcePath,': ',exitcode)
|
||||
rc = 'die'
|
||||
end
|
||||
return rc
|
||||
end
|
||||
end,
|
||||
|
||||
-----
|
||||
-- Spawns the recursive startup sync
|
||||
--
|
||||
init = function(event)
|
||||
local config = event.config
|
||||
local inlet = event.inlet
|
||||
local excludes = inlet.getExcludes()
|
||||
local target = config.host .. ':' .. config.targetdir
|
||||
local delete = nil
|
||||
if config.delete then delete = { '--delete', '--ignore-errors' }; end
|
||||
|
||||
if #excludes == 0 then
|
||||
log('Normal', 'Recursive startup rsync: ',config.source,' -> ',target)
|
||||
spawn(
|
||||
event, config.rsyncBinary,
|
||||
delete,
|
||||
'-r',
|
||||
config.rsyncOpts,
|
||||
config.source,
|
||||
target
|
||||
)
|
||||
else
|
||||
local exS = table.concat(excludes, '\n')
|
||||
log('Normal', 'Recursive startup rsync: ',config.source,
|
||||
' -> ',target,' with excludes.')
|
||||
spawn(
|
||||
event, config.rsyncBinary,
|
||||
'<', exS,
|
||||
'--exclude-from=-',
|
||||
delete,
|
||||
'-r',
|
||||
config.rsyncOpts,
|
||||
config.source,
|
||||
target
|
||||
)
|
||||
end
|
||||
end,
|
||||
|
||||
-----
|
||||
-- Checks the configuration.
|
||||
--
|
||||
prepare = function(config)
|
||||
if not config.host then error('default.rsyncssh needs "host" configured', 4) end
|
||||
if not config.targetdir then error('default.rsyncssh needs "targetdir" configured', 4) end
|
||||
|
||||
if config.rsyncOps then
|
||||
error('did you mean rsyncOpts with "t"?', 4)
|
||||
end
|
||||
|
||||
-- appends a slash to the targetdir if missing
|
||||
if string.sub(config.targetdir, -1) ~= '/' then
|
||||
config.targetdir = config.targetdir .. '/'
|
||||
end
|
||||
end,
|
||||
|
||||
-----
|
||||
-- The rsync binary called.
|
||||
--
|
||||
rsyncBinary = '/usr/bin/rsync',
|
||||
|
||||
-----
|
||||
-- Calls rsync with this default short opts.
|
||||
--
|
||||
rsyncOpts = '-lts',
|
||||
|
||||
-----
|
||||
-- allow processes
|
||||
--
|
||||
maxProcesses = 1,
|
||||
|
||||
------
|
||||
-- Let the core not split move events.
|
||||
--
|
||||
onMove = true,
|
||||
|
||||
-----
|
||||
-- Default delay.
|
||||
--
|
||||
delay = 15,
|
||||
|
||||
|
||||
-----
|
||||
-- By default do deletes.
|
||||
--
|
||||
delete = true,
|
||||
|
||||
-----
|
||||
-- rsync exit codes
|
||||
--
|
||||
rsyncExitCodes = default.rsyncExitCodes,
|
||||
|
||||
-----
|
||||
-- ssh exit codes
|
||||
--
|
||||
sshExitCodes = default.sshExitCodes,
|
||||
|
||||
-----
|
||||
-- Delimiter, the binary and the paramters passed to xargs
|
||||
-- xargs is used to delete multiple remote files, when ssh access is
|
||||
-- available this is simpler than to build filters for rsync for this.
|
||||
-- Default uses '0' as limiter, you might override this for old systems.
|
||||
--
|
||||
xargs = {
|
||||
binary = '/usr/bin/xargs',
|
||||
delimiter = '\000',
|
||||
xparams = {'-0', 'rm -rf'}
|
||||
}
|
||||
-- ssh settings
|
||||
ssh = {
|
||||
binary = true,
|
||||
identityFile = true,
|
||||
options = true,
|
||||
port = true,
|
||||
_extra = true
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
--
|
||||
-- Returns true for non Init, Blanket and Move events.
|
||||
--
|
||||
local eventNotInitBlankMove =
|
||||
function
|
||||
(
|
||||
event
|
||||
)
|
||||
-- TODO use a table
|
||||
if event.etype == 'Move'
|
||||
or event.etype == 'Init'
|
||||
or event.etype == 'Blanket'
|
||||
then
|
||||
return 'break'
|
||||
else
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--
|
||||
-- Replaces what rsync would consider filter rules by literals.
|
||||
--
|
||||
local replaceRsyncFilter =
|
||||
function
|
||||
(
|
||||
path
|
||||
)
|
||||
if not path then return end
|
||||
|
||||
return(
|
||||
path
|
||||
:gsub( '%?', '\\?' )
|
||||
:gsub( '%*', '\\*' )
|
||||
:gsub( '%[', '\\[' )
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
---
|
||||
--- Check if _extra arguments are of correct form
|
||||
---
|
||||
local checkSSH = function (args)
|
||||
local SINGLES = "46AaCfGgKkMNnqsTtVvXxYy"
|
||||
local needs_more = true
|
||||
local is_data = false
|
||||
local rv = true
|
||||
for index, value in ipairs(args) do
|
||||
if is_data == false then
|
||||
if string.sub(value, 1, 1) ~= "-" then
|
||||
log('Warn', "_extra argument does not start with -")
|
||||
rv = false
|
||||
end
|
||||
needs_more = true
|
||||
end
|
||||
for i = 1, #SINGLES do
|
||||
if string.sub(SINGLES, i, i) == string.sub(value, 2, 2) then
|
||||
needs_more = false
|
||||
break
|
||||
end
|
||||
end
|
||||
if needs_more == false then
|
||||
is_data = false
|
||||
else
|
||||
is_data = true
|
||||
end
|
||||
end
|
||||
if needs_more == true then
|
||||
log('Warn', "passend argument requires more arguments")
|
||||
rv = false
|
||||
end
|
||||
return rv
|
||||
end
|
||||
|
||||
--
|
||||
-- Spawns rsync for a list of events
|
||||
--
|
||||
rsyncssh.action = function
|
||||
(
|
||||
inlet
|
||||
)
|
||||
local config = inlet.getConfig( )
|
||||
|
||||
local event, event2 = inlet.getEvent( )
|
||||
|
||||
-- makes move local on target host
|
||||
-- if the move fails, it deletes the source
|
||||
if event.etype == 'Move'
|
||||
then
|
||||
local path1 = config.targetdir .. event.path
|
||||
|
||||
local path2 = config.targetdir .. event2.path
|
||||
|
||||
path1 = "'" .. path1:gsub ('\'', '\'"\'"\'') .. "'"
|
||||
path2 = "'" .. path2:gsub ('\'', '\'"\'"\'') .. "'"
|
||||
|
||||
log(
|
||||
'Normal',
|
||||
'Moving ',
|
||||
event.path,
|
||||
' -> ',
|
||||
event2.path
|
||||
)
|
||||
|
||||
spawn(
|
||||
event,
|
||||
config.ssh.binary,
|
||||
config.ssh._computed,
|
||||
config.host,
|
||||
'mv',
|
||||
path1,
|
||||
path2,
|
||||
'||', 'rm', '-rf',
|
||||
path1
|
||||
)
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
-- otherwise a rsync is spawned
|
||||
local elist = inlet.getEvents( eventNotInitBlankMove )
|
||||
|
||||
-- gets the list of paths for the event list
|
||||
-- deletes create multi match patterns
|
||||
local paths = elist.getPaths( )
|
||||
|
||||
--
|
||||
-- Replaces what rsync would consider filter rules by literals
|
||||
--
|
||||
local function sub( p )
|
||||
if not p then return end
|
||||
|
||||
return p:
|
||||
gsub( '%?', '\\?' ):
|
||||
gsub( '%*', '\\*' ):
|
||||
gsub( '%[', '\\[' ):
|
||||
gsub( '%]', '\\]' )
|
||||
end
|
||||
|
||||
--
|
||||
-- Gets the list of paths for the event list
|
||||
--
|
||||
-- Deletes create multi match patterns
|
||||
--
|
||||
local paths = elist.getPaths(
|
||||
function( etype, path1, path2 )
|
||||
if string.byte( path1, -1 ) == 47 and etype == 'Delete' then
|
||||
return sub( path1 )..'***', sub( path2 )
|
||||
else
|
||||
return sub( path1 ), sub( path2 )
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
-- stores all filters by integer index
|
||||
local filterI = { }
|
||||
|
||||
-- stores all filters with path index
|
||||
local filterP = { }
|
||||
|
||||
-- adds one path to the filter
|
||||
local function addToFilter( path )
|
||||
if filterP[ path ] then return end
|
||||
|
||||
filterP[ path ] = true
|
||||
|
||||
table.insert( filterI, path )
|
||||
end
|
||||
|
||||
-- adds a path to the filter.
|
||||
--
|
||||
-- rsync needs to have entries for all steps in the path,
|
||||
-- so the file for example d1/d2/d3/f1 needs following filters:
|
||||
-- 'd1/', 'd1/d2/', 'd1/d2/d3/' and 'd1/d2/d3/f1'
|
||||
for _, path in ipairs( paths )
|
||||
do
|
||||
if path and path ~= ''
|
||||
then
|
||||
addToFilter(path)
|
||||
|
||||
local pp = string.match( path, '^(.*/)[^/]+/?' )
|
||||
|
||||
while pp
|
||||
do
|
||||
addToFilter(pp)
|
||||
pp = string.match( pp, '^(.*/)[^/]+/?' )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
log(
|
||||
'Normal',
|
||||
'Calling rsync with filter-list of new/modified files/dirs\n',
|
||||
table.concat( filterI, '\n' )
|
||||
)
|
||||
|
||||
local config = inlet.getConfig( )
|
||||
|
||||
local delete = nil
|
||||
|
||||
if config.delete == true or config.delete == 'running'
|
||||
then
|
||||
delete = { '--delete', '--ignore-errors' }
|
||||
end
|
||||
|
||||
spawn(
|
||||
elist,
|
||||
config.rsync.binary,
|
||||
'<', table.concat( filterI, '\000' ),
|
||||
config.rsync._computed,
|
||||
'-r',
|
||||
delete,
|
||||
'--force',
|
||||
'--from0',
|
||||
'--include-from=-',
|
||||
'--exclude=*',
|
||||
config.source,
|
||||
config.host .. ':' .. config.targetdir
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
----
|
||||
---- NOTE: This optimized version can be used once
|
||||
---- https://bugzilla.samba.org/show_bug.cgi?id=12569
|
||||
---- is fixed.
|
||||
----
|
||||
--
|
||||
-- Spawns rsync for a list of events
|
||||
--
|
||||
--rsyncssh.action = function
|
||||
--(
|
||||
-- inlet
|
||||
--)
|
||||
-- local config = inlet.getConfig( )
|
||||
--
|
||||
-- local event, event2 = inlet.getEvent( )
|
||||
--
|
||||
-- -- makes move local on target host
|
||||
-- -- if the move fails, it deletes the source
|
||||
-- if event.etype == 'Move'
|
||||
-- then
|
||||
-- local path1 = config.targetdir .. event.path
|
||||
--
|
||||
-- local path2 = config.targetdir .. event2.path
|
||||
--
|
||||
-- path1 = "'" .. path1:gsub ('\'', '\'"\'"\'') .. "'"
|
||||
-- path2 = "'" .. path2:gsub ('\'', '\'"\'"\'') .. "'"
|
||||
--
|
||||
-- log(
|
||||
-- 'Normal',
|
||||
-- 'Moving ',
|
||||
-- event.path,
|
||||
-- ' -> ',
|
||||
-- event2.path
|
||||
-- )
|
||||
--
|
||||
-- spawn(
|
||||
-- event,
|
||||
-- config.ssh.binary,
|
||||
-- config.ssh._computed,
|
||||
-- config.host,
|
||||
-- 'mv',
|
||||
-- path1,
|
||||
-- path2,
|
||||
-- '||', 'rm', '-rf',
|
||||
-- path1
|
||||
-- )
|
||||
--
|
||||
-- return
|
||||
-- end
|
||||
--
|
||||
-- -- otherwise a rsync is spawned
|
||||
-- local elist = inlet.getEvents( eventNotInitBlankMove )
|
||||
--
|
||||
-- -- gets the list of paths for the event list
|
||||
-- -- deletes create multi match patterns
|
||||
-- local paths = elist.getPaths( )
|
||||
--
|
||||
-- -- removes trailing slashes from dirs.
|
||||
-- for k, v in ipairs( paths )
|
||||
-- do
|
||||
-- if string.byte( v, -1 ) == 47
|
||||
-- then
|
||||
-- paths[ k ] = string.sub( v, 1, -2 )
|
||||
-- end
|
||||
-- end
|
||||
--
|
||||
-- log(
|
||||
-- 'Normal',
|
||||
-- 'Rsyncing list\n',
|
||||
-- table.concat( paths, '\n' )
|
||||
-- )
|
||||
--
|
||||
-- local delete = nil
|
||||
--
|
||||
-- if config.delete == true
|
||||
-- or config.delete == 'running'
|
||||
-- then
|
||||
-- delete = { '--delete-missing-args', '--ignore-errors' }
|
||||
-- end
|
||||
--
|
||||
-- spawn(
|
||||
-- elist,
|
||||
-- config.rsync.binary,
|
||||
-- '<', table.concat( paths, '\000' ),
|
||||
-- config.rsync._computed,
|
||||
-- delete,
|
||||
-- '--force',
|
||||
-- '--from0',
|
||||
-- '--files-from=-',
|
||||
-- config.source,
|
||||
-- config.host .. ':' .. config.targetdir
|
||||
-- )
|
||||
--end
|
||||
|
||||
|
||||
--
|
||||
-- Called when collecting a finished child process
|
||||
--
|
||||
rsyncssh.collect = function
|
||||
(
|
||||
agent,
|
||||
exitcode
|
||||
)
|
||||
local config = agent.config
|
||||
|
||||
if not agent.isList and agent.etype == 'Init'
|
||||
then
|
||||
local rc = config.rsyncExitCodes[exitcode]
|
||||
|
||||
if rc == 'ok'
|
||||
then
|
||||
log( 'Normal', 'Startup of "', agent.source, '" finished: ', exitcode )
|
||||
elseif rc == 'again'
|
||||
then
|
||||
if settings('insist')
|
||||
then
|
||||
log( 'Normal', 'Retrying startup of "', agent.source, '": ', exitcode )
|
||||
else
|
||||
log(
|
||||
'Error',
|
||||
'Temporary or permanent failure on startup of "',
|
||||
agent.source, '". Terminating since "insist" is not set.'
|
||||
)
|
||||
|
||||
terminate( -1 ) -- ERRNO
|
||||
end
|
||||
elseif rc == 'die'
|
||||
then
|
||||
log( 'Error', 'Failure on startup of "', agent.source, '": ', exitcode )
|
||||
else
|
||||
log( 'Error', 'Unknown exitcode on startup of "', agent.source, ': "', exitcode )
|
||||
|
||||
rc = 'die'
|
||||
end
|
||||
|
||||
return rc
|
||||
end
|
||||
|
||||
if agent.isList
|
||||
then
|
||||
local rc = config.rsyncExitCodes[ exitcode ]
|
||||
|
||||
if rc == 'ok'
|
||||
then
|
||||
log( 'Normal', 'Finished (list): ', exitcode )
|
||||
elseif rc == 'again'
|
||||
then
|
||||
log( 'Normal', 'Retrying (list): ', exitcode )
|
||||
elseif rc == 'die'
|
||||
then
|
||||
log( 'Error', 'Failure (list): ', exitcode )
|
||||
else
|
||||
log( 'Error', 'Unknown exitcode (list): ', exitcode )
|
||||
|
||||
rc = 'die'
|
||||
end
|
||||
return rc
|
||||
else
|
||||
local rc = config.sshExitCodes[exitcode]
|
||||
|
||||
if rc == 'ok'
|
||||
then
|
||||
log( 'Normal', 'Finished ', agent.etype,' ', agent.sourcePath, ': ', exitcode )
|
||||
elseif rc == 'again'
|
||||
then
|
||||
log( 'Normal', 'Retrying ', agent.etype, ' ', agent.sourcePath, ': ', exitcode )
|
||||
elseif rc == 'die'
|
||||
then
|
||||
log( 'Normal', 'Failure ', agent.etype, ' ', agent.sourcePath, ': ', exitcode )
|
||||
else
|
||||
log( 'Error', 'Unknown exitcode ', agent.etype,' ', agent.sourcePath,': ', exitcode )
|
||||
|
||||
rc = 'die'
|
||||
end
|
||||
|
||||
return rc
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--
|
||||
-- checks the configuration.
|
||||
--
|
||||
rsyncssh.prepare = function
|
||||
(
|
||||
config,
|
||||
level
|
||||
)
|
||||
default.rsync.prepare( config, level + 1, true )
|
||||
|
||||
if not config.host
|
||||
then
|
||||
error( 'default.rsyncssh needs "host" configured', level )
|
||||
end
|
||||
|
||||
if not config.targetdir
|
||||
then
|
||||
error( 'default.rsyncssh needs "targetdir" configured', level )
|
||||
end
|
||||
|
||||
--
|
||||
-- computes the ssh options
|
||||
--
|
||||
if config.ssh._computed
|
||||
then
|
||||
error( 'please do not use the internal rsync._computed parameter', level )
|
||||
end
|
||||
|
||||
if config.maxProcesses ~= 1
|
||||
then
|
||||
error( 'default.rsyncssh must have maxProcesses set to 1.', level )
|
||||
end
|
||||
|
||||
local cssh = config.ssh;
|
||||
|
||||
cssh._computed = { }
|
||||
|
||||
local computed = cssh._computed
|
||||
|
||||
local computedN = 1
|
||||
|
||||
local rsyncc = config.rsync._computed
|
||||
|
||||
if cssh.identityFile
|
||||
then
|
||||
computed[ computedN ] = '-i'
|
||||
|
||||
computed[ computedN + 1 ] = cssh.identityFile
|
||||
|
||||
computedN = computedN + 2
|
||||
|
||||
if not config.rsync._rshIndex
|
||||
then
|
||||
config.rsync._rshIndex = #rsyncc + 1
|
||||
|
||||
rsyncc[ config.rsync._rshIndex ] = '--rsh=ssh'
|
||||
end
|
||||
|
||||
rsyncc[ config.rsync._rshIndex ] =
|
||||
rsyncc[ config.rsync._rshIndex ] ..
|
||||
' -i ' ..
|
||||
cssh.identityFile
|
||||
end
|
||||
|
||||
if cssh.options
|
||||
then
|
||||
for k, v in pairs( cssh.options )
|
||||
do
|
||||
computed[ computedN ] = '-o'
|
||||
|
||||
computed[ computedN + 1 ] = k .. '=' .. v
|
||||
|
||||
computedN = computedN + 2
|
||||
|
||||
if not config.rsync._rshIndex
|
||||
then
|
||||
config.rsync._rshIndex = #rsyncc + 1
|
||||
|
||||
rsyncc[ config.rsync._rshIndex ] = '--rsh=ssh'
|
||||
end
|
||||
|
||||
rsyncc[ config.rsync._rshIndex ] =
|
||||
table.concat(
|
||||
{
|
||||
rsyncc[ config.rsync._rshIndex ],
|
||||
' -o ',
|
||||
k,
|
||||
'=',
|
||||
v
|
||||
},
|
||||
''
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
if cssh.port
|
||||
then
|
||||
computed[ computedN ] = '-p'
|
||||
|
||||
computed[ computedN + 1 ] = cssh.port
|
||||
|
||||
computedN = computedN + 2
|
||||
|
||||
if not config.rsync._rshIndex
|
||||
then
|
||||
config.rsync._rshIndex = #rsyncc + 1
|
||||
|
||||
rsyncc[ config.rsync._rshIndex ] = '--rsh=ssh'
|
||||
end
|
||||
|
||||
rsyncc[ config.rsync._rshIndex ] =
|
||||
rsyncc[ config.rsync._rshIndex ] .. ' -p ' .. cssh.port
|
||||
end
|
||||
|
||||
if cssh._extra
|
||||
then
|
||||
if checkSSH(cssh._extra) == false then
|
||||
log( 'Warn', 'The ssh._extra parameter is a list of arguments, ensure it is written correctly')
|
||||
end
|
||||
for k, v in ipairs( cssh._extra )
|
||||
do
|
||||
computed[ computedN ] = v
|
||||
|
||||
computedN = computedN + 1
|
||||
|
||||
if not config.rsync._rshIndex
|
||||
then
|
||||
config.rsync._rshIndex = #rsyncc + 1
|
||||
|
||||
rsyncc[ config.rsync._rshIndex ] = '--rsh=ssh'
|
||||
end
|
||||
|
||||
rsyncc[ config.rsync._rshIndex ] =
|
||||
rsyncc[ config.rsync._rshIndex ] .. ' ' .. v
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
-- appends a slash to the targetdir if missing
|
||||
-- and is not ':' for home dir
|
||||
if string.sub( config.targetdir, -1 ) ~= '/'
|
||||
and string.sub( config.targetdir, -1 ) ~= ':'
|
||||
then
|
||||
config.targetdir = config.targetdir .. '/'
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--
|
||||
-- allow processes
|
||||
--
|
||||
rsyncssh.maxProcesses = 1
|
||||
|
||||
--
|
||||
-- The core should not split move events
|
||||
--
|
||||
rsyncssh.onMove = true
|
||||
|
||||
--
|
||||
-- default delay
|
||||
--
|
||||
rsyncssh.delay = 15
|
||||
|
||||
|
||||
--
|
||||
-- no default exit codes
|
||||
--
|
||||
rsyncssh.exitcodes = false
|
||||
|
||||
--
|
||||
-- rsync exit codes
|
||||
--
|
||||
rsyncssh.rsyncExitCodes = default.rsyncExitCodes
|
||||
|
||||
--
|
||||
-- ssh exit codes
|
||||
--
|
||||
rsyncssh.sshExitCodes = default.sshExitCodes
|
||||
|
||||
|
||||
--
|
||||
-- ssh calls configuration
|
||||
--
|
||||
-- ssh is used to move and delete files on the target host
|
||||
--
|
||||
rsyncssh.ssh = {
|
||||
|
||||
--
|
||||
-- the binary called
|
||||
--
|
||||
binary = 'ssh',
|
||||
|
||||
--
|
||||
-- if set adds this key to ssh
|
||||
--
|
||||
identityFile = nil,
|
||||
|
||||
--
|
||||
-- if set adds this special options to ssh
|
||||
--
|
||||
options = nil,
|
||||
|
||||
--
|
||||
-- if set connect to this port
|
||||
--
|
||||
port = nil,
|
||||
|
||||
--
|
||||
-- extra parameters
|
||||
--
|
||||
_extra = { }
|
||||
}
|
||||
|
||||
|
|
557
default.lua
557
default.lua
|
@ -8,159 +8,438 @@
|
|||
-- Authors: Axel Kittenberger <axkibe@gmail.com>
|
||||
--============================================================================
|
||||
|
||||
if default then error('default already loaded'); end
|
||||
if default
|
||||
then
|
||||
error( 'default already loaded' )
|
||||
end
|
||||
|
||||
default = {
|
||||
-----
|
||||
-- Default action calls user scripts on**** functions.
|
||||
--
|
||||
action = function(inlet)
|
||||
-- in case of moves getEvent returns the origin and dest of the move
|
||||
local event, event2 = inlet.getEvent()
|
||||
local config = inlet.getConfig()
|
||||
local func = config['on'.. event.etype]
|
||||
if func then
|
||||
func(event, event2)
|
||||
end
|
||||
-- if function didnt change the wait status its not interested
|
||||
-- in this event -> drop it.
|
||||
if event.status == 'wait' then
|
||||
inlet.discardEvent(event)
|
||||
end
|
||||
end,
|
||||
--- @diagnostic disable-next-line: lowercase-global
|
||||
default = { }
|
||||
|
||||
|
||||
-----
|
||||
-- Default collector.
|
||||
--
|
||||
-- Called when collecting a finished child process
|
||||
--
|
||||
collect = function(agent, exitcode)
|
||||
local config = agent.config
|
||||
local rc
|
||||
if config.exitcodes then
|
||||
rc = config.exitcodes[exitcode]
|
||||
elseif exitcode == 0 then
|
||||
rc = 'ok'
|
||||
--
|
||||
-- Only this items are inherited from the default
|
||||
-- table
|
||||
--
|
||||
default._merge = {
|
||||
action = true,
|
||||
checkgauge = true,
|
||||
collect = true,
|
||||
delay = true,
|
||||
init = true,
|
||||
maxDelays = true,
|
||||
maxProcesses = true,
|
||||
prepare = true,
|
||||
}
|
||||
|
||||
--
|
||||
-- used to ensure there aren't typos in the keys
|
||||
--
|
||||
default.checkgauge = {
|
||||
action = true,
|
||||
checkgauge = true,
|
||||
collect = true,
|
||||
crontab = true,
|
||||
delay = true,
|
||||
exitcodes = true,
|
||||
init = true,
|
||||
full = true,
|
||||
maxDelays = true,
|
||||
maxProcesses = true,
|
||||
onAttrib = true,
|
||||
onCreate = true,
|
||||
onModify = true,
|
||||
onDelete = true,
|
||||
onStartup = true,
|
||||
onMove = true,
|
||||
onFull = true,
|
||||
prepare = true,
|
||||
source = true,
|
||||
target = true,
|
||||
tunnel = true,
|
||||
}
|
||||
|
||||
--
|
||||
-- On default action the user's on*** scripts are called.
|
||||
--
|
||||
default.action = function
|
||||
(
|
||||
inlet -- the inlet of the active sync.
|
||||
)
|
||||
-- in case of moves getEvent returns the origin and dest of the move
|
||||
local event, event2 = inlet.getEvent( )
|
||||
|
||||
local config = inlet.getConfig( )
|
||||
|
||||
local func = config[ 'on'.. event.etype ]
|
||||
|
||||
if type( func ) == 'function'
|
||||
then
|
||||
func( event, event2 )
|
||||
end
|
||||
|
||||
-- if function didnt change the wait status its not interested
|
||||
-- in this event -> drop it.
|
||||
if event.status == 'wait'
|
||||
then
|
||||
inlet.discardEvent( event )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
--
|
||||
-- Default collector.
|
||||
--
|
||||
-- Called when collecting a finished child process
|
||||
--
|
||||
default.collect = function
|
||||
(
|
||||
agent, -- event or event list being collected
|
||||
exitcode -- the exitcode of the spawned process
|
||||
)
|
||||
local config = agent.config
|
||||
|
||||
local rc
|
||||
|
||||
if config.exitcodes
|
||||
then
|
||||
rc = config.exitcodes[ exitcode ]
|
||||
elseif exitcode == 0
|
||||
then
|
||||
rc = 'ok'
|
||||
else
|
||||
rc = 'die'
|
||||
end
|
||||
|
||||
-- TODO synchronize with similar code before
|
||||
if not agent.isList and agent.etype == 'Init'
|
||||
then
|
||||
if rc == 'ok'
|
||||
then
|
||||
log(
|
||||
'Normal',
|
||||
'Startup of ',
|
||||
agent.source,
|
||||
' -> ',
|
||||
agent.target,
|
||||
' finished.'
|
||||
)
|
||||
if settings('onepass')
|
||||
then
|
||||
log(
|
||||
'Normal',
|
||||
'onepass config set, exiting'
|
||||
)
|
||||
terminate( 0 )
|
||||
end
|
||||
return 'ok'
|
||||
elseif rc == 'again'
|
||||
then
|
||||
if settings( 'insist' )
|
||||
then
|
||||
log(
|
||||
'Normal',
|
||||
'Retrying startup of ',
|
||||
agent.source,
|
||||
' -> ',
|
||||
agent.target,
|
||||
': ',
|
||||
exitcode
|
||||
)
|
||||
|
||||
return 'again'
|
||||
else
|
||||
log(
|
||||
'Error',
|
||||
'Temporary or permanent failure on startup of ',
|
||||
agent.source,
|
||||
' -> ',
|
||||
agent.target,
|
||||
'. Terminating since "insist" is not set.'
|
||||
)
|
||||
|
||||
terminate( -1 )
|
||||
end
|
||||
elseif rc == 'die'
|
||||
then
|
||||
log(
|
||||
'Error',
|
||||
'Failure on startup of ',
|
||||
agent.source,
|
||||
' -> ',
|
||||
agent.target,
|
||||
'.'
|
||||
)
|
||||
|
||||
terminate( -1 )
|
||||
else
|
||||
log(
|
||||
'Error',
|
||||
'Unknown exitcode "',
|
||||
exitcode,
|
||||
'" on startup of ',
|
||||
agent.source,
|
||||
' -> ',
|
||||
agent.target,
|
||||
'.'
|
||||
)
|
||||
return 'die'
|
||||
end
|
||||
end
|
||||
|
||||
if agent.isList
|
||||
then
|
||||
if rc == 'ok'
|
||||
then
|
||||
log(
|
||||
'Normal',
|
||||
'Finished a list after exitcode: ',
|
||||
exitcode
|
||||
)
|
||||
elseif rc == 'again'
|
||||
then
|
||||
log(
|
||||
'Normal',
|
||||
'Retrying a list after exitcode = ',
|
||||
exitcode
|
||||
)
|
||||
elseif rc == 'die'
|
||||
then
|
||||
log(
|
||||
'Error',
|
||||
'Failure with a list with exitcode = ',
|
||||
exitcode
|
||||
)
|
||||
else
|
||||
log(
|
||||
'Error',
|
||||
'Unknown exitcode "',exitcode,'" with a list'
|
||||
)
|
||||
|
||||
rc = 'die'
|
||||
end
|
||||
|
||||
-- TODO synchronize with similar code before
|
||||
if not agent.isList and agent.etype == 'Init' then
|
||||
if rc == 'ok' then
|
||||
log('Normal', 'Startup of "',agent.source,'" finished.')
|
||||
return 'ok'
|
||||
elseif rc == 'again' then
|
||||
log('Normal', 'Retrying startup of "',agent.source,'".')
|
||||
return "again"
|
||||
elseif rc == 'die' then
|
||||
log('Error', 'Failure on startup of "',agent.source,'".')
|
||||
terminate(-1) -- ERRNO
|
||||
else
|
||||
log('Error', 'Unknown exitcode "',exitcode,'" on startup of "',agent.source,'".')
|
||||
return 'die'
|
||||
end
|
||||
end
|
||||
|
||||
if agent.isList then
|
||||
if rc == 'ok' then log('Normal', 'Finished a list = ',exitcode)
|
||||
elseif rc == 'again' then log('Normal', 'Retrying a list on exitcode = ',exitcode)
|
||||
elseif rc == 'die' then log('Error', 'Failure with a list on exitcode = ',exitcode)
|
||||
else
|
||||
log('Error', 'Unknown exitcode "',exitcode,'" with a list')
|
||||
rc = 'die'
|
||||
end
|
||||
else
|
||||
if rc == 'ok'
|
||||
then
|
||||
log(
|
||||
'Normal',
|
||||
'Finished ',
|
||||
agent.etype,
|
||||
' on ',
|
||||
agent.sourcePath,
|
||||
' = ',
|
||||
exitcode
|
||||
)
|
||||
elseif rc == 'again'
|
||||
then
|
||||
log(
|
||||
'Normal',
|
||||
'Retrying ',
|
||||
agent.etype,
|
||||
' on ',
|
||||
agent.sourcePath,
|
||||
' = ',
|
||||
exitcode
|
||||
)
|
||||
elseif rc == 'die'
|
||||
then
|
||||
log(
|
||||
'Error',
|
||||
'Failure with ',
|
||||
agent.etype,
|
||||
' on ',
|
||||
agent.sourcePath,
|
||||
' = ',
|
||||
exitcode
|
||||
)
|
||||
else
|
||||
if rc == 'ok' then
|
||||
log('Normal', 'Retrying ',agent.etype,' on ',agent.sourcePath,' = ',exitcode)
|
||||
elseif rc == 'again' then
|
||||
log('Normal', 'Finished ',agent.etype,' on ',agent.sourcePath,' = ',exitcode)
|
||||
elseif rc == 'die' then
|
||||
log('Error', 'Failure with ',agent.etype,' on ',agent.sourcePath,' = ',exitcode)
|
||||
else
|
||||
log('Normal', 'Unknown exitcode "',exitcode,'" with ', agent.etype,
|
||||
' on ',agent.sourcePath,' = ',exitcode)
|
||||
rc = 'die'
|
||||
end
|
||||
log(
|
||||
'Normal',
|
||||
'Unknown exitcode "',
|
||||
exitcode,
|
||||
'" with ',
|
||||
agent.etype,
|
||||
' on ',
|
||||
agent.sourcePath,
|
||||
' = ',
|
||||
exitcode
|
||||
)
|
||||
|
||||
rc = 'die'
|
||||
end
|
||||
return rc
|
||||
end,
|
||||
end
|
||||
|
||||
return rc
|
||||
end
|
||||
|
||||
|
||||
--
|
||||
-- Called on the Init event sent
|
||||
-- on (re)initialization of Lsyncd for every sync
|
||||
--
|
||||
default.init = function
|
||||
(
|
||||
event -- the precreated init event.
|
||||
)
|
||||
local config = event.config
|
||||
|
||||
local inlet = event.inlet
|
||||
|
||||
-- user functions
|
||||
-- calls a startup if given by user script.
|
||||
if type( config.onStartup ) == 'function'
|
||||
then
|
||||
config.onStartup( event )
|
||||
-- TODO honor some return codes of startup like "warmstart".
|
||||
end
|
||||
|
||||
if event.status == 'wait'
|
||||
then
|
||||
-- user script did not spawn anything
|
||||
-- thus the blanket event is deleted again.
|
||||
inlet.discardEvent( event )
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--
|
||||
-- The collapsor tries not to have more than these delays.
|
||||
-- So the delay queue does not grow too large
|
||||
-- since calculation for stacking events is n*log( n ) (or so)
|
||||
--
|
||||
default.maxDelays = 1000
|
||||
|
||||
|
||||
--
|
||||
-- The maximum number of processes Lsyncd will
|
||||
-- simultanously spawn for this sync.
|
||||
--
|
||||
default.maxProcesses = 1
|
||||
|
||||
|
||||
--
|
||||
-- Exitcodes of rsync and what to do.
|
||||
-- TODO move to rsync
|
||||
--
|
||||
default.rsyncExitCodes = {
|
||||
|
||||
-----
|
||||
-- called on (re)initialization of Lsyncd.
|
||||
--
|
||||
init = function(event)
|
||||
local config = event.config
|
||||
local inlet = event.inlet
|
||||
-- user functions
|
||||
-- calls a startup if given by user script.
|
||||
if type(config.onStartup) == 'function' then
|
||||
local startup = config.onStartup(event)
|
||||
-- TODO honor some return codes of startup like "warmstart".
|
||||
end
|
||||
|
||||
if event.status == 'wait' then
|
||||
-- user script did not spawn anything
|
||||
-- thus the blanket event is deleted again.
|
||||
inlet.discardEvent(event)
|
||||
end
|
||||
end,
|
||||
|
||||
-----
|
||||
-- The maximum number of processes Lsyncd will spawn simultanously for
|
||||
-- one sync.
|
||||
-- if another config provides the same table
|
||||
-- this will not be inherited (merged) into that one
|
||||
--
|
||||
maxProcesses = 1,
|
||||
|
||||
-----
|
||||
-- Try not to have more than these delays.
|
||||
-- not too large, since total calculation for stacking
|
||||
-- events is n*log(n) or so..
|
||||
-- if it does not, integer keys are to be copied
|
||||
-- verbatim
|
||||
--
|
||||
maxDelays = 1000,
|
||||
_merge = false,
|
||||
_verbatim = true,
|
||||
|
||||
-----
|
||||
-- a default configuration using /bin/cp|rm|mv.
|
||||
--
|
||||
direct = default_direct,
|
||||
[ 0 ] = 'ok',
|
||||
[ 1 ] = 'die',
|
||||
[ 2 ] = 'die',
|
||||
[ 3 ] = 'again',
|
||||
[ 4 ] = 'die',
|
||||
[ 5 ] = 'again',
|
||||
[ 6 ] = 'again',
|
||||
[ 10 ] = 'again',
|
||||
[ 11 ] = 'again',
|
||||
[ 12 ] = 'again',
|
||||
[ 14 ] = 'again',
|
||||
[ 20 ] = 'again',
|
||||
[ 21 ] = 'again',
|
||||
[ 22 ] = 'again',
|
||||
|
||||
------
|
||||
-- Exitcodes of rsync and what to do.
|
||||
--
|
||||
rsyncExitCodes = {
|
||||
[ 0] = 'ok',
|
||||
[ 1] = 'die',
|
||||
[ 2] = 'die',
|
||||
[ 3] = 'again',
|
||||
[ 4] = 'die',
|
||||
[ 5] = 'again',
|
||||
[ 6] = 'again',
|
||||
[ 10] = 'again',
|
||||
[ 11] = 'again',
|
||||
[ 12] = 'again',
|
||||
[ 14] = 'again',
|
||||
[ 20] = 'again',
|
||||
[ 21] = 'again',
|
||||
[ 22] = 'again',
|
||||
[ 23] = 'ok', -- partial transfers are ok, since Lsyncd has registered the event that
|
||||
[ 24] = 'ok', -- caused the transfer to be partial and will recall rsync.
|
||||
[ 25] = 'die',
|
||||
[ 30] = 'again',
|
||||
[ 35] = 'again',
|
||||
[255] = 'again',
|
||||
},
|
||||
-- partial transfers are ok, since Lsyncd has registered the event that
|
||||
-- caused the transfer to be partial and will recall rsync.
|
||||
[ 23 ] = 'ok',
|
||||
[ 24 ] = 'ok',
|
||||
|
||||
-----
|
||||
-- Exitcodes of ssh and what to do.
|
||||
--
|
||||
sshExitCodes = {
|
||||
[0] = 'ok',
|
||||
[255] = 'again',
|
||||
},
|
||||
[ 25 ] = 'die',
|
||||
[ 30 ] = 'again',
|
||||
[ 35 ] = 'again',
|
||||
|
||||
-----
|
||||
-- Minimum seconds between two writes of a status file.
|
||||
--
|
||||
statusInterval = 10,
|
||||
[ 255 ] = 'again',
|
||||
}
|
||||
|
||||
|
||||
--
|
||||
-- Exitcodes of ssh and what to do.
|
||||
--
|
||||
default.sshExitCodes =
|
||||
{
|
||||
--
|
||||
-- if another config provides the same table
|
||||
-- this will not be inherited (merged) into that one
|
||||
--
|
||||
-- if it does not, integer keys are to be copied
|
||||
-- verbatim
|
||||
--
|
||||
_merge = false,
|
||||
_verbatim = true,
|
||||
|
||||
[ 0 ] = 'ok',
|
||||
[ 255 ] = 'again',
|
||||
}
|
||||
|
||||
|
||||
--
|
||||
-- Minimum seconds between two writes of the status file.
|
||||
--
|
||||
default.statusInterval = 10
|
||||
|
||||
|
||||
--
|
||||
-- Checks all keys to be in the checkgauge.
|
||||
--
|
||||
local function check
|
||||
(
|
||||
config,
|
||||
gauge,
|
||||
subtable,
|
||||
level
|
||||
)
|
||||
for k, v in pairs( config )
|
||||
do
|
||||
if not gauge[ k ]
|
||||
then
|
||||
error(
|
||||
'Parameter "' .. subtable .. k .. '" unknown.'
|
||||
.. ' ( if this is not a typo add it to checkgauge )',
|
||||
level
|
||||
);
|
||||
end
|
||||
|
||||
if type( gauge [ k ] ) == 'table'
|
||||
then
|
||||
if type( v ) ~= 'table'
|
||||
then
|
||||
error(
|
||||
'Parameter "' .. subtable .. k .. '" must be a table.',
|
||||
level
|
||||
)
|
||||
end
|
||||
|
||||
check(
|
||||
config[ k ],
|
||||
gauge[ k ],
|
||||
subtable .. k .. '.',
|
||||
level + 1
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
default.prepare = function
|
||||
(
|
||||
config, -- the config to prepare for
|
||||
level -- current callback level for error reporting
|
||||
)
|
||||
|
||||
local gauge = config.checkgauge
|
||||
|
||||
if not gauge then return end
|
||||
|
||||
check( config, gauge, '', level + 1 )
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
#!/bin/sh
|
||||
# removes all stuff generated by cmake / make
|
||||
rm -rf AdditionalInfo.txt config.h Makefile build/ CMakeCache.txt CMakeFiles/ cmake_install.cmake install_manifest.txt defaults.c runner.c *.o *.out lsyncd
|
|
@ -0,0 +1 @@
|
|||
_site/
|
|
@ -0,0 +1,4 @@
|
|||
name: Lsyncd
|
||||
markdown: kramdown
|
||||
url : /lsyncd
|
||||
baseurl : /lsyncd
|
|
@ -0,0 +1,19 @@
|
|||
- title: Lsyncd
|
||||
docs:
|
||||
- /
|
||||
- /download
|
||||
- /help
|
||||
- title: Manual
|
||||
docs:
|
||||
- /manual/building
|
||||
- /manual/invoking
|
||||
- /manual/config/file
|
||||
- /manual/config/layer4
|
||||
- /manual/config/layer3
|
||||
- /manual/config/layer2
|
||||
- /manual/config/layer1
|
||||
- /manual/examples
|
||||
- title: Appendices
|
||||
docs:
|
||||
- /faq
|
||||
- /appendices/devdocs
|
|
@ -0,0 +1,93 @@
|
|||
{% if jekyll.environment == "local"
|
||||
%}{% assign suffix = 'index.html'
|
||||
%}{% else
|
||||
%}{% assign suffix = ''
|
||||
%}{% endif
|
||||
%}{% assign base = ''
|
||||
%}{% assign depth = page.url | split: '/' | size
|
||||
%}{% if depth <= 1
|
||||
%}{% assign base = '.'
|
||||
%}{% elsif depth == 2
|
||||
%}{% assign base = '..'
|
||||
%}{% elsif depth == 3
|
||||
%}{% assign base = '../..'
|
||||
%}{% elsif depth == 4
|
||||
%}{% assign base = '../../..'
|
||||
%}{% endif
|
||||
%}<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
{% if page.url != "/" %}
|
||||
<title>Lsyncd - {{ page.title }}</title>
|
||||
{% else %}
|
||||
<title>{{ page.title }}</title>
|
||||
{% endif %}
|
||||
<!-- link to main stylesheet -->
|
||||
<link rel="stylesheet" type="text/css" href="{{ base }}/css/main.css">
|
||||
<link rel="stylesheet" type="text/css" href="{{ base }}/css/syntax.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="pillar">
|
||||
<nav>
|
||||
<div id="menu">
|
||||
{% for section in site.data.docs %}
|
||||
<h4>{{section.title}}</h4>
|
||||
<ul>
|
||||
{% for doc in section.docs %}
|
||||
{% assign doc_url = doc %}
|
||||
{% if doc_url != "/" %}
|
||||
{% assign doc_url = doc | append: "/" %}
|
||||
{% endif %}
|
||||
{% assign p = site.pages | where:"url", doc_url | first %}
|
||||
{% if page.tab %}
|
||||
{% assign tab = page.tab | prepend:"/" | append:"/" %}
|
||||
{% endif %}
|
||||
{% if p.short %}
|
||||
{% assign title = p.short %}
|
||||
{% else %}
|
||||
{% assign title = p.title %}
|
||||
{% endif %}
|
||||
<li><a {% if doc_url == page.url or doc_url == tab %}class="current"{% endif %} href="{{ base| append: p.url | append: suffix }}">{{ title }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endfor %}
|
||||
<div id="menufooter"></div>
|
||||
</div>
|
||||
<div id="ribbonwrap"><a href="https://github.com/lsyncd/lsyncd" id="ribbon"></a></div>
|
||||
</nav>
|
||||
<div id="container">
|
||||
<a href="https://github.com/lsyncd/lsyncd/edit/master/docs{{ page.url }}index.md" id="improvethis"></a>
|
||||
<h1>{{ page.title }}</h1>
|
||||
{{ content }}
|
||||
|
||||
{% assign docs = site.data.docs | map: 'docs' | join: ',' | split: ',' %}
|
||||
{% for doc in docs %}
|
||||
{% assign doc_url = doc %}
|
||||
{% if doc_url != "/" %}
|
||||
{% assign doc_url = doc_url | append: "/" %}
|
||||
{% endif %}
|
||||
{% if doc_url == page.url %}
|
||||
<div id="footnav" class="prenext">
|
||||
{% if forloop.first %}
|
||||
{% else %}
|
||||
{% assign previous = forloop.index0 | minus: 1 %}
|
||||
{% assign previous_page = docs[previous] %}
|
||||
<a href="{{ site.url | append: previous_page }}" class="prev"></a>
|
||||
{% endif %}
|
||||
|
||||
{% if forloop.last %}
|
||||
{% else %}
|
||||
{% assign next = forloop.index0 | plus: 1 %}
|
||||
{% assign next_page = docs[next] %}
|
||||
<a href="{{ site.url | append: next_page }}" class="next"></a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="clear"></div>
|
||||
{% break %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,78 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
{% if page.url != "/" %}
|
||||
<title>Lsyncd - {{ page.title }}</title>
|
||||
{% else %}
|
||||
<title>{{ page.title }}</title>
|
||||
{% endif %}
|
||||
<!-- link to main stylesheet -->
|
||||
<link rel="stylesheet" type="text/css" href="{{ site.url }}/css/main.css">
|
||||
<link rel="stylesheet" type="text/css" href="{{ site.url }}/css/syntax.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="note3"><b>Note, this manual is for upcoming Lsyncd 3 and currently in development.</b></div>
|
||||
<div id="pillar">
|
||||
<nav>
|
||||
<div id="menu">
|
||||
{% for section in site.data.docs %}
|
||||
<h4>{{section.title}}</h4>
|
||||
<ul>
|
||||
{% for doc in section.docs %}
|
||||
{% assign doc_url = doc %}
|
||||
{% if doc_url != "/" %}
|
||||
{% assign doc_url = doc | append: "/" %}
|
||||
{% endif %}
|
||||
{% assign p = site.pages | where:"url", doc_url | first %}
|
||||
{% if page.tab %}
|
||||
{% assign tab = page.tab | prepend:"/" | append:"/" %}
|
||||
{% endif %}
|
||||
{% if p.short %}
|
||||
{% assign title = p.short %}
|
||||
{% else %}
|
||||
{% assign title = p.title %}
|
||||
{% endif %}
|
||||
<li><a {% if doc_url == page.url or doc_url == tab %}class="current"{% endif %} href="{{ site.url | append: p.url }}">{{ title }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endfor %}
|
||||
<div id="menufooter"></div>
|
||||
</div>
|
||||
<div id="ribbonwrap"><a href="https://github.com/axkibe/lsyncd" id="ribbon"></a></div>
|
||||
</nav>
|
||||
<div id="container">
|
||||
<a href="https://github.com/axkibe/lsyncd/edit/gh-pages{{ page.url }}index.md" id="improvethis"></a>
|
||||
<h1>{{ page.title }}</h1>
|
||||
{{ content }}
|
||||
|
||||
{% assign docs = site.data.docs | map: 'docs' | join: ',' | split: ',' %}
|
||||
{% for doc in docs %}
|
||||
{% assign doc_url = doc %}
|
||||
{% if doc_url != "/" %}
|
||||
{% assign doc_url = doc_url | append: "/" %}
|
||||
{% endif %}
|
||||
{% if doc_url == page.url %}
|
||||
<div id="footnav" class="prenext">
|
||||
{% if forloop.first %}
|
||||
{% else %}
|
||||
{% assign previous = forloop.index0 | minus: 1 %}
|
||||
{% assign previous_page = docs[previous] %}
|
||||
<a href="{{ site.url | append: previous_page }}" class="prev"></a>
|
||||
{% endif %}
|
||||
|
||||
{% if forloop.last %}
|
||||
{% else %}
|
||||
{% assign next = forloop.index0 | plus: 1 %}
|
||||
{% assign next_page = docs[next] %}
|
||||
<a href="{{ site.url | append: next_page }}" class="next"></a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="clear"></div>
|
||||
{% break %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,46 @@
|
|||
---
|
||||
layout: default
|
||||
title: Developer Docs
|
||||
short: Developer Docs
|
||||
---
|
||||
Code structure / Style guide
|
||||
----------------------------
|
||||
At large, Lsyncd's documentation contained in source code comments is well developed - at least compared to many other software projects. Here thus only a few outlining words.
|
||||
|
||||
Lsyncd 2 is partially written in C, partially written in Lua. For some code pieces it is a free decision in which it could be coded, but for most one of the two was more appropriate. Generally the goal was if there was no good reason to code something in C do it in Lua. The C core thus does all the stuff close to the operating system:
|
||||
* inotify interface
|
||||
* process management, zombie collection
|
||||
* signals
|
||||
* pipes
|
||||
* logging (also in the core, so it can log itself comfortably)
|
||||
* alarms (Lsyncd uses kernel jiffies as alarms, thus it should be year Y2K38 safe (2038 is the year in which an often used time measurement - seconds since 1970 - will overflow.)
|
||||
Everything else is done in lsyncd.lua.
|
||||
|
||||
While Lua is not an object oriented language itself, it supplies a set of features which can emulate its features quite nicely. Shortly spoken, it supports prototyping. Lsyncd makes use of local function scoping to create blocks of code which are loosely connected with each other. A major feature of object oriented language.
|
||||
|
||||
A typical "class" in Lsyncd looks like this.
|
||||
|
||||
```Lua
|
||||
MyClass = (function()
|
||||
local private = "only visible here"
|
||||
local function new()
|
||||
return {foo="bar"}
|
||||
end
|
||||
-- public interface
|
||||
return { new = new }
|
||||
end)()
|
||||
```
|
||||
|
||||
In plain interpretation this creates a global variable called "MyClass". It defines an unnamed function with internal variables and subfunctions. This functions returns a table of all functions that should be visible from the outside. Then the unnamed function is immediately called on the global definition of "MyClass" and the public interface table stored in "MyClass". `MyClass.new()` is then used to create objects of this class. Or if it is a "Singelton", it has no new function, but other functions, that would be considered "static" in an object oriented language.
|
||||
|
||||
Lua has developed a culture for personal adaption - "power patches". To not throw a stick into any package maintainer Lsyncd uses standard from the stock Lua (5.1).
|
||||
|
||||
One note about the style of sync{} configs. As you can see in Layer 4 configuration the configuration is selected by merely adding the configuration table as parameter. In the implementation it does nothing else than to take any value with a number as key and which has a table as value and copy all of its key/value entries that are not there already. This even works recursively if that config again imports another config.
|
||||
|
||||
Why Lua and not ...?
|
||||
--------------------
|
||||
Lua has been chosen for Lsyncd 2 much out of the same reasons why C was chosen for Lsyncd 1.x. Lsyncd should be small and fast and should come with as few as possible dependencies.
|
||||
|
||||
The move from a pure C implementation as in Lsyncd 1.x to using Lua was motivated by enhancing the configuration file format. XML was starting to get clunky for the requirements of Lsyncd. When used Lua bindings already for config file parsing, there was little reason not to move large parts of Lsyncd internal logic into Lua as well. With its garbage collector, memory management became much easier, and with its highly optimized tables as dictionaries, some things even become faster, as Lsyncd 1.x linearly traversed a lot of lists, while Lsyncd 2 uses Lua tables as hash tables everywhere.
|
||||
|
||||
Lua was chosen over other script languages not because I think Lua is the greatest thing on earth - like many coders think about their favorite language, but because it is considered to be a appropriate tool for this task. Many coders like [X] so much, they want to do everything with it, also because after a while they know [X] very well, and are of course faster in solving an issue than learning [Y]. I on the other hand didn't know Lua before Lsyncd 2 at all, and decided explicitly for it out of pragmatic reasons. First it is said have originated out of the need for configuration files for C applications. Much the use case Lsyncd needs. So the C interface is handy and fast. Since Lsyncd has its parts close to the system still coded in C this interfacing makes things easier. Lua is very fast. Without affronting other script languages one can say it is at least in the top tier regarding speed. It may be the fastest scripting language there is, but this statement might raise debates. But not without reason it got quite some attention from the gaming industry. It gets even faster with LuaJIT and it gets into the range of JavaJIT (which is a compiled and not a script language). Lua has also a very slim standard library, while this is a stopper for people just wanting to quickly write some script this plays into the hands of Lsyncd. It comes with little requirements on the machine to be installed and Lsyncd comes with its own custom core functionally anyway. And although the Lua syntax looks a bit dirty at first - like implicit on the fly creation of globals - with a little configuration this can be banned. Like Lsyncd does in normal operation modes.
|
|
@ -0,0 +1,257 @@
|
|||
html {
|
||||
height: 100%;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: rgb( 245, 245, 245 );
|
||||
font: 13px/1.4 Helvetica, arial, nimbussansl, liberationsans, freesans, clean, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#pillar {
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
max-width: 70em;
|
||||
min-height: 100%;
|
||||
overflow: auto;
|
||||
background-color: white;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
a:link, a:visited {
|
||||
color: blue;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
nav {
|
||||
background-color: rgb( 200, 200, 200 );
|
||||
position : absolute;
|
||||
right : 0;
|
||||
padding: 0;
|
||||
width: 11.1em;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#menu {
|
||||
background-color: rgba( 200, 200, 200, 0.95 );
|
||||
z-index: 20;
|
||||
position: relative;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 11.1em;
|
||||
}
|
||||
|
||||
#menufooter {
|
||||
position: absolute;
|
||||
bottom: -2em;
|
||||
height: 2em;
|
||||
width: 11.1em;
|
||||
background-color: transparent;
|
||||
background-image: linear-gradient(to bottom, rgba(200, 200, 200, 0.95), rgba(200, 200, 200, 0));
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
nav h4 {
|
||||
border-bottom: 1px solid black;
|
||||
padding-left: 1em;
|
||||
margin-right: 1em;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
nav a {
|
||||
display : block;
|
||||
position : relative;
|
||||
margin-left: 1em;
|
||||
padding: 0.2em 0 0.2em 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
|
||||
nav a.current {
|
||||
background-color: rgb( 220, 220, 220 );
|
||||
padding-left: 1em;
|
||||
margin-left: 0;
|
||||
margin-right: 1em;
|
||||
border-top-right-radius: 0.8em;
|
||||
border-bottom-right-radius: 0.8em;
|
||||
}
|
||||
|
||||
|
||||
nav a.current::before {
|
||||
content : "";
|
||||
border-color : white;
|
||||
border-width : 1em;
|
||||
border-style : solid;
|
||||
border-radius : 1em;
|
||||
position : absolute;
|
||||
left: -1.5em;
|
||||
height : 0;
|
||||
top : -0.12em;
|
||||
width: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
|
||||
nav ul {
|
||||
list-style-type: none;
|
||||
padding : 0;
|
||||
}
|
||||
|
||||
#container {
|
||||
margin: 0 13em 0 2em;
|
||||
}
|
||||
|
||||
table {
|
||||
padding : 0;
|
||||
margin : 0;
|
||||
border-spacing : 0;
|
||||
}
|
||||
|
||||
tr:nth-child( even ) {
|
||||
background-color: rgb( 240, 240, 240 );
|
||||
}
|
||||
|
||||
tr:first-child td {
|
||||
border-top : 1px solid rgb( 220, 220, 220 )
|
||||
}
|
||||
|
||||
tr td:first-child, tr th:first-child {
|
||||
border-left : 1px solid rgb( 220, 220, 220 )
|
||||
}
|
||||
|
||||
thead::after {
|
||||
line-height: 0.4em;
|
||||
content: "\200C";
|
||||
display:block;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: rgb( 230, 230, 230 );
|
||||
border-top : 1px solid rgb( 210, 210, 210 );
|
||||
border-bottom : 1px solid rgb( 210, 210, 210 );
|
||||
border-right : 1px solid rgb( 210, 210, 210 );
|
||||
padding : 0.8em 1em;
|
||||
margin : 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
th:first-child {
|
||||
border-top-left-radius: 1em;
|
||||
border-bottom-left-radius: 1em;
|
||||
}
|
||||
|
||||
th:last-child {
|
||||
border-top-right-radius: 1em;
|
||||
border-bottom-right-radius: 1em;
|
||||
}
|
||||
|
||||
td {
|
||||
border-bottom : 1px solid rgb( 220, 220, 220 );
|
||||
border-right : 1px solid rgb( 220, 220, 220 );
|
||||
padding : 1em;
|
||||
margin : 0;
|
||||
}
|
||||
|
||||
tr:first-child td:first-child { border-top-left-radius: 1em; }
|
||||
tr:first-child td:last-child { border-top-right-radius: 1em; }
|
||||
tr:last-child td:first-child { border-bottom-left-radius: 1em; }
|
||||
tr:last-child td:last-child { border-bottom-right-radius: 1em; }
|
||||
|
||||
#ribbonwrap {
|
||||
position: absolute;
|
||||
right: 14.1em;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
#ribbon {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 12.1em;
|
||||
height: 12.1em;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#ribbon::before, #ribbon::after {
|
||||
display: block;
|
||||
position: absolute;
|
||||
transform: rotate(-45deg);
|
||||
top: auto;
|
||||
bottom: 3.15em;
|
||||
height: 1.54em;
|
||||
width: 15.38em;
|
||||
right: -3.23em;
|
||||
}
|
||||
|
||||
#ribbon::before {
|
||||
content: "";
|
||||
background-color: #666;
|
||||
background-image: linear-gradient(to bottom, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.15));
|
||||
padding: 0.38em 0;
|
||||
box-shadow: 0 .15em .23em 0 rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
#ribbon::after {
|
||||
content: "Fork me on Github";
|
||||
color: white;
|
||||
font: 700 1em "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
line-height: 1.54em;
|
||||
padding: 0.15em 0;
|
||||
margin: 0.15em 0;
|
||||
text-indent: 0;
|
||||
text-align: center;
|
||||
border-color: rgba(255, 255, 255, 0.7);
|
||||
border-style: dotted;
|
||||
border-width: 0.08em 0;
|
||||
}
|
||||
|
||||
#footnav {
|
||||
}
|
||||
|
||||
.prenext {
|
||||
}
|
||||
|
||||
.prenext a:hover::before {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.prenext .prev::before, .prenext .next:before {
|
||||
background-color: rgb( 240, 240, 240 );
|
||||
padding: 0.2em 0.8em;
|
||||
border-radius: 0.8em;
|
||||
margin: 1em 0 1em 0;
|
||||
}
|
||||
|
||||
.prenext .prev::before {
|
||||
content : "\2039\2039\00a0prev";
|
||||
float: left;
|
||||
}
|
||||
|
||||
.prenext .next::before {
|
||||
content : "next\00a0\203A\203A";
|
||||
float: right;
|
||||
}
|
||||
|
||||
#improvethis {
|
||||
float: right;
|
||||
color: #bbb;
|
||||
margin: 1em 0 0 0;
|
||||
}
|
||||
|
||||
|
||||
#improvethis::before {
|
||||
content : "\270E\00a0Improve\00a0this\00a0page";
|
||||
}
|
||||
|
||||
.highlighter-rouge {
|
||||
background-color: #f8f8f8;
|
||||
border-radius: 0.4em;
|
||||
padding: 0.1em 0.2em;
|
||||
}
|
|
@ -0,0 +1,212 @@
|
|||
.highlight table td { padding: 5px; }
|
||||
.highlight table pre { margin: 0; }
|
||||
.highlight .cm {
|
||||
color: #999988;
|
||||
font-style: italic;
|
||||
}
|
||||
.highlight .cp {
|
||||
color: #999999;
|
||||
font-weight: bold;
|
||||
}
|
||||
.highlight .c1 {
|
||||
color: #999988;
|
||||
font-style: italic;
|
||||
}
|
||||
.highlight .cs {
|
||||
color: #999999;
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
}
|
||||
.highlight .c, .highlight .cd {
|
||||
color: #999988;
|
||||
font-style: italic;
|
||||
}
|
||||
.highlight .err {
|
||||
color: #a61717;
|
||||
background-color: #e3d2d2;
|
||||
}
|
||||
.highlight .gd {
|
||||
color: #000000;
|
||||
background-color: #ffdddd;
|
||||
}
|
||||
.highlight .ge {
|
||||
color: #000000;
|
||||
font-style: italic;
|
||||
}
|
||||
.highlight .gr {
|
||||
color: #aa0000;
|
||||
}
|
||||
.highlight .gh {
|
||||
color: #999999;
|
||||
}
|
||||
.highlight .gi {
|
||||
color: #000000;
|
||||
background-color: #ddffdd;
|
||||
}
|
||||
.highlight .go {
|
||||
color: #888888;
|
||||
}
|
||||
.highlight .gp {
|
||||
color: #555555;
|
||||
}
|
||||
.highlight .gs {
|
||||
font-weight: bold;
|
||||
}
|
||||
.highlight .gu {
|
||||
color: #aaaaaa;
|
||||
}
|
||||
.highlight .gt {
|
||||
color: #aa0000;
|
||||
}
|
||||
.highlight .kc {
|
||||
color: #000000;
|
||||
font-weight: bold;
|
||||
}
|
||||
.highlight .kd {
|
||||
color: #000000;
|
||||
font-weight: bold;
|
||||
}
|
||||
.highlight .kn {
|
||||
color: #000000;
|
||||
font-weight: bold;
|
||||
}
|
||||
.highlight .kp {
|
||||
color: #000000;
|
||||
font-weight: bold;
|
||||
}
|
||||
.highlight .kr {
|
||||
color: #000000;
|
||||
font-weight: bold;
|
||||
}
|
||||
.highlight .kt {
|
||||
color: #445588;
|
||||
font-weight: bold;
|
||||
}
|
||||
.highlight .k, .highlight .kv {
|
||||
color: #000000;
|
||||
font-weight: bold;
|
||||
}
|
||||
.highlight .mf {
|
||||
color: #009999;
|
||||
}
|
||||
.highlight .mh {
|
||||
color: #009999;
|
||||
}
|
||||
.highlight .il {
|
||||
color: #009999;
|
||||
}
|
||||
.highlight .mi {
|
||||
color: #009999;
|
||||
}
|
||||
.highlight .mo {
|
||||
color: #009999;
|
||||
}
|
||||
.highlight .m, .highlight .mb, .highlight .mx {
|
||||
color: #009999;
|
||||
}
|
||||
.highlight .sb {
|
||||
color: #d14;
|
||||
}
|
||||
.highlight .sc {
|
||||
color: #d14;
|
||||
}
|
||||
.highlight .sd {
|
||||
color: #d14;
|
||||
}
|
||||
.highlight .s2 {
|
||||
color: #d14;
|
||||
}
|
||||
.highlight .se {
|
||||
color: #d14;
|
||||
}
|
||||
.highlight .sh {
|
||||
color: #d14;
|
||||
}
|
||||
.highlight .si {
|
||||
color: #d14;
|
||||
}
|
||||
.highlight .sx {
|
||||
color: #d14;
|
||||
}
|
||||
.highlight .sr {
|
||||
color: #009926;
|
||||
}
|
||||
.highlight .s1 {
|
||||
color: #d14;
|
||||
}
|
||||
.highlight .ss {
|
||||
color: #990073;
|
||||
}
|
||||
.highlight .s {
|
||||
color: #d14;
|
||||
}
|
||||
.highlight .na {
|
||||
color: #008080;
|
||||
}
|
||||
.highlight .bp {
|
||||
color: #999999;
|
||||
}
|
||||
.highlight .nb {
|
||||
color: #0086B3;
|
||||
}
|
||||
.highlight .nc {
|
||||
color: #445588;
|
||||
font-weight: bold;
|
||||
}
|
||||
.highlight .no {
|
||||
color: #008080;
|
||||
}
|
||||
.highlight .nd {
|
||||
color: #3c5d5d;
|
||||
font-weight: bold;
|
||||
}
|
||||
.highlight .ni {
|
||||
color: #800080;
|
||||
}
|
||||
.highlight .ne {
|
||||
color: #990000;
|
||||
font-weight: bold;
|
||||
}
|
||||
.highlight .nf {
|
||||
color: #990000;
|
||||
font-weight: bold;
|
||||
}
|
||||
.highlight .nl {
|
||||
color: #990000;
|
||||
font-weight: bold;
|
||||
}
|
||||
.highlight .nn {
|
||||
color: #555555;
|
||||
}
|
||||
.highlight .nt {
|
||||
color: #000080;
|
||||
}
|
||||
.highlight .vc {
|
||||
color: #008080;
|
||||
}
|
||||
.highlight .vg {
|
||||
color: #008080;
|
||||
}
|
||||
.highlight .vi {
|
||||
color: #008080;
|
||||
}
|
||||
.highlight .nv {
|
||||
color: #008080;
|
||||
}
|
||||
.highlight .ow {
|
||||
color: #000000;
|
||||
font-weight: bold;
|
||||
}
|
||||
.highlight .o {
|
||||
color: #000000;
|
||||
font-weight: bold;
|
||||
}
|
||||
.highlight .w {
|
||||
color: #bbbbbb;
|
||||
}
|
||||
.highlight {
|
||||
background-color: #f8f8f8;
|
||||
border-radius: 1em;
|
||||
padding: 0.1em 1em;
|
||||
margin: 0;
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
layout: default
|
||||
title: "Download"
|
||||
---
|
||||
Current Release
|
||||
=======================
|
||||
All releases are on the [releases page](https://github.com/lsyncd/lsyncd/releases)
|
||||
|
||||
HEAD Development
|
||||
================
|
||||
{% highlight shell %}
|
||||
git clone https://github.com/lsyncd/lsyncd.git
|
||||
{% endhighlight %}
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
layout: default
|
||||
title: Frequently Asked Questions
|
||||
short: FAQ
|
||||
---
|
||||
* [How can I call a script after each rsync operation?](postscript)
|
||||
* [How can I sync from one source to multiple targets?](multiple-targets)
|
||||
* [The startup sync works but after that Lsyncd doesn't do anything!](nothing-after-startup)
|
|
@ -0,0 +1,32 @@
|
|||
---
|
||||
layout: default
|
||||
title: "FAQ: How can I sync from one source to multiple targets?"
|
||||
---
|
||||
If you got multiple targets, you simple specify the sync command multiple times.
|
||||
|
||||
{% highlight lua %}
|
||||
sync{ default.rsync, source='/sourcedir', target='targethost1:/targetdir' }
|
||||
sync{ default.rsync, source='/sourcedir', target='targethost2:/targetdir' }
|
||||
sync{ default.rsync, source='/sourcedir', target='targethost3:/targetdir' }
|
||||
{% endhighlight %}
|
||||
|
||||
Lsyncd will notice multiple uses of the same source directory or the use of a subdirectory of an already used source directory and creates only watch per subdirectoy watched in any sync.
|
||||
|
||||
To remedy the multiplication of the same configuration you can even use a loop to configure multiple targets.
|
||||
|
||||
This is the same configuration as before using a loop:
|
||||
|
||||
{% highlight lua %}
|
||||
targets = {
|
||||
'targethost1:/targetdir',
|
||||
'targethost2:/targetdir',
|
||||
'targethost3:/targetdir',
|
||||
}
|
||||
|
||||
for _, target in ipairs( targets )
|
||||
do
|
||||
sync{ default.rsync, source='/sourcedir', target=target }
|
||||
end
|
||||
{% endhighlight %}
|
||||
|
||||
## [‹‹ back to FAQ index](../)
|
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
layout: default
|
||||
title: "FAQ: The startup sync works but after that Lsyncd doesn't do anything!"
|
||||
---
|
||||
This almost always caused by the fact you specified a network mounted directory as source.
|
||||
|
||||
Lsyncd requires the kernels inotify or fsevents interface to get noted of file changes. No known network filesystem (known to Lsyncd authors) supports forwarding file notifcations events.
|
||||
|
||||
Thus Lsyncd needs to run on the system where the files are located physically.
|
||||
|
||||
## [‹‹ back to FAQ index](../)
|
|
@ -0,0 +1,42 @@
|
|||
---
|
||||
layout: default
|
||||
title: "FAQ: How can I call a script before or after each rsync operation?"
|
||||
---
|
||||
The issue with this quite frequent request is, by itself it complicates error handling a lot. What should Lsyncd do, when the script fails that it ought to run after each rsync call? If it should recall the post script it would require a new state for each rsync event which would complicate Lsyncd code quite a bit.
|
||||
|
||||
The easiest way to get around this, is by replacing the rsync binary Lsyncd calls by a script from you, that calls rsync and does whatever you want to do, when rsync completes. The only thing to take care is that Lsyncd communicates with rsync using stdin/out/err-pipes and thus better not interfere with these.
|
||||
|
||||
Also take care the script properly forwards the exit code rsync returned.
|
||||
|
||||
This is an example bash script to wrap around rsync:
|
||||
|
||||
{% highlight shell %}
|
||||
#!/bin/bash
|
||||
/usr/bin/rsync "$@"
|
||||
result=$?
|
||||
(
|
||||
if [ $result -eq 0 ]; then
|
||||
echo "my commands";
|
||||
fi
|
||||
) >/dev/null 2>/dev/null </dev/null
|
||||
|
||||
exit $result
|
||||
{% endhighlight %}
|
||||
|
||||
It does not do error handling for post commands. If you need this, you'll have to code them here fitting your requirements.
|
||||
|
||||
Above script can be used as rsync wrapper replacement like this:
|
||||
|
||||
|
||||
{% highlight lua %}
|
||||
sync {
|
||||
default.rsync,
|
||||
source = "/path/to/source",
|
||||
target = "targethost::targetdir",
|
||||
rsync = {
|
||||
binary = "/path/to/bash/handler.sh"
|
||||
}
|
||||
}
|
||||
{% endhighlight %}
|
||||
|
||||
## [‹‹ back to FAQ index](../)
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
layout: default
|
||||
title: "Getting and Giving Help"
|
||||
short: "Help"
|
||||
---
|
||||
Discussion Group
|
||||
----------------
|
||||
There is a [discussion group to all things related Lsyncd](https://github.com/lsyncd/lsyncd/discussions)
|
||||
|
||||
Issues
|
||||
------
|
||||
For cases Lsyncd is missbehaving there are [Issues](https://github.com/lsyncd/lsyncd/issues) on Github.
|
||||
|
||||
Please check:
|
||||
|
||||
* This isn't a usage error.
|
||||
* Your running the latest Lsyncd version.
|
||||
* You include all things needed to reproduce the error, like complete configuration scripts.
|
||||
|
||||
Source & Improvement
|
||||
--------------------
|
||||
[Fork Lsyncd at Github](https://github.com/lsyncd/lsyncd)
|
|
@ -0,0 +1,47 @@
|
|||
---
|
||||
layout: default
|
||||
title: "Lsyncd - Live Syncing (Mirror) Daemon"
|
||||
short: "Welcome"
|
||||
---
|
||||
Description
|
||||
-----------
|
||||
Lsyncd uses a filesystem event interface (inotify or fsevents) to watch for changes to local files and directories. Lsyncd collates these events for several seconds and then spawns one or more processes to synchronize the changes to a remote filesystem. The default synchronization method is [rsync](http://rsync.samba.org/). Thus, Lsyncd is a light-weight live mirror solution. Lsyncd is comparatively easy to install and does not require new filesystems or block devices. Lysncd does not hamper local filesystem performance.
|
||||
|
||||
As an alternative to rsync, Lsyncd can also push changes via rsync+ssh. Rsync+ssh allows for much more efficient synchronization when a file or direcotry is renamed or moved to a new location in the local tree. (In contrast, plain rsync performs a move by deleting the old file and then retransmitting the whole file.)
|
||||
|
||||
Fine-grained customization can be achieved through the config file. Custom action configs can even be written from scratch in cascading layers ranging from shell scripts to code written in the [Lua language](http://www.lua.org/). Thus, simple, powerful and flexible configurations are possible.
|
||||
|
||||
Lsyncd 2.2.1 requires rsync >= 3.1 on all source and target machines.
|
||||
|
||||
License: [GPLv2](http://www.fsf.org/licensing/licenses/info/GPLv2.html) or any later GPL version.
|
||||
|
||||
When to use
|
||||
-----------
|
||||
Lsyncd is designed to synchronize a slowly changing local directory tree to a remote mirror. Lsyncd is especially useful to sync data from a secure area to a not-so-secure area.
|
||||
|
||||
Other synchronization tools
|
||||
------------------------
|
||||
[DRBD](http://www.drbd.org) operates on block device level. This makes it useful for synchronizing systems that are under heavy load. Lsyncd on the other hand does not require you to change block devices and/or mount points, allows you to change uid/gid of the transferred files, separates the receiver through the one-way nature of rsync. DRBD is likely the better option if you are syncing databases.
|
||||
|
||||
[GlusterFS](http://www.gluster.org) and [BindFS](http://bindfs.org/) use a FUSE-Filesystem to interject kernel/userspace filesystem events.
|
||||
|
||||
[Mirror](https://github.com/stephenh/mirror) is an asynchronous synchronisation tool that takes use of the inotify notifications much like Lsyncd. The main differences are: it is developed specifically for master-master use, thus running on a daemon on both systems, uses its own transportation layer instead of rsync and is Java instead of Lsyncd's C core with Lua scripting.
|
||||
|
||||
Lsyncd usage examples
|
||||
---------------------
|
||||
{% highlight shell %}
|
||||
lsyncd -rsync /home remotehost.org::share/
|
||||
{% endhighlight %}
|
||||
|
||||
This watches and rsyncs the local directory /home with all sub-directories and
|
||||
transfers them to 'remotehost' using the rsync-share 'share'.
|
||||
|
||||
{% highlight shell %}
|
||||
lsyncd -rsyncssh /home remotehost.org backup-home/
|
||||
{% endhighlight %}
|
||||
|
||||
This will also rsync/watch '/home', but it uses a ssh connection to make moves local on the remotehost instead of re-transmitting the moved file over the wire.
|
||||
|
||||
Disclaimer
|
||||
----------
|
||||
Besides the usual disclaimer in the license, we want to specifically emphasize that neither the authors, nor any organization associated with the authors, can or will be held responsible for data-loss caused by possible malfunctions of Lsyncd.
|
|
@ -0,0 +1,161 @@
|
|||
'\" t
|
||||
.\" Title: lsyncd
|
||||
.\" Author: [see the "AUTHOR" section]
|
||||
.\" Generator: DocBook XSL Stylesheets vsnapshot <http://docbook.sf.net/>
|
||||
.\" Date: January 2017
|
||||
.\" Manual: Lsyncd
|
||||
.\" Source: Lsyncd 2.2.1
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "LSYNCD" "1" "January 2017" "Lsyncd 2\&.2\&.1" "Lsyncd"
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" * Define some portability stuff
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
.\" http://bugs.debian.org/507673
|
||||
.\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html
|
||||
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
.ie \n(.g .ds Aq \(aq
|
||||
.el .ds Aq '
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" * set default formatting
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" disable hyphenation
|
||||
.nh
|
||||
.\" disable justification (adjust text to left margin only)
|
||||
.ad l
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" * MAIN CONTENT STARTS HERE *
|
||||
.\" -----------------------------------------------------------------
|
||||
.SH "NAME"
|
||||
lsyncd \- a daemon to continuously synchronize directory trees
|
||||
.SH "SYNOPSIS"
|
||||
.PP
|
||||
config file
|
||||
.RS 4
|
||||
\ \&
|
||||
\fBlsyncd\fR
|
||||
[\fIOPTIONS\fR]
|
||||
\fICONFIG\-FILE\fR
|
||||
.RE
|
||||
.PP
|
||||
default rsync behaviour
|
||||
.RS 4
|
||||
\ \&
|
||||
\fBlsyncd\fR
|
||||
[\fIOPTIONS\fR] \-rsync
|
||||
\fISOURCEDIR\fR
|
||||
\fITARGET\fR
|
||||
\&...
|
||||
.RE
|
||||
.PP
|
||||
default rync+ssh behaviour (moves and deletes through ssh)
|
||||
.RS 4
|
||||
\ \&
|
||||
\fBlsyncd\fR
|
||||
[\fIOPTIONS\fR] \-rsyncssh
|
||||
\fISOURCEDIR\fR
|
||||
\fITARGETHOST\fR
|
||||
\fITARGETDIR\fR
|
||||
\&...
|
||||
.RE
|
||||
.PP
|
||||
default direct behaviour (local file operations/rsync)
|
||||
.RS 4
|
||||
\ \&
|
||||
\fBlsyncd\fR
|
||||
[\fIOPTIONS\fR] \-direct
|
||||
\fISOURCEDIR\fR
|
||||
\fITARGETDIR\fR
|
||||
\&...
|
||||
.RE
|
||||
.SH "DESCRIPTION"
|
||||
.sp
|
||||
Lsyncd(1) watches local directory trees through an event monitor interface (inotify, fsevents)\&. It aggregates and combines events for a few seconds and then spawns one or more processes to synchronize the changes\&. By default this is rsync(1)\&. Lsyncd is thus a light\-weight asynchronous live mirror solution that is comparatively easy to install not requiring new filesystems or block devices and does not hamper local filesystem performance\&.
|
||||
.sp
|
||||
Rsync+ssh is an advanced action configuration that uses a SSH(1) to act file and directory moves directly on the target instead of re\-transmitting the move destination over the wire\&.
|
||||
.sp
|
||||
Fine\-grained customization can be achieved through the CONFIG\-FILE\&. Custom action configs can even be written from scratch in cascading layers ranging from shell scripts to code written in the LUA(1) language\&. This way simplicity can be balanced with powerfulness\&. See the online manual for details on the CONFIG\-FILE https://axkibe\&.github\&.io/lsyncd/manual/config/file/ \&.
|
||||
.sp
|
||||
Note that under normal configuration Lsyncd will delete pre\-existing files in the target directories that are not present in the respective source directory\&.
|
||||
.SH "OPTIONS"
|
||||
.PP
|
||||
\fB\-delay\fR \fISECS\fR
|
||||
.RS 4
|
||||
Overrides the default delay times\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-help\fR
|
||||
.RS 4
|
||||
Show a help message\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-insist\fR
|
||||
.RS 4
|
||||
Continues start up even if rsync cannot connect\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-log\fR \fILEVEL\fR
|
||||
.RS 4
|
||||
Controls which kind of events are logged\&. By default Lsyncd logs
|
||||
\fINormal\fR
|
||||
and
|
||||
\fIError\fR
|
||||
Messages\&.
|
||||
\fB\-log scarce\fR
|
||||
will make Lsyncd log Error messages only\&.
|
||||
\fB\-log all\fR
|
||||
will log all debug messages\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-log\fR \fICategory\fR
|
||||
.RS 4
|
||||
Turns on a specific debug message\&. E\&.g\&.
|
||||
\fB\-log Exec\fR
|
||||
will log all processes as they are spawned\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-nodaemon\fR
|
||||
.RS 4
|
||||
Lsyncd will not detach from the invoker and log as well to stdout/err\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-pidfile\fR \fIFILE\fR
|
||||
.RS 4
|
||||
Lsyncd will write its process ID in
|
||||
\fIFILE\fR\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-runner\fR \fIFILE\fR
|
||||
.RS 4
|
||||
Makes the Lsyncd core load the part of Lsyncd written in Lua from
|
||||
\fIFILE\fR\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-version\fR
|
||||
.RS 4
|
||||
Writes version information and exits\&.
|
||||
.RE
|
||||
.SH "EXIT STATUS"
|
||||
.PP
|
||||
\fB(128+SIGNUM)\fR
|
||||
.RS 4
|
||||
Terminated by Signal (143 by TERM)
|
||||
.RE
|
||||
.PP
|
||||
\fB\-1\fR
|
||||
.RS 4
|
||||
Failure (syntax, unrecoverable error condition, internal failure)
|
||||
.RE
|
||||
.SH "SEE ALSO"
|
||||
.sp
|
||||
Online Manual: https://lsyncd\&.github\&.io/lsyncd/
|
||||
.SH "VERSION"
|
||||
.sp
|
||||
This man page is for lsyncd(1) version 2\&.2\&.0
|
||||
.SH "AUTHOR"
|
||||
.sp
|
||||
Axel Kittenberger, <axkibe@gmail\&.com> 2010\-2017 Daniel Poelzleithner, <poelzleithner@b1\-systems\&.de> 2021\-2023
|
||||
.SH "COPYING"
|
||||
.sp
|
||||
Copyright (C) 2010\-2017 Axel Kittenberger\&. Free use of this software is granted under the terms of the GNU General Public License (GPL) version 2, or any later version\&. Free redistrubition of this Documentation (/doc directory) is granted under the terms of the Creative Commons 3\&.0 Attribution License (CC\-3\&.0\-BY)\&.
|
|
@ -3,8 +3,8 @@ lsyncd(1)
|
|||
:doctype: manpage
|
||||
:man source: Lsyncd
|
||||
:man manual: Lsyncd
|
||||
:man version: 2.0.6
|
||||
:date: August 2011
|
||||
:man version: 2.2.1
|
||||
:date: January 2017
|
||||
|
||||
NAME
|
||||
----
|
||||
|
@ -13,16 +13,16 @@ lsyncd - a daemon to continuously synchronize directory trees
|
|||
SYNOPSIS
|
||||
--------
|
||||
config file:::
|
||||
{nbsp}*lsyncd* ['OPTIONS'] 'CONFIG-FILE'
|
||||
{nbsp} *lsyncd* ['OPTIONS'] 'CONFIG-FILE'
|
||||
|
||||
default rsync behaviour:::
|
||||
{nbsp}*lsyncd* ['OPTIONS'] -rsync 'SOURCEDIR' 'TARGET' ...
|
||||
{nbsp} *lsyncd* ['OPTIONS'] -rsync 'SOURCEDIR' 'TARGET' ...
|
||||
|
||||
default rync+ssh bevahiour (moves and deletes through ssh):::
|
||||
{nbsp}*lsyncd* ['OPTIONS'] -rsyncssh 'SOURCEDIR' 'TARGETHOST' 'TARGETDIR' ...
|
||||
default rync+ssh behaviour (moves and deletes through ssh):::
|
||||
{nbsp} *lsyncd* ['OPTIONS'] -rsyncssh 'SOURCEDIR' 'TARGETHOST' 'TARGETDIR' ...
|
||||
|
||||
default direct bevahiour (local file operations/rsync):::
|
||||
{nbsp}*lsyncd* ['OPTIONS'] -direct 'SOURCEDIR' 'TARGETDIR' ...
|
||||
default direct behaviour (local file operations/rsync):::
|
||||
{nbsp} *lsyncd* ['OPTIONS'] -direct 'SOURCEDIR' 'TARGETDIR' ...
|
||||
|
||||
DESCRIPTION
|
||||
------------
|
||||
|
@ -31,17 +31,17 @@ Lsyncd(1) watches local directory trees through an event monitor interface
|
|||
then spawns one or more processes to synchronize the changes. By default this
|
||||
is rsync(1). Lsyncd is thus a light-weight asynchronous live mirror solution
|
||||
that is comparatively easy to install not requiring new filesystems or
|
||||
blockdevices and does not hamper local filesystem performance.
|
||||
block devices and does not hamper local filesystem performance.
|
||||
|
||||
Rsync+ssh is an advanced action configuration that uses a SSH(1) to act file
|
||||
and directory moves directly on the target instead of retransmitting the move
|
||||
and directory moves directly on the target instead of re-transmitting the move
|
||||
destination over the wire.
|
||||
|
||||
Fine-grained customization can be achieved through the CONFIG-FILE. Custom
|
||||
action configs can even be written from scratch in cascading layers ranging
|
||||
from shell scripts to code written in the LUA(1) language. This way simplicity
|
||||
can be balanced with powerfulness. See the online manual for details on the
|
||||
CONFIG-FILE http://code.google.com/p/lsyncd/wiki/Lsyncd20Manual
|
||||
CONFIG-FILE https://lsyncd.github.io/lsyncd/manual/config/file/ .
|
||||
|
||||
Note that under normal configuration Lsyncd will delete pre-existing files in
|
||||
the target directories that are not present in the respective source directory.
|
||||
|
@ -55,7 +55,7 @@ OPTIONS
|
|||
Show a help message.
|
||||
|
||||
*-insist*::
|
||||
Continues startup even if a startup rsync cannot connect.
|
||||
Continues start up even if rsync cannot connect.
|
||||
|
||||
*-log* 'LEVEL'::
|
||||
Controls which kind of events are logged. By default Lsyncd logs 'Normal'
|
||||
|
@ -69,6 +69,9 @@ OPTIONS
|
|||
*-nodaemon*::
|
||||
Lsyncd will not detach from the invoker and log as well to stdout/err.
|
||||
|
||||
*-onepass*::
|
||||
Sync once and exit
|
||||
|
||||
*-pidfile* 'FILE'::
|
||||
Lsyncd will write its process ID in 'FILE'.
|
||||
|
||||
|
@ -80,27 +83,29 @@ OPTIONS
|
|||
|
||||
EXIT STATUS
|
||||
-----------
|
||||
*0*::
|
||||
Terminated on a TERM signal(7)
|
||||
*(128+SIGNUM)*::
|
||||
Terminated by Signal (143 by TERM)
|
||||
|
||||
*-1*::
|
||||
Failure (syntax, unrecoverable error condition, internal failure)
|
||||
|
||||
|
||||
SEE ALSO
|
||||
--------
|
||||
Online Manual: http://code.google.com/p/lsyncd/wiki/Lsyncd2
|
||||
Online Manual: https://lsyncd.github.io/lsyncd/
|
||||
|
||||
VERSION
|
||||
------
|
||||
This man page is for lsyncd(1) version 2.0.5
|
||||
This man page is for lsyncd(1) version 2.2.0
|
||||
|
||||
AUTHOR
|
||||
------
|
||||
Axel Kittenberger, <axkibe@gmail.com> 2010-2011
|
||||
Axel Kittenberger, <axkibe@gmail.com> 2010-2017
|
||||
Daniel Poelzleithner, <poelzleithner@b1-systems.de> 2021-2023
|
||||
|
||||
COPYING
|
||||
-------
|
||||
Copyright \(C) 2010-2011 Axel Kittenberger. Free use of this software is granted
|
||||
Copyright \(C) 2010-2017 Axel Kittenberger. Free use of this software is granted
|
||||
under the terms of the GNU General Public License (GPL) version 2, or any later
|
||||
version. Free redistrubition of this Documentation (/doc directory) is granted
|
||||
under the terms of the Creative Commons 3.0 Attribution License (CC-3.0-BY).
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
layout: default
|
||||
title: Building
|
||||
---
|
||||
|
||||
## Requirements
|
||||
|
||||
### Lua >= 5.2
|
||||
Lsyncd depends on Lua 5.2 or greater; that is 5.2 or 5.3. For most distributions you need to install the liblua??, the liblua??-dev and the lua?? package, with ?? being the respective Lua version.
|
||||
|
||||
### cmake >= 2.8
|
||||
|
||||
To configure Lsyncd to your system, cmake >= 2.8 is required
|
||||
|
||||
### rsync >= 3.1
|
||||
During runtime Lsyncd needs rsync > 3.1 installed both on source and target systems.
|
||||
|
||||
## Compiling
|
||||
|
||||
With these requirements fulfilled building Lsyncd should be a straight forward process. Unpack the downloaded tar.gz file and run:
|
||||
|
||||
{% highlight shell %}
|
||||
cmake .
|
||||
make
|
||||
sudo make install
|
||||
{% endhighlight %}
|
|
@ -0,0 +1,99 @@
|
|||
---
|
||||
layout: default
|
||||
title: "The Configuration File"
|
||||
short: "Config File"
|
||||
---
|
||||
Lsyncd configuration files are valid [Lua syntax](http://www.lua.org/). It is designed to be simple yet potent. While rich configuration and simplicity are not opposites by themselves, some trade-offs are inevitable. To achieve both goals as far as possible, Lsyncd configuration can be done at different layers. Lower layers add adaptability while the interface becomes more engaging.
|
||||
|
||||
Settings
|
||||
--------
|
||||
For scripts of all layers, the ```settings``` call can be used to alter daemon-wide configurations.
|
||||
|
||||
For example, the following code will instruct Lsyncd to log into ```/tmp/lsyncd.log```, periodically update the file ```/tmp/lsyncd.status``` with its status and to not detach as a daemon.
|
||||
|
||||
{% highlight lua %}
|
||||
settings {
|
||||
logfile = "/tmp/lsyncd.log",
|
||||
statusFile = "/tmp/lsyncd.status",
|
||||
nodaemon = true,
|
||||
}
|
||||
{% endhighlight %}
|
||||
|
||||
**Caution**
|
||||
If you are upgrading from 2.0.x, please notice that `settings` became a function from a variable, so you **MUST** delete the equal sign '=' between `settings` and the `{`.
|
||||
|
||||
Valid keys for settings are:
|
||||
|
||||
<table>
|
||||
|
||||
<tr><td> logfile
|
||||
</td><td> =
|
||||
</td><td> FILENAME
|
||||
</td><td> logs into this file
|
||||
</td></tr>
|
||||
|
||||
<tr><td> pidfile
|
||||
</td><td> =
|
||||
</td><td> FILENAME
|
||||
</td><td> logs PID into this file
|
||||
</td></tr>
|
||||
|
||||
<tr><td> nodaemon
|
||||
</td><td> =
|
||||
</td><td> BOOL
|
||||
</td><td> does not detach
|
||||
</td></tr>
|
||||
|
||||
<tr><td> statusFile
|
||||
</td><td> =
|
||||
</td><td> FILENAME
|
||||
</td><td> periodically writes a status report to this file
|
||||
</td></tr>
|
||||
|
||||
<tr><td> statusInterval
|
||||
</td><td> =
|
||||
</td><td> NUMBER
|
||||
</td><td> writes the status file at shortest after this number of seconds has passed (default: 10)
|
||||
</td></tr>
|
||||
|
||||
<tr><td> logfacility
|
||||
</td><td> =
|
||||
</td><td> STRING
|
||||
</td><td> syslog facility, default "user"
|
||||
</td></tr>
|
||||
|
||||
<tr><td> logident
|
||||
</td><td> =
|
||||
</td><td> STRING
|
||||
</td><td> syslog identification (tag), default "lsyncd"
|
||||
</td></tr>
|
||||
|
||||
<tr><td> insist
|
||||
</td><td> =
|
||||
</td><td> BOOL
|
||||
</td><td> keep running at startup although one or more targets failed due to not being reachable.
|
||||
</td></tr>
|
||||
|
||||
<tr><td> inotifyMode
|
||||
</td><td> =
|
||||
</td><td> STRING
|
||||
</td><td> Specifies on inotify systems what kind of changes to listen to. Can be "Modify", "CloseWrite" (default) or "CloseWrite or Modify".
|
||||
</td></tr>
|
||||
|
||||
<tr><td> maxProcesses
|
||||
</td><td> =
|
||||
</td><td> NUMBER
|
||||
</td><td> Lysncd will not spawn more than these number of processes. This adds across all sync{}s.
|
||||
</td></tr>
|
||||
|
||||
</table>
|
||||
|
||||
Additionally some parameters can be configured, which are inherited by all _Syncs_ (see Layer 3)
|
||||
|
||||
<table>
|
||||
<tr><td> maxDelays
|
||||
</td><td> =
|
||||
</td><td> NUMBER
|
||||
</td><td> When this amount of delayed events is queued, actions will be spawned, even below the delay timer.
|
||||
</td></tr>
|
||||
</table>
|
|
@ -0,0 +1,182 @@
|
|||
---
|
||||
layout: default
|
||||
title: "Config Layer 1: Inlets"
|
||||
short: "Config Layer 1"
|
||||
---
|
||||
Layer 2 allows you to create one process per one event. However, as with default rsync behavior you might want to call one process for several events. This can be done with inlets. When any event becomes ready Lsyncd calls the ```action``` entry with ```inlet``` as parameter. The ```inlet``` can be used to grab ready single events or event lists.
|
||||
|
||||
For example this is the action used by default.rsync:
|
||||
|
||||
{% highlight lua %}
|
||||
action = function( inlet )
|
||||
local elist = inlet.getEvents( )
|
||||
local config = inlet.getConfig( )
|
||||
local paths = elist.getPaths( )
|
||||
log( "Normal", "rsyncing list\n", table.concat( paths, '\n' ) )
|
||||
spawn(elist, '/usr/bin/rsync',
|
||||
'<', table.concat( paths, '\000' ),
|
||||
'--delete',
|
||||
config.rsync._computed,
|
||||
'--from0',
|
||||
'--include-from=-',
|
||||
'--exclude=*',
|
||||
config.source,
|
||||
config.target
|
||||
)
|
||||
end
|
||||
{% endhighlight %}
|
||||
|
||||
Inlet functions are:
|
||||
|
||||
| Function | Description |
|
||||
|:---------|:------------|
|
||||
| inlet.getEvent() | Retrieves the next `event` as in Layer 2 configuration. Multiple calls to getEvent() will return the same event unless it has spawn{}ed an action. |
|
||||
| inlet.getEvents(test) | Returns a list of all events that are ready. `test` is optional for a function that will be called for every event to test if it should be included in the list. It has one parameter the `event` and returns true if an event should be included. If nil every ready event will be included in the list |
|
||||
| inlet.discardEvent() | Discards an event. The next call to getEvent will thus receive another event, even if no action has been spawned for this event |
|
||||
| inlet.getConfig() | returns the same as `event.config`. The configuration of the sync{} |
|
||||
| inlet.addExclude() | adds an exclusion pattern to this sync (see Exclusions) |
|
||||
| inlet.rmExclude() | removes an exclusion pattern from this sync |
|
||||
| inlet.createBlanketEvent() | puts an `event` on the top of the Delay FIFO that blocks all events and is blocked by all events. This is used for onStartup. |
|
||||
|
||||
The list returned by getEvents can be handed to spawn{} as _agent_ just as well as singular events.
|
||||
|
||||
Lists have following functions
|
||||
|
||||
| Function | Description |
|
||||
|:----------|:------------|
|
||||
| elist.getPaths(delimiter) | returns a string of the paths (as in `event.path` separated by `delimiter`. By default \n is used as delimiter. |
|
||||
| elist.getSourcePaths(delimiter) | returns a string of the sourcePaths (as in `event.sourcePath` separated by `delimiter`. By default \n is used as delimiter. |
|
||||
|
||||
Take care calling getEvents() and its function since depending on the amount of events, they will cause quite some CPU load.
|
||||
|
||||
Layer 2 functions is nothing else than following layer 1 action loaded by the default if the user script did not provide one itself.
|
||||
|
||||
{% highlight lua %}
|
||||
-----
|
||||
-- Default action calls user scripts on**** functions.
|
||||
--
|
||||
action = function( inlet )
|
||||
-- in case of moves getEvent returns the origin and destination of the move
|
||||
local event, event2 = inlet.getEvent( )
|
||||
local config = inlet.getConfig( )
|
||||
local func = config[ 'on'.. event.etype ]
|
||||
if func
|
||||
then
|
||||
func( event, event2 )
|
||||
end
|
||||
-- if function didnt change the wait status its not interested
|
||||
-- in this event -> drop it.
|
||||
if event.status == "wait"
|
||||
then
|
||||
inlet.discardEvent( event )
|
||||
end
|
||||
end,
|
||||
{% endhighlight %}
|
||||
|
||||
Lsyncd will automatically split Move events into Create and Delete events if no "onMove" field is found in the config. When handling moves in layer 1 `action` function, simply set "onMove" to be "true".
|
||||
|
||||
Other than `action` Lsyncd calls `init` for each sync{} on initialization. This is the default init function which is loaded if the user script does not have one. It provides the onStartup() functionality for layer 2 and 3.
|
||||
|
||||
{% highlight lua %}
|
||||
-----
|
||||
-- called on (re)initalizing of lsyncd.
|
||||
--
|
||||
init = function( inlet )
|
||||
local config = inlet.getConfig( )
|
||||
-- calls a startup if provided by user script.
|
||||
if type( config.onStartup ) == "function"
|
||||
then
|
||||
local event = inlet.createBlanketEvent( )
|
||||
config.onStartup( event )
|
||||
if event.status == 'wait'
|
||||
then
|
||||
-- user script did not spawn anything
|
||||
-- thus the blanket event is deleted again.
|
||||
inlet.discardEvent( event )
|
||||
end
|
||||
end
|
||||
end,
|
||||
{% endhighlight %}
|
||||
|
||||
As another example this is the init of `default.rsync`. As specialty it changes the configuration in that it adds a slash to target if not there already.
|
||||
|
||||
{% highlight lua %}
|
||||
-----
|
||||
-- Spawns the recursive startup sync
|
||||
--
|
||||
init = function( inlet )
|
||||
local config = inlet.getConfig( )
|
||||
local event = inlet.createBlanketEvent( )
|
||||
if string.sub(config.target, -1) ~= "/"
|
||||
then
|
||||
config.target = config.target .. "/"
|
||||
end
|
||||
|
||||
log("Normal", "recursive startup rsync: ", config.source,
|
||||
" -> ", config.target)
|
||||
|
||||
spawn(event,
|
||||
"/usr/bin/rsync",
|
||||
"--delete",
|
||||
config.rsync._computed .. "r",
|
||||
config.source,
|
||||
config.target
|
||||
)
|
||||
end,
|
||||
{% endhighlight %}
|
||||
|
||||
When child processes are finished and their zombie processes are collected, Lsyncd calls the function of the `collect` entry. When collect return "again" the status of the agent (an event or an event list) will be set on "wait" again, and will become ready in `delay` seconds (or 1 second if smaller).
|
||||
|
||||
The default collect function looks in the exitcodes[] table for an entry of the exit code. Otherwise most of the unfortunately longer code below does nothing but making nice log message.
|
||||
|
||||
{% highlight lua %}
|
||||
-----
|
||||
-- Called when collecting a finished child process
|
||||
--
|
||||
collect = function(agent, exitcode)
|
||||
local config = agent.config
|
||||
|
||||
if not agent.isList and agent.etype == "Blanket" then
|
||||
if exitcode == 0
|
||||
then
|
||||
log("Normal", "Startup of '",agent.source,"' finished.")
|
||||
elseif config.exitcodes and
|
||||
config.exitcodes[exitcode] == "again"
|
||||
then
|
||||
log("Normal",
|
||||
"Retrying startup of '",agent.source,"'.")
|
||||
return "again"
|
||||
else
|
||||
log("Error", "Failure on startup of '",agent.source,"'.")
|
||||
terminate(-1) -- ERRNO
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
local rc = config.exitcodes and config.exitcodes[exitcode]
|
||||
if rc == "die"
|
||||
then
|
||||
return rc
|
||||
end
|
||||
|
||||
if agent.isList
|
||||
then
|
||||
if rc == "again"
|
||||
then
|
||||
log("Normal", "Retrying a list on exitcode = ",exitcode)
|
||||
else
|
||||
log("Normal", "Finished a list = ",exitcode)
|
||||
end
|
||||
else
|
||||
if rc == "again"
|
||||
then
|
||||
log("Normal", "Retrying ",agent.etype,
|
||||
" on ",agent.sourcePath," = ",exitcode)
|
||||
else
|
||||
log("Normal", "Finished ",agent.etype,
|
||||
" on ",agent.sourcePath," = ",exitcode)
|
||||
end
|
||||
end
|
||||
return rc
|
||||
end,
|
||||
{% endhighlight %}
|
|
@ -0,0 +1,94 @@
|
|||
---
|
||||
layout: default
|
||||
title: "Config Layer 2: Advanced onAction"
|
||||
short: "Config Layer 2"
|
||||
---
|
||||
While Layer 4 and 3 feel like normal configuration files, Layer 2 and 1 enter the realm of coding. It is thus supposed you have some coding knowledge when using Layer 2 or 1.
|
||||
|
||||
Instead of designating actions as strings as in Layer 3 Lua functions can used to do some small scripts right within Lsyncd.
|
||||
|
||||
This example will convert any file with the suffix ".ps" created in a directory into a PDF.
|
||||
|
||||
{% highlight lua %}
|
||||
autopdf = {
|
||||
onCreate = function(event)
|
||||
log("Normal", "got an onCreate Event")
|
||||
if string.ends(event.pathname, ".ps") then
|
||||
spawn(event, "/usr/bin/ps2pdf", event.sourcePath)
|
||||
end
|
||||
end
|
||||
}
|
||||
{% endhighlight %}
|
||||
|
||||
The function can take any valid Lua code.
|
||||
|
||||
Lsyncd provides you a set of functions to be used in user scripts.
|
||||
|
||||
log(Category, ...)
|
||||
------------------
|
||||
Logs a message into file/stdout/syslog. The first parameter is the logging category all others are strings to be logged. A logging category must start with a capital letter. "Normal" and "Error" are standard categories for log messages. All others are categories for debugging.
|
||||
|
||||
spawn(Event, Binary, ...)
|
||||
--------------------------
|
||||
Spawns a new process associated with the event (or event list, see below) as first parameter. The second parameter specifies a binary to call. All others are arguments for the binary.
|
||||
|
||||
If the third parameter is "<", then along with fourth parameter they will not be passed as arguments to the binary. The fourth parameter is a string that will piped through stdin to the binary.
|
||||
|
||||
Do not use Lua's ```os.execute``` as opposed to Lsyncd's ```spawn()``` it will block and thus block the whole Lsyncd daemon until the command is completed. Lsyncd's ```spawn``` on the other hand returns immediately while the child process runs.
|
||||
|
||||
spawnShell(Event, Command, ... )
|
||||
--------------------------------
|
||||
The same as spawn(), only it will invoke a shell. Any parameters are referred as $1, $2, $3 and so on in the command.
|
||||
|
||||
By the way, this is the simple implementation of spawnShell:
|
||||
|
||||
{% highlight lua %}
|
||||
function spawnShell(agent, command, ...)
|
||||
return spawn(agent, "/bin/sh", "-c", command, "/bin/sh", ...)
|
||||
end
|
||||
{% endhighlight %}
|
||||
|
||||
terminate(exitcode)
|
||||
-------------------
|
||||
Lets Lsyncd terminate with ```exitcode```.
|
||||
|
||||
event
|
||||
-----
|
||||
Variables of the actions are given by the _event_ field. It has following fields.
|
||||
|
||||
|Field|Meaning|
|
||||
|:----|:----|
|
||||
| event.config | the configuration as called with sync{} |
|
||||
| event.inlet | see [layer 1](../layer1/) about inlets |
|
||||
| event.etype | the event type. Can be 'ATTRIB', 'CREATE', 'MODIFY', 'DELETE', 'MOVE' |
|
||||
| event.status | the status of the event. 'wait' when it is ready to be spawned and 'active' if there is a process running associated with this event |
|
||||
| event.isdir | true if the event relates to a directory |
|
||||
| event.name | the filename, directories end with a slash |
|
||||
| event.basename | the filename, directories do not end with a slash |
|
||||
| event.path | see ^path of [Layer 3](../layer3/#all-possible-variables) |
|
||||
| event.pathname | see ^pathname of [Layer 3](../layer3/#all-possible-variables) |
|
||||
| event.source | see ^source of [Layer 3](../layer3/#all-possible-variables) |
|
||||
| event.sourcePath | see ^sourcePath of [Layer 3](../layer3/#all-possible-variables) |
|
||||
| event.sourcePathname | see ^sourcePathname of [Layer 3](../layer3/#all-possible-variables) |
|
||||
| event.target | see ^target of [Layer 3](../layer3/#all-possible-variables) |
|
||||
| event.targetPath | see ^targetPath of [Layer 3](../layer3/#all-possible-variables) |
|
||||
| event.targetPathname | see ^targetPathname of [Layer 3](../layer3/#all-possible-variables) |
|
||||
|
||||
onMove actions have two events as parameter, the origin and the destination of the move.
|
||||
|
||||
This example will tattle about all moves within the observed directory tree.
|
||||
|
||||
{% highlight lua %}
|
||||
tattleMove = {
|
||||
onMove = function(oEvent, dEvent)
|
||||
log("Normal", "A moved happened from ",
|
||||
oEvent.pathname, " to ", dEvent.pathname)
|
||||
end,
|
||||
}
|
||||
{% endhighlight %}
|
||||
|
||||
Action functions have to be short and fast. They are running right within Lsyncd's one and only main thread. If you have to do any more time consuming calculations _spawn{}_ a child process instead.
|
||||
|
||||
There can only be one child process associated to a event.
|
||||
|
||||
Layer 3 is nothing else than Lsyncd automatically write Layer 2 functions for you on initialization. Start Lsyncd with ```-log FWrite``` on a Layer 3 configuration to see what functions it dynamically writes and loads for you. Thus Layer 3 and 2 can also be be mixed at will.
|
|
@ -0,0 +1,175 @@
|
|||
---
|
||||
layout: default
|
||||
title: "Config Layer 3: Simple onAction"
|
||||
short: "Config Layer 3"
|
||||
---
|
||||
Simple onAction
|
||||
---------------
|
||||
In this layer, custom configurations can be created. This example will use bash commands to keep a local directory in sync.
|
||||
|
||||
{% highlight lua %}
|
||||
bash = {
|
||||
delay = 5,
|
||||
maxProcesses = 3,
|
||||
onCreate = "cp -r ^sourcePathname ^targetPathname",
|
||||
onModify = "cp -r ^sourcePathname ^targetPathname",
|
||||
onDelete = "rm -rf ^targetPathname",
|
||||
onMove = "mv ^o.targetPathname ^d.targetPathname",
|
||||
onStartup = '[[ if [ "$(ls -A ^source)" ]; then cp -r ^source* ^target; fi]]',
|
||||
}
|
||||
{% endhighlight %}
|
||||
|
||||
The example explained step by step. Technically, any Lsyncd configuration is a Lua table with a set of keys filled out. Thus it starts by creating a variable called ```bash``` and assigns it a table with = { ... }.
|
||||
|
||||
{% highlight lua %}
|
||||
bash = {
|
||||
...
|
||||
}
|
||||
{% endhighlight %}
|
||||
|
||||
Now the table is filled with entries. Every entry having a key left of the equal sign and its value right of it. If no delay is specified, this means immediate actions for Lsyncd. This example wants to aggregate changes for 5 seconds thus the next entry is:
|
||||
|
||||
{% highlight lua %}
|
||||
delay = 5,
|
||||
{% endhighlight %}
|
||||
|
||||
And a comma is needed since to mark the end of an entry.
|
||||
|
||||
Actions
|
||||
-------
|
||||
Actions are specified by the 6 keys:
|
||||
|
||||
<table>
|
||||
|
||||
<tr><td> onAttrib
|
||||
</td><td> called when only attributes changed
|
||||
</td></tr>
|
||||
|
||||
<tr><td> onCreate
|
||||
</td><td> called on a new file or directory
|
||||
</td></tr>
|
||||
|
||||
<tr><td> onModify
|
||||
</td><td> called when a file has changed
|
||||
</td></tr>
|
||||
|
||||
<tr><td> onDelete
|
||||
</td><td> called when a file or directory has been deleted
|
||||
</td></tr>
|
||||
|
||||
<tr><td> onMove
|
||||
</td><td> called when a file or directory has been moved within the observed directory tree
|
||||
</td></tr>
|
||||
|
||||
<tr><td> onStartup
|
||||
</td><td> called on the start of Lsyncd
|
||||
</td></tr>
|
||||
|
||||
</table>
|
||||
|
||||
When there is no ```onMove``` or the move goes into or out of the observed directory tree, it is split into an ```onDelete``` of the move origin and an ```onCreate``` of the move destination. That is if either is within the observed directory tree. ```onStartup``` will always block all other actions for this _Sync_ until completed.
|
||||
|
||||
The action to be taken is specified as a Lua string. Thus actions can be delimited with anything Lua allows, these are 'TEXT', "TEXT", or '[[TEXT]] as used in ```onStartup``` in the example above.
|
||||
|
||||
Any action starting with a "/" instructs Lsyncd to directly call the binary file at the beginning instead of spawning an additional shell. For example
|
||||
|
||||
{% highlight lua %}
|
||||
onCreate = "/usr/bin/zip /usr/var/all.zip ^sourcePath"
|
||||
onModify = "/usr/bin/zip /usr/var/all.zip ^sourcePath"
|
||||
{% endhighlight %}
|
||||
|
||||
will add any newly created and modified files to /usr/var/all.zip using absolute path names. Any action not starting with a "/" will result in Lsyncd spawning a shell to execute the action as command.
|
||||
|
||||
Variables
|
||||
---------
|
||||
Variable arguments are specified with the caret symbol ^. It has been chosen over $ or other symbols to be less conflicting with standard shell conventions.
|
||||
|
||||
Note that variables will always be implicitly quoted in double quotes, so if you want them to be a part of another double-quoted string, you will have to go one layer deeper, e.g.
|
||||
|
||||
{% highlight lua %}
|
||||
onCreate = '[[ su user -c "/usr/bin/zip /usr/var/all.zip ^o.sourcePath " ]],
|
||||
{% endhighlight %}
|
||||
|
||||
will expand to ```su user -c "/usr/bin/zip /usr/var/all.zip "source""``` which is incorrect and will break. You have to rewrite the above statement one layer deeper as
|
||||
|
||||
{% highlight lua %}
|
||||
onCreate = function(event)
|
||||
spawnShell('[[ su user -c "/usr/bin/zip /usr/var/all.zip \"$1\"" ]], event.sourcePath)
|
||||
end
|
||||
{% endhighlight %}
|
||||
|
||||
|
||||
All possible variables
|
||||
----------------------
|
||||
<table>
|
||||
|
||||
<tr><td> ^source
|
||||
</td><td> the absolute path of the observed source directory
|
||||
</td></tr>
|
||||
|
||||
<tr><td> ^target
|
||||
</td><td> the "target" attribute of the config
|
||||
</td></tr>
|
||||
|
||||
<tr><td> ^path
|
||||
</td><td> the relative path of the file or directory to the observed directory; directories have a slash at the end.
|
||||
</td></tr>
|
||||
|
||||
<tr><td> ^pathname
|
||||
</td><td> the relative path of the file or directory to the observed directory; directories have no slash at the end.
|
||||
</td></tr>
|
||||
|
||||
|
||||
<tr><td> ^sourcePath
|
||||
</td><td> the absolute path of the observed source directory and the relative path of the file or directory; this equals the absolute local path of the file or directory. Directories have a slash at the end.
|
||||
</td></tr>
|
||||
|
||||
<tr><td> ^sourcePathname
|
||||
</td><td> same as ^sourcePath, but directories have no slash at the end.
|
||||
</td></tr>
|
||||
|
||||
<tr><td> ^targetPath
|
||||
</td><td> The "target" attributed of the config appended by the relative path of the file or directory. Directories have a slash at the end.
|
||||
</td></tr>
|
||||
|
||||
<tr><td> ^targetPathname
|
||||
</td><td> same as ^targetPath, but directories have no slash at the end.
|
||||
</td></tr>
|
||||
|
||||
</table>
|
||||
|
||||
For ```onMoves``` a _o._ and or _d._ can be prepended to path, pathname, sourcePath sourcePathname, targetPath and targetPathname to specify the move origin or destination. Without neither the variables refers to the move origin.
|
||||
|
||||
From the example above, it moves the file or directory in the target directory.
|
||||
{% highlight lua %}
|
||||
onMove = "mv ^o.targetPathname ^d.targetPathname",
|
||||
{% endhighlight %}
|
||||
|
||||
Execution control (exit codes)
|
||||
------------------------------
|
||||
A few words on the startup of the example. It looks a little more complicated, but it is just some bash scripting, nothing Lsyncd specific. It simply does a recursive copy of the source to the target, but first tests if there is anything in the source file. Otherwise the command returns a non-zero error code.
|
||||
|
||||
{% highlight lua %}
|
||||
onStartup = '[[if [ "$(ls -A ^source)" ]; then cp -r ^source* ^target; fi]],
|
||||
{% endhighlight %}
|
||||
|
||||
By default Lsyncd ignores all exit codes except onStartup which must return 0 for it to continue. You can change this behavior by adding a ```exitcodes``` table.
|
||||
|
||||
{% highlight lua %}
|
||||
exitcodes = {[0] = "ok", [1] = "again", [2] = "die"}
|
||||
{% endhighlight %}
|
||||
The keys specify for the exit code the string of the desired action.
|
||||
|
||||
<table>
|
||||
|
||||
<tr><td> again
|
||||
</td><td> respawns the action after {{delay}} seconds, or 1 second if delay is immediate
|
||||
</td></tr>
|
||||
|
||||
<tr><td> die
|
||||
</td><td> lets Lsyncd terminate.
|
||||
</td></tr>
|
||||
|
||||
</table>
|
||||
|
||||
All other values let Lsyncd continue normally.
|
|
@ -0,0 +1,735 @@
|
|||
---
|
||||
layout: default
|
||||
title: "Config Layer 4: Default Config"
|
||||
short: "Config Layer 4"
|
||||
---
|
||||
You can simply choose from a set of three default implementations which are: __rsync__, __rsyncssh__ and __direct__.
|
||||
|
||||
To sync a local directory using the default rsync behavior, just add this to a config file:
|
||||
|
||||
{% highlight lua %}
|
||||
sync {
|
||||
default.rsync,
|
||||
source = "DIRNAME",
|
||||
target = "DIRNAME"
|
||||
}
|
||||
{% endhighlight %}
|
||||
|
||||
The order of the arguments is of no importance. If target is a local directory, take care that it is an absolute pathname. You can add multiple syncs that way. The source directories may be identical or differ without problems. ```source``` is an universal parameter that must be given for every sync. All other ```sync``` parameters can differ depending on the behavior selected. Optionally you can override the default or settings values ```maxDelays``` or ```maxProcesses``` per _Sync_.
|
||||
|
||||
One can also skip the initial rsync process by setting the default ```init``` function to false:
|
||||
|
||||
{% highlight lua %}
|
||||
sync {
|
||||
default.rsync,
|
||||
source = "DIRNAME",
|
||||
target = "DIRNAME",
|
||||
init = false
|
||||
}
|
||||
{% endhighlight %}
|
||||
|
||||
This is an optimization which can be dangerous; so, please use it only if you are sure that source and target are synchronized when Lsyncd is started.
|
||||
|
||||
The default behaviors you can select from are following:
|
||||
|
||||
Shared Settings
|
||||
---------------
|
||||
|
||||
Following settings are shared between all defaults:
|
||||
|
||||
| Name | Description |
|
||||
|-----------------|------------------|
|
||||
| source | Source directory |
|
||||
| crontab | See section `Periodic Full-Sync`|
|
||||
|
||||
|
||||
|
||||
default.rsync
|
||||
-------------
|
||||
|
||||
The default rsync configuration will aggregate events up to ```delay``` seconds or 1000 separate uncollapsible events, which ever happens first. Then it will spawn one Rsync with a filter of all files that changed. The filter list is transmitted to Rsync trough a pipe. A call from Lsyncd to Rsync will thus look like this:
|
||||
|
||||
{% highlight shell %}
|
||||
/usr/bin/rsync -ltsd --delete --include-from=- --exclude=* SOURCE TARGET
|
||||
{% endhighlight %}
|
||||
|
||||
You can change the options Rsync is called and the Rsync binary that is call with the ```rsync``` parameter.
|
||||
|
||||
Example:
|
||||
|
||||
{% highlight lua %}
|
||||
sync {
|
||||
default.rsync,
|
||||
source = "/home/user/src/",
|
||||
target = "foohost.com:~/trg/",
|
||||
delay = 15,
|
||||
rsync = {
|
||||
binary = "/usr/local/bin/rsync",
|
||||
archive = true,
|
||||
compress = true
|
||||
}
|
||||
}
|
||||
{% endhighlight %}
|
||||
|
||||
Additional settings:
|
||||
| Name | Description |
|
||||
|-----------------|-------------|
|
||||
| batchSizeLimit | Files larger then this limit should not be batched into on transfer. Only makes sense with processes > 1 which prevents rsyncssh |
|
||||
|
||||
|
||||
Below is a table of options for the ```rsync``` parameter. Please have a look at the Rsync documentation for an in depth explanation.
|
||||
|
||||
<table>
|
||||
<tr><td> <b>parameter</b>
|
||||
</td><td> <b>=</b>
|
||||
</td><td> <b>TYPE</b>
|
||||
</td><td> <b>default value</b>
|
||||
</td><td> <b>comment</b>
|
||||
</td></tr>
|
||||
|
||||
<tr><td> acls
|
||||
</td><td> =
|
||||
</td><td> BOOL
|
||||
</td><td> false
|
||||
</td><td>
|
||||
</td></tr>
|
||||
|
||||
<tr><td> append
|
||||
</td><td> =
|
||||
</td><td> BOOL
|
||||
</td><td> false
|
||||
</td><td> (Lsyncd >= 2.2.0)
|
||||
</td></tr>
|
||||
|
||||
<tr><td> append-verify
|
||||
</td><td> =
|
||||
</td><td> BOOL
|
||||
</td><td> false
|
||||
</td><td> (Lsyncd >= 2.2.0)
|
||||
</td></tr>
|
||||
|
||||
<tr><td> archive
|
||||
</td><td> =
|
||||
</td><td> BOOL
|
||||
</td><td> false
|
||||
</td><td>
|
||||
</td></tr>
|
||||
|
||||
<tr><td> backup
|
||||
</td><td> =
|
||||
</td><td> BOOL
|
||||
</td><td> false
|
||||
</td><td> (Lsyncd >= 2.2.0)
|
||||
</td></tr>
|
||||
|
||||
<tr><td> backup_dir
|
||||
</td><td> =
|
||||
</td><td> DIR
|
||||
</td><td> false
|
||||
</td><td> (Lsyncd >= 2.2.0)
|
||||
</td></tr>
|
||||
|
||||
<tr><td> binary
|
||||
</td><td> =
|
||||
</td><td> FILENAME
|
||||
</td><td> "/usr/bin/rsync"
|
||||
</td><td> Lsyncd calls this binary as rsync
|
||||
</td></tr>
|
||||
|
||||
<tr><td> checksum
|
||||
</td><td> =
|
||||
</td><td> BOOL
|
||||
</td><td> false
|
||||
</td><td>
|
||||
</td></tr>
|
||||
|
||||
<tr><td> chmod
|
||||
</td><td> =
|
||||
</td><td> STRING
|
||||
</td><td>
|
||||
</td><td> (Lsyncd >= 2.2.0)
|
||||
</td></tr>
|
||||
|
||||
<tr><td> chown
|
||||
</td><td> =
|
||||
</td><td> USER:GROUP
|
||||
</td><td>
|
||||
</td><td> (Lsyncd >= 2.2.0)
|
||||
</td></tr>
|
||||
|
||||
<tr><td> compress
|
||||
</td><td> =
|
||||
</td><td> BOOL
|
||||
</td><td> false
|
||||
</td><td>
|
||||
</td></tr>
|
||||
|
||||
<tr><td> copy_dirlinks
|
||||
</td><td> =
|
||||
</td><td> BOOL
|
||||
</td><td> false
|
||||
</td><td> (Lsyncd >= 2.2.0)
|
||||
</td></tr>
|
||||
|
||||
<tr><td> copy_links
|
||||
</td><td> =
|
||||
</td><td> BOOL
|
||||
</td><td> false
|
||||
</td><td>
|
||||
</td></tr>
|
||||
|
||||
<tr><td> cvs_exclude
|
||||
</td><td> =
|
||||
</td><td> BOOL
|
||||
</td><td>
|
||||
</td></tr>
|
||||
|
||||
<tr><td> dry_run
|
||||
</td><td> =
|
||||
</td><td> BOOL
|
||||
</td><td> false
|
||||
</td><td>
|
||||
</td></tr>
|
||||
|
||||
<tr><td> exclude
|
||||
</td><td> =
|
||||
</td><td> PATTERN
|
||||
</td><td>
|
||||
</td><td> TABLE of PATTERNs also allowed
|
||||
</td></tr>
|
||||
|
||||
<tr><td> excludeFrom
|
||||
</td><td> =
|
||||
</td><td> FILENAME
|
||||
</td><td>
|
||||
</td><td>
|
||||
</td></tr>
|
||||
|
||||
<tr><td> executability
|
||||
</td><td> =
|
||||
</td><td> BOOL
|
||||
</td><td> false
|
||||
</td><td>
|
||||
</td></tr>
|
||||
|
||||
<tr><td> existing
|
||||
</td><td> =
|
||||
</td><td> BOOL
|
||||
</td><td> false
|
||||
</td><td> (Lsyncd >= 2.2.0)
|
||||
</td></tr>
|
||||
|
||||
<tr><td> filter
|
||||
</td><td> =
|
||||
</td><td> TABLE of STRINGS
|
||||
</td><td>
|
||||
</td><td> (Lsyncd >= 2.2.3)
|
||||
</td></tr>
|
||||
|
||||
<tr><td> group
|
||||
</td><td> =
|
||||
</td><td> BOOL
|
||||
</td><td> false
|
||||
</td><td>
|
||||
</td></tr>
|
||||
|
||||
<tr><td> groupmap
|
||||
</td><td> =
|
||||
</td><td> STRING
|
||||
</td><td>
|
||||
</td><td> (Lsyncd >= 2.2.0)
|
||||
</td></tr>
|
||||
|
||||
<tr><td> hard_links
|
||||
</td><td> =
|
||||
</td><td> BOOL
|
||||
</td><td> false
|
||||
</td><td>
|
||||
</td></tr>
|
||||
|
||||
<tr><td> ignore_times
|
||||
</td><td> =
|
||||
</td><td> BOOL
|
||||
</td><td> false
|
||||
</td><td>
|
||||
</td></tr>
|
||||
|
||||
<tr><td> inplace
|
||||
</td><td> =
|
||||
</td><td> BOOL
|
||||
</td><td> false
|
||||
</td><td> (Lsyncd >= 2.1.6)
|
||||
</td></tr>
|
||||
|
||||
<tr><td> ipv4
|
||||
</td><td> =
|
||||
</td><td> BOOL
|
||||
</td><td> false
|
||||
</td><td>
|
||||
</td></tr>
|
||||
|
||||
<tr><td> ipv6
|
||||
</td><td> =
|
||||
</td><td> BOOL
|
||||
</td><td> false
|
||||
</td><td>
|
||||
</td></tr>
|
||||
|
||||
<tr><td> links
|
||||
</td><td> =
|
||||
</td><td> BOOL
|
||||
</td><td> true
|
||||
</td><td>
|
||||
</td></tr>
|
||||
|
||||
<tr><td> one_file_system
|
||||
</td><td> =
|
||||
</td><td> BOOL
|
||||
</td><td> false
|
||||
</td><td>
|
||||
</td></tr>
|
||||
|
||||
<tr><td> owner
|
||||
</td><td> =
|
||||
</td><td> BOOL
|
||||
</td><td> false
|
||||
</td><td>
|
||||
</td></tr>
|
||||
|
||||
<tr><td> password_file
|
||||
</td><td> =
|
||||
</td><td> FILENAME
|
||||
</td><td>
|
||||
</td><td> (Lsyncd >= 2.1.2)
|
||||
</td></tr>
|
||||
|
||||
<tr><td> perms
|
||||
</td><td> =
|
||||
</td><td> BOOL
|
||||
</td><td> false
|
||||
</td><td>
|
||||
</td></tr>
|
||||
|
||||
<tr><td> protect_args
|
||||
</td><td> =
|
||||
</td><td> BOOL
|
||||
</td><td> true
|
||||
</td><td>
|
||||
</td></tr>
|
||||
|
||||
<tr><td> prune_empty_dirs
|
||||
</td><td> =
|
||||
</td><td> BOOL
|
||||
</td><td> false
|
||||
</td><td>
|
||||
</td></tr>
|
||||
|
||||
<tr><td> quiet
|
||||
</td><td> =
|
||||
</td><td> BOOL
|
||||
</td><td> false
|
||||
</td><td>
|
||||
</td></tr>
|
||||
|
||||
<tr><td> rsh
|
||||
</td><td> =
|
||||
</td><td> COMMAND
|
||||
</td><td>
|
||||
</td><td>
|
||||
</td></tr>
|
||||
|
||||
<tr><td> rsync_path
|
||||
</td><td> =
|
||||
</td><td> PATH
|
||||
</td><td>
|
||||
</td><td> (path to rsync on remote host)
|
||||
</td></tr>
|
||||
|
||||
<tr><td> sparse
|
||||
</td><td> =
|
||||
</td><td> BOOL
|
||||
</td><td> false
|
||||
</td><td>
|
||||
</td></tr>
|
||||
|
||||
<tr><td> suffix
|
||||
</td><td> =
|
||||
</td><td> SUFFIX
|
||||
</td><td>
|
||||
</td><td> (Lsyncd >= 2.2.0)
|
||||
</td></tr>
|
||||
|
||||
<tr><td> temp_dir
|
||||
</td><td> =
|
||||
</td><td> DIR
|
||||
</td><td>
|
||||
</td><td>
|
||||
</td></tr>
|
||||
|
||||
<tr><td> times
|
||||
</td><td> =
|
||||
</td><td> BOOL
|
||||
</td><td> true
|
||||
</td><td>
|
||||
</td></tr>
|
||||
|
||||
<tr><td> update
|
||||
</td><td> =
|
||||
</td><td> BOOL
|
||||
</td><td> false
|
||||
</td><td>
|
||||
</td></tr>
|
||||
|
||||
<tr><td> usermap
|
||||
</td><td> =
|
||||
</td><td> STRING
|
||||
</td><td>
|
||||
</td><td> (Lsyncd >= 2.2.0)
|
||||
</td></tr>
|
||||
|
||||
<tr><td> verbose
|
||||
</td><td> =
|
||||
</td><td> BOOL
|
||||
</td><td> false
|
||||
</td><td>
|
||||
</td></tr>
|
||||
|
||||
<tr><td> whole_file
|
||||
</td><td> =
|
||||
</td><td> BOOL
|
||||
</td><td> false
|
||||
</td><td>
|
||||
</td></tr>
|
||||
|
||||
<tr><td> xattrs
|
||||
</td><td> =
|
||||
</td><td> BOOL
|
||||
</td><td> false
|
||||
</td><td>
|
||||
</td></tr>
|
||||
|
||||
<tr><td> _extra
|
||||
</td><td> =
|
||||
</td><td> TABLE of STRINGS.
|
||||
</td><td>
|
||||
</td><td> If absolutely needed, additional arguments can be specified as a TABLE of STRINGS(example: <tt>{ "--omit-dir-times", "--omit-link-times" }</tt>). Note that the underscore highlights this as workaround. If you need something that is not covered by the above options, please request it via a feature request on the project website. Most notably, do not add -r for recursive or -a which implies recursive, since Lsyncd will handle that by itself. Additionally do not add -R for relative, which will ruin Lsyncd <-> Rsync communication.
|
||||
</td></tr>
|
||||
|
||||
</table>
|
||||
|
||||
default.rsyncssh
|
||||
----------------
|
||||
|
||||
This configuration differs from the standard rsync configuration in that it uses ssh commands to move files or directories locally at the target host instead of deleting and transferring again. This configuration does spawn Rsync processes like default.rsync but additionally will spawn ```/usr/bin/ssh HOST mv ORIGIN DESTINATION``` commands.
|
||||
|
||||
Different to default.rsync it does not take an uniform ```target``` parameter, but needs ```host``` and ```targetdir``` separated.
|
||||
|
||||
Rsync's options can be changed with the ```rsync``` parameter like in default.rsync described above.
|
||||
|
||||
Additional to that ssh can be configured via the ```ssh``` parameter.
|
||||
|
||||
<table>
|
||||
|
||||
<tr><td> binary
|
||||
</td><td> =
|
||||
</td><td> FILENAME
|
||||
</td><td> Lsyncd calls this binary as ssh (default: /usr/bin/ssh)
|
||||
</td></tr>
|
||||
|
||||
<tr><td> identityFile
|
||||
</td><td> =
|
||||
</td><td> FILE
|
||||
</td><td> Uses this file to identify for public key authentication.
|
||||
</td></tr>
|
||||
|
||||
<tr><td> options
|
||||
</td><td> =
|
||||
</td><td> TABLE
|
||||
</td><td> A table of addition extended options to pass to ssh's -o option.
|
||||
</td></tr>
|
||||
|
||||
<tr><td> port
|
||||
</td><td> =
|
||||
</td><td> PORT
|
||||
</td><td> Adds --port=PORT to the ssh call.
|
||||
</td></tr>
|
||||
|
||||
<tr><td> _extra
|
||||
</td><td> =
|
||||
</td><td> STRING TABLE
|
||||
</td><td> Similar to rsync._extra this can be used as quick workaround if absolutely needed.
|
||||
</td></tr>
|
||||
|
||||
</table>
|
||||
|
||||
Example:
|
||||
|
||||
{% highlight lua %}
|
||||
settings {
|
||||
logfile = "/var/log/lsyncd.log",
|
||||
statusFile = "/var/log/lsyncd-status.log",
|
||||
statusInterval = 20
|
||||
}
|
||||
|
||||
sync {
|
||||
default.rsyncssh,
|
||||
source="/srcdir",
|
||||
host="remotehost",
|
||||
excludeFrom="/etc/lsyncd.exclude",
|
||||
targetdir="/dstdir",
|
||||
rsync = {
|
||||
archive = true,
|
||||
compress = false,
|
||||
whole_file = false
|
||||
},
|
||||
ssh = {
|
||||
port = 1234
|
||||
}
|
||||
}
|
||||
{% endhighlight %}
|
||||
|
||||
Please note the comma between the ```rsync``` parameter set and the ```ssh``` parameter set.
|
||||
|
||||
__Caution__
|
||||
If you are upgrading from 2.0.x, please notice that `settings` became a function from a variable, so you __MUST__ delete the equal sign '=' between `settings` and the `{`.
|
||||
|
||||
Lsyncd will call ```xargs``` on the remote host to handle multiple tasks in a single connection. Xargs options can be specified by the xargs parameter.
|
||||
|
||||
<table>
|
||||
|
||||
<tr><td> binary
|
||||
</td><td> =
|
||||
</td><td> FILENAME
|
||||
</td><td> Lsyncd calls this binary as xargs on the remote host (default: /usr/bin/xargs)
|
||||
</td></tr>
|
||||
|
||||
<tr><td> delimiter
|
||||
</td><td> =
|
||||
</td><td> DELIMITER
|
||||
</td><td> delimiting character to separate filenames. By default the 0 character is used. Very old holds may need newline instead.
|
||||
</td></tr>
|
||||
|
||||
<tr><td> _extra
|
||||
</td><td> =
|
||||
</td><td> STRING TABLE
|
||||
</td><td> By default { '-0', 'rm -rf' }. Remove the -0 if you chose newline delimiter instead. Otherwise leave it as is.
|
||||
</td></tr>
|
||||
|
||||
</table>
|
||||
|
||||
Example:
|
||||
|
||||
{% highlight lua %}
|
||||
sync {
|
||||
default.rsyncssh,
|
||||
source = "/home/user/src/",
|
||||
host = "foohost.com",
|
||||
targetdir = "~/trg/",
|
||||
}
|
||||
{% endhighlight %}
|
||||
|
||||
default.direct
|
||||
-------------
|
||||
|
||||
Default.direct can be used to keep two local directories in sync with better performance than using default.rsync. Default.direct uses (just like default.rsync) rsync on startup to initially synchronize the target directory with the source directory. However, during normal operation default.direct uses /bin/cp, /bin/rm and /bin/mv to keep the synchronization. All parameters are just like default.rsync.
|
||||
|
||||
Example:
|
||||
|
||||
{% highlight lua %}
|
||||
sync {
|
||||
default.direct,
|
||||
source = "/home/user/src/",
|
||||
target = "/home/user/trg/"
|
||||
}
|
||||
{% endhighlight %}
|
||||
|
||||
Exclusions
|
||||
----------
|
||||
|
||||
Two additional parameters can be specified to sync{}:
|
||||
|
||||
<table>
|
||||
|
||||
<tr><td> excludeFrom
|
||||
</td><td> =
|
||||
</td><td> FILENAME
|
||||
</td><td> loads exclusion rules from this file, on rule per line
|
||||
</td></tr>
|
||||
|
||||
<tr><td> exclude
|
||||
</td><td> =
|
||||
</td><td> LIST
|
||||
</td><td> loads exclusion rules from this list of strings
|
||||
</td></tr>
|
||||
|
||||
</table>
|
||||
|
||||
Exclusion rules are modeled after rsync's exclusion patterns but are a bit simpler. Lsyncd supports these features:
|
||||
|
||||
* Generally if any segment of the pathname (see below Layer 3) of an event matches the text, it is excluded. E.g. the file "/bin/foo/bar" matches the rule "foo".
|
||||
* If the rule starts with a slash, it will only be matched at the beginning of the pathname
|
||||
* If the rule ends with a slash, it will only be matched at the end of a pathname
|
||||
* ? matches any character that is not a slash.
|
||||
* ```*``` matches zero or more characters that are not a slash
|
||||
* ```**``` matches zero or more characters, this can be slashes.
|
||||
|
||||
Example:
|
||||
|
||||
{% highlight lua %}
|
||||
sync {
|
||||
default.rsync,
|
||||
source = "/home/user/src/",
|
||||
targetdir = "/home/user/dst/",
|
||||
exclude = { '_.bak' , '_.tmp' }
|
||||
}
|
||||
{% endhighlight %}
|
||||
|
||||
Deletions
|
||||
---------
|
||||
|
||||
By default Lsyncd will delete files on the target that are not present at the source since this is a fundamental part of the idea of keeping the target in sync with the source. However, many users requested exceptions for this, for various reasons, so all default implementations take ```delete``` as an additional parameter.
|
||||
|
||||
Valid values for ```delete``` are:
|
||||
|
||||
<table>
|
||||
|
||||
<tr><td> delete
|
||||
</td><td> =
|
||||
</td><td> true
|
||||
</td><td> Default. Lsyncd will delete on the target whatever is not in the source. At startup and what's being deleted during normal operation.
|
||||
</td></tr>
|
||||
|
||||
<tr><td> delete
|
||||
</td><td> =
|
||||
</td><td> false
|
||||
</td><td> Lsyncd will not delete any files on the target. Not on startup nor on normal operation. (Overwrites are possible though)
|
||||
</td></tr>
|
||||
|
||||
<tr><td> delete
|
||||
</td><td> =
|
||||
</td><td> 'startup'
|
||||
</td><td> Lsyncd will delete files on the target when it starts up but not on normal operation.
|
||||
</td></tr>
|
||||
|
||||
<tr><td> delete
|
||||
</td><td> =
|
||||
</td><td> 'running'
|
||||
</td><td> Lsyncd will not delete files on the target when it starts up but will delete those that are removed during normal operation.
|
||||
</td></tr>
|
||||
|
||||
</table>
|
||||
|
||||
Tunnels
|
||||
-------
|
||||
|
||||
*New in: 2.3.0*
|
||||
|
||||
Lsyncd is able to start and manage external programs to provide a tunnel for data transfer.
|
||||
Additionally it can spawn multiple connections and load-balance connections among them. A tunnel is created through the `tunnel` function.
|
||||
|
||||
```
|
||||
{% highlight lua %}
|
||||
sync {
|
||||
default.rsync,
|
||||
tunnel = tunnel {
|
||||
command = {"ssh", "-N", "-L", "localhost:5432:localhost:873", "tunnel@testmachine"},
|
||||
}
|
||||
target = "rsync://localhost:5432/projects",
|
||||
source = "/home/user/src/",
|
||||
}
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
You can then set the shell for the tunnel user to `/bin/false` and configure the rsyncd server side appropriately.
|
||||
|
||||
Valid arguments for `tunnel` are:
|
||||
|
||||
| Argument | Description | Default | Valid values |
|
||||
|------------|-------------------------------------------------------|-----------|-------------------------------|
|
||||
| mode | Mode in which the tunnel is run | command | command, pool |
|
||||
| command | _Required_ Command to run | nil | Table of arguments |
|
||||
| parallel | How many connections to run. Only for pool mode | 1 | 1+ |
|
||||
| retryDelay | Seconds to wait until tunnel is restarted | 10 | Number |
|
||||
| readyDelay | Seconds after program start to consider tunnel up | 5 | Number |
|
||||
| localhost | Name of the local host variable | localhost | String |
|
||||
|
||||
## Pool Mode
|
||||
|
||||
In pool mode, lsyncd allocates a new local port which is then passed as variable to the host command.
|
||||
All variables can be substituded by `^variable` syntax.
|
||||
See [../../../../examples/lrsyncssh-tunnel.lua](lrsyncssh-tunnel.lua) for a extended configuration.
|
||||
|
||||
### List of variables
|
||||
|
||||
| Name | Description |
|
||||
|------------|-----------------------------------------------|
|
||||
| localport | Port allocated for the selected connection |
|
||||
| localhost | Local hostname used. Default localhost |
|
||||
|
||||
### Example
|
||||
|
||||
Run 2 tunnel ssh processes and 4 rsync processes at the same time. Use extra transfers for all files larger 30 MB.
|
||||
|
||||
```lua
|
||||
sync {
|
||||
default.rsync,
|
||||
tunnel = tunnel {
|
||||
command = {"ssh", "-N", "-L", "localhost:^localport:localhost:873", "user@testmachine"},
|
||||
mode = "pool",
|
||||
parallel = 2,
|
||||
},
|
||||
source = "/data/projects",
|
||||
target = "rsync://localhost:^localport/projects",
|
||||
delay = 5,
|
||||
batchSizeLimit = 1024 * 1024 * 30,
|
||||
maxProcesses = 4
|
||||
}
|
||||
```
|
||||
|
||||
This example will open 2 ssh connections for port forwarding and load balance 4 parallel rsync
|
||||
processes in a round roubin fashion.
|
||||
|
||||
### Workflow in Poolmodel
|
||||
|
||||
When a sync with a tunnel parameter is started, all events are queued until the tunnel reaches the
|
||||
`UP` state, which is when one successful tunnel process exists for at least `readyDelay` seconds.
|
||||
Dead tunnel processes are automatically restarted. When the tunnel process count drops to 0, tunnel falls back to the
|
||||
`CONNECTING` state. There is a `retryDelay` seconds delay between each attempt to restart the tunnel.
|
||||
|
||||
Once the tunnel is UP, a full transfer is initiated. Subsequent transfers are then load balanced over multiple connections.
|
||||
|
||||
### Notes on Poolmode
|
||||
|
||||
* Pool mode only works with `rsync` backend, since there is no way to prevent multiple transfers to the same file in a relieable way, the rsync backend only supports `maxProcesses = 1` which renders pool mode useless. Since the remote side rsync daemon can prevent file trashing, the rsync backend is safe.
|
||||
|
||||
|
||||
## Periodic Full-Sync
|
||||
|
||||
*New in: 2.3.0*
|
||||
|
||||
It is possible to trigger a full sync command from within lsync with the crontab feature. This requires that [lua-crontab](https://github.com/logiceditor-com/lua-crontab) is installed on the system. The crontab configuration accepts a list of `crontab` patterns to which a full sync will be triggered.
|
||||
|
||||
```lua
|
||||
sync {
|
||||
...
|
||||
crontab = {
|
||||
-- does a full sync once a day at 3:00:01
|
||||
"1 0 3 * * *",
|
||||
},
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
### Field destination
|
||||
|
||||
Each field is seperated by `" "` and can contain multiple values seperated by `,`.
|
||||
|
||||
| FIELD | VALUES | SPECIAL CHARACTERS |
|
||||
|--------------|-----------------|--------------------|
|
||||
| Seconds | 0-59 | , - * |
|
||||
| Minutes | 0-59 | , - * |
|
||||
| Hours | 0-23 | , - * |
|
||||
| Day of month | 1-31 | , - * |
|
||||
| Month | 1-12 or JAN-DEC | , - * |
|
||||
| Day of week | 0-6 or SUN-SAT | , - * |
|
||||
|
|
@ -0,0 +1,283 @@
|
|||
---
|
||||
layout: default
|
||||
title: "Example: Auto-Image-Magic"
|
||||
tab: "manual/examples"
|
||||
---
|
||||
|
||||
This [example](..) is a layer 1 script to make a special "magic" directory in which image files will be converted automatically therein.
|
||||
|
||||
The full script:
|
||||
|
||||
{% highlight lua %}
|
||||
local formats = { jpg = true, gif = true, png = true }
|
||||
|
||||
convert = {
|
||||
delay = 0,
|
||||
|
||||
maxProcesses = 99,
|
||||
|
||||
action = function(inlet)
|
||||
local event = inlet.getEvent()
|
||||
|
||||
if event.isdir then
|
||||
-- ignores events on dirs
|
||||
inlet.discardEvent(event)
|
||||
return
|
||||
end
|
||||
|
||||
-- extract extension and basefilename
|
||||
local p = event.pathname
|
||||
local ext = string.match(p, ".*%.([^.]+)$")
|
||||
local base = string.match(p, "(.*)%.[^.]+$")
|
||||
if not formats[ext] then
|
||||
-- an unknown extenion
|
||||
log("Normal", "not doing something on ."..ext)
|
||||
inlet.discardEvent(event)
|
||||
return
|
||||
end
|
||||
|
||||
-- autoconvert on create and modify
|
||||
if event.etype == "Create" or event.etype == "Modify" then
|
||||
-- builds one bash command
|
||||
local cmd = ""
|
||||
-- do for all other extensions
|
||||
for k, _ in pairs(formats) do
|
||||
if k ~= ext then
|
||||
-- excludes files to be created, so no
|
||||
-- followup actions will occur
|
||||
inlet.addExclude(base..'.'..k)
|
||||
if cmd ~= "" then
|
||||
cmd = cmd .. " && "
|
||||
end
|
||||
cmd = cmd..
|
||||
'/usr/bin/convert "'..
|
||||
event.source..p..'" "'..
|
||||
event.source..base..'.'..k..
|
||||
'" || /bin/true'
|
||||
end
|
||||
end
|
||||
log("Normal", "Converting "..p)
|
||||
spawnShell(event, cmd)
|
||||
return
|
||||
end
|
||||
|
||||
-- deletes all formats if you delete one
|
||||
if event.etype == "Delete" then
|
||||
-- builds one bash command
|
||||
local cmd = ""
|
||||
-- do for all other extensions
|
||||
for k, _ in pairs(formats) do
|
||||
if k ~= ext then
|
||||
-- excludes files to be deleted, so no
|
||||
-- followup actions will occur
|
||||
inlet.addExclude(base..'.'..k)
|
||||
if cmd ~= "" then
|
||||
cmd = cmd .. " && "
|
||||
end
|
||||
cmd = cmd..
|
||||
'rm "'..event.source..base..'.'..k..
|
||||
'" || /bin/true'
|
||||
end
|
||||
end
|
||||
log("Normal", "Deleting all "..p)
|
||||
spawnShell(event, cmd)
|
||||
return
|
||||
end
|
||||
|
||||
-- ignores other events.
|
||||
inlet.discardEvent(event)
|
||||
end,
|
||||
|
||||
-----
|
||||
-- Removes excludes when convertions are finished
|
||||
--
|
||||
collect = function(event, exitcode)
|
||||
local p = event.pathname
|
||||
local ext = string.match(p, ".*%.([^.]+)$")
|
||||
local base = string.match(p, "(.*)%.[^.]+$")
|
||||
local inlet = event.inlet
|
||||
|
||||
if event.etype == "Create" or
|
||||
event.etype == "Modify" or
|
||||
event.etype == "Delete"
|
||||
then
|
||||
for k, _ in pairs(formats) do
|
||||
inlet.rmExclude(base..'.'..k)
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
}
|
||||
|
||||
sync{convert, source="magicdir", recursive=false}
|
||||
{% endhighlight %}
|
||||
|
||||
This creates a local table of all supported file formats. The file formats are used as keys.
|
||||
|
||||
{% highlight lua %}
|
||||
local formats = { jpg=true, gif=true, png=true, }
|
||||
{% endhighlight %}
|
||||
|
||||
Configures actions to be instant and there is unlimits the amount the conversion to be done at once. Well not unlimited but set the limit pretty high.
|
||||
|
||||
{% highlight lua %}
|
||||
convert = {
|
||||
delay = 0,
|
||||
maxProcesses = 99,
|
||||
{% endhighlight %}
|
||||
|
||||
This script uses the _layer 1_ inlet interface altough it greps only single events and not lists. It does this instead of _layer 2_ as it needs to do common operations for all kind of events.
|
||||
|
||||
{% highlight lua %}
|
||||
action = function(inlet)
|
||||
local event = inlet.getEvent()
|
||||
{% endhighlight %}
|
||||
|
||||
Ignores directories. As using _layer 1_ it has to explicitly discard events it does not spawn actions for.
|
||||
|
||||
{% highlight lua %}
|
||||
if event.isdir then
|
||||
-- ignores events on dirs
|
||||
inlet.discardEvent(event)
|
||||
return
|
||||
end
|
||||
{% endhighlight %}
|
||||
|
||||
Uses Lua string patterns to extract the file extension from the rest - here called base.
|
||||
|
||||
{% highlight lua %}
|
||||
-- extract extension and basefilename
|
||||
local p = event.pathname
|
||||
local ext = string.match(p, ".*%.([^.]+)$")
|
||||
local base = string.match(p, "(.*)%.[^.]+$")
|
||||
{% endhighlight %}
|
||||
|
||||
Looks the extension up in the formats table. This can be done, since formats are keys in that table. If not an image format it bails out.
|
||||
|
||||
{% highlight lua %}
|
||||
if not formats[ext] then
|
||||
-- an unknown extenion
|
||||
log("Normal", "not doing something on ."..ext)
|
||||
inlet.discardEvent(event)
|
||||
return
|
||||
end
|
||||
{% endhighlight %}
|
||||
|
||||
|
||||
Following actions will done on "Create" and "Modify" events.
|
||||
|
||||
{% highlight lua %}
|
||||
-- autoconvert on create and modify
|
||||
if event.etype == "Create" or event.etype == "Modify" then
|
||||
{% endhighlight %}
|
||||
|
||||
This script builds a bash command using a string.
|
||||
|
||||
{% highlight lua %}
|
||||
-- builds one bash command
|
||||
local cmd = ""
|
||||
{% endhighlight %}
|
||||
|
||||
It iterates for all image formats and excludes the one which is the source image.
|
||||
|
||||
{% highlight lua %}
|
||||
-- do for all other extensions
|
||||
for k, _ in pairs(formats) do
|
||||
if k ~= ext then
|
||||
{% endhighlight %}
|
||||
|
||||
This is a little trick. It creates Exclusions for the converted images. As this images are not placed in a target directory but right next to the source image in the source directory they would otherwise trigger Create actions as well.
|
||||
|
||||
{% highlight lua %}
|
||||
-- excludes files to be created, so no
|
||||
-- followup actions will occur
|
||||
inlet.addExclude(base..'.'..k)
|
||||
{% endhighlight %}
|
||||
|
||||
And for every image to be converted adds the calls to the arguments. It uses ```" || /bin/true "``` to let the shell continue if one conversion fails. In that it chains the conversion with '&&' they will be called sequentially.
|
||||
|
||||
{% highlight lua %}
|
||||
if cmd ~= "" then
|
||||
cmd = cmd .. " && "
|
||||
end
|
||||
cmd = cmd..
|
||||
'/usr/bin/convert "'..
|
||||
event.source..p..'" "'..
|
||||
event.source..base..'.'..k..
|
||||
'" || /bin/true'
|
||||
{% endhighlight %}
|
||||
|
||||
And eventually it spawns the shell doing the conversions and is finished.
|
||||
|
||||
{% highlight lua %}
|
||||
end
|
||||
end
|
||||
log("Normal", "Converting "..p)
|
||||
spawnShell(event, cmd)
|
||||
return
|
||||
end
|
||||
{% endhighlight %}
|
||||
|
||||
For deletions it does technically something similar, but it deletes all other file formats of the image.
|
||||
|
||||
{% highlight lua %}
|
||||
-- deletes all formats if you delete one
|
||||
if event.etype == "Delete" then
|
||||
-- builds one bash command
|
||||
local cmd = ""
|
||||
-- do for all other extensions
|
||||
for k, _ in pairs(formats) do
|
||||
if k ~= ext then
|
||||
-- excludes files to be deleted, so no
|
||||
-- followup actions will occur
|
||||
inlet.addExclude(base..'.'..k)
|
||||
if cmd ~= "" then
|
||||
cmd = cmd .. " && "
|
||||
end
|
||||
cmd = cmd..
|
||||
'rm "'..event.source..base..'.'..k..
|
||||
'" || /bin/true'
|
||||
end
|
||||
end
|
||||
log("Normal", "Deleting all "..p)
|
||||
spawnShell(event, cmd)
|
||||
return
|
||||
end
|
||||
{% endhighlight %}
|
||||
|
||||
and not to forget to nicely discard all other events.
|
||||
|
||||
{% highlight lua %}
|
||||
-- ignores other events.
|
||||
inlet.discardEvent(event)
|
||||
end,
|
||||
{% endhighlight %}
|
||||
|
||||
collect is called when the conversions finished. It will remove the temporary excludes again.
|
||||
|
||||
{% highlight lua %}
|
||||
-----
|
||||
-- Removes excludes when convertions are finished
|
||||
--
|
||||
collect = function(event, exitcode)
|
||||
local p = event.pathname
|
||||
local ext = string.match(p, ".*%.([^.]+)$")
|
||||
local base = string.match(p, "(.*)%.[^.]+$")
|
||||
local inlet = event.inlet
|
||||
|
||||
if event.etype == "Create" or
|
||||
event.etype == "Modify" or
|
||||
event.etype == "Delete"
|
||||
then
|
||||
for k, _ in pairs(formats) do
|
||||
inlet.rmExclude(base..'.'..k)
|
||||
end
|
||||
end
|
||||
end,
|
||||
{% endhighlight %}
|
||||
|
||||
And finally use the configuration to watch "magicdir".
|
||||
|
||||
{% highlight lua %}
|
||||
sync{convert, source="magicdir", recursive=false}
|
||||
{% endhighlight %}
|
|
@ -0,0 +1,90 @@
|
|||
---
|
||||
layout: default
|
||||
title: "Example: GForce"
|
||||
tab: "manual/examples"
|
||||
---
|
||||
A Layer 3 [example](..) that forces a directory tree to be read/writeable by a group.
|
||||
|
||||
{% highlight lua %}
|
||||
-----
|
||||
-- User configuration file for lsyncd.
|
||||
--
|
||||
-- This example refers to a common problem in unix.
|
||||
--
|
||||
-- You have a shared directory for a set of users and you want
|
||||
-- to ensure all users have read and write permissions on all
|
||||
-- files in there. Unfortunally sometimes users mess with their
|
||||
-- umask, and create files in there that are not read/write/deleteable
|
||||
-- by others. Usually this involves frequent handfixes by a sysadmin,
|
||||
-- or a cron job that recursively chmods/chowns the whole directory.
|
||||
--
|
||||
-- This is another approach to use lsyncd to continously fix permissions.
|
||||
--
|
||||
-- One second after a file is created/modified it checks for its permissions
|
||||
-- and forces group permissions on it.
|
||||
--
|
||||
-- This example regards more the handcraft of bash scripting than lsyncd.
|
||||
-- An alternative to this would be to load a Lua-Posix library and do the
|
||||
-- permission changes right within the onAction handlers.
|
||||
|
||||
----
|
||||
-- forces this group.
|
||||
--
|
||||
fgroup = "staff"
|
||||
|
||||
-----
|
||||
-- script for all changes.
|
||||
--
|
||||
command =
|
||||
-- checks if the group is the one enforced and sets them if not
|
||||
'[[
|
||||
perm=`stat -c %A ^sourcePathname`
|
||||
if test `stat -c %G ^sourcePathname` != ]]..fgroup..'[[; then
|
||||
/bin/chgrp ]]..fgroup..'[[ ^sourcePathname || /bin/true;
|
||||
fi
|
||||
]] ..
|
||||
|
||||
-- checks if the group permissions are rw and sets them
|
||||
'[[
|
||||
if test `expr match $perm "....rw"` = 0; then
|
||||
/bin/chmod g+rw ^sourcePathname || /bin/true;
|
||||
fi
|
||||
]] ..
|
||||
|
||||
-- and forces the executable bit for directories.
|
||||
'[[
|
||||
if test -d ^sourcePathname; then
|
||||
if test `expr match $perm "......x"` -eq 0; then
|
||||
/bin/chmod g+x ^^sourcePathname || /bin/true;
|
||||
fi
|
||||
fi
|
||||
]]
|
||||
|
||||
-- on startup recursively sets all group ownerships
|
||||
-- all group permissions are set to 'rw'
|
||||
-- and to executable flag for directories
|
||||
--
|
||||
-- the hash in the first line is important, otherwise due to the starting
|
||||
-- slash, Lsyncd would think it is a call to the binary /bin/chgrp only
|
||||
-- and would optimize the shell call away.
|
||||
--
|
||||
startup =
|
||||
'[[#
|
||||
/bin/chgrp -R ]]..fgroup..'[[ ^source || /bin/true &&
|
||||
/bin/chmod -R g+rw ^source || /bin/true &&
|
||||
/usr/bin/find ^source -type d | xargs chmod g+x
|
||||
]]
|
||||
|
||||
gforce = {
|
||||
maxProcesses = 99,
|
||||
delay = 1,
|
||||
onStartup = startup,
|
||||
onAttrib = command,
|
||||
onCreate = command,
|
||||
onModify = command,
|
||||
-- does nothing on moves, they won't change permissions
|
||||
onMove = true,
|
||||
}
|
||||
|
||||
sync{gforce, source="/path/to/share"}
|
||||
{% endhighlight %}
|
|
@ -0,0 +1,39 @@
|
|||
---
|
||||
layout: default
|
||||
title: "Examples"
|
||||
---
|
||||
Layer 4 Examples
|
||||
----------------
|
||||
|
||||
bash sync:
|
||||
<pre>
|
||||
sync{bash, source="/home/lonewolf/teste1", target="/home/lonewolf/teste2"}
|
||||
</pre>
|
||||
|
||||
rsyncssh option:
|
||||
|
||||
<pre>
|
||||
sync{default.rsyncssh,
|
||||
source="/var/www/live_site_resources",
|
||||
host="192.168.129.90",
|
||||
targetdir="/var/www/live_site_resources",
|
||||
delete="running",
|
||||
exclude={ ".*", "*.tmp" },
|
||||
rsync = {
|
||||
compress = false,
|
||||
checksums = false,
|
||||
_extra = {"--bwlimit=50000"},
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
|
||||
Layer 3 Examples
|
||||
----------------
|
||||
* [GForce](gforce): forces a local directory tree to be read/writable by a group.
|
||||
|
||||
Layer 2 Examples
|
||||
----------------
|
||||
|
||||
Layer 1 Examples
|
||||
----------------
|
||||
* [Auto Image Magic](auto-image-magic): creates a "magic" directory in which all images placed into will be converted to other file formats
|
|
@ -0,0 +1,90 @@
|
|||
---
|
||||
layout: default
|
||||
title: Invoking
|
||||
---
|
||||
As most Unix tools, Lsyncd will print a synopsis of its command line options when called with --help.
|
||||
|
||||
```console
|
||||
lsyncd --help
|
||||
lsyncd -help
|
||||
```
|
||||
|
||||
The two hyphens are redundant for Lsyncd. It has no short one letter options and one hyphen will always result into the same as specifying two.
|
||||
|
||||
Also like most Unix tools, ```--version``` or ```-version``` will let Lsyncd print its version number.
|
||||
|
||||
```console
|
||||
lsyncd -version
|
||||
```
|
||||
|
||||
Lsyncd 2.1 is designed to be predominantly configured through a config file (see below). The config file can thus be the only command line option.
|
||||
|
||||
```console
|
||||
lsyncd CONFIGFILE
|
||||
```
|
||||
|
||||
Although for standard use or quick testing it can be cursorily configured by command line options. The following will keep a local source and destination directory in sync using rsync:
|
||||
|
||||
```console
|
||||
lsyncd -rsync /home/USER/src /home/USER/dst
|
||||
```
|
||||
|
||||
The target can here be anything that Rsync recognizes.
|
||||
|
||||
```console
|
||||
lsyncd -rsync /home/USER/src remotehost:dst
|
||||
```
|
||||
|
||||
Two (or more) targets are configured by calling -rsync twice (or several times).
|
||||
|
||||
```console
|
||||
lsyncd -rsync /home/USER/src remotehost1:dst -rsync /home/USER/src remotehost2:dst
|
||||
```
|
||||
|
||||
A disadvantage with Rsync synchronization is that normally directory and file moves result in a deletion of the move origin and a retransfer of the move destination of the wire. However, Lsyncd 2 can use ssh commands to move the directories and files locally on the target. To use this use ```-rsyncssh``` followed by the local source directory, the remote host and the target directory there. The REMOTEHOST can include a user like ```me@remotehost.com```.
|
||||
|
||||
```console
|
||||
lsyncd -rsyncssh /home/USER/src REMOTEHOST TARGETDIR
|
||||
```
|
||||
|
||||
When testing Lsyncd configurations ```-nodaemon``` is a pretty handy flag. With this option, Lsyncd will not detach and will not become a daemon. All log messages are additionally to the configured logging facilities printed on the console (_stdout_ and _stderr_).
|
||||
|
||||
|
||||
```console
|
||||
lsyncd -nodaemon CONFIGFILE
|
||||
```
|
||||
|
||||
There is a difference in behaviour when running with -nodaemon. Lsyncd will not change its working directory to `/`, as it does when becoming a daemon. Thus relative targets like `./target` will work with `-nodaemon` but must be specified to absolute paths to work in daemon mode. The source directories will also be turned into absolute paths by Lsyncd. The reason targets are not resolved to absolute paths while sources are is because Lsyncd itself does not care about the format of the target specifier which can also be remote hosts, rsyncd modules, etc. It is opaquely handed to rsync. It cares about the observed directories though.
|
||||
|
||||
|
||||
*New in 2.3.0*
|
||||
|
||||
To only trigger the initial full sync and not monitor the file system, you can use the `-onepass` option.
|
||||
|
||||
```console
|
||||
lsyncd -onepass CONFIGFILE
|
||||
```
|
||||
|
||||
|
||||
## Logging
|
||||
|
||||
|
||||
All log messages are sorted in categories. By default Lsyncd is scarce with log messages. You can turn Lsyncd into a motormouth by specifying ```-log all```.
|
||||
|
||||
```console
|
||||
lsyncd -log all CONFIGFILE
|
||||
```
|
||||
|
||||
This might easily become too much. A particularly useful category is "Exec" which will log the command lines of all processes Lsyncd spawns.
|
||||
|
||||
```console
|
||||
lsyncd -log Exec CONFIGFILE
|
||||
```
|
||||
|
||||
When the initial startup sync fails by default Lsyncd will terminate with an error message. It has been designed this way, so configuration failures are visibly reported to a possibly beginning user. However, in production a remote target might be done, but you want Lsyncd to start nevertheless and keep trying to sync to the remote target until it is up.
|
||||
|
||||
```console
|
||||
lsyncd -insist -rsync /home/USER/src remotehost:dst
|
||||
```
|
||||
|
||||
In production mode it is recommended to have insist on. It can also be specified in the settings{} command in a config file.
|
|
@ -1,8 +1,8 @@
|
|||
-----
|
||||
-- User configuration file for lsyncd.
|
||||
--
|
||||
--
|
||||
-- While this example does not do anything it shows
|
||||
-- how user custom alarms can be now. It will log
|
||||
-- how user custom alarms can be now. It will log
|
||||
-- "Beep!" every 5 seconds.
|
||||
--
|
||||
settings.nodaemon = true
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
-----
|
||||
-- User configuration file for lsyncd.
|
||||
--
|
||||
-- This example uses local bash commands to keep two local
|
||||
--
|
||||
-- This example uses local bash commands to keep two local
|
||||
-- directory trees in sync.
|
||||
--
|
||||
settings = {
|
||||
settings {
|
||||
logfile = "/tmp/lsyncd.log",
|
||||
statusFile = "/tmp/lsyncd.stat",
|
||||
statusIntervall = 1,
|
||||
|
@ -15,12 +15,12 @@ settings = {
|
|||
-- for testing purposes. prefix can be used to slow commands down.
|
||||
-- prefix = "sleep 5 && "
|
||||
--
|
||||
prefix = ""
|
||||
local prefix = ""
|
||||
|
||||
-----
|
||||
-- for testing purposes. uses bash command to hold local dirs in sync.
|
||||
--
|
||||
bash = {
|
||||
local bash = {
|
||||
delay = 0,
|
||||
|
||||
maxProcesses = 1,
|
||||
|
@ -28,16 +28,16 @@ bash = {
|
|||
-- calls `cp -r SOURCE/* TARGET` only when there is something in SOURCE
|
||||
-- otherwise it deletes contents in the target if there.
|
||||
onStartup = [[
|
||||
if [ "$(ls -A ^source)" ]; then
|
||||
cp -r ^source* ^target;
|
||||
else
|
||||
if [ "$(ls -A ^source)" ]; then
|
||||
cp -r ^source* ^target;
|
||||
else
|
||||
if [ "$(ls -A ^target)" ]; then rm -rf ^target/*; fi
|
||||
fi]],
|
||||
|
||||
onCreate = prefix..[[cp -r ^sourcePath ^targetPathdir]],
|
||||
|
||||
|
||||
onModify = prefix..[[cp -r ^sourcePath ^targetPathdir]],
|
||||
|
||||
|
||||
onDelete = prefix..[[rm -rf ^targetPath]],
|
||||
|
||||
onMove = prefix..[[mv ^o.targetPath ^d.targetPath]],
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
-----
|
||||
-- User configuration file for lsyncd.
|
||||
--
|
||||
-- This example uses local bash commands to keep two local
|
||||
-- directory trees in sync.
|
||||
--
|
||||
-- This example uses just echos the operations
|
||||
--
|
||||
|
||||
-----
|
||||
-- for testing purposes. just echos what is happening.
|
||||
--
|
||||
echo = {
|
||||
local echo = {
|
||||
maxProcesses = 1,
|
||||
delay = 1,
|
||||
onStartup = "/bin/echo telling about ^source",
|
||||
onStartup = "/bin/echo telling about ^source",
|
||||
onAttrib = "/bin/echo attrib ^pathname",
|
||||
onCreate = "/bin/echo create ^pathname",
|
||||
onDelete = "/bin/echo delete ^pathname",
|
||||
|
|
|
@ -0,0 +1,163 @@
|
|||
--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
-- User configuration file for lsyncd.
|
||||
--
|
||||
-- Syncs with 'lftp'.
|
||||
--
|
||||
--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
lftp = {
|
||||
|
||||
-----
|
||||
-- Spawns rsync for a list of events
|
||||
--
|
||||
action = function(inlet)
|
||||
|
||||
-- gets all events ready for syncing
|
||||
local elist = inlet.getEvents(
|
||||
function(event)
|
||||
return event.etype ~= 'Init' and event.etype ~= 'Blanket'
|
||||
end
|
||||
)
|
||||
|
||||
-----
|
||||
-- replaces filter rule by literals
|
||||
--
|
||||
local function sub(p)
|
||||
if not p then
|
||||
return
|
||||
end
|
||||
return p:gsub('%?', '\\?'):
|
||||
gsub('%*', '\\*'):
|
||||
gsub('%[', '\\['):
|
||||
gsub('%]', '\\]')
|
||||
end
|
||||
|
||||
local config = inlet.getConfig()
|
||||
|
||||
local commands = elist.getPaths(
|
||||
function(etype, path1, path2)
|
||||
if etype == 'Delete' then
|
||||
if string.byte(path1, -1) == 47 then
|
||||
return 'rm -r '..
|
||||
config.targetdir..sub(path1)
|
||||
else
|
||||
return 'rm '..
|
||||
config.targetdir..sub(path1)
|
||||
end
|
||||
elseif
|
||||
etype == 'Create' or
|
||||
etype == 'Modify' or
|
||||
etype == 'Attrib'
|
||||
then
|
||||
if string.byte(path1, -1) == 47 then
|
||||
return 'mirror -R '..
|
||||
config.source..sub(path1)..' '..
|
||||
config.targetdir..sub(path1)
|
||||
else
|
||||
return 'put '..
|
||||
config.source..sub(path1)..
|
||||
' -o '..config.targetdir..sub(path1)
|
||||
end
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
if #commands == 0 then
|
||||
spawn(elist, '/bin/true')
|
||||
return
|
||||
end
|
||||
|
||||
commands = table.concat(commands, ';\n')
|
||||
|
||||
log('Normal', 'Calling lftp with commands\n', commands)
|
||||
|
||||
spawn(elist, '/usr/bin/lftp',
|
||||
'<', commands,
|
||||
'-u', config.user..','..config.pass, config.host
|
||||
)
|
||||
end,
|
||||
|
||||
-----
|
||||
-- Spawns the recursive startup sync
|
||||
--
|
||||
init = function(event)
|
||||
local config = event.config
|
||||
local inlet = event.inlet
|
||||
local excludes = inlet.getExcludes()
|
||||
local delete = nil
|
||||
if config.delete then delete = { '--delete', '--ignore-errors' }; end
|
||||
|
||||
if #excludes ~= 0 then
|
||||
error('lftp does not work with excludes', 4)
|
||||
end
|
||||
|
||||
log('Normal', 'recursive startup lftp: ', config.source, ' to host: ', config.host)
|
||||
|
||||
spawn(event, '/usr/bin/lftp',
|
||||
'-c',
|
||||
'open -u '..config.user..','..config.pass..' '..config.host..'; '..
|
||||
'mirror -R -e '..config.source..' '..config.targetdir..';'
|
||||
)
|
||||
end,
|
||||
|
||||
-----
|
||||
-- Checks the configuration.
|
||||
--
|
||||
prepare = function(config)
|
||||
|
||||
if not config.host then
|
||||
error('lftps needs "host" configured', 4);
|
||||
end
|
||||
|
||||
if not config.user then
|
||||
error('lftps needs "user" configured', 4);
|
||||
end
|
||||
|
||||
if not config.pass then
|
||||
error('lftps needs "pass" configured', 4);
|
||||
end
|
||||
|
||||
if not config.targetdir then
|
||||
error('lftp needs "targetdir" configured', 4)
|
||||
end
|
||||
|
||||
if config.target then
|
||||
error('lftp needs NOT "target" configured', 4)
|
||||
end
|
||||
|
||||
if config.exclude then
|
||||
error('lftp does not work with excludes', 4)
|
||||
end
|
||||
|
||||
if config.rsyncOpts then
|
||||
error('lftp needs NOT "rsyncOpts" configured', 4)
|
||||
end
|
||||
|
||||
if string.sub(config.targetdir, -1) == '/' then
|
||||
error('please make targetdir not end with a /', 4)
|
||||
end
|
||||
|
||||
end,
|
||||
|
||||
-----
|
||||
-- Exit codes for rsync.
|
||||
--
|
||||
exitcodes = {
|
||||
[ 0] = 'ok',
|
||||
[ 1] = 'ok',
|
||||
},
|
||||
|
||||
-----
|
||||
-- Default delay
|
||||
--
|
||||
delay = 1,
|
||||
}
|
||||
|
||||
sync{
|
||||
lftp,
|
||||
host = 'localhost',
|
||||
user = 'test',
|
||||
pass = 'test',
|
||||
source = 'src',
|
||||
targetdir = '.',
|
||||
}
|
|
@ -1,55 +1,55 @@
|
|||
-----
|
||||
-- User configuration file for lsyncd.
|
||||
--
|
||||
-- This example refers to a common problem in unix.
|
||||
--
|
||||
--
|
||||
-- This example refers to one common challenge in multiuser unix systems.
|
||||
--
|
||||
-- You have a shared directory for a set of users and you want
|
||||
-- to ensure all users have read and write permissions on all
|
||||
-- files in there. Unfortunally sometimes users mess with their
|
||||
-- files in there. Unfortunally sometimes users mess with their
|
||||
-- umask, and create files in there that are not read/write/deleteable
|
||||
-- by others. Usually this involves frequent handfixes by a sysadmin,
|
||||
-- or a cron job that recursively chmods/chowns the whole directory.
|
||||
--
|
||||
-- This is another approach to use lsyncd to continously fix permissions.
|
||||
--
|
||||
-- One second after a file is created/modified it checks for its permissions
|
||||
--
|
||||
-- One second after a file is created/modified it checks for its permissions
|
||||
-- and forces group permissions on it.
|
||||
--
|
||||
-- This example regards more the handcraft of bash scripting than lsyncd.
|
||||
-- An alternative to this would be to load a Lua-Posix library and do the
|
||||
-- An alternative to this would be to load a Lua-Posix library and do the
|
||||
-- permission changes right within the onAction handlers.
|
||||
|
||||
----
|
||||
-- forces this group.
|
||||
--
|
||||
fgroup = "staff"
|
||||
local fgroup = "staff"
|
||||
|
||||
-----
|
||||
-- script for all changes.
|
||||
--
|
||||
command =
|
||||
-- checks if the group is the one enforced and sets them if not
|
||||
local command =
|
||||
-- checks if the group is the one enforced and sets them if not
|
||||
[[
|
||||
perm=`stat -c %A ^sourcePathname`
|
||||
if [ `stat -c %G ^sourcePathname` != ]]..fgroup..[[ ]; then
|
||||
/bin/chgrp ]]..fgroup..[[ ^sourcePathname || /bin/true;
|
||||
fi
|
||||
/bin/chgrp ]]..fgroup..[[ ^sourcePathname || /bin/true;
|
||||
fi
|
||||
]] ..
|
||||
|
||||
-- checks if the group permissions are rw and sets them
|
||||
-- checks if the group permissions are rw and sets them
|
||||
[[
|
||||
if [ `expr match $perm "....rw"` == 0 ]; then
|
||||
/bin/chmod g+rw ^sourcePathname || /bin/true;
|
||||
fi
|
||||
if [ `expr match $perm "....rw"` == 0 ]; then
|
||||
/bin/chmod g+rw ^sourcePathname || /bin/true;
|
||||
fi
|
||||
]] ..
|
||||
|
||||
-- and forces the executable bit for directories.
|
||||
[[
|
||||
if [ -d ^sourcePathname ]; then
|
||||
if [ `expr match $perm "......x"` == 0 ]; then
|
||||
if [ `expr match $perm "......x"` == 0 ]; then
|
||||
/bin/chmod g+x ^^sourcePathname || /bin/true;
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
]]
|
||||
|
||||
-- on startup recursevily sets all group ownerships
|
||||
|
@ -58,14 +58,14 @@ fi
|
|||
--
|
||||
-- the carret as first char tells Lsycnd to call a shell altough it
|
||||
-- starts with a slash otherwisw
|
||||
--
|
||||
startup =
|
||||
--
|
||||
local startup =
|
||||
[[^/bin/chgrp -R ]]..fgroup..[[ ^source || /bin/true &&
|
||||
/bin/chmod -R g+rw ^source || /bin/true &&
|
||||
/usr/bin/find ^source -type d | xargs chmod g+x
|
||||
/usr/bin/find ^source -type d | xargs chmod g+x
|
||||
]]
|
||||
|
||||
gforce = {
|
||||
local gforce = {
|
||||
maxProcesses = 99,
|
||||
delay = 1,
|
||||
onStartup = startup,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
----
|
||||
-- Lsyncd user-script that creates a "magic" image converter directory.
|
||||
--
|
||||
--
|
||||
-- This configuration will automatically convert all images that are placed
|
||||
-- in the directory 'magicdir' all resulting images are placed in the same
|
||||
-- directory!
|
||||
|
@ -12,7 +12,7 @@
|
|||
--
|
||||
local formats = { jpg=true, gif=true, png=true, }
|
||||
|
||||
convert = {
|
||||
local convert = {
|
||||
delay = 0,
|
||||
|
||||
maxProcesses = 99,
|
||||
|
@ -50,7 +50,7 @@ convert = {
|
|||
if cmd ~= "" then
|
||||
cmd = cmd .. " && "
|
||||
end
|
||||
cmd = cmd..
|
||||
cmd = cmd..
|
||||
'/usr/bin/convert "'..
|
||||
event.source..p..'" "'..
|
||||
event.source..base..'.'..k..
|
||||
|
@ -61,7 +61,7 @@ convert = {
|
|||
spawnShell(event, cmd)
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
-- deletes all formats if you delete one
|
||||
if event.etype == "Delete" then
|
||||
-- builds one bash command
|
||||
|
@ -99,8 +99,8 @@ convert = {
|
|||
local inlet = event.inlet
|
||||
|
||||
if event.etype == "Create" or
|
||||
event.etype == "Modify" or
|
||||
event.etype == "Delete"
|
||||
event.etype == "Modify" or
|
||||
event.etype == "Delete"
|
||||
then
|
||||
for k, _ in pairs(formats) do
|
||||
inlet.rmExclude(base..'.'..k)
|
||||
|
|
|
@ -11,29 +11,43 @@ local rsyncpostcmd = {
|
|||
-- based on default rsync.
|
||||
default.rsync,
|
||||
|
||||
checkgauge = {
|
||||
default.rsync.checkgauge,
|
||||
host = true,
|
||||
targetdir = true,
|
||||
target = true,
|
||||
postcmd = true,
|
||||
},
|
||||
|
||||
-- for this config it is important to keep maxProcesses at 1, so
|
||||
-- the postcmds will only be spawned after the rsync completed
|
||||
maxProcesses = 1,
|
||||
|
||||
-- called whenever something is to be done
|
||||
action = function(inlet)
|
||||
local event = inlet.getEvent()
|
||||
local config = inlet.getConfig()
|
||||
action = function
|
||||
(
|
||||
inlet
|
||||
)
|
||||
local event = inlet.getEvent( )
|
||||
local config = inlet.getConfig( )
|
||||
-- if the event is a blanket event and not the startup,
|
||||
-- its there to spawn the webservice restart at the target.
|
||||
if event.etype == "Blanket" then
|
||||
if event.etype == 'Blanket'
|
||||
then
|
||||
-- uses rawget to test if "isPostcmd" has been set without
|
||||
-- triggering an error if not.
|
||||
local isPostcmd = rawget(event, "isPostcmd")
|
||||
if event.isPostcmd then
|
||||
spawn(event, "/usr/bin/ssh",
|
||||
local isPostcmd = rawget( event, 'isPostcmd' )
|
||||
|
||||
if isPostcmd
|
||||
then
|
||||
spawn(event, "/usr/bin/ssh",
|
||||
config.host, config.postcmd)
|
||||
return
|
||||
return
|
||||
else
|
||||
-- this is the startup, forwards it to default routine.
|
||||
return default.rsync.action(inlet)
|
||||
end
|
||||
error("this should never be reached")
|
||||
-- this is the startup, forwards it to default routine.
|
||||
return default.rsync.action(inlet)
|
||||
end
|
||||
error( 'this should never be reached' )
|
||||
end
|
||||
-- for any other event, a blanket event is created that
|
||||
-- will stack on the queue and do the postcmd when its finished
|
||||
|
@ -45,43 +59,60 @@ local rsyncpostcmd = {
|
|||
|
||||
-- called when a process exited.
|
||||
-- this can be a rsync command, the startup rsync or the postcmd
|
||||
collect = function(agent, exitcode)
|
||||
collect = function
|
||||
(
|
||||
agent,
|
||||
exitcode
|
||||
)
|
||||
-- for the ssh commands 255 is network error -> try again
|
||||
local isPostcmd = rawget(agent, "isPostcmd")
|
||||
if not agent.isList and agent.etype == "Blanket" and isPostcmd then
|
||||
if exitcode == 255 then
|
||||
return "again"
|
||||
end
|
||||
local isPostcmd = rawget( agent, 'isPostcmd' )
|
||||
|
||||
if not agent.isList and agent.etype == "Blanket" and isPostcmd
|
||||
then
|
||||
if exitcode == 255 then return 'again' end
|
||||
|
||||
return
|
||||
else
|
||||
--- everything else, forward to default collection handler
|
||||
return default.collect(agent,exitcode)
|
||||
return default.collect( agent,exitcode )
|
||||
end
|
||||
error("this should never be reached")
|
||||
end
|
||||
error( 'this should never be reached' )
|
||||
end,
|
||||
|
||||
-- called before anything else
|
||||
-- builds the target from host and targetdir
|
||||
prepare = function(config)
|
||||
if not config.host then
|
||||
error("rsyncpostcmd neets 'host' configured", 4)
|
||||
prepare = function
|
||||
(
|
||||
config,
|
||||
level,
|
||||
skipTarget
|
||||
)
|
||||
if not config.host
|
||||
then
|
||||
error( 'rsyncpostcmd needs "host" configured', 4 )
|
||||
end
|
||||
if not config.targetdir then
|
||||
error("rsyncpostcmd needs 'targetdir' configured", 4)
|
||||
|
||||
if not config.targetdir
|
||||
then
|
||||
error( 'rsyncpostcmd needs "targetdir" configured', 4)
|
||||
end
|
||||
if not config.target then
|
||||
|
||||
if not config.target
|
||||
then
|
||||
config.target = config.host .. ":" .. config.targetdir
|
||||
end
|
||||
return default.rsync.prepare(config)
|
||||
|
||||
return default.rsync.prepare(config, level, skipTarget)
|
||||
end
|
||||
}
|
||||
|
||||
|
||||
sync {
|
||||
rsyncpostcmd,
|
||||
source = "src",
|
||||
host = "beetle",
|
||||
targetdir = "/path/to/trg",
|
||||
postcmd = "/usr/local/bin/restart-servelt.sh",
|
||||
rsyncpostcmd,
|
||||
delay = 3,
|
||||
source = '/path/to/src',
|
||||
host = 'localhost',
|
||||
targetdir = '/path/to/trg',
|
||||
postcmd = '/usr/local/bin/dopostcmd',
|
||||
}
|
||||
|
||||
|
|
|
@ -3,14 +3,14 @@
|
|||
--
|
||||
-- Simple example for default rsync.
|
||||
--
|
||||
settings = {
|
||||
settings {
|
||||
statusFile = "/tmp/lsyncd.stat",
|
||||
statusInterval = 1,
|
||||
}
|
||||
|
||||
sync{
|
||||
default.rsync,
|
||||
source="src",
|
||||
target="trg",
|
||||
default.rsync,
|
||||
source="src",
|
||||
target="trg",
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
settings {
|
||||
logfile = "/home/lsync/job1/lsyncd.log",
|
||||
statusFile = "/home/lsync/job1/lsyncd.status",
|
||||
insist = true
|
||||
}
|
||||
|
||||
sync {
|
||||
default.rsyncssh,
|
||||
source = "/data/projects",
|
||||
host = "offsitehost",
|
||||
targetdir = "/data/projects",
|
||||
excludeFrom = "/home/lsync/job1/lsyncd.exclude",
|
||||
delay = 5,
|
||||
rsync = {
|
||||
verbose = true,
|
||||
inplace = true,
|
||||
_extra = {
|
||||
"--info=progress2"
|
||||
}
|
||||
},
|
||||
ssh = {
|
||||
identityFile = "/home/lsync/.ssh/id_rsa_new",
|
||||
options = {
|
||||
User = "poelzi",
|
||||
StrictHostKeyChecking = "no",
|
||||
Compression = "no",
|
||||
Cipher = "aes256-gcm@openssh.com"
|
||||
},
|
||||
_extra = {
|
||||
"-T",
|
||||
"-c",
|
||||
"aes256-gcm@openssh.com"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
-- This is an advanced tunnel config that uses the tunnel, load balancing
|
||||
-- and extra large file transfers
|
||||
|
||||
sync {
|
||||
default.rsync,
|
||||
tunnel = tunnel {
|
||||
command = {"ssh", "-N", "-L", "localhost:${localport}:localhost:873", "user@testmachine"},
|
||||
mode = "pool",
|
||||
parallel = 2,
|
||||
},
|
||||
crontab = {
|
||||
-- does a full sync once a day at 3:00:01
|
||||
"1 0 3 * * *"
|
||||
},
|
||||
source = "/data/projects",
|
||||
target = "rsync://localhost:${localport}/projects",
|
||||
delay = 5,
|
||||
batchSizeLimit = 1024 * 1024 * 30,
|
||||
maxProcesses = 4,
|
||||
rsync = {
|
||||
inplace = true,
|
||||
}
|
||||
}
|
||||
|
||||
-- On your target machine configure rsyncd.conf like this:
|
||||
-- [projects]
|
||||
-- uid = myuser
|
||||
-- gid = mygroup
|
||||
-- path = /srv/projects
|
||||
-- read only = false
|
||||
|
||||
-- If you restrict the ssh key or server to allow only port forwarding and no shell
|
||||
-- this is a very secure setup
|
|
@ -0,0 +1,222 @@
|
|||
----
|
||||
-- Example lsyncd configuration for syncing with an Amazon S3 bucket
|
||||
--
|
||||
-- This requires the official AWS CLI to be available, and that credentials
|
||||
-- bet set up through some external method, such as environment variables,
|
||||
-- IAM profiles or the AWS SDK configuration.
|
||||
--
|
||||
-- The AWS CLI sync exclude rules are not as powerful as the ones supported by
|
||||
-- lsyncd. Hence, some of the do not translate perfectly. For example, '*'
|
||||
-- (asterisk) matches slashes, while it does not in lsyncd. Hence it is a good
|
||||
-- idea to only use exclude patterns for full directories, either by using a
|
||||
-- trailing / (slash) or ** (double asterisk), as those will be correctly
|
||||
-- translated.
|
||||
--
|
||||
-- An initialSync options is provided as a convenience, since it's not easy to
|
||||
-- make sure exclusion rules match when doing it manually. It will *pull* from
|
||||
-- the target bucket to the local dir (the opposite of the regular behavior)
|
||||
-- then exit immediately.
|
||||
--
|
||||
-- Author: Daniel Miranda <danielkza2@gmail.com>
|
||||
--
|
||||
local s3 = {}
|
||||
|
||||
s3.checkgauge = {
|
||||
onCreate = false,
|
||||
onModify = false,
|
||||
onDelete = false,
|
||||
onStartup = false,
|
||||
onMove = false,
|
||||
|
||||
delete = true,
|
||||
exclude = true,
|
||||
excludeFrom = true,
|
||||
target = true,
|
||||
|
||||
s3 = {
|
||||
-- Path to the AWS CLI binary
|
||||
awscliBinary = true,
|
||||
-- Extra options to pass to the AWS CLI (as a list)
|
||||
awscliOptions = true,
|
||||
-- Whether to do a dry-run, and not make any real changes
|
||||
dryrun = true,
|
||||
-- Do an initial pull from the bucket and exit immediately.
|
||||
initialSync = true
|
||||
}
|
||||
}
|
||||
|
||||
-- Generate a list of exclude flags for the AWS CLI based on the lsyncd
|
||||
-- patterns provided. Cache it to avoid re-generating it every time.
|
||||
|
||||
local s3Excludes = function(config, excludes)
|
||||
if config.s3._excludes == nil then
|
||||
config.s3._excludes = {}
|
||||
for _, pat in ipairs(excludes) do
|
||||
pat = pat:gsub('%*%*', '[[ANY]]')
|
||||
pat = pat:gsub('%?', '[[ANY_BUT_SLASH_ONCE]]')
|
||||
pat = pat:gsub('/$', '/*')
|
||||
pat = pat:gsub('%[%[ANY%]%]', '*')
|
||||
pat = pat:gsub('%[%[ANY_BUT_SLASH_ONCE%]%]', '[^/]')
|
||||
|
||||
if pat:match('^/') then
|
||||
pat = pat:sub(2, -1)
|
||||
else
|
||||
pat = '*/' .. pat
|
||||
end
|
||||
|
||||
table.insert(config.s3._excludes, '--exclude')
|
||||
table.insert(config.s3._excludes, pat)
|
||||
end
|
||||
|
||||
log('s3Excludes', table.concat(config.s3._excludes, '\n'))
|
||||
end
|
||||
|
||||
return config.s3._excludes
|
||||
end
|
||||
|
||||
-- Generates a command line to call the AWS CLI as configured, with the provided
|
||||
-- S3 action (such as cp, mv, rm or sync).
|
||||
-- Returns a tuple of (binaryPath, arguments)
|
||||
local awscliCommand = function(verb, config)
|
||||
local bin = config.s3.awscliBinary
|
||||
local args = {'s3', verb, '--only-show-errors'}
|
||||
if config.s3.dryrun then
|
||||
table.insert(args, '--dryrun')
|
||||
end
|
||||
|
||||
if verb == 'sync'
|
||||
and (config.delete == true or config.delete == 'startup')
|
||||
then
|
||||
table.insert(args, '--delete')
|
||||
end
|
||||
|
||||
for _, opt in ipairs(config.s3.awscliOptions) do
|
||||
table.insert(args, opt)
|
||||
end
|
||||
|
||||
return bin, args
|
||||
end
|
||||
|
||||
s3.action = function(inlet)
|
||||
local event, event2 = inlet.getEvent()
|
||||
-- S3 never actually deals with directories - they are just an illusion
|
||||
-- created based on the common prefixes of objects. Hence discard any events
|
||||
-- that do not concern files.
|
||||
if event.isdir then
|
||||
inlet.discardEvent(event)
|
||||
return
|
||||
end
|
||||
|
||||
local config = inlet.getConfig()
|
||||
if event.etype == 'Create' or event.etype == 'Modify' then
|
||||
local bin, args = awscliCommand('cp', config)
|
||||
spawn(
|
||||
event,
|
||||
bin,
|
||||
args,
|
||||
event.sourcePath,
|
||||
event.targetPath
|
||||
)
|
||||
elseif event.etype == 'Delete' then
|
||||
if config.delete ~= true and config.delete ~= 'running' then
|
||||
inlet.discardEvent(event)
|
||||
return
|
||||
end
|
||||
|
||||
local bin, args = awscliCommand('rm', config)
|
||||
spawn(
|
||||
event,
|
||||
bin,
|
||||
args,
|
||||
event.targetPath
|
||||
)
|
||||
elseif event.etype == 'Move' then
|
||||
local bin, args = awscliCommand('mv', config)
|
||||
spawn(
|
||||
event,
|
||||
bin,
|
||||
args,
|
||||
event.targetPath,
|
||||
event2.targetPath
|
||||
)
|
||||
else
|
||||
log('Warn', 'ignored an event of type "', event.etype, '"')
|
||||
inlet.discardEvent(event)
|
||||
end
|
||||
end
|
||||
|
||||
s3.init = function(event)
|
||||
local config = event.config
|
||||
local inlet = event.inlet
|
||||
local excludes = s3Excludes(config, inlet.getExcludes())
|
||||
local bin, args = awscliCommand('sync', config)
|
||||
|
||||
-- Do a pull when initialSync is enabled.
|
||||
if config.s3.initialSync then
|
||||
spawn(
|
||||
event,
|
||||
bin,
|
||||
args,
|
||||
excludes,
|
||||
config.target,
|
||||
event.sourcePath
|
||||
)
|
||||
-- And a push, as usual, otherwise
|
||||
else
|
||||
spawn(
|
||||
event,
|
||||
bin,
|
||||
args,
|
||||
excludes,
|
||||
event.sourcePath,
|
||||
config.target
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
-- Define a collect callback so we can terminate immediately when initialSync
|
||||
-- is enabled
|
||||
s3.collect = function(agent, exitcode)
|
||||
local config = agent.config
|
||||
if not agent.isList and agent.etype == 'Init' and config.s3.initialSync then
|
||||
terminate(exitcode == 0 and 0 or -1)
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
s3.prepare = function(config, level)
|
||||
default.prepare(config, level + 1)
|
||||
|
||||
config.target = config.target:gsub('/+$', '')
|
||||
if not config.target:match('^s3://') then
|
||||
config.target = 's3://' .. config.target
|
||||
end
|
||||
end
|
||||
|
||||
s3.s3 = {
|
||||
awscliBinary = '/usr/bin/aws',
|
||||
awscliOptions = {},
|
||||
dryrun = false,
|
||||
initialSync = false
|
||||
}
|
||||
s3.delete = false
|
||||
s3.delay = 10
|
||||
s3.maxProcesses = 1
|
||||
|
||||
sync {
|
||||
s3,
|
||||
source = '/my/dir',
|
||||
target = 's3://my-bucket/my-path',
|
||||
delay = 30,
|
||||
delete = true,
|
||||
maxProcesses = 2,
|
||||
exclude = {
|
||||
'/sub/folder/',
|
||||
},
|
||||
s3 = {
|
||||
awscliBinary = '/usr/local/bin/aws',
|
||||
awscliOptions = {'--acl', 'public-read'},
|
||||
dryrun = false
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
-----
|
||||
-- An Lsyncd+IRC-Bot Config
|
||||
--
|
||||
-- Logs into an IRC channel and tells there everything that happens in the
|
||||
-- Logs into an IRC channel and tells there everything that happens in the
|
||||
-- watched directory tree.
|
||||
--
|
||||
-- The challenge coding Lsyncd configs taking use of TCP sockets is
|
||||
|
@ -10,7 +10,7 @@
|
|||
-- no longer spawning processes (this example doesnt do any, but maybe you
|
||||
-- might want to do that as well), blocking is just bad.
|
||||
--
|
||||
-- This demo codes just minimal IRC functionality.
|
||||
-- This demo codes just minimal IRC functionality.
|
||||
-- it does not respond to anything else than IRC PING messages.
|
||||
--
|
||||
-- There is no flood control, if a lot happens the IRC server will disconnect
|
||||
|
@ -21,15 +21,16 @@ require("socket")
|
|||
|
||||
-- For demo reasons, do not detach
|
||||
settings.nodaemon = true
|
||||
hostname = "irc.freenode.org"
|
||||
local hostname = "irc.freenode.org"
|
||||
--hostname = "127.0.0.1"
|
||||
port = 6667
|
||||
nick = "lbot01"
|
||||
chan = "##lfile01"
|
||||
local port = 6667
|
||||
local nick = "lbot01"
|
||||
local chan = "##lfile01"
|
||||
|
||||
-- this blocks until the connection is established
|
||||
-- for once lets say this ok since Lsyncd didnt yet actually
|
||||
-- start.
|
||||
--- @diagnostic disable-next-line: undefined-global
|
||||
local ircSocket, err = socket.connect(hostname, port)
|
||||
if not ircSocket then
|
||||
log("Error", "Cannot connect to IRC: ", err)
|
||||
|
@ -55,7 +56,7 @@ local function ircWritey(fd)
|
|||
end
|
||||
|
||||
----
|
||||
-- Called when there is data on the socket
|
||||
-- Called when there is data on the socket
|
||||
local function ircReady(socket)
|
||||
local l, err, ircRBuf = ircSocket:receive("*l", ircRBuf)
|
||||
if not l then
|
||||
|
@ -88,7 +89,7 @@ function writeIRC(...)
|
|||
log("Error", "IRC connection failed: ", err)
|
||||
terminate(-1)
|
||||
end
|
||||
|
||||
|
||||
--- logs what has been send, without the linefeed.
|
||||
if (ircWBuf:sub(s, s) == "\n") then
|
||||
log("Normal", "ircout:", ircWBuf:sub(1, s - 1))
|
||||
|
@ -133,8 +134,8 @@ local function action(inlet)
|
|||
end
|
||||
|
||||
-- Watch a directory, and use a second for delay to aggregate events a little.
|
||||
sync{source = "src",
|
||||
action = action,
|
||||
sync{source = "src",
|
||||
action = action,
|
||||
delay = 1,
|
||||
onMove = true}
|
||||
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"locked": {
|
||||
"lastModified": 1659877975,
|
||||
"narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1666940097,
|
||||
"narHash": "sha256-spcDvKqQU9iSMAjh5uj9gfu8Gu7vIFFkOKH2CCWvOVY=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "7269939a5d5610f2eec933607dc4d646394b29b8",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"ref": "release-22.05",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
|
@ -0,0 +1,174 @@
|
|||
{
|
||||
description = "Lsyncd (Live Syncing Daemon)";
|
||||
|
||||
inputs.nixpkgs.url = "github:nixos/nixpkgs/release-22.05";
|
||||
inputs.flake-utils.url = "github:numtide/flake-utils";
|
||||
|
||||
outputs = { self, nixpkgs, flake-utils }:
|
||||
flake-utils.lib.eachDefaultSystem
|
||||
(system:
|
||||
let
|
||||
pkgs = (import nixpkgs {
|
||||
inherit system;
|
||||
# Makes the config pure as well. See <nixpkgs>/top-level/impure.nix:
|
||||
config = {
|
||||
allowBroken = true;
|
||||
};}); #.legacyPackages.${system};
|
||||
defaultDeps = with pkgs; [
|
||||
glib
|
||||
rsync
|
||||
openssh
|
||||
];
|
||||
nativeDeps = with pkgs; [
|
||||
curl
|
||||
asciidoc
|
||||
jekyll
|
||||
gcc
|
||||
cmake
|
||||
gnumake
|
||||
];
|
||||
version = builtins.elemAt
|
||||
(builtins.match ''.*lsyncd_version = '([0-9\.]*)'.*''
|
||||
(builtins.substring 0 1200
|
||||
(builtins.readFile ./lsyncd.lua))) 0;
|
||||
# mylua5_4 = pkgs.lua5_4.override({
|
||||
# packageOverrides = luaself: luaprev: {
|
||||
# luarocks = luaprev.luarocks-3_7;
|
||||
# };
|
||||
# });
|
||||
luaposix35 = mylua: mylua.pkgs.buildLuarocksPackage {
|
||||
pname = "luaposix";
|
||||
lua = mylua;
|
||||
version = "35.1-1";
|
||||
knownRockspec = (pkgs.fetchurl {
|
||||
url = "https://luarocks.org/luaposix-35.1-1.rockspec";
|
||||
sha256 = "1n6c7qyabj2y95jmbhf8fxbrp9i73kphmwalsam07f9w9h995xh1";
|
||||
}).outPath;
|
||||
src = pkgs.fetchurl {
|
||||
url = "http://github.com/luaposix/luaposix/archive/v35.1.zip";
|
||||
sha256 = "1c03chkzwr2p1wd0hs1bafl2890fqbrfc3qk0wxbd202gc6128zi";
|
||||
};
|
||||
|
||||
#
|
||||
propagatedBuildInputs = [ mylua ];
|
||||
|
||||
meta = {
|
||||
homepage = "http://github.com/luaposix/luaposix/";
|
||||
description = "Lua bindings for POSIX";
|
||||
license.fullName = "MIT/X11";
|
||||
};
|
||||
};
|
||||
|
||||
buildExtensions = luapkgs: (
|
||||
let
|
||||
nucleo = luapkgs.buildLuarocksPackage {
|
||||
pname = "lua-nucleo";
|
||||
version = "1.1.0-1";
|
||||
knownRockspec = (pkgs.fetchurl {
|
||||
url = "https://luarocks.org/lua-nucleo-1.1.0-1.rockspec";
|
||||
sha256 = "02ly51wav1pxiahf6lflr4vks550bisdq4ir9cy1lxn9v2zmcbim";
|
||||
}).outPath;
|
||||
src = pkgs.fetchgit ( removeAttrs (builtins.fromJSON ''{
|
||||
"url": "https://github.com/lua-nucleo/lua-nucleo.git",
|
||||
"rev": "76835968ff30f182367abd58637560402990e0b1",
|
||||
"date": "2021-04-26T11:51:34+03:00",
|
||||
"path": "/nix/store/3ycmrh0j64qxm4f04yxmn3y42imc8bv5-lua-nucleo",
|
||||
"sha256": "15kydmj64jhxv5ksayfgkwzmgzd7raj7xp636x8a7c3ybiirs90n",
|
||||
"fetchSubmodules": true,
|
||||
"deepClone": false,
|
||||
"leaveDotGit": false
|
||||
}
|
||||
'') ["date" "path"]) ;
|
||||
|
||||
disabled = with luapkgs; (luaOlder "5.1");
|
||||
|
||||
meta = {
|
||||
homepage = "http://github.com/lua-nucleo/lua-nucleo";
|
||||
description = "A random collection of core and utility level Lua libraries";
|
||||
license.fullName = "MIT/X11";
|
||||
};
|
||||
};
|
||||
in
|
||||
luapkgs.buildLuarocksPackage {
|
||||
pname = "lua-crontab";
|
||||
version = "1.0.0-1";
|
||||
knownRockspec = (pkgs.fetchurl {
|
||||
url = "https://luarocks.org/lua-crontab-1.0.0-1.rockspec";
|
||||
sha256 = "1aynwxq488sxd2lyng4wnswfkqna5n07sfmdainlqlhcb6jan161";
|
||||
}).outPath;
|
||||
src = pkgs.fetchgit ( removeAttrs (builtins.fromJSON ''{
|
||||
"url": "https://github.com/logiceditor-com/lua-crontab.git",
|
||||
"rev": "e3929a572e8164f968da4dcbdf1c4464a2870699",
|
||||
"date": "2021-07-29T14:12:08+03:00",
|
||||
"path": "/nix/store/rsc49m4f1mjqbffaq7axcf31rgxxfjb3-lua-crontab",
|
||||
"sha256": "0zkqslw3vg495k8g010cz931vlzfyynq4kcwi1jbbppia521z6rx",
|
||||
"fetchSubmodules": true,
|
||||
"deepClone": false,
|
||||
"leaveDotGit": false
|
||||
}
|
||||
'') ["date" "path"]) ;
|
||||
|
||||
propagatedBuildInputs = [ nucleo ];
|
||||
|
||||
meta = {
|
||||
homepage = "http://github.com/logiceditor-com/lua-crontab";
|
||||
description = "Stores crontab-like rules for events and calculates timestamps for their occurrences";
|
||||
license.fullName = "MIT/X11";
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
buildTypes = {
|
||||
lua5_1 = [pkgs.lua5_1 pkgs.lua51Packages.luaposix (buildExtensions pkgs.lua51Packages)];
|
||||
lua5_2 = [pkgs.lua5_2 pkgs.lua52Packages.luaposix (buildExtensions pkgs.lua52Packages)];
|
||||
lua5_3 = [pkgs.lua5_3 pkgs.lua53Packages.luaposix (buildExtensions pkgs.lua53Packages)];
|
||||
# lua5_4 = [mylua5_4pkgs.lua5_3 (luaposix35 mylua5_4)];
|
||||
lua5_4 = [pkgs.lua5_4 (luaposix35 pkgs.lua5_4) (buildExtensions pkgs.lua5_4.pkgs)];
|
||||
};
|
||||
|
||||
# buildTypes = {
|
||||
# lua5_1 = [(pkgs.lua5_1.withPackages (ps: [ps.luaposix (buildExtensions pkgs.lua51Packages)]))];
|
||||
# lua5_2 = [(pkgs.lua5_2.withPackages (ps: [ps.luaposix (buildExtensions pkgs.lua52Packages)]))];
|
||||
# lua5_3 = [(pkgs.lua5_3.withPackages (ps: [ps.luaposix (buildExtensions pkgs.lua53Packages)]))];
|
||||
# lua5_4 = [(pkgs.lua5_4.withPackages (ps: [ps.luaposix (buildExtensions pkgs.lua54Packages)]))];
|
||||
# };
|
||||
in
|
||||
let
|
||||
mkLsync = luaPackages: pkgs.stdenv.mkDerivation ({
|
||||
inherit version;
|
||||
name = "lsyncd";
|
||||
|
||||
src = ./.;
|
||||
|
||||
buildPhase = ''
|
||||
make all manpage docs-html
|
||||
'';
|
||||
|
||||
buildInputs = defaultDeps ++ luaPackages;
|
||||
nativeBuildInputs = nativeDeps;
|
||||
});
|
||||
mkDev = extras: pkgs.mkShell {
|
||||
propagatedBuildInputs = defaultDeps ++ extras;
|
||||
};
|
||||
in
|
||||
{
|
||||
packages = {
|
||||
lsyncd = mkLsync buildTypes.lua5_3;
|
||||
lsyncd_lua5_1 = mkLsync buildTypes.lua5_1;
|
||||
lsyncd_lua5_2 = mkLsync buildTypes.lua5_2;
|
||||
lsyncd_lua5_3 = mkLsync buildTypes.lua5_3;
|
||||
lsyncd_lua5_4 = mkLsync buildTypes.lua5_4;
|
||||
};
|
||||
|
||||
devShells = {
|
||||
lsyncd = mkDev buildTypes.lua5_3;
|
||||
lsyncd_lua5_1 = mkDev buildTypes.lua5_1;
|
||||
lsyncd_lua5_2 = mkDev buildTypes.lua5_2;
|
||||
lsyncd_lua5_3 = mkDev buildTypes.lua5_3;
|
||||
lsyncd_lua5_4 = mkDev buildTypes.lua5_4;
|
||||
};
|
||||
|
||||
defaultPackage = self.packages.${system}.lsyncd;
|
||||
}
|
||||
);
|
||||
}
|
24
fsevents.c
24
fsevents.c
|
@ -49,9 +49,11 @@
|
|||
#define DEV_FSEVENTS "/dev/fsevents"
|
||||
|
||||
/* buffer for reading from the device */
|
||||
#define FSEVENT_BUFSIZ 131072
|
||||
#define FSEVENT_BUFSIZ 131072
|
||||
|
||||
/* limited by MAX_KFS_EVENTS */
|
||||
#define EVENT_QUEUE_SIZE 4096
|
||||
#define EVENT_QUEUE_SIZE 4096
|
||||
|
||||
#define KFS_NUM_ARGS FSE_MAX_ARGS
|
||||
|
||||
/* OS 10.5 structuce */
|
||||
|
@ -151,6 +153,10 @@ handle_event(lua_State *L, struct kfs_event *event, ssize_t mlen)
|
|||
const char *trg = NULL;
|
||||
const char *etype = NULL;
|
||||
int isdir = -1;
|
||||
#ifdef LSYNCD_TARGET_APPLE \
|
||||
// Since macOS 10.15, fsevent paths are prefixed with this string.
|
||||
const char *const INOTIFY_PREFIX = "/System/Volumes/Data";
|
||||
#endif
|
||||
|
||||
if (event->type == FSE_EVENTS_DROPPED) {
|
||||
logstring("Fsevents", "Events dropped!");
|
||||
|
@ -175,8 +181,13 @@ handle_event(lua_State *L, struct kfs_event *event, ssize_t mlen)
|
|||
logstring("Fsevents", "contains dropped events");
|
||||
}*/
|
||||
} else {
|
||||
printlogf(L, "Error", "unknown event(%d) in fsevents.",
|
||||
atype);
|
||||
printlogf(
|
||||
L,
|
||||
"Error",
|
||||
"unknown event(%d) in fsevents.",
|
||||
atype
|
||||
);
|
||||
|
||||
exit(-1); // ERRNO
|
||||
}
|
||||
|
||||
|
@ -258,6 +269,11 @@ handle_event(lua_State *L, struct kfs_event *event, ssize_t mlen)
|
|||
lua_pushstring(L, etype);
|
||||
lua_pushboolean(L, isdir);
|
||||
l_now(L);
|
||||
#ifdef LSYNCD_TARGET_APPLE
|
||||
if (!strncmp(path, INOTIFY_PREFIX, strlen(INOTIFY_PREFIX ))) {
|
||||
path += strlen(INOTIFY_PREFIX);
|
||||
}
|
||||
#endif
|
||||
lua_pushstring(L, path);
|
||||
if (trg) {
|
||||
lua_pushstring(L, trg);
|
||||
|
|
587
inotify.c
587
inotify.c
|
@ -1,21 +1,17 @@
|
|||
/**
|
||||
* inotify.c from Lsyncd - Live (Mirror) Syncing Demon
|
||||
*
|
||||
* License: GPLv2 (see COPYING) or any later version
|
||||
*
|
||||
* Authors: Axel Kittenberger <axkibe@gmail.com>
|
||||
*
|
||||
* -----------------------------------------------------------------------
|
||||
*
|
||||
* Event interface for Lsyncd to Linux´ inotify.
|
||||
*/
|
||||
/*
|
||||
| inotify.c from Lsyncd - Live (Mirror) Syncing Demon
|
||||
|
|
||||
| License: GPLv2 (see COPYING) or any later version
|
||||
|
|
||||
| Authors: Axel Kittenberger <axkibe@gmail.com>
|
||||
|
|
||||
| -----------------------------------------------------------------------
|
||||
|
|
||||
| Event interface for Lsyncd to Linux´ inotify.
|
||||
*/
|
||||
|
||||
#include "lsyncd.h"
|
||||
|
||||
#ifndef HAVE_SYS_INOTIFY_H
|
||||
# error Missing <sys/inotify.h>; supply kernel-headers and rerun configure.
|
||||
#endif
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <sys/times.h>
|
||||
#include <sys/types.h>
|
||||
|
@ -39,234 +35,355 @@
|
|||
#include <lualib.h>
|
||||
#include <lauxlib.h>
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* Event types.
|
||||
*/
|
||||
|
||||
/*
|
||||
| Event types.
|
||||
*/
|
||||
static const char * ATTRIB = "Attrib";
|
||||
static const char * MODIFY = "Modify";
|
||||
static const char * CREATE = "Create";
|
||||
static const char * DELETE = "Delete";
|
||||
static const char * MOVE = "Move";
|
||||
|
||||
/**
|
||||
|
||||
/*
|
||||
* The inotify file descriptor.
|
||||
*/
|
||||
static int inotify_fd = -1;
|
||||
|
||||
/**
|
||||
* Standard inotify events to listen to.
|
||||
*/
|
||||
|
||||
/*
|
||||
| Standard inotify events to listen to.
|
||||
*/
|
||||
static const uint32_t standard_event_mask =
|
||||
IN_ATTRIB | IN_CLOSE_WRITE | IN_CREATE |
|
||||
IN_DELETE | IN_DELETE_SELF | IN_MOVED_FROM |
|
||||
IN_MOVED_TO | IN_DONT_FOLLOW | IN_ONLYDIR;
|
||||
IN_ATTRIB |
|
||||
IN_CLOSE_WRITE |
|
||||
IN_CREATE |
|
||||
IN_DELETE |
|
||||
IN_DELETE_SELF |
|
||||
IN_MOVED_FROM |
|
||||
IN_MOVED_TO |
|
||||
IN_DONT_FOLLOW |
|
||||
IN_ONLYDIR;
|
||||
|
||||
|
||||
/**
|
||||
* Adds an inotify watch
|
||||
*
|
||||
* @param dir (Lua stack) path to directory
|
||||
* @param inotifyMode (Lua stack) path to directory
|
||||
* @return (Lua stack) numeric watch descriptor
|
||||
*/
|
||||
/*
|
||||
| Adds an inotify watch
|
||||
|
|
||||
| param dir (Lua stack) path to directory
|
||||
| param inotifyMode (Lua stack) which inotify event to react upon
|
||||
| "CloseWrite", "CloseWrite or Modify"
|
||||
|
|
||||
| returns (Lua stack) numeric watch descriptor
|
||||
*/
|
||||
static int
|
||||
l_addwatch(lua_State *L)
|
||||
l_addwatch( lua_State *L )
|
||||
{
|
||||
const char *path = luaL_checkstring(L, 1);
|
||||
const char *imode = luaL_checkstring(L, 2);
|
||||
const char *path = luaL_checkstring( L, 1 );
|
||||
const char *imode = luaL_checkstring( L, 2 );
|
||||
uint32_t mask = standard_event_mask;
|
||||
if (*imode) {
|
||||
if (!strcmp(imode, "Modify")) {
|
||||
// act on modify instead of closeWrite
|
||||
mask |= IN_MODIFY;
|
||||
|
||||
// checks the desired inotify reaction mode
|
||||
if (*imode)
|
||||
{
|
||||
if ( !strcmp( imode, "Modify" ) )
|
||||
{
|
||||
// acts on modify instead of closeWrite
|
||||
mask |= IN_MODIFY;
|
||||
mask &= ~IN_CLOSE_WRITE;
|
||||
} else if (!strcmp(imode, "CloseWrite")) {
|
||||
// default
|
||||
} else if (!strcmp(imode, "CloseWrite or Modify")) {
|
||||
}
|
||||
else if ( !strcmp( imode, "CloseWrite" ) )
|
||||
{
|
||||
// thats default
|
||||
}
|
||||
else if ( !strcmp( imode, "CloseWrite or Modify" ) )
|
||||
{
|
||||
// acts on modify and closeWrite
|
||||
mask |= IN_MODIFY;
|
||||
} else if (!strcmp(imode, "CloseWrite after Modify")) {
|
||||
}
|
||||
else if ( ! strcmp( imode, "CloseWrite after Modify") )
|
||||
{
|
||||
// might be done in future
|
||||
printlogf(L, "Error", "'CloseWrite after Modify' not implemented.");
|
||||
exit(-1); // ERRNO
|
||||
} else {
|
||||
printlogf(L, "Error", "'%s' not a valid inotfiyMode.", imode);
|
||||
exit(-1); // ERRNO
|
||||
printlogf(
|
||||
L, "Error",
|
||||
"'CloseWrite after Modify' not implemented."
|
||||
);
|
||||
exit(-1);
|
||||
}
|
||||
else
|
||||
{
|
||||
printlogf(
|
||||
L, "Error",
|
||||
"'%s' not a valid inotfiyMode.",
|
||||
imode
|
||||
);
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
// kernel call to create the inotify watch
|
||||
int wd = inotify_add_watch( inotify_fd, path, mask );
|
||||
|
||||
int wd = inotify_add_watch(inotify_fd, path, mask);
|
||||
if (wd < 0) {
|
||||
if (errno == ENOSPC) {
|
||||
printlogf(L, "Error", "Terminating since out of inotify watches.");
|
||||
printlogf(L, "Error", "Consider increasing /proc/sys/fs/inotify/max_user_watches");
|
||||
if( wd < 0 )
|
||||
{
|
||||
if( errno == ENOSPC )
|
||||
{
|
||||
printlogf(
|
||||
L, "Error",
|
||||
"%s\n%s",
|
||||
"Terminating since out of inotify watches.",
|
||||
"Consider increasing /proc/sys/fs/inotify/max_user_watches"
|
||||
);
|
||||
exit(-1); // ERRNO.
|
||||
}
|
||||
printlogf(L, "Inotify", "addwatch(%s)->%d; err=%d:%s", path, wd, errno, strerror(errno));
|
||||
} else {
|
||||
printlogf(L, "Inotify", "addwatch(%s)->%d", path, wd);
|
||||
|
||||
printlogf(
|
||||
L, "Inotify",
|
||||
"addwatch( %s )-> %d; err= %d : %s",
|
||||
path, wd, errno, strerror( errno )
|
||||
);
|
||||
}
|
||||
lua_pushinteger(L, wd);
|
||||
else
|
||||
{
|
||||
printlogf(L, "Inotify", "addwatch( %s )-> %d ", path, wd );
|
||||
}
|
||||
lua_pushinteger( L, wd );
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an inotify watch
|
||||
*
|
||||
* @param dir (Lua stack) numeric watch descriptor
|
||||
* @return nil
|
||||
*/
|
||||
|
||||
/*
|
||||
* Removes an inotify watch.
|
||||
*
|
||||
* param dir (Lua stack) numeric watch descriptor
|
||||
*
|
||||
* return nil
|
||||
*/
|
||||
static int
|
||||
l_rmwatch(lua_State *L)
|
||||
l_rmwatch( lua_State *L )
|
||||
{
|
||||
int wd = luaL_checkinteger(L, 1);
|
||||
inotify_rm_watch(inotify_fd, wd);
|
||||
printlogf(L, "Inotify", "rmwatch()<-%d", wd);
|
||||
int wd = luaL_checkinteger( L, 1 );
|
||||
inotify_rm_watch( inotify_fd, wd );
|
||||
printlogf( L, "Inotify", "rmwatch()<-%d", wd );
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cores inotify functions.
|
||||
*/
|
||||
static const luaL_reg linotfylib[] = {
|
||||
{"addwatch", l_addwatch },
|
||||
{"rmwatch", l_rmwatch },
|
||||
{NULL, NULL}
|
||||
|
||||
/*
|
||||
| Lsyncd's core's inotify functions.
|
||||
*/
|
||||
static const luaL_Reg linotfylib[ ] =
|
||||
{
|
||||
{ "addwatch", l_addwatch },
|
||||
{ "rmwatch", l_rmwatch },
|
||||
{ NULL, NULL}
|
||||
};
|
||||
|
||||
/**
|
||||
* Buffer for MOVE_FROM events.
|
||||
* Lsyncd buffers MOVE_FROM events to check if
|
||||
* they are followed by MOVE_TO events with identical cookie
|
||||
* then they are condensed into one move event to be sent to the
|
||||
* runner
|
||||
*/
|
||||
|
||||
/*
|
||||
| Buffer for MOVE_FROM events.
|
||||
| Lsyncd buffers MOVE_FROM events to check if
|
||||
| they are followed by MOVE_TO events with identical cookie
|
||||
| then they are condensed into one move event to be sent to the
|
||||
| runner
|
||||
*/
|
||||
static struct inotify_event * move_event_buf = NULL;
|
||||
|
||||
/**
|
||||
* Memory allocated for move_event_buf
|
||||
*/
|
||||
|
||||
/*
|
||||
| Memory allocated for move_event_buf
|
||||
*/
|
||||
static size_t move_event_buf_size = 0;
|
||||
|
||||
/**
|
||||
* true if the buffer is used.
|
||||
*/
|
||||
|
||||
/*
|
||||
| True if the buffer is used.
|
||||
*/
|
||||
static bool move_event = false;
|
||||
|
||||
/**
|
||||
* Handles an inotify event.
|
||||
*/
|
||||
|
||||
/*
|
||||
| Handles an inotify event.
|
||||
*/
|
||||
static void
|
||||
handle_event(lua_State *L,
|
||||
struct inotify_event *event)
|
||||
handle_event(
|
||||
lua_State *L,
|
||||
struct inotify_event *event
|
||||
)
|
||||
{
|
||||
const char *event_type = NULL;
|
||||
|
||||
// used to execute two events in case of unmatched MOVE_FROM buffer
|
||||
struct inotify_event *after_buf = NULL;
|
||||
if (event && (IN_Q_OVERFLOW & event->mask)) {
|
||||
/* and overflow happened, tells the runner */
|
||||
load_runner_func(L, "overflow");
|
||||
if (lua_pcall(L, 0, 0, -2)) {
|
||||
exit(-1); // ERRNO
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
if( event && ( IN_Q_OVERFLOW & event->mask ) )
|
||||
{
|
||||
// and overflow happened, tells the runner
|
||||
load_runner_func( L, "overflow" );
|
||||
|
||||
if( lua_pcall( L, 0, 0, -2 ) ) exit( -1 );
|
||||
|
||||
lua_pop( L, 1 );
|
||||
|
||||
hup = 1;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// cancel on ignored or resetting
|
||||
if (event && (IN_IGNORED & event->mask)) {
|
||||
if( event && ( IN_IGNORED & event->mask ) )
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (event && event->len == 0) {
|
||||
|
||||
if( event && event->len == 0 )
|
||||
{
|
||||
// sometimes inotify sends such strange events,
|
||||
// (e.g. when touching a dir
|
||||
return;
|
||||
}
|
||||
|
||||
if (event == NULL) {
|
||||
if( event == NULL )
|
||||
{
|
||||
// a buffered MOVE_FROM is not followed by anything,
|
||||
// thus it is unary
|
||||
event = move_event_buf;
|
||||
event_type = "Delete";
|
||||
move_event = false;
|
||||
} else if (move_event &&
|
||||
( !(IN_MOVED_TO & event->mask) ||
|
||||
event->cookie != move_event_buf->cookie) ) {
|
||||
}
|
||||
else if(
|
||||
move_event
|
||||
&& (
|
||||
!( IN_MOVED_TO & event->mask )
|
||||
|| event->cookie != move_event_buf->cookie
|
||||
)
|
||||
)
|
||||
{
|
||||
// there is a MOVE_FROM event in the buffer and this is not the match
|
||||
// continue in this function iteration to handle the buffer instead */
|
||||
logstring("Inotify", "icore, changing unary MOVE_FROM into DELETE")
|
||||
logstring(
|
||||
"Inotify",
|
||||
"icore, changing unary MOVE_FROM into DELETE"
|
||||
);
|
||||
|
||||
after_buf = event;
|
||||
|
||||
event = move_event_buf;
|
||||
|
||||
event_type = "Delete";
|
||||
|
||||
move_event = false;
|
||||
} else if ( move_event &&
|
||||
(IN_MOVED_TO & event->mask) &&
|
||||
event->cookie == move_event_buf->cookie ) {
|
||||
}
|
||||
else if(
|
||||
move_event
|
||||
&& ( IN_MOVED_TO & event->mask )
|
||||
&& event->cookie == move_event_buf->cookie
|
||||
)
|
||||
{
|
||||
// this is indeed a matched move */
|
||||
event_type = "Move";
|
||||
move_event = false;
|
||||
} else if (IN_MOVED_FROM & event->mask) {
|
||||
}
|
||||
else if( IN_MOVED_FROM & event->mask )
|
||||
{
|
||||
// just the MOVE_FROM, buffers this event, and wait if next event is
|
||||
// a matching MOVED_TO of this was an unary move out of the watched
|
||||
// tree.
|
||||
size_t el = sizeof(struct inotify_event) + event->len;
|
||||
if (move_event_buf_size < el) {
|
||||
size_t el = sizeof( struct inotify_event ) + event->len;
|
||||
|
||||
if( move_event_buf_size < el )
|
||||
{
|
||||
move_event_buf_size = el;
|
||||
move_event_buf = s_realloc(move_event_buf, el);
|
||||
|
||||
move_event_buf = s_realloc( move_event_buf, el );
|
||||
}
|
||||
memcpy(move_event_buf, event, el);
|
||||
|
||||
memcpy( move_event_buf, event, el );
|
||||
|
||||
move_event = true;
|
||||
|
||||
return;
|
||||
} else if (IN_MOVED_TO & event->mask) {
|
||||
|
||||
}
|
||||
else if( IN_MOVED_TO & event->mask )
|
||||
{
|
||||
// must be an unary move-to
|
||||
event_type = CREATE;
|
||||
} else if (IN_ATTRIB & event->mask) {
|
||||
}
|
||||
else if( IN_ATTRIB & event->mask )
|
||||
{
|
||||
// just attrib change
|
||||
event_type = ATTRIB;
|
||||
} else if ((IN_CLOSE_WRITE | IN_MODIFY) & event->mask) {
|
||||
}
|
||||
else if( ( IN_CLOSE_WRITE | IN_MODIFY) & event->mask )
|
||||
{
|
||||
// modify, or closed after written something
|
||||
// the event type received depends settings.inotifyMode
|
||||
event_type = MODIFY;
|
||||
} else if (IN_CREATE & event->mask) {
|
||||
}
|
||||
else if( IN_CREATE & event->mask )
|
||||
{
|
||||
// a new file
|
||||
event_type = CREATE;
|
||||
} else if (IN_DELETE & event->mask) {
|
||||
}
|
||||
else if( IN_DELETE & event->mask )
|
||||
{
|
||||
// rm'ed
|
||||
event_type = DELETE;
|
||||
} else {
|
||||
logstring("Inotify", "icore, skipped some inotify event.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logstring(
|
||||
"Inotify",
|
||||
"skipped some inotify event."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// and hands over to runner
|
||||
load_runner_func(L, "inotifyEvent");
|
||||
if (!event_type) {
|
||||
logstring("Error", "Internal: unknown event in handle_event()");
|
||||
exit(-1); // ERRNO
|
||||
// hands the event over to the runner
|
||||
load_runner_func( L, "inotifyEvent" );
|
||||
|
||||
if( !event_type )
|
||||
{
|
||||
logstring(
|
||||
"Error",
|
||||
"internal failure: unknown event in handle_event()"
|
||||
);
|
||||
|
||||
exit( -1 );
|
||||
}
|
||||
lua_pushstring(L, event_type);
|
||||
if (event_type != MOVE) {
|
||||
lua_pushnumber(L, event->wd);
|
||||
} else {
|
||||
lua_pushnumber(L, move_event_buf->wd);
|
||||
|
||||
lua_pushstring( L, event_type );
|
||||
|
||||
if( event_type != MOVE )
|
||||
{
|
||||
lua_pushnumber( L, event->wd );
|
||||
}
|
||||
lua_pushboolean(L, (event->mask & IN_ISDIR) != 0);
|
||||
l_now(L);
|
||||
if (event_type == MOVE) {
|
||||
lua_pushstring(L, move_event_buf->name);
|
||||
lua_pushnumber(L, event->wd);
|
||||
lua_pushstring(L, event->name);
|
||||
} else {
|
||||
lua_pushstring(L, event->name);
|
||||
lua_pushnil(L);
|
||||
lua_pushnil(L);
|
||||
else
|
||||
{
|
||||
lua_pushnumber( L, move_event_buf->wd );
|
||||
}
|
||||
if (lua_pcall(L, 7, 0, -9)) {
|
||||
exit(-1); // ERRNO
|
||||
lua_pushboolean( L, ( event->mask & IN_ISDIR ) != 0 );
|
||||
|
||||
l_now( L );
|
||||
|
||||
if( event_type == MOVE )
|
||||
{
|
||||
lua_pushstring( L, move_event_buf->name );
|
||||
lua_pushnumber( L, event->wd );
|
||||
lua_pushstring( L, event->name );
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
else
|
||||
{
|
||||
lua_pushstring( L, event->name );
|
||||
lua_pushnil( L );
|
||||
lua_pushnil( L );
|
||||
}
|
||||
|
||||
if( lua_pcall( L, 7, 0, -9 ) ) exit( -1 );
|
||||
|
||||
lua_pop( L, 1 );
|
||||
|
||||
// if there is a buffered event, executes it
|
||||
if (after_buf) {
|
||||
|
@ -275,31 +392,42 @@ handle_event(lua_State *L,
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* buffer to read inotify events into
|
||||
*/
|
||||
|
||||
/*
|
||||
| buffer to read inotify events into
|
||||
*/
|
||||
static size_t readbuf_size = 2048;
|
||||
|
||||
static char * readbuf = NULL;
|
||||
|
||||
/**
|
||||
* Called by function pointer from when the inotify file descriptor
|
||||
* became ready. Reads it contents and forward all received events
|
||||
* to the runner.
|
||||
*/
|
||||
|
||||
/*
|
||||
| Called when the inotify file descriptor became ready.
|
||||
| Reads it contents and forwards all received events
|
||||
| to the runner.
|
||||
*/
|
||||
static void
|
||||
inotify_ready(lua_State *L, struct observance *obs)
|
||||
inotify_ready(
|
||||
lua_State *L,
|
||||
struct observance *obs
|
||||
)
|
||||
{
|
||||
if (obs->fd != inotify_fd) {
|
||||
logstring("Error", "Internal, inotify_fd != ob->fd");
|
||||
exit(-1); // ERRNO
|
||||
// sanity check
|
||||
if( obs->fd != inotify_fd )
|
||||
{
|
||||
logstring( "Error", "internal failure, inotify_fd != obs->fd" );
|
||||
exit( -1 );
|
||||
}
|
||||
while(true) {
|
||||
|
||||
while( true )
|
||||
{
|
||||
ptrdiff_t len;
|
||||
int err;
|
||||
do {
|
||||
len = read (inotify_fd, readbuf, readbuf_size);
|
||||
len = read( inotify_fd, readbuf, readbuf_size );
|
||||
err = errno;
|
||||
if (len < 0 && err == EINVAL) {
|
||||
if( len < 0 && err == EINVAL )
|
||||
{
|
||||
// kernel > 2.6.21 indicates that way that way that
|
||||
// the buffer was too small to fit a filename.
|
||||
// double its size and try again. When using a lower
|
||||
|
@ -309,91 +437,134 @@ inotify_ready(lua_State *L, struct observance *obs)
|
|||
readbuf_size *= 2;
|
||||
readbuf = s_realloc(readbuf, readbuf_size);
|
||||
}
|
||||
} while(len < 0 && err == EINVAL);
|
||||
if (len == 0) {
|
||||
// nothing more inotify
|
||||
} while( len < 0 && err == EINVAL );
|
||||
|
||||
if( len == 0 )
|
||||
{
|
||||
// no more inotify events
|
||||
break;
|
||||
}
|
||||
if (len < 0) {
|
||||
|
||||
if (len < 0)
|
||||
{
|
||||
if (err == EAGAIN) {
|
||||
// nothing more inotify
|
||||
break;
|
||||
} else {
|
||||
printlogf(L, "Error", "Read fail on inotify");
|
||||
exit(-1); // ERRNO
|
||||
}
|
||||
else
|
||||
{
|
||||
printlogf(
|
||||
L, "Error",
|
||||
"Read fail on inotify"
|
||||
);
|
||||
exit( -1 );
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
int i = 0;
|
||||
while (i < len && !hup && !term) {
|
||||
while( i < len && !hup && !term )
|
||||
{
|
||||
struct inotify_event *event =
|
||||
(struct inotify_event *) &readbuf[i];
|
||||
handle_event(L, event);
|
||||
i += sizeof(struct inotify_event) + event->len;
|
||||
( struct inotify_event * )
|
||||
(readbuf + i);
|
||||
|
||||
handle_event( L, event );
|
||||
|
||||
i += sizeof( struct inotify_event ) + event->len;
|
||||
}
|
||||
}
|
||||
if (!move_event) {
|
||||
|
||||
if( !move_event )
|
||||
{
|
||||
// give it a pause if not endangering splitting a move
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// checks if there is an unary MOVE_FROM left in the buffer
|
||||
if (move_event) {
|
||||
logstring("Inotify", "icore, handling unary move from.");
|
||||
handle_event(L, NULL);
|
||||
if( move_event )
|
||||
{
|
||||
logstring(
|
||||
"Inotify",
|
||||
"handling unary move from."
|
||||
);
|
||||
handle_event( L, NULL );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* registers inotify functions.
|
||||
*/
|
||||
|
||||
/*
|
||||
| Registers the inotify functions.
|
||||
*/
|
||||
extern void
|
||||
register_inotify(lua_State *L)
|
||||
register_inotify( lua_State *L )
|
||||
{
|
||||
lua_pushstring(L, "inotify");
|
||||
luaL_register(L, "inotify", linotfylib);
|
||||
lua_compat_register( L, LSYNCD_INOTIFYLIBNAME, linotfylib );
|
||||
}
|
||||
|
||||
/**
|
||||
* closes inotify
|
||||
*/
|
||||
|
||||
/*
|
||||
| Cleans up the inotify handling.
|
||||
*/
|
||||
static void
|
||||
inotify_tidy(struct observance *obs)
|
||||
inotify_tidy( struct observance *obs )
|
||||
{
|
||||
if (obs->fd != inotify_fd) {
|
||||
logstring("Error", "Internal, inotify_fd != ob->fd");
|
||||
exit(-1); // ERRNO
|
||||
if( obs->fd != inotify_fd )
|
||||
{
|
||||
logstring(
|
||||
"Error",
|
||||
"internal failure: inotify_fd != ob->fd"
|
||||
);
|
||||
|
||||
exit( -1 );
|
||||
}
|
||||
close(inotify_fd);
|
||||
free(readbuf);
|
||||
|
||||
close( inotify_fd );
|
||||
|
||||
free( readbuf );
|
||||
|
||||
readbuf = NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* opens and initalizes inotify.
|
||||
*/
|
||||
/*
|
||||
| Initalizes inotify handling
|
||||
*/
|
||||
extern void
|
||||
open_inotify(lua_State *L)
|
||||
open_inotify( lua_State *L )
|
||||
{
|
||||
if (readbuf) {
|
||||
logstring("Error",
|
||||
"internal fail, inotify readbuf!=NULL in open_inotify()")
|
||||
exit(-1); // ERRNO
|
||||
if( readbuf )
|
||||
{
|
||||
logstring(
|
||||
"Error",
|
||||
"internal failure, inotify readbuf != NULL in open_inotify()"
|
||||
)
|
||||
exit(-1);
|
||||
}
|
||||
readbuf = s_malloc(readbuf_size);
|
||||
|
||||
inotify_fd = inotify_init();
|
||||
if (inotify_fd < 0) {
|
||||
printlogf(L, "Error",
|
||||
"Cannot access inotify monitor! (%d:%s)",
|
||||
errno, strerror(errno));
|
||||
exit(-1); // ERRNO
|
||||
readbuf = s_malloc( readbuf_size );
|
||||
|
||||
inotify_fd = inotify_init( );
|
||||
|
||||
if( inotify_fd < 0 )
|
||||
{
|
||||
printlogf(
|
||||
L,
|
||||
"Error",
|
||||
"Cannot access inotify monitor! ( %d : %s )",
|
||||
errno, strerror(errno)
|
||||
);
|
||||
exit( -1 );
|
||||
}
|
||||
printlogf(L, "Inotify", "inotify fd = %d", inotify_fd);
|
||||
|
||||
close_exec_fd(inotify_fd);
|
||||
non_block_fd(inotify_fd);
|
||||
observe_fd(inotify_fd, inotify_ready, NULL, inotify_tidy, NULL);
|
||||
printlogf(
|
||||
L, "Inotify",
|
||||
"inotify fd = %d",
|
||||
inotify_fd
|
||||
);
|
||||
|
||||
close_exec_fd( inotify_fd );
|
||||
non_block_fd( inotify_fd );
|
||||
observe_fd( inotify_fd, inotify_ready, NULL, inotify_tidy, NULL );
|
||||
}
|
||||
|
||||
|
|
38
lsyncd.h
38
lsyncd.h
|
@ -12,10 +12,14 @@
|
|||
#define LSYNCD_H
|
||||
|
||||
// some older machines need this to see pselect
|
||||
#define _DEFAULT_SOURCE 1
|
||||
#define _BSD_SOURCE 1
|
||||
#define _XOPEN_SOURCE 700
|
||||
#define _DARWIN_C_SOURCE 1
|
||||
|
||||
#define LUA_COMPAT_ALL
|
||||
#define LUA_COMPAT_5_1
|
||||
|
||||
// includes needed for headerfile
|
||||
#include "config.h"
|
||||
|
||||
|
@ -26,6 +30,23 @@
|
|||
#define LUA_USE_APICHECK 1
|
||||
#include <lua.h>
|
||||
|
||||
#define LSYNCD_LIBNAME "lsyncd"
|
||||
#define LSYNCD_INOTIFYLIBNAME "inotify"
|
||||
|
||||
/*
|
||||
| Workaround to register a library for different lua versions.
|
||||
*/
|
||||
#if LUA_VERSION_NUM > 502
|
||||
#define lua_compat_register( L, name, lib ) \
|
||||
{ \
|
||||
lua_newtable((L)); \
|
||||
luaL_setfuncs((L), (lib), 0); \
|
||||
}
|
||||
#else
|
||||
#define lua_compat_register( L, name, lib ) \
|
||||
{luaL_register( (L), (name), (lib) );}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Lsyncd runtime configuration
|
||||
*/
|
||||
|
@ -36,6 +57,7 @@ extern struct settings {
|
|||
int log_facility; // The syslog facility
|
||||
int log_level; // -1 logs everything, 0 normal mode, LOG_ERROR errors only.
|
||||
bool nodaemon; // True if Lsyncd shall not daemonize.
|
||||
bool onepass; // True if Lsyncd should exit after first sync pass
|
||||
char * pidfile; // If not NULL Lsyncd writes its pid into this file.
|
||||
|
||||
} settings;
|
||||
|
@ -136,26 +158,18 @@ extern void observe_fd(
|
|||
// stops the core to observe a file descriptor
|
||||
extern void nonobserve_fd(int fd);
|
||||
|
||||
/**
|
||||
/*
|
||||
* inotify
|
||||
*/
|
||||
#ifdef LSYNCD_WITH_INOTIFY
|
||||
#ifdef WITH_INOTIFY
|
||||
extern void register_inotify(lua_State *L);
|
||||
extern void open_inotify(lua_State *L);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* fanotify
|
||||
*/
|
||||
#ifdef LSYNCD_WITH_FANOTIFY
|
||||
extern void register_fanotify(lua_State *L);
|
||||
extern void open_fanotify(lua_State *L);
|
||||
#endif
|
||||
|
||||
/**
|
||||
/*
|
||||
* /dev/fsevents
|
||||
*/
|
||||
#ifdef LSYNCD_WITH_FSEVENTS
|
||||
#ifdef WITH_FSEVENTS
|
||||
extern void open_fsevents(lua_State *L);
|
||||
#endif
|
||||
|
||||
|
|
7370
lsyncd.lua
7370
lsyncd.lua
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,15 @@
|
|||
|
||||
# ax_subst_l.m4 - Substitute every var in the given comma seperated list -*-Autoconf-*-
|
||||
#
|
||||
# Copyright (C) 2012 Dennis Schridde
|
||||
#
|
||||
# This file is free software; the authors give
|
||||
# unlimited permission to copy and/or distribute it, with or without
|
||||
# modifications, as long as this notice is preserved.
|
||||
|
||||
# serial 1
|
||||
|
||||
# Substitute every var in the given comma seperated list
|
||||
AC_DEFUN([AX_SUBST_L],[
|
||||
m4_foreach([__var__], [$@], [AC_SUBST(__var__)])
|
||||
])
|
|
@ -1,42 +1,55 @@
|
|||
#!/usr/bin/lua
|
||||
-- a heavy duty test.
|
||||
-- makes thousends of random changes to the source tree
|
||||
|
||||
require('posix')
|
||||
dofile('tests/testlib.lua')
|
||||
require( 'posix' )
|
||||
|
||||
cwriteln('****************************************************************')
|
||||
cwriteln(' Testing default.direct with random data activity ')
|
||||
cwriteln('****************************************************************')
|
||||
dofile( 'tests/testlib.lua' )
|
||||
|
||||
local tdir, srcdir, trgdir = mktemps()
|
||||
cwriteln( '****************************************************************' )
|
||||
cwriteln( ' Testing default.direct with random data activity ' )
|
||||
cwriteln( '****************************************************************' )
|
||||
|
||||
local tdir, srcdir, trgdir = mktemps( )
|
||||
|
||||
-- makes some startup data
|
||||
churn(srcdir, 10)
|
||||
churn( srcdir, 10, false )
|
||||
|
||||
local logs = {'-log', 'Exec', '-log', 'Delay' }
|
||||
local logs = { }
|
||||
--local logs = {'-log', 'Exec', '-log', 'Delay' }
|
||||
local pid = spawn(
|
||||
'./lsyncd',
|
||||
'-nodaemon',
|
||||
'-direct', srcdir, trgdir,
|
||||
unpack(logs)
|
||||
table.unpack( logs )
|
||||
)
|
||||
|
||||
cwriteln('waiting for Lsyncd to startup')
|
||||
posix.sleep(1)
|
||||
cwriteln( 'waiting for Lsyncd to startup' )
|
||||
posix.sleep( 1 )
|
||||
|
||||
churn(srcdir, 500)
|
||||
churn( srcdir, 500, false )
|
||||
|
||||
cwriteln('waiting for Lsyncd to finish its jobs.')
|
||||
posix.sleep(10)
|
||||
cwriteln( 'waiting for Lsyncd to finish its jobs.' )
|
||||
posix.sleep( 10 )
|
||||
|
||||
cwriteln('killing the Lsyncd daemon')
|
||||
posix.kill(pid)
|
||||
local _, exitmsg, lexitcode = posix.wait(lpid)
|
||||
cwriteln('Exitcode of Lsyncd = ',exitmsg,' ',lexitcode)
|
||||
cwriteln( 'killing the Lsyncd daemon' )
|
||||
posix.kill( pid )
|
||||
|
||||
exitcode = os.execute('diff -r '..srcdir..' '..trgdir)
|
||||
cwriteln('Exitcode of diff = "', exitcode, '"')
|
||||
local _, exitmsg, lexitcode = posix.wait( pid )
|
||||
cwriteln( 'Exitcode of Lsyncd = ', exitmsg, ' ', lexitcode )
|
||||
|
||||
if exitcode ~= 0 then os.exit(1) else os.exit(0) end
|
||||
local result, code = execute( 'diff -r ' .. srcdir .. ' ' .. trgdir )
|
||||
|
||||
if result == 'exit'
|
||||
then
|
||||
cwriteln( 'Exitcode of diff = ', code )
|
||||
else
|
||||
cwriteln( 'Signal terminating diff = ', code )
|
||||
end
|
||||
|
||||
if code ~= 0
|
||||
then
|
||||
os.exit( 1 )
|
||||
else
|
||||
os.exit( 0 )
|
||||
end
|
||||
|
||||
|
|
|
@ -1,42 +1,57 @@
|
|||
#!/usr/bin/lua
|
||||
-- a heavy duty test.
|
||||
-- makes thousends of random changes to the source tree
|
||||
require("posix")
|
||||
dofile("tests/testlib.lua")
|
||||
|
||||
cwriteln("****************************************************************")
|
||||
cwriteln(" Testing default.rsync with random data activity")
|
||||
cwriteln("****************************************************************")
|
||||
require( 'posix' )
|
||||
dofile( 'tests/testlib.lua' )
|
||||
|
||||
local tdir, srcdir, trgdir = mktemps()
|
||||
cwriteln( '****************************************************************' )
|
||||
cwriteln( ' Testing default.rsync with random data activity' )
|
||||
cwriteln( '****************************************************************' )
|
||||
|
||||
local tdir, srcdir, trgdir = mktemps( )
|
||||
|
||||
-- makes some startup data
|
||||
churn(srcdir, 100)
|
||||
churn( srcdir, 100, true )
|
||||
|
||||
local logs = {}
|
||||
-- logs = {"-log", "Delay", "-log", "Fsevents" }
|
||||
local pid = spawn("./lsyncd", "-nodaemon", "-delay", "5",
|
||||
"-rsync", srcdir, trgdir, unpack(logs))
|
||||
local logs = { }
|
||||
-- logs = { "-log", "Delay", "-log", "Fsevents" }
|
||||
local pid = spawn(
|
||||
'./lsyncd',
|
||||
'-nodaemon',
|
||||
'-delay', '5',
|
||||
'-rsync', srcdir, trgdir,
|
||||
table.unpack( logs )
|
||||
)
|
||||
|
||||
cwriteln("waiting for Lsyncd to startup")
|
||||
posix.sleep(1)
|
||||
cwriteln( 'waiting for Lsyncd to startup' )
|
||||
|
||||
churn(srcdir, 500)
|
||||
posix.sleep( 1 )
|
||||
|
||||
cwriteln("waiting for Lsyncd to finish its jobs.")
|
||||
posix.sleep(10)
|
||||
churn( srcdir, 500, false )
|
||||
|
||||
cwriteln("killing the Lsyncd daemon")
|
||||
posix.kill(pid)
|
||||
local _, exitmsg, lexitcode = posix.wait(lpid)
|
||||
cwriteln("Exitcode of Lsyncd = ", exitmsg, " ", lexitcode)
|
||||
cwriteln( 'waiting for Lsyncd to finish its jobs.' )
|
||||
|
||||
exitcode = os.execute("diff -r "..srcdir.." "..trgdir)
|
||||
cwriteln("Exitcode of diff = '", exitcode, "'")
|
||||
if exitcode ~= 0 then
|
||||
os.exit(1)
|
||||
posix.sleep( 10 )
|
||||
|
||||
cwriteln( 'killing the Lsyncd daemon' )
|
||||
|
||||
posix.kill( pid )
|
||||
local _, exitmsg, lexitcode = posix.wait( pid )
|
||||
cwriteln( 'Exitcode of Lsyncd = ', exitmsg, ' ', lexitcode )
|
||||
|
||||
local result, code = execute( 'diff -r ' .. srcdir .. ' ' .. trgdir )
|
||||
|
||||
if result == 'exit'
|
||||
then
|
||||
cwriteln( 'Exitcode of diff = ', code )
|
||||
else
|
||||
os.exit(0)
|
||||
cwriteln( 'Signal terminating diff = ', code )
|
||||
end
|
||||
|
||||
if code ~= 0
|
||||
then
|
||||
os.exit( 1 )
|
||||
else
|
||||
os.exit( 0 )
|
||||
end
|
||||
|
||||
|
|
|
@ -1,46 +1,65 @@
|
|||
#!/usr/bin/lua
|
||||
-- a heavy duty test.
|
||||
-- makes thousends of random changes to the source tree
|
||||
require("posix")
|
||||
dofile("tests/testlib.lua")
|
||||
|
||||
cwriteln("****************************************************************");
|
||||
cwriteln(" Testing default.rsyncssh with random data activity");
|
||||
cwriteln("****************************************************************");
|
||||
cwriteln("( this test needs passwordless ssh localhost access )");
|
||||
cwriteln("( for current user )");
|
||||
require( 'posix' )
|
||||
|
||||
dofile( 'tests/testlib.lua' )
|
||||
|
||||
cwriteln( '****************************************************************' )
|
||||
cwriteln( ' Testing default.rsyncssh with random data activity ' )
|
||||
cwriteln( '****************************************************************' )
|
||||
|
||||
local tdir, srcdir, trgdir = mktemps()
|
||||
|
||||
-- makes some startup data
|
||||
churn(srcdir, 100)
|
||||
churn( srcdir, 5, true )
|
||||
|
||||
local logs = {}
|
||||
logs = {"-log", "Delay" }
|
||||
logs = { '-log', 'Delay' }
|
||||
|
||||
local pid = spawn("./lsyncd", "-nodaemon", "-delay", "5",
|
||||
"-rsyncssh", srcdir, "localhost", trgdir,
|
||||
unpack(logs))
|
||||
local pid = spawn(
|
||||
'./lsyncd',
|
||||
'-nodaemon',
|
||||
'-delay',
|
||||
'5',
|
||||
'-sshopts',
|
||||
'-i tests/ssh/id_rsa -p 2468 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null',
|
||||
'-rsyncssh',
|
||||
srcdir,
|
||||
'localhost',
|
||||
trgdir,
|
||||
table.unpack(logs)
|
||||
)
|
||||
|
||||
cwriteln("waiting for Lsyncd to startup")
|
||||
posix.sleep(1)
|
||||
cwriteln( 'waiting for Lsyncd to startup' )
|
||||
posix.sleep( 1 )
|
||||
|
||||
churn(srcdir, 100)
|
||||
churn( srcdir, 150, false )
|
||||
|
||||
cwriteln("waiting for Lsyncd to finish its jobs.")
|
||||
posix.sleep(10)
|
||||
cwriteln( 'waiting for Lsyncd to finish its jobs.' )
|
||||
posix.sleep( 10 )
|
||||
|
||||
cwriteln( 'killing the Lsyncd daemon' )
|
||||
|
||||
cwriteln("killing the Lsyncd daemon")
|
||||
posix.kill(pid)
|
||||
local _, exitmsg, lexitcode = posix.wait(lpid)
|
||||
cwriteln("Exitcode of Lsyncd = ", exitmsg, " ", lexitcode)
|
||||
|
||||
exitcode = os.execute("diff -r "..srcdir.." "..trgdir)
|
||||
cwriteln("Exitcode of diff = '", exitcode, "'")
|
||||
if exitcode ~= 0 then
|
||||
os.exit(1)
|
||||
local _, exitmsg, lexitcode = posix.wait( pid )
|
||||
|
||||
cwriteln( 'Exitcode of Lsyncd = ', exitmsg, ' ', lexitcode )
|
||||
|
||||
local result, code = execute( 'diff -r ' .. srcdir .. ' ' .. trgdir )
|
||||
|
||||
if result == 'exit'
|
||||
then
|
||||
cwriteln( 'Exitcode of diff = ', code )
|
||||
else
|
||||
os.exit(0)
|
||||
cwriteln( 'Signal terminating diff = ', code )
|
||||
end
|
||||
|
||||
if code ~= 0
|
||||
then
|
||||
os.exit( 1 )
|
||||
else
|
||||
os.exit( 0 )
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
|
||||
function cleanup() {
|
||||
echo "** abort. cleanup ssh server"
|
||||
cd `dirname $BASH_SOURCE`/..
|
||||
# CLEANUP=`dirname $BASH_SOURCE`/../teardown.lua
|
||||
lua tests/teardown.lua
|
||||
}
|
||||
|
||||
trap cleanup INT EXIT
|
||||
|
||||
SRC=`pwd`
|
||||
BUILD_FOLDER=`mktemp -d`
|
||||
echo "Build folder: $BUILD_FOLDER"
|
||||
cd $BUILD_FOLDER
|
||||
cmake $SRC
|
||||
make VERBOSE=1
|
||||
make run-tests
|
||||
rm -rf $BUILD_FOLDER
|
|
@ -0,0 +1,149 @@
|
|||
require( 'posix' )
|
||||
dofile( 'tests/testlib.lua' )
|
||||
|
||||
cwriteln( '****************************************************************' )
|
||||
cwriteln( ' Testing crontab (rsync)' )
|
||||
cwriteln( '****************************************************************' )
|
||||
|
||||
local tdir, srcdir, trgdir = mktemps( )
|
||||
local logfile = tdir .. "log"
|
||||
local cfgfile = tdir .. "config.lua"
|
||||
local range = 5
|
||||
local log = {"-log", "all"}
|
||||
|
||||
writefile(cfgfile, [[
|
||||
settings {
|
||||
logfile = "]]..logfile..[[",
|
||||
nodaemon = true,
|
||||
}
|
||||
|
||||
sync {
|
||||
default.rsync,
|
||||
crontab = {
|
||||
-- trigger full sync every 1 minute
|
||||
"*/10 * * * * *",
|
||||
},
|
||||
source = "]]..srcdir..[[",
|
||||
action = function (inlet)
|
||||
local e = inlet.getEvent( );
|
||||
print("inhibit action ".. e.path.. " = " .. e.etype);
|
||||
if e.etype ~= "Full" then
|
||||
inlet.discardEvent(e);
|
||||
return;
|
||||
end
|
||||
return default.rsync.action(inlet);
|
||||
end,
|
||||
target = "]]..trgdir..[[",
|
||||
delay = 2,
|
||||
delete = true,
|
||||
rsync = {
|
||||
verbose = true,
|
||||
inplace = true,
|
||||
_extra = {
|
||||
"-vv",
|
||||
"--info=progress2"
|
||||
}
|
||||
},
|
||||
filter = {
|
||||
'- /xb**',
|
||||
'+ /x**',
|
||||
'- /**',
|
||||
},
|
||||
}
|
||||
]])
|
||||
|
||||
-- writes all files
|
||||
local function writefiles
|
||||
( )
|
||||
writefile( srcdir .. 'xbc', 'xbc' )
|
||||
writefile( srcdir .. 'xcc', 'xcc' )
|
||||
writefile( srcdir .. 'yaa', 'yaa' )
|
||||
posix.mkdir( srcdir .. 'xbx' )
|
||||
writefile( srcdir .. 'xbx/a', 'xbxa' )
|
||||
posix.mkdir( srcdir .. 'xcx' )
|
||||
writefile( srcdir .. 'xcx/x', 'xcxx' )
|
||||
writefile( srcdir .. 'xda', 'xda', '700' )
|
||||
writefile( srcdir .. 'xdb', 'xdb', '755' )
|
||||
end
|
||||
|
||||
-- test all files
|
||||
local function testfiles
|
||||
( )
|
||||
testfile( trgdir .. 'xbc', false )
|
||||
testfile( trgdir .. 'xcc', true )
|
||||
testfile( trgdir .. 'yaa', false )
|
||||
testfile( trgdir .. 'xbx/a', false )
|
||||
testfile( trgdir .. 'xcx/x', true )
|
||||
testfile( trgdir .. 'xda', true )
|
||||
testfile( trgdir .. 'xdb', true )
|
||||
end
|
||||
|
||||
|
||||
cwriteln( 'testing crontab' )
|
||||
|
||||
writefiles( )
|
||||
|
||||
cwriteln( 'starting Lsyncd' )
|
||||
|
||||
local pid = spawn( './lsyncd', cfgfile, '-log', 'all' )
|
||||
|
||||
cwriteln( 'waiting for Lsyncd to start' )
|
||||
|
||||
posix.sleep( 3 )
|
||||
|
||||
cwriteln( 'testing filters after startup' )
|
||||
|
||||
testfiles( )
|
||||
|
||||
cwriteln( 'ok, removing sources' )
|
||||
|
||||
if srcdir:sub( 1,4 ) ~= '/tmp'
|
||||
then
|
||||
-- just to make sure before rm -rf
|
||||
cwriteln( 'exit before drama, srcdir is "', srcdir, '"' )
|
||||
|
||||
os.exit( 1 )
|
||||
end
|
||||
|
||||
os.execute( 'rm -rf '..srcdir..'/*' )
|
||||
|
||||
cwriteln( 'waiting for Lsyncd to remove destination' )
|
||||
|
||||
posix.sleep( 20 )
|
||||
|
||||
local result, code = execute( 'diff -urN ' .. srcdir .. ' ' .. trgdir )
|
||||
|
||||
if result ~= 'exit' or code ~= 0
|
||||
then
|
||||
cwriteln( 'fail, target directory not empty!' )
|
||||
posix.kill( pid )
|
||||
|
||||
os.exit( 1 )
|
||||
end
|
||||
|
||||
cwriteln( 'writing files after startup' )
|
||||
|
||||
writefiles( )
|
||||
|
||||
cwriteln( 'waiting for Lsyncd to transmit changes' )
|
||||
|
||||
posix.sleep( 20 )
|
||||
|
||||
testfiles( )
|
||||
|
||||
cwriteln( 'killing started Lsyncd' )
|
||||
|
||||
posix.kill( pid )
|
||||
local _, exitmsg, exitcode = posix.wait( pid )
|
||||
|
||||
cwriteln( 'Exitcode of Lsyncd = ', exitmsg, ' ', exitcode );
|
||||
|
||||
if exitcode == 143
|
||||
then
|
||||
cwriteln( 'OK' )
|
||||
os.exit( 0 )
|
||||
else
|
||||
os.exit( 1 )
|
||||
end
|
||||
|
||||
-- TODO remove temp
|
|
@ -1,111 +1,152 @@
|
|||
#!/usr/bin/lua
|
||||
require("posix")
|
||||
dofile("tests/testlib.lua")
|
||||
require( 'posix' )
|
||||
dofile( 'tests/testlib.lua' )
|
||||
|
||||
cwriteln("****************************************************************")
|
||||
cwriteln(" Testing excludes ")
|
||||
cwriteln("****************************************************************")
|
||||
cwriteln( '****************************************************************' )
|
||||
cwriteln( ' Testing excludes (rsync)' )
|
||||
cwriteln(' ****************************************************************' )
|
||||
|
||||
local tdir, srcdir, trgdir = mktemps()
|
||||
local logfile = tdir .. "log"
|
||||
local cfgfile = tdir .. "config.lua"
|
||||
local tdir, srcdir, trgdir = mktemps( )
|
||||
local logfile = tdir .. 'log'
|
||||
local cfgfile = tdir .. 'config.lua'
|
||||
local range = 5
|
||||
local log = {"-log", "all"}
|
||||
local log = { '-log', 'all' }
|
||||
|
||||
writefile(cfgfile, [[
|
||||
settings = {
|
||||
logfile = "]]..logfile..[[",
|
||||
nodaemon = true,
|
||||
delay = 3,
|
||||
settings {
|
||||
logfile = "]]..logfile..[[",
|
||||
nodaemon = true,
|
||||
}
|
||||
|
||||
sync {
|
||||
default.rsync,
|
||||
default.rsync,
|
||||
source = "]]..srcdir..[[",
|
||||
target = "]]..trgdir..[[",
|
||||
delay = 3,
|
||||
exclude = {
|
||||
"erf",
|
||||
"erf",
|
||||
"/eaf",
|
||||
"erd/",
|
||||
"/ead/",
|
||||
},
|
||||
}]]);
|
||||
}]])
|
||||
|
||||
-- writes all files
|
||||
local function writefiles()
|
||||
posix.mkdir(srcdir .. "d");
|
||||
writefile(srcdir .. "erf", "erf");
|
||||
writefile(srcdir .. "eaf", "erf");
|
||||
writefile(srcdir .. "erd", "erd");
|
||||
writefile(srcdir .. "ead", "ead");
|
||||
writefile(srcdir .. "d/erf", "erf");
|
||||
writefile(srcdir .. "d/eaf", "erf");
|
||||
writefile(srcdir .. "d/erd", "erd");
|
||||
writefile(srcdir .. "d/ead", "ead");
|
||||
local function writefiles
|
||||
( )
|
||||
posix.mkdir( srcdir .. 'd' )
|
||||
writefile( srcdir .. 'erf', 'erf' )
|
||||
writefile( srcdir .. 'eaf', 'erf' )
|
||||
writefile( srcdir .. 'erd', 'erd' )
|
||||
writefile( srcdir .. 'ead', 'ead' )
|
||||
writefile( srcdir .. 'd/erf', 'erf' )
|
||||
writefile( srcdir .. 'd/eaf', 'erf' )
|
||||
writefile( srcdir .. 'd/erd', 'erd' )
|
||||
writefile( srcdir .. 'd/ead', 'ead' )
|
||||
end
|
||||
|
||||
-- test if the filename exists, fails if this is different to expect
|
||||
local function testfile(filename, expect)
|
||||
local stat, err = posix.stat(filename)
|
||||
if stat and not expect then
|
||||
cwriteln("failure: ",filename," should be excluded");
|
||||
os.exit(1);
|
||||
--
|
||||
-- Tests if the filename exists
|
||||
-- fails if this is different to expect.
|
||||
--
|
||||
local function testfile
|
||||
(
|
||||
filename,
|
||||
expect
|
||||
)
|
||||
local stat, err = posix.stat( filename )
|
||||
|
||||
if stat and not expect
|
||||
then
|
||||
cwriteln( 'failure: ', filename, ' should be excluded')
|
||||
|
||||
os.exit( 1 )
|
||||
end
|
||||
if not stat and expect then
|
||||
cwriteln("failure: ",filename," should not be excluded");
|
||||
os.exit(1);
|
||||
|
||||
if not stat and expect
|
||||
then
|
||||
cwriteln( 'failure: ', filename, ' should not be excluded' )
|
||||
os.exit( 1 )
|
||||
end
|
||||
end
|
||||
|
||||
-- test all files
|
||||
local function testfiles()
|
||||
testfile(trgdir .. "erf", false);
|
||||
testfile(trgdir .. "eaf", false);
|
||||
testfile(trgdir .. "erd", true);
|
||||
testfile(trgdir .. "ead", true);
|
||||
testfile(trgdir .. "d/erf", false);
|
||||
testfile(trgdir .. "d/eaf", true);
|
||||
testfile(trgdir .. "d/erd", true);
|
||||
testfile(trgdir .. "d/ead", true);
|
||||
local function testfiles
|
||||
( )
|
||||
testfile( trgdir .. 'erf', false )
|
||||
testfile( trgdir .. 'eaf', false )
|
||||
testfile( trgdir .. 'erd', true )
|
||||
testfile( trgdir .. 'ead', true )
|
||||
testfile( trgdir .. 'd/erf', false )
|
||||
testfile( trgdir .. 'd/eaf', true )
|
||||
testfile( trgdir .. 'd/erd', true )
|
||||
testfile( trgdir .. 'd/ead', true )
|
||||
end
|
||||
|
||||
|
||||
cwriteln("testing startup excludes");
|
||||
writefiles();
|
||||
cwriteln("starting Lsyncd");
|
||||
local pid = spawn("./lsyncd", cfgfile);
|
||||
cwriteln("waiting for Lsyncd to start");
|
||||
posix.sleep(3)
|
||||
cwriteln("testing excludes after startup");
|
||||
testfiles();
|
||||
cwriteln("ok, removing sources");
|
||||
if srcdir:sub(1,4) ~= "/tmp" then
|
||||
cwriteln( 'testing startup excludes' )
|
||||
|
||||
writefiles( )
|
||||
|
||||
cwriteln( 'starting Lsyncd' )
|
||||
|
||||
local pid = spawn( './lsyncd', cfgfile, '-log', 'all' )
|
||||
|
||||
cwriteln( 'waiting for Lsyncd to start' )
|
||||
|
||||
posix.sleep( 3 )
|
||||
|
||||
cwriteln( 'testing excludes after startup' )
|
||||
|
||||
testfiles( )
|
||||
|
||||
cwriteln( 'ok, removing sources' )
|
||||
|
||||
if srcdir:sub( 1,4 ) ~= '/tmp'
|
||||
then
|
||||
-- just to make sure before rm -rf
|
||||
cwriteln("exist before drama, srcdir is '", srcdir, "'");
|
||||
os.exit(1);
|
||||
end
|
||||
os.execute("rm -rf "..srcdir.."/*");
|
||||
cwriteln("waiting for Lsyncd to remove destination");
|
||||
posix.sleep(5);
|
||||
if os.execute("diff -urN "..srcdir.." "..trgdir) ~= 0 then
|
||||
cwriteln("fail, target directory not empty!");
|
||||
os.exit(1);
|
||||
cwriteln( 'exit before drama, srcdir is "', srcdir, '"' )
|
||||
|
||||
os.exit( 1 )
|
||||
end
|
||||
|
||||
cwriteln("writing files after startup");
|
||||
writefiles();
|
||||
cwriteln("waiting for Lsyncd to transmit changes");
|
||||
posix.sleep(5);
|
||||
testfiles();
|
||||
os.execute( 'rm -rf '..srcdir..'/*' )
|
||||
|
||||
cwriteln("killing started Lsyncd");
|
||||
posix.kill(pid);
|
||||
local _, exitmsg, lexitcode = posix.wait(lpid);
|
||||
cwriteln("Exitcode of Lsyncd = ", exitmsg, " ", lexitcode);
|
||||
posix.sleep(1);
|
||||
if lexitcode == 0 then
|
||||
cwriteln("OK");
|
||||
cwriteln( 'waiting for Lsyncd to remove destination' )
|
||||
|
||||
posix.sleep( 5 )
|
||||
|
||||
local result, code = execute( 'diff -urN ' .. srcdir .. ' ' .. trgdir )
|
||||
|
||||
if result ~= 'exit' or code ~= 0
|
||||
then
|
||||
cwriteln( 'fail, target directory not empty!' )
|
||||
|
||||
os.exit( 1 )
|
||||
end
|
||||
|
||||
cwriteln( 'writing files after startup' )
|
||||
|
||||
writefiles( )
|
||||
|
||||
cwriteln( 'waiting for Lsyncd to transmit changes' )
|
||||
|
||||
posix.sleep( 5 )
|
||||
|
||||
testfiles( )
|
||||
|
||||
cwriteln( 'killing started Lsyncd' )
|
||||
|
||||
posix.kill( pid )
|
||||
local _, exitmsg, exitcode = posix.wait( pid )
|
||||
|
||||
cwriteln( 'Exitcode of Lsyncd = ', exitmsg, ' ', exitcode );
|
||||
|
||||
if exitcode == 143
|
||||
then
|
||||
cwriteln( 'OK' )
|
||||
os.exit( 0 )
|
||||
else
|
||||
os.exit( 1 )
|
||||
end
|
||||
os.exit(lexitcode);
|
||||
|
||||
-- TODO remove temp
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
#!/usr/bin/lua
|
||||
require( 'posix' )
|
||||
dofile( 'tests/testlib.lua' )
|
||||
|
||||
require('posix')
|
||||
dofile('tests/testlib.lua')
|
||||
cwriteln( '****************************************************************' );
|
||||
cwriteln( ' Testing excludes (rsyncssh)' );
|
||||
cwriteln( '****************************************************************' );
|
||||
cwriteln( ' (this test needs passwordless ssh localhost access ' );
|
||||
cwriteln( ' for current user)' );
|
||||
|
||||
cwriteln('****************************************************************');
|
||||
cwriteln(' Testing excludes');
|
||||
cwriteln('****************************************************************');
|
||||
cwriteln(' (this test needs passwordless ssh localhost access ');
|
||||
cwriteln(' for current user)');
|
||||
|
||||
local tdir, srcdir, trgdir = mktemps()
|
||||
local tdir, srcdir, trgdir = mktemps( )
|
||||
local logfile = tdir .. 'log'
|
||||
local cfgfile = tdir .. 'config.lua'
|
||||
local range = 5
|
||||
|
@ -17,19 +15,27 @@ local log = {}
|
|||
log = {'-log', 'all'}
|
||||
|
||||
writefile(cfgfile, [[
|
||||
settings = {
|
||||
logfile = ']]..logfile..[[',
|
||||
nodaemon = true,
|
||||
delay = 3,
|
||||
settings {
|
||||
logfile = ']]..logfile..[[',
|
||||
nodaemon = true
|
||||
}
|
||||
|
||||
sync {
|
||||
default.rsyncssh,
|
||||
default.rsyncssh,
|
||||
delay = 3,
|
||||
host = 'localhost',
|
||||
ssh = {
|
||||
port= 2468,
|
||||
identityFile = "]] .. script_path() .. [[/ssh/id_rsa",
|
||||
options = {
|
||||
StrictHostKeyChecking="no",
|
||||
UserKnownHostsFile="/dev/null",
|
||||
},
|
||||
},
|
||||
source = ']]..srcdir..[[',
|
||||
targetdir = ']]..trgdir..[[',
|
||||
exclude = {
|
||||
'erf',
|
||||
'erf',
|
||||
'/eaf',
|
||||
'erd/',
|
||||
'/ead/',
|
||||
|
@ -37,80 +43,102 @@ sync {
|
|||
}]]);
|
||||
|
||||
-- writes all files
|
||||
local function writefiles()
|
||||
posix.mkdir(srcdir .. 'd');
|
||||
writefile(srcdir .. 'erf', 'erf');
|
||||
writefile(srcdir .. 'eaf', 'erf');
|
||||
writefile(srcdir .. 'erd', 'erd');
|
||||
writefile(srcdir .. 'ead', 'ead');
|
||||
writefile(srcdir .. 'd/erf', 'erf');
|
||||
writefile(srcdir .. 'd/eaf', 'erf');
|
||||
writefile(srcdir .. 'd/erd', 'erd');
|
||||
writefile(srcdir .. 'd/ead', 'ead');
|
||||
local function writefiles
|
||||
( )
|
||||
posix.mkdir( srcdir .. 'd' )
|
||||
writefile( srcdir .. 'erf', 'erf' )
|
||||
writefile( srcdir .. 'eaf', 'erf' )
|
||||
writefile( srcdir .. 'erd', 'erd' )
|
||||
writefile( srcdir .. 'ead', 'ead' )
|
||||
writefile( srcdir .. 'd/erf', 'erf' )
|
||||
writefile( srcdir .. 'd/eaf', 'erf' )
|
||||
writefile( srcdir .. 'd/erd', 'erd' )
|
||||
writefile( srcdir .. 'd/ead', 'ead' )
|
||||
end
|
||||
|
||||
-- test if the filename exists, fails if this is different to expect
|
||||
local function testfile(filename, expect)
|
||||
local stat, err = posix.stat(filename)
|
||||
if stat and not expect then
|
||||
cwriteln('failure: ',filename,' should be excluded');
|
||||
os.exit(1);
|
||||
local function testfile
|
||||
(
|
||||
filename,
|
||||
expect
|
||||
)
|
||||
local stat, err = posix.stat( filename )
|
||||
|
||||
if stat and not expect
|
||||
then
|
||||
cwriteln( 'failure: ',filename,' should be excluded' );
|
||||
os.exit( 1 );
|
||||
end
|
||||
if not stat and expect then
|
||||
cwriteln('failure: ',filename,' should not be excluded');
|
||||
os.exit(1);
|
||||
|
||||
if not stat and expect
|
||||
then
|
||||
cwriteln( 'failure: ',filename,' should not be excluded' );
|
||||
os.exit( 1 );
|
||||
end
|
||||
end
|
||||
|
||||
-- test all files
|
||||
local function testfiles()
|
||||
testfile(trgdir .. 'erf', false);
|
||||
testfile(trgdir .. 'eaf', false);
|
||||
testfile(trgdir .. 'erd', true);
|
||||
testfile(trgdir .. 'ead', true);
|
||||
testfile(trgdir .. 'd/erf', false);
|
||||
testfile(trgdir .. 'd/eaf', true);
|
||||
testfile(trgdir .. 'd/erd', true);
|
||||
testfile(trgdir .. 'd/ead', true);
|
||||
local function testfiles
|
||||
( )
|
||||
testfile( trgdir .. 'erf', false )
|
||||
testfile( trgdir .. 'eaf', false )
|
||||
testfile( trgdir .. 'erd', true )
|
||||
testfile( trgdir .. 'ead', true )
|
||||
testfile( trgdir .. 'd/erf', false )
|
||||
testfile( trgdir .. 'd/eaf', true )
|
||||
testfile( trgdir .. 'd/erd', true )
|
||||
testfile( trgdir .. 'd/ead', true )
|
||||
end
|
||||
|
||||
|
||||
cwriteln('testing startup excludes');
|
||||
writefiles();
|
||||
cwriteln('starting Lsyncd');
|
||||
local pid = spawn('./lsyncd', cfgfile, unpack(log));
|
||||
cwriteln('waiting for Lsyncd to start');
|
||||
posix.sleep(10)
|
||||
cwriteln('testing excludes after startup');
|
||||
testfiles();
|
||||
cwriteln('ok, removing sources');
|
||||
if srcdir:sub(1,4) ~= '/tmp' then
|
||||
cwriteln( 'testing startup excludes' )
|
||||
writefiles( )
|
||||
cwriteln( 'starting Lsyncd' )
|
||||
local pid = spawn( './lsyncd', cfgfile, table.unpack( log ) )
|
||||
cwriteln( 'waiting for Lsyncd to start' )
|
||||
posix.sleep( 10 )
|
||||
cwriteln( 'testing excludes after startup' )
|
||||
testfiles( )
|
||||
cwriteln( 'ok, removing sources' )
|
||||
|
||||
if srcdir:sub(1,4) ~= '/tmp'
|
||||
then
|
||||
-- just to make sure before rm -rf
|
||||
cwriteln('exist before drama, srcdir is "', srcdir, '"');
|
||||
os.exit(1);
|
||||
end
|
||||
os.execute('rm -rf '..srcdir..'/*');
|
||||
cwriteln('waiting for Lsyncd to remove destination');
|
||||
posix.sleep(5);
|
||||
if os.execute('diff -urN '..srcdir..' '..trgdir) ~= 0 then
|
||||
cwriteln('fail, target directory not empty!');
|
||||
os.exit(1);
|
||||
cwriteln('exit before drama, srcdir is "', srcdir, '"')
|
||||
|
||||
os.exit( 1 )
|
||||
end
|
||||
|
||||
cwriteln('writing files after startup');
|
||||
writefiles();
|
||||
cwriteln('waiting for Lsyncd to transmit changes');
|
||||
posix.sleep(15);
|
||||
testfiles();
|
||||
os.execute( 'rm -rf ' .. srcdir .. '/*' )
|
||||
cwriteln( 'waiting for Lsyncd to remove destination' )
|
||||
posix.sleep( 5 )
|
||||
|
||||
cwriteln('killing started Lsyncd');
|
||||
posix.kill(pid);
|
||||
local _, exitmsg, lexitcode = posix.wait(lpid);
|
||||
cwriteln('Exitcode of Lsyncd = ', exitmsg, ' ', lexitcode);
|
||||
posix.sleep(1);
|
||||
if lexitcode == 0 then
|
||||
cwriteln('OK');
|
||||
local result, code = execute( 'diff -urN '..srcdir..' '..trgdir )
|
||||
|
||||
if result ~= 'exit' or code ~= 0
|
||||
then
|
||||
cwriteln( 'fail, target directory not empty!' );
|
||||
os.exit( 1 );
|
||||
end
|
||||
|
||||
cwriteln( 'writing files after startup' )
|
||||
writefiles( )
|
||||
cwriteln( 'waiting for Lsyncd to transmit changes' )
|
||||
posix.sleep( 15 )
|
||||
testfiles( )
|
||||
|
||||
cwriteln( 'killing started Lsyncd' )
|
||||
posix.kill( pid )
|
||||
local _, exitmsg, lexitcode = posix.wait( pid )
|
||||
cwriteln( 'Exitcode of Lsyncd = ', exitmsg, ' ', lexitcode )
|
||||
posix.sleep( 1 )
|
||||
|
||||
if lexitcode == 143
|
||||
then
|
||||
cwriteln( 'OK' );
|
||||
os.exit( 0 );
|
||||
else
|
||||
os.exit( 1 );
|
||||
end
|
||||
os.exit(lexitcode);
|
||||
|
||||
-- TODO remove temp
|
||||
|
|
|
@ -0,0 +1,146 @@
|
|||
require( 'posix' )
|
||||
dofile( 'tests/testlib.lua' )
|
||||
|
||||
cwriteln( '****************************************************************' )
|
||||
cwriteln( ' Testing filters (rsync)' )
|
||||
cwriteln( '****************************************************************' )
|
||||
|
||||
local tdir, srcdir, trgdir = mktemps( )
|
||||
local logfile = tdir .. "log"
|
||||
local cfgfile = tdir .. "config.lua"
|
||||
local range = 5
|
||||
local log = {"-log", "all"}
|
||||
|
||||
writefile(cfgfile, [[
|
||||
settings {
|
||||
logfile = ']]..logfile..[[',
|
||||
nodaemon = true,
|
||||
}
|
||||
|
||||
sync {
|
||||
default.rsync,
|
||||
source = ']]..srcdir..[[',
|
||||
target = ']]..trgdir..[[',
|
||||
delay = 3,
|
||||
filter = {
|
||||
'- /ab**',
|
||||
'+ /a**',
|
||||
'- /**',
|
||||
},
|
||||
}]])
|
||||
|
||||
-- writes all files
|
||||
local function writefiles
|
||||
( )
|
||||
writefile( srcdir .. 'abc', 'abc' )
|
||||
writefile( srcdir .. 'acc', 'acc' )
|
||||
writefile( srcdir .. 'baa', 'baa' )
|
||||
posix.mkdir( srcdir .. 'abx' )
|
||||
writefile( srcdir .. 'abx/a', 'abxa' )
|
||||
posix.mkdir( srcdir .. 'acx' )
|
||||
writefile( srcdir .. 'acx/x', 'acxx' )
|
||||
end
|
||||
|
||||
--
|
||||
-- Tests if the filename exists
|
||||
-- fails if this is different to expect.
|
||||
--
|
||||
local function testfile
|
||||
(
|
||||
filename,
|
||||
expect
|
||||
)
|
||||
local stat, err = posix.stat( filename )
|
||||
|
||||
if stat and not expect
|
||||
then
|
||||
cwriteln( 'failure: ', filename, ' should be filtered')
|
||||
|
||||
os.exit( 1 )
|
||||
end
|
||||
|
||||
if not stat and expect
|
||||
then
|
||||
cwriteln( 'failure: ', filename, ' should not be filtered' )
|
||||
os.exit( 1 )
|
||||
end
|
||||
end
|
||||
|
||||
-- test all files
|
||||
local function testfiles
|
||||
( )
|
||||
testfile( trgdir .. 'abc', false )
|
||||
testfile( trgdir .. 'acc', true )
|
||||
testfile( trgdir .. 'baa', false )
|
||||
testfile( trgdir .. 'abx/a', false )
|
||||
testfile( trgdir .. 'acx/x', true )
|
||||
end
|
||||
|
||||
|
||||
cwriteln( 'testing startup filters' )
|
||||
|
||||
writefiles( )
|
||||
|
||||
cwriteln( 'starting Lsyncd' )
|
||||
|
||||
local pid = spawn( './lsyncd', cfgfile, '-log', 'all' )
|
||||
|
||||
cwriteln( 'waiting for Lsyncd to start' )
|
||||
|
||||
posix.sleep( 3 )
|
||||
|
||||
cwriteln( 'testing filters after startup' )
|
||||
|
||||
testfiles( )
|
||||
|
||||
cwriteln( 'ok, removing sources' )
|
||||
|
||||
if srcdir:sub( 1,4 ) ~= '/tmp'
|
||||
then
|
||||
-- just to make sure before rm -rf
|
||||
cwriteln( 'exit before drama, srcdir is "', srcdir, '"' )
|
||||
|
||||
os.exit( 1 )
|
||||
end
|
||||
|
||||
os.execute( 'rm -rf '..srcdir..'/*' )
|
||||
|
||||
cwriteln( 'waiting for Lsyncd to remove destination' )
|
||||
|
||||
posix.sleep( 5 )
|
||||
|
||||
local result, code = execute( 'diff -urN ' .. srcdir .. ' ' .. trgdir )
|
||||
|
||||
if result ~= 'exit' or code ~= 0
|
||||
then
|
||||
cwriteln( 'fail, target directory not empty!' )
|
||||
|
||||
os.exit( 1 )
|
||||
end
|
||||
|
||||
cwriteln( 'writing files after startup' )
|
||||
|
||||
writefiles( )
|
||||
|
||||
cwriteln( 'waiting for Lsyncd to transmit changes' )
|
||||
|
||||
posix.sleep( 5 )
|
||||
|
||||
testfiles( )
|
||||
|
||||
cwriteln( 'killing started Lsyncd' )
|
||||
|
||||
posix.kill( pid )
|
||||
local _, exitmsg, exitcode = posix.wait( pid )
|
||||
|
||||
cwriteln( 'Exitcode of Lsyncd = ', exitmsg, ' ', exitcode );
|
||||
|
||||
if exitcode == 143
|
||||
then
|
||||
cwriteln( 'OK' )
|
||||
os.exit( 0 )
|
||||
else
|
||||
os.exit( 1 )
|
||||
end
|
||||
|
||||
-- TODO remove temp
|
|
@ -1,34 +1,45 @@
|
|||
#!/usr/bin/lua
|
||||
require("posix")
|
||||
dofile("tests/testlib.lua")
|
||||
require( 'posix' )
|
||||
dofile( 'tests/testlib.lua' )
|
||||
|
||||
cwriteln("****************************************************************")
|
||||
cwriteln(" Testing layer 4 default rsync with simulated data activity ")
|
||||
cwriteln("****************************************************************")
|
||||
cwriteln( '****************************************************************' )
|
||||
cwriteln( ' Testing layer 4 default rsync with simulated data activity ' )
|
||||
cwriteln( '****************************************************************' )
|
||||
|
||||
local tdir, srcdir, trgdir = mktemps()
|
||||
local logfile = tdir .. "log"
|
||||
local logfile = tdir .. 'log'
|
||||
local range = 5
|
||||
local log = {"-log", "all"}
|
||||
local log = { '-log', 'all' }
|
||||
|
||||
posix.mkdir(srcdir .. "d")
|
||||
posix.mkdir(srcdir .. "d/e")
|
||||
if not writefile(srcdir .. "d/e/f1", 'test') then
|
||||
os.exit(1)
|
||||
posix.mkdir( srcdir .. 'd' )
|
||||
posix.mkdir( srcdir .. 'd/e' )
|
||||
|
||||
if not writefile( srcdir .. "d/e/f1", 'test' )
|
||||
then
|
||||
os.exit( 1 )
|
||||
end
|
||||
cwriteln("starting Lsyncd")
|
||||
cwriteln( 'starting Lsyncd' )
|
||||
|
||||
logs = {}
|
||||
local pid = spawn("./lsyncd", "-logfile", logfile, "-nodaemon", "-delay", "5",
|
||||
"-rsync", srcdir, trgdir, unpack(logs))
|
||||
cwriteln("waiting for lsyncd to start")
|
||||
posix.sleep(2)
|
||||
local logs = { }
|
||||
local pid =
|
||||
spawn(
|
||||
'./lsyncd',
|
||||
'-logfile', logfile,
|
||||
'-nodaemon',
|
||||
'-delay', '5',
|
||||
"-rsync", srcdir, trgdir,
|
||||
table.unpack( logs )
|
||||
)
|
||||
|
||||
cwriteln("* making some data")
|
||||
cwriteln("* creating d[x]/e/f2")
|
||||
for i = 1, range do
|
||||
cwriteln("[cp -r "..srcdir.."d "..srcdir.."d"..i.."]")
|
||||
os.execute("cp -r "..srcdir.."d "..srcdir.."d"..i)
|
||||
cwriteln( 'waiting for lsyncd to start' )
|
||||
posix.sleep( 2 )
|
||||
|
||||
cwriteln( '* making some data' )
|
||||
cwriteln( '* creating d[x]/e/f2' )
|
||||
|
||||
for i = 1, range
|
||||
do
|
||||
cwriteln( '[cp -r ' .. srcdir .. 'd ' .. srcdir .. 'd' .. i .. ']' )
|
||||
os.execute( 'cp -r ' .. srcdir .. 'd ' .. srcdir .. 'd' .. i )
|
||||
end
|
||||
|
||||
-- mkdir -p "$S"/m/n
|
||||
|
@ -38,23 +49,31 @@ end
|
|||
-- echo 'test4' > "$S"/m${i}/n/another
|
||||
-- done
|
||||
|
||||
cwriteln("* waiting for Lsyncd to do its job.")
|
||||
posix.sleep(10)
|
||||
cwriteln( '* waiting for Lsyncd to do its job.' )
|
||||
posix.sleep( 10 )
|
||||
|
||||
cwriteln("* killing Lsyncd")
|
||||
cwriteln( '* killing Lsyncd' )
|
||||
|
||||
posix.kill(pid)
|
||||
local _, exitmsg, lexitcode = posix.wait(lpid)
|
||||
cwriteln("Exitcode of Lsyncd = ", exitmsg, " ", lexitcode)
|
||||
posix.sleep(1)
|
||||
posix.kill( pid )
|
||||
local _, exitmsg, lexitcode = posix.wait(pid)
|
||||
cwriteln( 'Exitcode of Lsyncd = ', exitmsg, ' ', lexitcode)
|
||||
posix.sleep( 1 )
|
||||
|
||||
cwriteln("* differences:")
|
||||
exitcode = os.execute("diff -urN "..srcdir.." "..trgdir)
|
||||
cwriteln("Exitcode of diff = '", exitcode, "'")
|
||||
if exitcode ~= 0 then
|
||||
os.exit(1)
|
||||
cwriteln( '* differences:' )
|
||||
local result, code = execute( 'diff -urN ' .. srcdir .. ' ' .. trgdir )
|
||||
|
||||
if result == 'exit'
|
||||
then
|
||||
cwriteln( 'Exitcode of diff = "', code, '"')
|
||||
else
|
||||
os.exit(0)
|
||||
cwriteln( 'Signal terminating diff = "', code, '"')
|
||||
end
|
||||
|
||||
if result ~= 'exit' or code ~= 0
|
||||
then
|
||||
os.exit( 1 )
|
||||
else
|
||||
os.exit( 0 )
|
||||
end
|
||||
|
||||
-- TODO remove temp
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
#!/usr/bin/lua
|
||||
require("posix")
|
||||
dofile("tests/testlib.lua")
|
||||
require( 'posix' )
|
||||
dofile( 'tests/testlib.lua' )
|
||||
|
||||
cwriteln("****************************************************************")
|
||||
cwriteln(" Testing Lsyncd scheduler ")
|
||||
cwriteln("****************************************************************")
|
||||
cwriteln( '****************************************************************' )
|
||||
cwriteln( ' Testing Lsyncd scheduler ' )
|
||||
cwriteln( '****************************************************************' )
|
||||
|
||||
local tdir, srcdir, trgdir = mktemps()
|
||||
local logfile = tdir .. "log"
|
||||
|
@ -12,10 +11,10 @@ local cfgfile = tdir .. "config.lua"
|
|||
local logs = {"-log", "all" }
|
||||
|
||||
writefile(cfgfile, [[
|
||||
settings = {
|
||||
logfile = "]]..logfile..[[",
|
||||
settings {
|
||||
logfile = "]]..logfile..[[",
|
||||
log = all,
|
||||
nodaemon = true,
|
||||
nodaemon = true,
|
||||
maxProcesses = 1
|
||||
}
|
||||
|
||||
|
@ -47,30 +46,39 @@ sync {ccircuit, source ="]]..srcdir..[[", target = "]]..trgdir..[["}
|
|||
|
||||
|
||||
-- test if the filename exists, fails if this is different to expect
|
||||
local function testfile(filename)
|
||||
local function testfile
|
||||
(
|
||||
filename
|
||||
)
|
||||
local stat, err = posix.stat(filename)
|
||||
if not stat then
|
||||
cwriteln("failure: ",filename," missing");
|
||||
os.exit(1);
|
||||
|
||||
if not stat
|
||||
then
|
||||
cwriteln( 'failure: ', filename, ' missing' )
|
||||
os.exit( 1 )
|
||||
end
|
||||
end
|
||||
|
||||
cwriteln("starting Lsyncd");
|
||||
local pid = spawn("./lsyncd", cfgfile, unpack(logs));
|
||||
cwriteln("waiting for Lsyncd to do a few cycles");
|
||||
posix.sleep(30)
|
||||
cwriteln("look if every circle got a chance to run");
|
||||
testfile(srcdir.."a")
|
||||
testfile(srcdir.."b")
|
||||
testfile(srcdir.."c")
|
||||
cwriteln("killing started Lsyncd");
|
||||
posix.kill(pid);
|
||||
local _, exitmsg, lexitcode = posix.wait(lpid);
|
||||
cwriteln("Exitcode of Lsyncd = ", exitmsg, " ", lexitcode);
|
||||
cwriteln( 'starting Lsyncd' )
|
||||
local pid = spawn( './lsyncd', cfgfile, table.unpack( logs ) )
|
||||
cwriteln( 'waiting for Lsyncd to do a few cycles' )
|
||||
posix.sleep( 30 )
|
||||
cwriteln( 'look if every circle got a chance to run' )
|
||||
testfile( srcdir..'a' )
|
||||
testfile( srcdir..'b' )
|
||||
testfile( srcdir..'c' )
|
||||
cwriteln( 'killing started Lsyncd' )
|
||||
posix.kill( pid )
|
||||
local _, exitmsg, lexitcode = posix.wait( pid )
|
||||
cwriteln( 'Exitcode of Lsyncd = ', exitmsg, ' ', lexitcode)
|
||||
posix.sleep(1);
|
||||
if lexitcode == 0 then
|
||||
cwriteln("OK");
|
||||
|
||||
if lexitcode == 143
|
||||
then
|
||||
cwriteln( 'OK' )
|
||||
os.exit( 0 )
|
||||
else
|
||||
os.exit( 1 )
|
||||
end
|
||||
os.exit(lexitcode);
|
||||
|
||||
-- TODO remove temp
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
-- a heavy duty test.
|
||||
-- makes thousends of random changes to the source tree
|
||||
|
||||
require( 'posix' )
|
||||
|
||||
dofile( 'tests/testlib.lua' )
|
||||
cwriteln( ' Start Testsuite ' )
|
||||
|
||||
startSshd()
|
|
@ -0,0 +1,8 @@
|
|||
-- a heavy duty test.
|
||||
-- makes thousends of random changes to the source tree
|
||||
|
||||
require( 'posix' )
|
||||
|
||||
dofile( 'tests/testlib.lua' )
|
||||
|
||||
stopSshd()
|
|
@ -0,0 +1,38 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
if [ -z $1 ]; then
|
||||
echo "usage: test_actions [directory]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
set -x
|
||||
|
||||
BLOBB1=$(realpath /tmp/src/../blobb1)
|
||||
|
||||
if [ ! -e $BLOBB1 ]; then
|
||||
echo "create outside blobb $BLOBB1"
|
||||
dd count=50 bs=1M if=/dev/urandom of=$BLOBB1
|
||||
echo done
|
||||
fi
|
||||
|
||||
while true; do
|
||||
mkdir -p $1/testdir
|
||||
sleep 3
|
||||
touch $1/testfile
|
||||
sleep 2
|
||||
echo "blubb" >> $1/testfile
|
||||
sleep 2
|
||||
mv $1/testfile $1/testdir
|
||||
sleep 2
|
||||
ln $BLOBB1 $1/blubb1
|
||||
ln $BLOBB1 $1/testdir/blubb2
|
||||
ln $BLOBB1 $1/blubb3
|
||||
sleep 30
|
||||
rm $1/testdir/testfile
|
||||
sleep 2
|
||||
rm -rf $1/testdir
|
||||
sleep 1
|
||||
rm $1/blubb1
|
||||
rm $1/blubb3
|
||||
sleep 5
|
||||
done
|
|
@ -1,136 +1,333 @@
|
|||
--- @diagnostic disable: lowercase-global, need-check-nil
|
||||
|
||||
-- common testing environment
|
||||
require('posix')
|
||||
posix = require( 'posix' )
|
||||
string = require( 'string' )
|
||||
|
||||
local sys_stat = require "posix.sys.stat"
|
||||
|
||||
-- escape codes to colorize output on terminal
|
||||
local c1='\027[47;34m'
|
||||
|
||||
local c0='\027[0m'
|
||||
|
||||
---
|
||||
-- writes colorized
|
||||
--
|
||||
function cwriteln(...)
|
||||
io.write(c1, ...)
|
||||
io.write(c0, '\n')
|
||||
-- compatibility with 5.1
|
||||
if table.unpack == nil then
|
||||
--- @diagnostic disable-next-line: deprecated
|
||||
table.unpack = unpack
|
||||
end
|
||||
|
||||
-----
|
||||
-- initializes the pseudo random generator
|
||||
-- if environemnt 'SEED' is set, use that as seed.
|
||||
local seed = os.getenv('SEED') or os.time()
|
||||
math.randomseed(seed)
|
||||
cwriteln('random seed: ', seed)
|
||||
--
|
||||
-- Writes colorized.
|
||||
--
|
||||
function cwriteln
|
||||
(...)
|
||||
io.write( c1, '++ ', ... )
|
||||
io.write( c0, '\n' )
|
||||
end
|
||||
|
||||
local lver = string.gmatch(_VERSION, "Lua (%d).(%d)")
|
||||
_LUA_VERSION_MAJOR, _LUA_VERSION_MINOR = lver()
|
||||
_LUA_VERSION_MAJOR = tonumber(_LUA_VERSION_MAJOR)
|
||||
_LUA_VERSION_MINOR = tonumber(_LUA_VERSION_MINOR)
|
||||
|
||||
-- Compatibility execute function
|
||||
function execute(...)
|
||||
if _LUA_VERSION_MAJOR <= 5 and
|
||||
_LUA_VERSION_MINOR < 2 then
|
||||
local rv = os.execute(...)
|
||||
return "exit", rv
|
||||
else
|
||||
local ok, why, code = os.execute(...)
|
||||
return why, code
|
||||
end
|
||||
end
|
||||
|
||||
-----
|
||||
-- creates a tmp directory
|
||||
--
|
||||
-- @returns the name of the directory
|
||||
-- Initializes the pseudo random generator
|
||||
--
|
||||
function mktempd()
|
||||
local f = io.popen('mktemp -td ltest.XXX', 'r')
|
||||
local s = f:read('*a')
|
||||
f:close()
|
||||
s = s:gsub('[\n\r]+', ' ')
|
||||
s = s:match('^%s*(.-)%s*$')
|
||||
-- If environment variable 'SEED' is set,
|
||||
-- that one is used seed.
|
||||
--
|
||||
local seed = tonumber(os.getenv( 'SEED')) or os.time( )
|
||||
math.randomseed( seed )
|
||||
|
||||
cwriteln( 'random seed: ', seed )
|
||||
|
||||
--
|
||||
-- Creates a tmp directory.
|
||||
--
|
||||
-- Returns the name of the directory.
|
||||
--
|
||||
function mktempd
|
||||
( )
|
||||
local f = io.popen( 'mktemp -td ltest.XXX', 'r' )
|
||||
|
||||
if f == nil then
|
||||
return error("can't create testing directory")
|
||||
end
|
||||
|
||||
local s = f:read( '*a' )
|
||||
|
||||
f:close( )
|
||||
|
||||
s = s:gsub( '[\n\r]+', ' ' )
|
||||
|
||||
s = s:match( '^%s*(.-)%s*$' )
|
||||
|
||||
return s
|
||||
end
|
||||
|
||||
-----
|
||||
-- creates a tmp directory with the
|
||||
-- typical lsyncd test architecture
|
||||
--
|
||||
-- @returns path of tmpdir
|
||||
-- path of srcdir
|
||||
-- path of trgdir
|
||||
-- Creates a tmp directory with the
|
||||
-- typical lsyncd test architecture.
|
||||
--
|
||||
|
||||
function mktemps()
|
||||
local tdir = mktempd()..'/'
|
||||
cwriteln('using ', tdir, ' as test root')
|
||||
-- returns path of tmpdir
|
||||
-- path of srcdir
|
||||
-- path of trgdir
|
||||
--
|
||||
function mktemps
|
||||
( )
|
||||
local tdir = mktempd() .. '/'
|
||||
cwriteln( 'using ', tdir, ' as test root' )
|
||||
local srcdir = tdir..'src/'
|
||||
local trgdir = tdir..'trg/'
|
||||
posix.mkdir(srcdir)
|
||||
posix.mkdir(trgdir)
|
||||
posix.mkdir( srcdir )
|
||||
posix.mkdir( trgdir )
|
||||
return tdir, srcdir, trgdir
|
||||
end
|
||||
|
||||
----
|
||||
-- Writes a file with 'text' in it.
|
||||
--
|
||||
-- Writes a file with 'text' in it
|
||||
-- and adds a newline.
|
||||
--
|
||||
function writefile(filename, text)
|
||||
local f = io.open(filename, 'w')
|
||||
if not f then
|
||||
cwriteln('Cannot open "'..filename..'" for writing.')
|
||||
function writefile
|
||||
(
|
||||
filename,
|
||||
text,
|
||||
mode
|
||||
)
|
||||
local f = io.open( filename, 'w' )
|
||||
|
||||
if not f
|
||||
then
|
||||
cwriteln( 'Cannot open "'..filename..'" for writing.' )
|
||||
return false
|
||||
end
|
||||
f:write(text)
|
||||
f:write('\n')
|
||||
f:close()
|
||||
|
||||
f:write( text )
|
||||
f:write( '\n' )
|
||||
f:close( )
|
||||
|
||||
if mode ~= nil then
|
||||
posix.chmod(filename, mode)
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
-----
|
||||
-- spawns a subprocess.
|
||||
function splitpath(P)
|
||||
local i = #P
|
||||
local ch = P:sub(i,i)
|
||||
while i > 0 and ch ~= "/" do
|
||||
i = i - 1
|
||||
ch = P:sub(i,i)
|
||||
end
|
||||
if i == 0 then
|
||||
return '',P
|
||||
else
|
||||
return P:sub(1,i-1), P:sub(i+1)
|
||||
end
|
||||
end
|
||||
|
||||
function isabs(p)
|
||||
return string.sub(p, 1, 2) == "/"
|
||||
end
|
||||
|
||||
function abspath(P,pwd)
|
||||
P = P:gsub('[\\/]$','')
|
||||
if not isabs(P) then
|
||||
local rv = posix.unistd.getcwd() .. "/" .. P
|
||||
return rv
|
||||
end
|
||||
return P
|
||||
end
|
||||
|
||||
|
||||
function script_path()
|
||||
-- local str = debug.getinfo(2, "S").source:sub(2)
|
||||
-- return str:match("(.*/)")
|
||||
local dir, file = splitpath(abspath(debug.getinfo(1).short_src))
|
||||
return dir
|
||||
end
|
||||
|
||||
function which(exec)
|
||||
local path = os.getenv("PATH")
|
||||
for match in (path..':'):gmatch("(.-)"..':') do
|
||||
local fname = match..'/'..exec
|
||||
local s = sys_stat.stat(fname)
|
||||
if s ~= nil then
|
||||
return fname
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--
|
||||
-- @returns the processes pid
|
||||
-- Starts test ssh server
|
||||
--
|
||||
function startSshd()
|
||||
-- local f = io.open(script_path() .. "ssh/sshd.pid", 'r')
|
||||
|
||||
-- if f
|
||||
-- then
|
||||
-- return false
|
||||
-- end
|
||||
|
||||
cwriteln(arg[0])
|
||||
cwriteln(script_path() .. "ssh/sshd_config")
|
||||
|
||||
local sshdPath = script_path() .. "/ssh/"
|
||||
|
||||
if posix.stat( sshdPath ) == nil then
|
||||
cwriteln("setup ssh server in " .. sshdPath)
|
||||
posix.mkdir(sshdPath)
|
||||
os.execute("ssh-keygen -t rsa -N '' -f" .. sshdPath .. "id_rsa")
|
||||
os.execute("cp ".. sshdPath .. "id_rsa.pub ".. sshdPath .. "authorized_keys")
|
||||
os.execute("ssh-keygen -t rsa -N '' -f ".. sshdPath .. "ssh_host_rsa_key")
|
||||
cwriteln("done")
|
||||
end
|
||||
|
||||
local f = io.open( sshdPath .. "sshd_config", 'w')
|
||||
local cfg = [[
|
||||
Port 2468
|
||||
HostKey ]] .. sshdPath .. [[ssh_host_rsa_key
|
||||
AuthorizedKeysFile ]] .. sshdPath .. [[authorized_keys
|
||||
ChallengeResponseAuthentication no
|
||||
UsePAM no
|
||||
#Subsystem sftp /usr/lib/ssh/sftp-server
|
||||
PidFile ]] .. sshdPath .. [[sshd.pid
|
||||
]]
|
||||
cwriteln("Use ssh config: "..cfg)
|
||||
f:write(cfg)
|
||||
f:close()
|
||||
--local which = io.popen("which sshd")
|
||||
exePath = which('sshd')
|
||||
cwriteln("Using sshd: "..exePath)
|
||||
|
||||
local pid = spawn(exePath, "-D", "-e", "-f", sshdPath .. "sshd_config")
|
||||
cwriteln( 'spawned sshd server: ' .. pid)
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
function strip(s)
|
||||
return s:match "^%s*(.-)%s*$"
|
||||
end
|
||||
|
||||
--
|
||||
-- Stop test ssh server
|
||||
--
|
||||
function stopSshd()
|
||||
local f = io.open(script_path() .. "/ssh/sshd.pid", 'r')
|
||||
|
||||
if not f
|
||||
then
|
||||
return false
|
||||
end
|
||||
pid = strip(f:read("*a"))
|
||||
posix.kill(tonumber(pid))
|
||||
end
|
||||
|
||||
--
|
||||
-- Spawns a subprocess.
|
||||
--
|
||||
-- Returns the processes pid.
|
||||
--
|
||||
function spawn(...)
|
||||
args = {...}
|
||||
cwriteln('spawning: ', table.concat(args, ' '))
|
||||
local pid = posix.fork()
|
||||
if pid < 0 then
|
||||
cwriteln('Error, failed fork!')
|
||||
os.exit(-1)
|
||||
args = { ... }
|
||||
|
||||
cwriteln( 'spawning: ', table.concat( args, ' ' ) )
|
||||
|
||||
local pid = posix.fork( )
|
||||
|
||||
if pid < 0
|
||||
then
|
||||
cwriteln( 'Error, failed fork!' )
|
||||
|
||||
os.exit( -1 )
|
||||
end
|
||||
if pid == 0 then
|
||||
posix.exec(...)
|
||||
|
||||
if pid == 0
|
||||
then
|
||||
posix.exec( ... )
|
||||
-- should not return
|
||||
cwriteln('Error, failed to spawn: ', ...)
|
||||
os.exit(-1);
|
||||
|
||||
cwriteln( 'Error, failed to spawn: ', ... )
|
||||
|
||||
os.exit( -1 )
|
||||
end
|
||||
|
||||
return pid
|
||||
end
|
||||
|
||||
-----
|
||||
--
|
||||
-- Makes a lot of random data
|
||||
--
|
||||
-- @param rootdir ... the directory to make data in
|
||||
-- @param n ... roughly how much data action will done
|
||||
--
|
||||
function churn(rootdir, n)
|
||||
function churn
|
||||
(
|
||||
rootdir, -- the directory to make data in
|
||||
n, -- roughly how much data action will be done
|
||||
init -- if true init random data only, no sleeps or moves
|
||||
)
|
||||
-- all dirs created, indexed by integer and path
|
||||
root = {name=''}
|
||||
alldirs = {root}
|
||||
dirsWithFileI = {}
|
||||
dirsWithFileD = {}
|
||||
root = { name = '' }
|
||||
alldirs = { root }
|
||||
dirsWithFileI = { }
|
||||
dirsWithFileD = { }
|
||||
|
||||
-----
|
||||
--
|
||||
-- returns the name of a directory
|
||||
--
|
||||
-- name is internal recursive paramter, keep it nil.
|
||||
--
|
||||
local function dirname(dir, name)
|
||||
local function dirname
|
||||
(
|
||||
dir,
|
||||
name
|
||||
)
|
||||
name = name or ''
|
||||
if not dir then
|
||||
|
||||
if not dir
|
||||
then
|
||||
return name
|
||||
end
|
||||
return dirname(dir.parent, dir.name .. '/' .. name)
|
||||
|
||||
return dirname( dir.parent, dir.name .. '/' .. name )
|
||||
end
|
||||
|
||||
-----
|
||||
--
|
||||
-- Picks a random dir.
|
||||
--
|
||||
local function pickDir(notRoot)
|
||||
if notRoot then
|
||||
if #alldirs <= 2 then
|
||||
local function pickDir
|
||||
(
|
||||
notRoot
|
||||
)
|
||||
if notRoot
|
||||
then
|
||||
if #alldirs <= 2
|
||||
then
|
||||
return nil
|
||||
end
|
||||
return alldirs[math.random(2, #alldirs)]
|
||||
|
||||
return alldirs[ math.random( 2, #alldirs ) ]
|
||||
end
|
||||
return alldirs[math.random(#alldirs)]
|
||||
|
||||
return alldirs[ math.random( #alldirs ) ]
|
||||
end
|
||||
|
||||
----
|
||||
--
|
||||
-- Picks a random file.
|
||||
--
|
||||
-- Returns 3 values:
|
||||
|
@ -138,216 +335,371 @@ function churn(rootdir, n)
|
|||
-- * the filename
|
||||
-- * number of files in directory
|
||||
--
|
||||
local function pickFile()
|
||||
local function pickFile
|
||||
( )
|
||||
-- picks the random directory
|
||||
if #dirsWithFileI < 1 then
|
||||
if #dirsWithFileI < 1
|
||||
then
|
||||
return
|
||||
end
|
||||
local rdir = dirsWithFileI[math.random(1, #dirsWithFileI)]
|
||||
if not rdir then
|
||||
|
||||
local rdir = dirsWithFileI[ math.random( 1, #dirsWithFileI ) ]
|
||||
|
||||
if not rdir
|
||||
then
|
||||
return
|
||||
end
|
||||
|
||||
-- counts the files in there
|
||||
local c = 0
|
||||
for name, _ in pairs(rdir) do
|
||||
if #name == 2 then
|
||||
|
||||
for name, _ in pairs(rdir)
|
||||
do
|
||||
if #name == 2
|
||||
then
|
||||
c = c + 1
|
||||
end
|
||||
end
|
||||
|
||||
-- picks one file at random
|
||||
local cr = math.random(1, c)
|
||||
local cr = math.random( 1, c )
|
||||
|
||||
local fn
|
||||
for name, _ in pairs(rdir) do
|
||||
if #name == 2 then
|
||||
|
||||
for name, _ in pairs( rdir )
|
||||
do
|
||||
if #name == 2
|
||||
then
|
||||
-- filenames are 2 chars wide.
|
||||
cr = cr - 1
|
||||
if cr == 0 then
|
||||
if cr == 0
|
||||
then
|
||||
fn = name
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return rdir, fn, c
|
||||
end
|
||||
|
||||
-----
|
||||
--
|
||||
-- Removes a reference to a file
|
||||
--
|
||||
-- @param dir --- directory reference
|
||||
-- @param fn --- filename
|
||||
-- @param c --- number of files in dir
|
||||
-- @param dir -- directory reference
|
||||
-- @param fn -- filename
|
||||
-- @param c -- number of files in dir
|
||||
--
|
||||
local function rmFileReference(dir, fn, c)
|
||||
local function rmFileReference
|
||||
( dir, fn, c )
|
||||
dir[fn] = nil
|
||||
if c == 1 then
|
||||
|
||||
if c == 1
|
||||
then
|
||||
-- if last file from origin dir, it has no files anymore
|
||||
for i, v in ipairs(dirsWithFileI) do
|
||||
if v == dir then
|
||||
table.remove(dirsWithFileI, i)
|
||||
for i, v in ipairs( dirsWithFileI )
|
||||
do
|
||||
if v == dir
|
||||
then
|
||||
table.remove( dirsWithFileI, i )
|
||||
|
||||
break
|
||||
end
|
||||
end
|
||||
dirsWithFileD[dir] = nil
|
||||
|
||||
dirsWithFileD[ dir ] = nil
|
||||
end
|
||||
end
|
||||
|
||||
----
|
||||
--
|
||||
-- possible randomized behaviour.
|
||||
-- just gives it a pause
|
||||
--
|
||||
local function sleep()
|
||||
cwriteln('..zzz..')
|
||||
posix.sleep(1)
|
||||
local function sleep
|
||||
( )
|
||||
cwriteln( '..zzz..' )
|
||||
|
||||
posix.sleep( 1 )
|
||||
end
|
||||
|
||||
----
|
||||
--
|
||||
-- possible randomized behaviour.
|
||||
-- creates a directory
|
||||
--
|
||||
local function mkdir()
|
||||
local function mkdir
|
||||
( )
|
||||
-- chooses a random directory to create it into
|
||||
local rdir = pickDir()
|
||||
local rdir = pickDir( )
|
||||
|
||||
-- creates a new random one letter name
|
||||
local nn = string.char(96 + math.random(26))
|
||||
if not rdir[nn] then
|
||||
local nn = string.char( 96 + math.random( 26 ) )
|
||||
|
||||
if not rdir[nn]
|
||||
then
|
||||
local ndir = {
|
||||
name = nn,
|
||||
parent = rdir,
|
||||
}
|
||||
local dn = dirname(ndir)
|
||||
rdir[nn] = dn
|
||||
table.insert(alldirs, ndir)
|
||||
cwriteln('mkdir '..rootdir..dn)
|
||||
posix.mkdir(rootdir..dn)
|
||||
|
||||
local dn = dirname( ndir )
|
||||
|
||||
rdir[ nn ] = dn
|
||||
|
||||
table.insert( alldirs, ndir )
|
||||
|
||||
cwriteln( 'mkdir '..rootdir..dn )
|
||||
|
||||
posix.mkdir( rootdir..dn )
|
||||
end
|
||||
end
|
||||
|
||||
----
|
||||
-- possible randomized behaviour.
|
||||
--
|
||||
-- Possible randomized behaviour:
|
||||
-- Creates a file.
|
||||
--
|
||||
local function mkfile()
|
||||
local function mkfile
|
||||
( )
|
||||
-- chooses a random directory to create it into
|
||||
local rdir = pickDir()
|
||||
|
||||
-- creates a new random one letter name
|
||||
local nn = 'f'..string.char(96 + math.random(26))
|
||||
local fn = dirname(rdir) .. nn
|
||||
cwriteln('mkfile '..rootdir..fn)
|
||||
local nn = 'f'..string.char( 96 + math.random( 26 ) )
|
||||
|
||||
local fn = dirname( rdir ) .. nn
|
||||
|
||||
cwriteln( 'mkfile ' .. rootdir .. fn )
|
||||
|
||||
local f = io.open(rootdir..fn, 'w')
|
||||
if f then
|
||||
for i=1,10 do
|
||||
f:write(string.char(96 + math.random(26)))
|
||||
|
||||
if f
|
||||
then
|
||||
for i = 1, 10
|
||||
do
|
||||
f:write( string.char( 96 + math.random( 26 ) ) )
|
||||
end
|
||||
f:write('\n')
|
||||
f:close()
|
||||
rdir[nn]=true
|
||||
if not dirsWithFileD[rdir] then
|
||||
table.insert(dirsWithFileI, rdir)
|
||||
dirsWithFileD[rdir]=true
|
||||
|
||||
f:write( '\n' )
|
||||
|
||||
f:close( )
|
||||
|
||||
rdir[ nn ]=true
|
||||
|
||||
if not dirsWithFileD[ rdir ]
|
||||
then
|
||||
table.insert( dirsWithFileI, rdir )
|
||||
|
||||
dirsWithFileD[ rdir ]=true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
----
|
||||
-- possible randomized behaviour,
|
||||
-- moves a directory.
|
||||
--
|
||||
local function mvdir()
|
||||
if #alldirs <= 2 then
|
||||
-- Possible randomized behaviour:
|
||||
-- Moves a directory.
|
||||
--
|
||||
local function mvdir
|
||||
( )
|
||||
if #alldirs <= 2
|
||||
then
|
||||
return
|
||||
end
|
||||
|
||||
-- chooses a random directory to move
|
||||
local odir = pickDir(true)
|
||||
local odir = pickDir( true )
|
||||
|
||||
-- chooses a random directory to move to
|
||||
local tdir = pickDir()
|
||||
local tdir = pickDir( )
|
||||
|
||||
-- makes sure tdir is not a subdir of odir
|
||||
local dd = tdir
|
||||
while dd do
|
||||
if odir == dd then
|
||||
|
||||
while dd
|
||||
do
|
||||
if odir == dd
|
||||
then
|
||||
return
|
||||
end
|
||||
|
||||
dd = dd.parent
|
||||
end
|
||||
|
||||
-- origin name in the target dir already
|
||||
if tdir[odir.name] ~= nil then
|
||||
if tdir[odir.name] ~= nil
|
||||
then
|
||||
return
|
||||
end
|
||||
local on = dirname(odir)
|
||||
local tn = dirname(tdir)
|
||||
cwriteln('mvdir ',rootdir,on,' -> ',rootdir,tn,odir.name)
|
||||
os.rename(rootdir..on, rootdir..tn..odir.name)
|
||||
odir.parent[odir.name] = nil
|
||||
|
||||
local on = dirname( odir )
|
||||
|
||||
local tn = dirname( tdir )
|
||||
|
||||
cwriteln( 'mvdir ', rootdir,on, ' -> ', rootdir, tn, odir.name )
|
||||
|
||||
os.rename( rootdir..on, rootdir..tn..odir.name )
|
||||
|
||||
odir.parent[ odir.name ] = nil
|
||||
|
||||
odir.parent = tdir
|
||||
tdir[odir.name] = odir
|
||||
|
||||
tdir[ odir.name ] = odir
|
||||
end
|
||||
|
||||
----
|
||||
--
|
||||
-- possible randomized behaviour,
|
||||
-- moves a file.
|
||||
--
|
||||
local function mvfile()
|
||||
local odir, fn, c = pickFile()
|
||||
if not odir then
|
||||
local function mvfile
|
||||
( )
|
||||
local odir, fn, c = pickFile( )
|
||||
|
||||
if not odir
|
||||
then
|
||||
return
|
||||
end
|
||||
|
||||
-- picks a directory with a file at random
|
||||
-- picks a target directory at random
|
||||
local tdir = pickDir()
|
||||
local on = dirname(odir)
|
||||
local tn = dirname(tdir)
|
||||
cwriteln('mvfile ',rootdir,on,fn,' -> ',rootdir,tn,fn)
|
||||
os.rename(rootdir..on..fn, rootdir..tn..fn)
|
||||
rmFileReference(odir, fn, c)
|
||||
local tdir = pickDir( )
|
||||
|
||||
tdir[fn] = true
|
||||
if not dirsWithFileD[tdir] then
|
||||
dirsWithFileD[tdir] = true
|
||||
table.insert(dirsWithFileI, tdir)
|
||||
local on = dirname( odir )
|
||||
|
||||
local tn = dirname( tdir )
|
||||
|
||||
cwriteln( 'mvfile ', rootdir, on, fn, ' -> ', rootdir, tn, fn )
|
||||
|
||||
os.rename( rootdir..on..fn, rootdir..tn..fn )
|
||||
rmFileReference( odir, fn, c )
|
||||
|
||||
tdir[ fn ] = true
|
||||
|
||||
if not dirsWithFileD[ tdir ]
|
||||
then
|
||||
dirsWithFileD[ tdir ] = true
|
||||
|
||||
table.insert( dirsWithFileI, tdir )
|
||||
end
|
||||
end
|
||||
|
||||
----
|
||||
-- possible randomized behaviour,
|
||||
-- removes a file.
|
||||
--
|
||||
local function rmfile()
|
||||
local dir, fn, c = pickFile()
|
||||
if dir then
|
||||
local dn = dirname(dir)
|
||||
cwriteln('rmfile ',rootdir,dn,fn)
|
||||
posix.unlink(rootdir..dn..fn)
|
||||
rmFileReference(dir, fn, c)
|
||||
-- Possible randomized behaviour:
|
||||
-- Removes a file.
|
||||
--
|
||||
local function rmfile
|
||||
( )
|
||||
local dir, fn, c = pickFile( )
|
||||
|
||||
if dir
|
||||
then
|
||||
local dn = dirname( dir )
|
||||
|
||||
cwriteln( 'rmfile ', rootdir, dn, fn )
|
||||
|
||||
posix.unlink( rootdir..dn..fn )
|
||||
|
||||
rmFileReference( dir, fn, c )
|
||||
end
|
||||
end
|
||||
|
||||
local dice = {
|
||||
{ 10, sleep },
|
||||
{ 20, mkfile },
|
||||
{ 20, mkdir },
|
||||
{ 20, mvdir },
|
||||
{ 20, rmfile },
|
||||
}
|
||||
local dice
|
||||
|
||||
cwriteln('making random data')
|
||||
local ndice = 0
|
||||
for i, d in ipairs(dice) do
|
||||
ndice = ndice + d[1]
|
||||
d[1] = ndice
|
||||
if init
|
||||
then
|
||||
dice =
|
||||
{
|
||||
{ 10, mkfile },
|
||||
{ 10, mkdir },
|
||||
}
|
||||
else
|
||||
dice =
|
||||
{
|
||||
{ 50, sleep },
|
||||
{ 20, mkfile },
|
||||
{ 20, mkdir },
|
||||
{ 20, mvdir },
|
||||
{ 20, rmfile },
|
||||
}
|
||||
end
|
||||
|
||||
for ai=1,n do
|
||||
cwriteln( 'making random data' )
|
||||
|
||||
local ndice = 0
|
||||
|
||||
for i, d in ipairs( dice )
|
||||
do
|
||||
ndice = ndice + d[ 1 ]
|
||||
d[ 1 ] = ndice
|
||||
end
|
||||
|
||||
for ai = 1, n
|
||||
do
|
||||
-- throws a die what to do
|
||||
local acn = math.random(ndice)
|
||||
for i, d in ipairs(dice) do
|
||||
if acn <= d[1] then
|
||||
d[2]()
|
||||
local acn = math.random( ndice )
|
||||
|
||||
for i, d in ipairs( dice )
|
||||
do
|
||||
if acn <= d[ 1 ]
|
||||
then
|
||||
d[ 2 ]( )
|
||||
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- check if tables are equal
|
||||
function isTableEqual(o1, o2, ignore_mt)
|
||||
if o1 == o2 then return true end
|
||||
local o1Type = type(o1)
|
||||
local o2Type = type(o2)
|
||||
if o1Type ~= o2Type then return false end
|
||||
if o1Type ~= 'table' then return false end
|
||||
|
||||
if not ignore_mt then
|
||||
local mt1 = getmetatable(o1)
|
||||
if mt1 and mt1.__eq then
|
||||
--compare using built in method
|
||||
return o1 == o2
|
||||
end
|
||||
end
|
||||
|
||||
local keySet = {}
|
||||
|
||||
for key1, value1 in pairs(o1) do
|
||||
local value2 = o2[key1]
|
||||
if value2 == nil or isTableEqual(value1, value2, ignore_mt) == false then
|
||||
return false
|
||||
end
|
||||
keySet[key1] = true
|
||||
end
|
||||
|
||||
for key2, _ in pairs(o2) do
|
||||
if not keySet[key2] then return false end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
--
|
||||
-- Tests if the filename exists
|
||||
-- fails if this is different to expect.
|
||||
--
|
||||
function testfile
|
||||
(
|
||||
filename,
|
||||
expect
|
||||
)
|
||||
local stat, err = posix.stat( filename )
|
||||
|
||||
if stat and not expect
|
||||
then
|
||||
cwriteln( 'failure: ', filename, ' should be filtered')
|
||||
|
||||
os.exit( 1 )
|
||||
end
|
||||
|
||||
if not stat and expect
|
||||
then
|
||||
cwriteln( 'failure: ', filename, ' should not be filtered' )
|
||||
os.exit( 1 )
|
||||
end
|
||||
end
|
|
@ -0,0 +1,79 @@
|
|||
dofile( 'tests/testlib.lua' )
|
||||
|
||||
cwriteln( '****************************************************************' )
|
||||
cwriteln( ' Testing Utils Functions ' )
|
||||
cwriteln( '****************************************************************' )
|
||||
|
||||
assert(isTableEqual(
|
||||
lsyncd.splitQuotedString("-p 22 -i '/home/test/bla blu/id_rsa'"),
|
||||
{"-p", "22", "-i", "/home/test/bla blu/id_rsa"}
|
||||
))
|
||||
|
||||
-- test string replacement
|
||||
local testData = {
|
||||
localPort = 1234,
|
||||
localHost = "localhorst"
|
||||
}
|
||||
|
||||
assert(isTableEqual(
|
||||
substitudeCommands({"-p^doesNotExist", "2^localHostA", "-i '^localPort'"}, testData),
|
||||
{"-p^doesNotExist", "2localhorstA", "-i '1234'"}
|
||||
))
|
||||
|
||||
assert(
|
||||
substitudeCommands("-p^doesNotExist 2^localHostA -i '^localPort'", testData),
|
||||
"-p^doesNotExist 2localhorstA -i '1234'"
|
||||
)
|
||||
|
||||
|
||||
assert(type(lsyncd.get_free_port()) == "number")
|
||||
|
||||
local function testQueue()
|
||||
local q = Queue.new()
|
||||
q:push(1)
|
||||
q:push(2)
|
||||
q:push(3)
|
||||
q:push(4)
|
||||
assert(q:size(), 4)
|
||||
assert(q[1], 1)
|
||||
assert(q[4], 4)
|
||||
|
||||
q:remove(4)
|
||||
assert(q:size(), 3)
|
||||
assert(q[3], 3)
|
||||
assert(q[1], 1)
|
||||
|
||||
q:remove(1)
|
||||
assert(q:size(), 2)
|
||||
assert(q[3], 3)
|
||||
assert(q[2], 2)
|
||||
assert(q.first, 2)
|
||||
assert(q.last, 3)
|
||||
|
||||
q:push(5)
|
||||
assert(q:size(), 3)
|
||||
assert(q.last, 4)
|
||||
assert(q.first, 2)
|
||||
assert(q[4], 5)
|
||||
assert(q[3], 3)
|
||||
assert(q[2], 2)
|
||||
|
||||
q:remove(3)
|
||||
assert(q:size(), 2)
|
||||
assert(q.last, 3)
|
||||
assert(q.first, 2)
|
||||
assert(q[2], 2)
|
||||
assert(q[3], 5)
|
||||
|
||||
q:inject(23)
|
||||
assert(q:size(), 3)
|
||||
assert(q.last, 3)
|
||||
assert(q.first, 1)
|
||||
assert(q[1], 23)
|
||||
assert(q[2], 2)
|
||||
assert(q[3], 5)
|
||||
end
|
||||
|
||||
testQueue()
|
||||
|
||||
os.exit(0)
|
Loading…
Reference in New Issue