pax_global_header00006660000000000000000000000064125241754230014517gustar00rootroot0000000000000052 comment=ebc4ccdc518432ec1b00332767da84573621ee92 clsync-0.4.1/000077500000000000000000000000001252417542300130145ustar00rootroot00000000000000clsync-0.4.1/.doxygen000066400000000000000000002353521252417542300145040ustar00rootroot00000000000000# Doxyfile 1.8.4 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. # # All text after a double hash (##) is considered a comment and is placed # in front of the TAG it is preceding . # All text after a hash (#) is considered a comment and will be ignored. # The format is: # TAG = value [value, ...] # For lists items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (" "). #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- # This tag specifies the encoding used for all characters in the config file # that follow. The default is UTF-8 which is also the encoding used for all # text before the first occurrence of this tag. Doxygen uses libiconv (or the # iconv built into libc) for the transcoding. See # http://www.gnu.org/software/libiconv for the list of possible encodings. DOXYFILE_ENCODING = UTF-8 # The PROJECT_NAME tag is a single word (or sequence of words) that should # identify the project. Note that if you do not use Doxywizard you need # to put quotes around the project name if it contains spaces. PROJECT_NAME = "clsync" # The PROJECT_NUMBER tag can be used to enter a project or revision number. # This could be handy for archiving the generated documentation or # if some version control system is used. PROJECT_NUMBER = # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer # a quick idea about the purpose of the project. Keep the description short. PROJECT_BRIEF = # With the PROJECT_LOGO tag one can specify an logo or icon that is # included in the documentation. The maximum height of the logo should not # exceed 55 pixels and the maximum width should not exceed 200 pixels. # Doxygen will copy the logo to the output directory. PROJECT_LOGO = # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) # base path where the generated documentation will be put. # If a relative path is entered, it will be relative to the location # where doxygen was started. If left blank the current directory will be used. OUTPUT_DIRECTORY = doc/doxygen # If the CREATE_SUBDIRS tag is set to YES, then doxygen will create # 4096 sub-directories (in 2 levels) under the output directory of each output # format and will distribute the generated files over these directories. # Enabling this option can be useful when feeding doxygen a huge amount of # source files, where putting all generated files in the same directory would # otherwise cause performance problems for the file system. CREATE_SUBDIRS = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. # The default language is English, other supported languages are: # Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, # Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, # Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English # messages), Korean, Korean-en, Latvian, Lithuanian, Norwegian, Macedonian, # Persian, Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, # Slovak, Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. OUTPUT_LANGUAGE = English # If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will # include brief member descriptions after the members that are listed in # the file and class documentation (similar to JavaDoc). # Set to NO to disable this. BRIEF_MEMBER_DESC = YES # If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend # the brief description of a member or function before the detailed description. # Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. REPEAT_BRIEF = YES # This tag implements a quasi-intelligent brief description abbreviator # that is used to form the text in various listings. Each string # in this list, if found as the leading text of the brief description, will be # stripped from the text and the result after processing the whole list, is # used as the annotated text. Otherwise, the brief description is used as-is. # If left blank, the following values are used ("$name" is automatically # replaced with the name of the entity): "The $name class" "The $name widget" # "The $name file" "is" "provides" "specifies" "contains" # "represents" "a" "an" "the" ABBREVIATE_BRIEF = # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then # Doxygen will generate a detailed section even if there is only a brief # description. ALWAYS_DETAILED_SEC = NO # If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all # inherited members of a class in the documentation of that class as if those # members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. INLINE_INHERITED_MEMB = YES # If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full # path before files name in the file list and in the header files. If set # to NO the shortest path that makes the file name unique will be used. FULL_PATH_NAMES = YES # If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag # can be used to strip a user-defined part of the path. Stripping is # only done if one of the specified strings matches the left-hand part of # the path. The tag can be used to show relative paths in the file list. # If left blank the directory from which doxygen is run is used as the # path to strip. Note that you specify absolute paths here, but also # relative paths, which will be relative from the directory where doxygen is # started. STRIP_FROM_PATH = # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of # the path mentioned in the documentation of a class, which tells # the reader which header file to include in order to use a class. # If left blank only the name of the header file containing the class # definition is used. Otherwise one should specify the include paths that # are normally passed to the compiler using the -I flag. STRIP_FROM_INC_PATH = # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter # (but less readable) file names. This can be useful if your file system # doesn't support long names like on DOS, Mac, or CD-ROM. SHORT_NAMES = NO # If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen # will interpret the first line (until the first dot) of a JavaDoc-style # comment as the brief description. If set to NO, the JavaDoc # comments will behave just like regular Qt-style comments # (thus requiring an explicit @brief command for a brief description.) JAVADOC_AUTOBRIEF = NO # If the QT_AUTOBRIEF tag is set to YES then Doxygen will # interpret the first line (until the first dot) of a Qt-style # comment as the brief description. If set to NO, the comments # will behave just like regular Qt-style comments (thus requiring # an explicit \brief command for a brief description.) QT_AUTOBRIEF = NO # The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen # treat a multi-line C++ special comment block (i.e. a block of //! or /// # comments) as a brief description. This used to be the default behaviour. # The new default is to treat a multi-line C++ comment block as a detailed # description. Set this tag to YES if you prefer the old behaviour instead. MULTILINE_CPP_IS_BRIEF = NO # If the INHERIT_DOCS tag is set to YES (the default) then an undocumented # member inherits the documentation from any documented member that it # re-implements. INHERIT_DOCS = YES # If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce # a new page for each member. If set to NO, the documentation of a member will # be part of the file/class/namespace that contains it. SEPARATE_MEMBER_PAGES = NO # The TAB_SIZE tag can be used to set the number of spaces in a tab. # Doxygen uses this value to replace tabs by spaces in code fragments. TAB_SIZE = 4 # This tag can be used to specify a number of aliases that acts # as commands in the documentation. An alias has the form "name=value". # For example adding "sideeffect=\par Side Effects:\n" will allow you to # put the command \sideeffect (or @sideeffect) in the documentation, which # will result in a user-defined paragraph with heading "Side Effects:". # You can put \n's in the value part of an alias to insert newlines. ALIASES = # This tag can be used to specify a number of word-keyword mappings (TCL only). # A mapping has the form "name=value". For example adding # "class=itcl::class" will allow you to use the command class in the # itcl::class meaning. TCL_SUBST = # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C # sources only. Doxygen will then generate output that is more tailored for C. # For instance, some of the names that are used will be different. The list # of all members will be omitted, etc. OPTIMIZE_OUTPUT_FOR_C = YES # Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java # sources only. Doxygen will then generate output that is more tailored for # Java. For instance, namespaces will be presented as packages, qualified # scopes will look different, etc. OPTIMIZE_OUTPUT_JAVA = NO # Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran # sources only. Doxygen will then generate output that is more tailored for # Fortran. OPTIMIZE_FOR_FORTRAN = NO # Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL # sources. Doxygen will then generate output that is tailored for # VHDL. OPTIMIZE_OUTPUT_VHDL = NO # Doxygen selects the parser to use depending on the extension of the files it # parses. With this tag you can assign which parser to use for a given # extension. Doxygen has a built-in mapping, but you can override or extend it # using this tag. The format is ext=language, where ext is a file extension, # and language is one of the parsers supported by doxygen: IDL, Java, # Javascript, CSharp, C, C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, # C++. For instance to make doxygen treat .inc files as Fortran files (default # is PHP), and .f files as C (default is Fortran), use: inc=Fortran f=C. Note # that for custom extensions you also need to set FILE_PATTERNS otherwise the # files are not read by doxygen. EXTENSION_MAPPING = # If MARKDOWN_SUPPORT is enabled (the default) then doxygen pre-processes all # comments according to the Markdown format, which allows for more readable # documentation. See http://daringfireball.net/projects/markdown/ for details. # The output of markdown processing is further processed by doxygen, so you # can mix doxygen, HTML, and XML commands with Markdown formatting. # Disable only in case of backward compatibilities issues. MARKDOWN_SUPPORT = YES # When enabled doxygen tries to link words that correspond to documented # classes, or namespaces to their corresponding documentation. Such a link can # be prevented in individual cases by by putting a % sign in front of the word # or globally by setting AUTOLINK_SUPPORT to NO. AUTOLINK_SUPPORT = YES # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want # to include (a tag file for) the STL sources as input, then you should # set this tag to YES in order to let doxygen match functions declarations and # definitions whose arguments contain STL classes (e.g. func(std::string); v.s. # func(std::string) {}). This also makes the inheritance and collaboration # diagrams that involve STL classes more complete and accurate. BUILTIN_STL_SUPPORT = NO # If you use Microsoft's C++/CLI language, you should set this option to YES to # enable parsing support. CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. # Doxygen will parse them like normal C++ but will assume all classes use public # instead of private inheritance when no explicit protection keyword is present. SIP_SUPPORT = NO # For Microsoft's IDL there are propget and propput attributes to indicate # getter and setter methods for a property. Setting this option to YES (the # default) will make doxygen replace the get and set methods by a property in # the documentation. This will only work if the methods are indeed getting or # setting a simple type. If this is not the case, or you want to show the # methods anyway, you should set this option to NO. IDL_PROPERTY_SUPPORT = YES # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC # tag is set to YES, then doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. DISTRIBUTE_GROUP_DOC = NO # Set the SUBGROUPING tag to YES (the default) to allow class member groups of # the same type (for instance a group of public functions) to be put as a # subgroup of that type (e.g. under the Public Functions section). Set it to # NO to prevent subgrouping. Alternatively, this can be done per class using # the \nosubgrouping command. SUBGROUPING = YES # When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and # unions are shown inside the group in which they are included (e.g. using # @ingroup) instead of on a separate page (for HTML and Man pages) or # section (for LaTeX and RTF). INLINE_GROUPED_CLASSES = NO # When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and # unions with only public data fields or simple typedef fields will be shown # inline in the documentation of the scope in which they are defined (i.e. file, # namespace, or group documentation), provided this scope is documented. If set # to NO (the default), structs, classes, and unions are shown on a separate # page (for HTML and Man pages) or section (for LaTeX and RTF). INLINE_SIMPLE_STRUCTS = NO # When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum # is documented as struct, union, or enum with the name of the typedef. So # typedef struct TypeS {} TypeT, will appear in the documentation as a struct # with name TypeT. When disabled the typedef will appear as a member of a file, # namespace, or class. And the struct will be named TypeS. This can typically # be useful for C code in case the coding convention dictates that all compound # types are typedef'ed and only the typedef is referenced, never the tag name. TYPEDEF_HIDES_STRUCT = NO # The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This # cache is used to resolve symbols given their name and scope. Since this can # be an expensive process and often the same symbol appear multiple times in # the code, doxygen keeps a cache of pre-resolved symbols. If the cache is too # small doxygen will become slower. If the cache is too large, memory is wasted. # The cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid # range is 0..9, the default is 0, corresponding to a cache size of 2^16 = 65536 # symbols. LOOKUP_CACHE_SIZE = 0 #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- # If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in # documentation are documented, even if no documentation was available. # Private class members and static file members will be hidden unless # the EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES EXTRACT_ALL = YES # If the EXTRACT_PRIVATE tag is set to YES all private members of a class # will be included in the documentation. EXTRACT_PRIVATE = YES # If the EXTRACT_PACKAGE tag is set to YES all members with package or internal # scope will be included in the documentation. EXTRACT_PACKAGE = YES # If the EXTRACT_STATIC tag is set to YES all static members of a file # will be included in the documentation. EXTRACT_STATIC = YES # If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) # defined locally in source files will be included in the documentation. # If set to NO only classes defined in header files are included. EXTRACT_LOCAL_CLASSES = YES # This flag is only useful for Objective-C code. When set to YES local # methods, which are defined in the implementation section but not in # the interface are included in the documentation. # If set to NO (the default) only methods in the interface are included. EXTRACT_LOCAL_METHODS = YES # If this flag is set to YES, the members of anonymous namespaces will be # extracted and appear in the documentation as a namespace called # 'anonymous_namespace{file}', where file will be replaced with the base # name of the file that contains the anonymous namespace. By default # anonymous namespaces are hidden. EXTRACT_ANON_NSPACES = YES # If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all # undocumented members of documented classes, files or namespaces. # If set to NO (the default) these members will be included in the # various overviews, but no documentation section is generated. # This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_MEMBERS = NO # If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. # If set to NO (the default) these classes will be included in the various # overviews. This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_CLASSES = NO # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all # friend (class|struct|union) declarations. # If set to NO (the default) these declarations will be included in the # documentation. HIDE_FRIEND_COMPOUNDS = NO # If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any # documentation blocks found inside the body of a function. # If set to NO (the default) these blocks will be appended to the # function's detailed documentation block. HIDE_IN_BODY_DOCS = NO # The INTERNAL_DOCS tag determines if documentation # that is typed after a \internal command is included. If the tag is set # to NO (the default) then the documentation will be excluded. # Set it to YES to include the internal documentation. INTERNAL_DOCS = YES # If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate # file names in lower-case letters. If set to YES upper-case letters are also # allowed. This is useful if you have classes or files whose names only differ # in case and if your file system supports case sensitive file names. Windows # and Mac users are advised to set this option to NO. CASE_SENSE_NAMES = YES # If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen # will show members with their full class and namespace scopes in the # documentation. If set to YES the scope will be hidden. HIDE_SCOPE_NAMES = NO # If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen # will put a list of the files that are included by a file in the documentation # of that file. SHOW_INCLUDE_FILES = YES # If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen # will list include files with double quotes in the documentation # rather than with sharp brackets. FORCE_LOCAL_INCLUDES = NO # If the INLINE_INFO tag is set to YES (the default) then a tag [inline] # is inserted in the documentation for inline members. INLINE_INFO = YES # If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen # will sort the (detailed) documentation of file and class members # alphabetically by member name. If set to NO the members will appear in # declaration order. SORT_MEMBER_DOCS = YES # If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the # brief documentation of file, namespace and class members alphabetically # by member name. If set to NO (the default) the members will appear in # declaration order. SORT_BRIEF_DOCS = NO # If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen # will sort the (brief and detailed) documentation of class members so that # constructors and destructors are listed first. If set to NO (the default) # the constructors will appear in the respective orders defined by # SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. # This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO # and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. SORT_MEMBERS_CTORS_1ST = NO # If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the # hierarchy of group names into alphabetical order. If set to NO (the default) # the group names will appear in their defined order. SORT_GROUP_NAMES = NO # If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be # sorted by fully-qualified names, including namespaces. If set to # NO (the default), the class list will be sorted only by class name, # not including the namespace part. # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. # Note: This option applies only to the class list, not to the # alphabetical list. SORT_BY_SCOPE_NAME = NO # If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to # do proper type resolution of all parameters of a function it will reject a # match between the prototype and the implementation of a member function even # if there is only one candidate or it is obvious which candidate to choose # by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen # will still accept a match between prototype and implementation in such cases. STRICT_PROTO_MATCHING = NO # The GENERATE_TODOLIST tag can be used to enable (YES) or # disable (NO) the todo list. This list is created by putting \todo # commands in the documentation. GENERATE_TODOLIST = YES # The GENERATE_TESTLIST tag can be used to enable (YES) or # disable (NO) the test list. This list is created by putting \test # commands in the documentation. GENERATE_TESTLIST = YES # The GENERATE_BUGLIST tag can be used to enable (YES) or # disable (NO) the bug list. This list is created by putting \bug # commands in the documentation. GENERATE_BUGLIST = YES # The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or # disable (NO) the deprecated list. This list is created by putting # \deprecated commands in the documentation. GENERATE_DEPRECATEDLIST= YES # The ENABLED_SECTIONS tag can be used to enable conditional # documentation sections, marked by \if section-label ... \endif # and \cond section-label ... \endcond blocks. ENABLED_SECTIONS = # The MAX_INITIALIZER_LINES tag determines the maximum number of lines # the initial value of a variable or macro consists of for it to appear in # the documentation. If the initializer consists of more lines than specified # here it will be hidden. Use a value of 0 to hide initializers completely. # The appearance of the initializer of individual variables and macros in the # documentation can be controlled using \showinitializer or \hideinitializer # command in the documentation regardless of this setting. MAX_INITIALIZER_LINES = 30 # Set the SHOW_USED_FILES tag to NO to disable the list of files generated # at the bottom of the documentation of classes and structs. If set to YES the # list will mention the files that were used to generate the documentation. SHOW_USED_FILES = YES # Set the SHOW_FILES tag to NO to disable the generation of the Files page. # This will remove the Files entry from the Quick Index and from the # Folder Tree View (if specified). The default is YES. SHOW_FILES = YES # Set the SHOW_NAMESPACES tag to NO to disable the generation of the # Namespaces page. # This will remove the Namespaces entry from the Quick Index # and from the Folder Tree View (if specified). The default is YES. SHOW_NAMESPACES = YES # The FILE_VERSION_FILTER tag can be used to specify a program or script that # doxygen should invoke to get the current version for each file (typically from # the version control system). Doxygen will invoke the program by executing (via # popen()) the command , where is the value of # the FILE_VERSION_FILTER tag, and is the name of an input file # provided by doxygen. Whatever the program writes to standard output # is used as the file version. See the manual for examples. FILE_VERSION_FILTER = # The LAYOUT_FILE tag can be used to specify a layout file which will be parsed # by doxygen. The layout file controls the global structure of the generated # output files in an output format independent way. To create the layout file # that represents doxygen's defaults, run doxygen with the -l option. # You can optionally specify a file name after the option, if omitted # DoxygenLayout.xml will be used as the name of the layout file. LAYOUT_FILE = # The CITE_BIB_FILES tag can be used to specify one or more bib files # containing the references data. This must be a list of .bib files. The # .bib extension is automatically appended if omitted. Using this command # requires the bibtex tool to be installed. See also # http://en.wikipedia.org/wiki/BibTeX for more info. For LaTeX the style # of the bibliography can be controlled using LATEX_BIB_STYLE. To use this # feature you need bibtex and perl available in the search path. Do not use # file names with spaces, bibtex cannot handle them. CITE_BIB_FILES = #--------------------------------------------------------------------------- # configuration options related to warning and progress messages #--------------------------------------------------------------------------- # The QUIET tag can be used to turn on/off the messages that are generated # by doxygen. Possible values are YES and NO. If left blank NO is used. QUIET = NO # The WARNINGS tag can be used to turn on/off the warning messages that are # generated by doxygen. Possible values are YES and NO. If left blank # NO is used. WARNINGS = YES # If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings # for undocumented members. If EXTRACT_ALL is set to YES then this flag will # automatically be disabled. WARN_IF_UNDOCUMENTED = YES # If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for # potential errors in the documentation, such as not documenting some # parameters in a documented function, or documenting parameters that # don't exist or using markup commands wrongly. WARN_IF_DOC_ERROR = YES # The WARN_NO_PARAMDOC option can be enabled to get warnings for # functions that are documented, but have no documentation for their parameters # or return value. If set to NO (the default) doxygen will only warn about # wrong or incomplete parameter documentation, but not about the absence of # documentation. WARN_NO_PARAMDOC = NO # The WARN_FORMAT tag determines the format of the warning messages that # doxygen can produce. The string should contain the $file, $line, and $text # tags, which will be replaced by the file and line number from which the # warning originated and the warning text. Optionally the format may contain # $version, which will be replaced by the version of the file (if it could # be obtained via FILE_VERSION_FILTER) WARN_FORMAT = "$file:$line: $text" # The WARN_LOGFILE tag can be used to specify a file to which warning # and error messages should be written. If left blank the output is written # to stderr. WARN_LOGFILE = #--------------------------------------------------------------------------- # configuration options related to the input files #--------------------------------------------------------------------------- # The INPUT tag can be used to specify the files and/or directories that contain # documented source files. You may enter file names like "myfile.cpp" or # directories like "/usr/src/myproject". Separate the files or directories # with spaces. INPUT = # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is # also the default input encoding. Doxygen uses libiconv (or the iconv built # into libc) for the transcoding. See http://www.gnu.org/software/libiconv for # the list of possible encodings. INPUT_ENCODING = UTF-8 # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp # and *.h) to filter out the source-files in the directories. If left # blank the following patterns are tested: # *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh # *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py # *.f90 *.f *.for *.vhd *.vhdl FILE_PATTERNS = *.c *.h # The RECURSIVE tag can be used to turn specify whether or not subdirectories # should be searched for input files as well. Possible values are YES and NO. # If left blank NO is used. RECURSIVE = NO # The EXCLUDE tag can be used to specify files and/or directories that should be # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. # Note that relative paths are relative to the directory from which doxygen is # run. EXCLUDE = # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded # from the input. EXCLUDE_SYMLINKS = NO # If the value of the INPUT tag contains directories, you can use the # EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude # certain files from those directories. Note that the wildcards are matched # against the file with absolute path, so to exclude all test directories # for example use the pattern */test/* EXCLUDE_PATTERNS = # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, # AClass::ANamespace, ANamespace::*Test EXCLUDE_SYMBOLS = printf_e xmalloc xcalloc xrealloc indexes_* # The EXAMPLE_PATH tag can be used to specify one or more files or # directories that contain example code fragments that are included (see # the \include command). EXAMPLE_PATH = # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp # and *.h) to filter out the source-files in the directories. If left # blank all files are included. EXAMPLE_PATTERNS = # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be # searched for input files to be used with the \include or \dontinclude # commands irrespective of the value of the RECURSIVE tag. # Possible values are YES and NO. If left blank NO is used. EXAMPLE_RECURSIVE = NO # The IMAGE_PATH tag can be used to specify one or more files or # directories that contain image that are included in the documentation (see # the \image command). IMAGE_PATH = # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program # by executing (via popen()) the command , where # is the value of the INPUT_FILTER tag, and is the name of an # input file. Doxygen will then use the output that the filter program writes # to standard output. # If FILTER_PATTERNS is specified, this tag will be ignored. # Note that the filter must not add or remove lines; it is applied before the # code is scanned, but not when the output code is generated. If lines are added # or removed, the anchors will not be placed correctly. INPUT_FILTER = # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. # Doxygen will compare the file name with each pattern and apply the # filter if there is a match. # The filters are a list of the form: # pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further # info on how filters are used. If FILTER_PATTERNS is empty or if # non of the patterns match the file name, INPUT_FILTER is applied. FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER) will be used to filter the input files when producing source # files to browse (i.e. when SOURCE_BROWSER is set to YES). FILTER_SOURCE_FILES = NO # The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file # pattern. A pattern will override the setting for FILTER_PATTERN (if any) # and it is also possible to disable source filtering for a specific pattern # using *.ext= (so without naming a filter). This option only has effect when # FILTER_SOURCE_FILES is enabled. FILTER_SOURCE_PATTERNS = # If the USE_MD_FILE_AS_MAINPAGE tag refers to the name of a markdown file that # is part of the input, its contents will be placed on the main page # (index.html). This can be useful if you have a project on for instance GitHub # and want reuse the introduction page also for the doxygen output. USE_MDFILE_AS_MAINPAGE = #--------------------------------------------------------------------------- # configuration options related to source browsing #--------------------------------------------------------------------------- # If the SOURCE_BROWSER tag is set to YES then a list of source files will # be generated. Documented entities will be cross-referenced with these sources. # Note: To get rid of all source code in the generated output, make sure also # VERBATIM_HEADERS is set to NO. SOURCE_BROWSER = YES # Setting the INLINE_SOURCES tag to YES will include the body # of functions and classes directly in the documentation. INLINE_SOURCES = NO # Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct # doxygen to hide any special comment blocks from generated source code # fragments. Normal C, C++ and Fortran comments will always remain visible. STRIP_CODE_COMMENTS = NO # If the REFERENCED_BY_RELATION tag is set to YES # then for each documented function all documented # functions referencing it will be listed. REFERENCED_BY_RELATION = NO # If the REFERENCES_RELATION tag is set to YES # then for each documented function all documented entities # called/used by that function will be listed. REFERENCES_RELATION = NO # If the REFERENCES_LINK_SOURCE tag is set to YES (the default) # and SOURCE_BROWSER tag is set to YES, then the hyperlinks from # functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will # link to the source code. # Otherwise they will link to the documentation. REFERENCES_LINK_SOURCE = YES # If the USE_HTAGS tag is set to YES then the references to source code # will point to the HTML generated by the htags(1) tool instead of doxygen # built-in source browser. The htags tool is part of GNU's global source # tagging system (see http://www.gnu.org/software/global/global.html). You # will need version 4.8.6 or higher. USE_HTAGS = NO # If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen # will generate a verbatim copy of the header file for each class for # which an include is specified. Set to NO to disable this. VERBATIM_HEADERS = YES #--------------------------------------------------------------------------- # configuration options related to the alphabetical class index #--------------------------------------------------------------------------- # If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index # of all compounds will be generated. Enable this if the project # contains a lot of classes, structs, unions or interfaces. ALPHABETICAL_INDEX = YES # If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then # the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns # in which this list will be split (can be a number in the range [1..20]) COLS_IN_ALPHA_INDEX = 5 # In case all classes in a project start with a common prefix, all # classes will be put under the same header in the alphabetical index. # The IGNORE_PREFIX tag can be used to specify one or more prefixes that # should be ignored while generating the index headers. IGNORE_PREFIX = #--------------------------------------------------------------------------- # configuration options related to the HTML output #--------------------------------------------------------------------------- # If the GENERATE_HTML tag is set to YES (the default) Doxygen will # generate HTML output. GENERATE_HTML = YES # The HTML_OUTPUT tag is used to specify where the HTML docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `html' will be used as the default path. HTML_OUTPUT = html # The HTML_FILE_EXTENSION tag can be used to specify the file extension for # each generated HTML page (for example: .htm,.php,.asp). If it is left blank # doxygen will generate files with .html extension. HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a personal HTML header for # each generated HTML page. If it is left blank doxygen will generate a # standard header. Note that when using a custom header you are responsible # for the proper inclusion of any scripts and style sheets that doxygen # needs, which is dependent on the configuration options used. # It is advised to generate a default header using "doxygen -w html # header.html footer.html stylesheet.css YourConfigFile" and then modify # that header. Note that the header is subject to change so you typically # have to redo this when upgrading to a newer version of doxygen or when # changing the value of configuration settings such as GENERATE_TREEVIEW! HTML_HEADER = # The HTML_FOOTER tag can be used to specify a personal HTML footer for # each generated HTML page. If it is left blank doxygen will generate a # standard footer. HTML_FOOTER = # The HTML_STYLESHEET tag can be used to specify a user-defined cascading # style sheet that is used by each HTML page. It can be used to # fine-tune the look of the HTML output. If left blank doxygen will # generate a default style sheet. Note that it is recommended to use # HTML_EXTRA_STYLESHEET instead of this one, as it is more robust and this # tag will in the future become obsolete. HTML_STYLESHEET = # The HTML_EXTRA_STYLESHEET tag can be used to specify an additional # user-defined cascading style sheet that is included after the standard # style sheets created by doxygen. Using this option one can overrule # certain style aspects. This is preferred over using HTML_STYLESHEET # since it does not replace the standard style sheet and is therefor more # robust against future updates. Doxygen will copy the style sheet file to # the output directory. HTML_EXTRA_STYLESHEET = # The HTML_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the HTML output directory. Note # that these files will be copied to the base HTML output directory. Use the # $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these # files. In the HTML_STYLESHEET file, use the file name only. Also note that # the files will be copied as-is; there are no commands or markers available. HTML_EXTRA_FILES = # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. # Doxygen will adjust the colors in the style sheet and background images # according to this color. Hue is specified as an angle on a colorwheel, # see http://en.wikipedia.org/wiki/Hue for more information. # For instance the value 0 represents red, 60 is yellow, 120 is green, # 180 is cyan, 240 is blue, 300 purple, and 360 is red again. # The allowed range is 0 to 359. HTML_COLORSTYLE_HUE = 220 # The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of # the colors in the HTML output. For a value of 0 the output will use # grayscales only. A value of 255 will produce the most vivid colors. HTML_COLORSTYLE_SAT = 100 # The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to # the luminance component of the colors in the HTML output. Values below # 100 gradually make the output lighter, whereas values above 100 make # the output darker. The value divided by 100 is the actual gamma applied, # so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2, # and 100 does not change the gamma. HTML_COLORSTYLE_GAMMA = 80 # If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML # page will contain the date and time when the page was generated. Setting # this to NO can help when comparing the output of multiple runs. HTML_TIMESTAMP = YES # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the # page has loaded. HTML_DYNAMIC_SECTIONS = NO # With HTML_INDEX_NUM_ENTRIES one can control the preferred number of # entries shown in the various tree structured indices initially; the user # can expand and collapse entries dynamically later on. Doxygen will expand # the tree to such a level that at most the specified number of entries are # visible (unless a fully collapsed tree already exceeds this amount). # So setting the number of entries 1 will produce a full collapsed tree by # default. 0 is a special value representing an infinite number of entries # and will result in a full expanded tree by default. HTML_INDEX_NUM_ENTRIES = 100 # If the GENERATE_DOCSET tag is set to YES, additional index files # will be generated that can be used as input for Apple's Xcode 3 # integrated development environment, introduced with OSX 10.5 (Leopard). # To create a documentation set, doxygen will generate a Makefile in the # HTML output directory. Running make will produce the docset in that # directory and running "make install" will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find # it at startup. # See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html # for more information. GENERATE_DOCSET = NO # When GENERATE_DOCSET tag is set to YES, this tag determines the name of the # feed. A documentation feed provides an umbrella under which multiple # documentation sets from a single provider (such as a company or product suite) # can be grouped. DOCSET_FEEDNAME = "Doxygen generated docs" # When GENERATE_DOCSET tag is set to YES, this tag specifies a string that # should uniquely identify the documentation set bundle. This should be a # reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen # will append .docset to the name. DOCSET_BUNDLE_ID = org.doxygen.Project # When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely # identify the documentation publisher. This should be a reverse domain-name # style string, e.g. com.mycompany.MyDocSet.documentation. DOCSET_PUBLISHER_ID = org.doxygen.Publisher # The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher. DOCSET_PUBLISHER_NAME = Publisher # If the GENERATE_HTMLHELP tag is set to YES, additional index files # will be generated that can be used as input for tools like the # Microsoft HTML help workshop to generate a compiled HTML help file (.chm) # of the generated HTML documentation. GENERATE_HTMLHELP = NO # If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can # be used to specify the file name of the resulting .chm file. You # can add a path in front of the file if the result should not be # written to the html output directory. CHM_FILE = # If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can # be used to specify the location (absolute path including file name) of # the HTML help compiler (hhc.exe). If non-empty doxygen will try to run # the HTML help compiler on the generated index.hhp. HHC_LOCATION = # If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag # controls if a separate .chi index file is generated (YES) or that # it should be included in the master .chm file (NO). GENERATE_CHI = NO # If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING # is used to encode HtmlHelp index (hhk), content (hhc) and project file # content. CHM_INDEX_ENCODING = # If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag # controls whether a binary table of contents is generated (YES) or a # normal table of contents (NO) in the .chm file. BINARY_TOC = NO # The TOC_EXPAND flag can be set to YES to add extra items for group members # to the contents of the HTML help documentation and to the tree view. TOC_EXPAND = NO # If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and # QHP_VIRTUAL_FOLDER are set, an additional index file will be generated # that can be used as input for Qt's qhelpgenerator to generate a # Qt Compressed Help (.qch) of the generated HTML documentation. GENERATE_QHP = NO # If the QHG_LOCATION tag is specified, the QCH_FILE tag can # be used to specify the file name of the resulting .qch file. # The path specified is relative to the HTML output folder. QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating # Qt Help Project output. For more information please see # http://doc.trolltech.com/qthelpproject.html#namespace QHP_NAMESPACE = org.doxygen.Project # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating # Qt Help Project output. For more information please see # http://doc.trolltech.com/qthelpproject.html#virtual-folders QHP_VIRTUAL_FOLDER = doc # If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to # add. For more information please see # http://doc.trolltech.com/qthelpproject.html#custom-filters QHP_CUST_FILTER_NAME = # The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the # custom filter to add. For more information please see # # Qt Help Project / Custom Filters. QHP_CUST_FILTER_ATTRS = # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this # project's # filter section matches. # # Qt Help Project / Filter Attributes. QHP_SECT_FILTER_ATTRS = # If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can # be used to specify the location of Qt's qhelpgenerator. # If non-empty doxygen will try to run qhelpgenerator on the generated # .qhp file. QHG_LOCATION = # If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files # will be generated, which together with the HTML files, form an Eclipse help # plugin. To install this plugin and make it available under the help contents # menu in Eclipse, the contents of the directory containing the HTML and XML # files needs to be copied into the plugins directory of eclipse. The name of # the directory within the plugins directory should be the same as # the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before # the help appears. GENERATE_ECLIPSEHELP = NO # A unique identifier for the eclipse help plugin. When installing the plugin # the directory name containing the HTML and XML files should also have # this name. ECLIPSE_DOC_ID = org.doxygen.Project # The DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) # at top of each HTML page. The value NO (the default) enables the index and # the value YES disables it. Since the tabs have the same information as the # navigation tree you can set this option to NO if you already set # GENERATE_TREEVIEW to YES. DISABLE_INDEX = NO # The GENERATE_TREEVIEW tag is used to specify whether a tree-like index # structure should be generated to display hierarchical information. # If the tag value is set to YES, a side panel will be generated # containing a tree-like index structure (just like the one that # is generated for HTML Help). For this to work a browser that supports # JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). # Windows users are probably better off using the HTML help feature. # Since the tree basically has the same information as the tab index you # could consider to set DISABLE_INDEX to NO when enabling this option. GENERATE_TREEVIEW = NO # The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values # (range [0,1..20]) that doxygen will group on one line in the generated HTML # documentation. Note that a value of 0 will completely suppress the enum # values from appearing in the overview section. ENUM_VALUES_PER_LINE = 4 # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be # used to set the initial width (in pixels) of the frame in which the tree # is shown. TREEVIEW_WIDTH = 250 # When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open # links to external symbols imported via tag files in a separate window. EXT_LINKS_IN_WINDOW = NO # Use this tag to change the font size of Latex formulas included # as images in the HTML documentation. The default is 10. Note that # when you change the font size after a successful doxygen run you need # to manually remove any form_*.png images from the HTML output directory # to force them to be regenerated. FORMULA_FONTSIZE = 10 # Use the FORMULA_TRANPARENT tag to determine whether or not the images # generated for formulas are transparent PNGs. Transparent PNGs are # not supported properly for IE 6.0, but are supported on all modern browsers. # Note that when changing this option you need to delete any form_*.png files # in the HTML output before the changes have effect. FORMULA_TRANSPARENT = YES # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax # (see http://www.mathjax.org) which uses client side Javascript for the # rendering instead of using prerendered bitmaps. Use this if you do not # have LaTeX installed or if you want to formulas look prettier in the HTML # output. When enabled you may also need to install MathJax separately and # configure the path to it using the MATHJAX_RELPATH option. USE_MATHJAX = NO # When MathJax is enabled you can set the default output format to be used for # the MathJax output. Supported types are HTML-CSS, NativeMML (i.e. MathML) and # SVG. The default value is HTML-CSS, which is slower, but has the best # compatibility. MATHJAX_FORMAT = HTML-CSS # When MathJax is enabled you need to specify the location relative to the # HTML output directory using the MATHJAX_RELPATH option. The destination # directory should contain the MathJax.js script. For instance, if the mathjax # directory is located at the same level as the HTML output directory, then # MATHJAX_RELPATH should be ../mathjax. The default value points to # the MathJax Content Delivery Network so you can quickly see the result without # installing MathJax. # However, it is strongly recommended to install a local # copy of MathJax from http://www.mathjax.org before deployment. MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest # The MATHJAX_EXTENSIONS tag can be used to specify one or MathJax extension # names that should be enabled during MathJax rendering. MATHJAX_EXTENSIONS = # The MATHJAX_CODEFILE tag can be used to specify a file with javascript # pieces of code that will be used on startup of the MathJax code. MATHJAX_CODEFILE = # When the SEARCHENGINE tag is enabled doxygen will generate a search box # for the HTML output. The underlying search engine uses javascript # and DHTML and should work on any modern browser. Note that when using # HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets # (GENERATE_DOCSET) there is already a search function so this one should # typically be disabled. For large projects the javascript based search engine # can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution. SEARCHENGINE = YES # When the SERVER_BASED_SEARCH tag is enabled the search engine will be # implemented using a web server instead of a web client using Javascript. # There are two flavours of web server based search depending on the # EXTERNAL_SEARCH setting. When disabled, doxygen will generate a PHP script for # searching and an index file used by the script. When EXTERNAL_SEARCH is # enabled the indexing and searching needs to be provided by external tools. # See the manual for details. SERVER_BASED_SEARCH = NO # When EXTERNAL_SEARCH is enabled doxygen will no longer generate the PHP # script for searching. Instead the search results are written to an XML file # which needs to be processed by an external indexer. Doxygen will invoke an # external search engine pointed to by the SEARCHENGINE_URL option to obtain # the search results. Doxygen ships with an example indexer (doxyindexer) and # search engine (doxysearch.cgi) which are based on the open source search # engine library Xapian. See the manual for configuration details. EXTERNAL_SEARCH = NO # The SEARCHENGINE_URL should point to a search engine hosted by a web server # which will returned the search results when EXTERNAL_SEARCH is enabled. # Doxygen ships with an example search engine (doxysearch) which is based on # the open source search engine library Xapian. See the manual for configuration # details. SEARCHENGINE_URL = # When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed # search data is written to a file for indexing by an external tool. With the # SEARCHDATA_FILE tag the name of this file can be specified. SEARCHDATA_FILE = searchdata.xml # When SERVER_BASED_SEARCH AND EXTERNAL_SEARCH are both enabled the # EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is # useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple # projects and redirect the results back to the right project. EXTERNAL_SEARCH_ID = # The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen # projects other than the one defined by this configuration file, but that are # all added to the same external search index. Each project needs to have a # unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id # of to a relative location where the documentation can be found. # The format is: EXTRA_SEARCH_MAPPINGS = id1=loc1 id2=loc2 ... EXTRA_SEARCH_MAPPINGS = #--------------------------------------------------------------------------- # configuration options related to the LaTeX output #--------------------------------------------------------------------------- # If the GENERATE_LATEX tag is set to YES (the default) Doxygen will # generate Latex output. GENERATE_LATEX = NO # The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `latex' will be used as the default path. LATEX_OUTPUT = latex # The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be # invoked. If left blank `latex' will be used as the default command name. # Note that when enabling USE_PDFLATEX this option is only used for # generating bitmaps for formulas in the HTML output, but not in the # Makefile that is written to the output directory. LATEX_CMD_NAME = latex # The MAKEINDEX_CMD_NAME tag can be used to specify the command name to # generate index for LaTeX. If left blank `makeindex' will be used as the # default command name. MAKEINDEX_CMD_NAME = makeindex # If the COMPACT_LATEX tag is set to YES Doxygen generates more compact # LaTeX documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_LATEX = NO # The PAPER_TYPE tag can be used to set the paper type that is used # by the printer. Possible values are: a4, letter, legal and # executive. If left blank a4 will be used. PAPER_TYPE = a4 # The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX # packages that should be included in the LaTeX output. EXTRA_PACKAGES = # The LATEX_HEADER tag can be used to specify a personal LaTeX header for # the generated latex document. The header should contain everything until # the first chapter. If it is left blank doxygen will generate a # standard header. Notice: only use this tag if you know what you are doing! LATEX_HEADER = # The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for # the generated latex document. The footer should contain everything after # the last chapter. If it is left blank doxygen will generate a # standard footer. Notice: only use this tag if you know what you are doing! LATEX_FOOTER = # The LATEX_EXTRA_FILES tag can be used to specify one or more extra images # or other source files which should be copied to the LaTeX output directory. # Note that the files will be copied as-is; there are no commands or markers # available. LATEX_EXTRA_FILES = # If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated # is prepared for conversion to pdf (using ps2pdf). The pdf file will # contain links (just like the HTML output) instead of page references # This makes the output suitable for online browsing using a pdf viewer. PDF_HYPERLINKS = YES # If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of # plain latex in the generated Makefile. Set this option to YES to get a # higher quality PDF documentation. USE_PDFLATEX = YES # If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. # command to the generated LaTeX files. This will instruct LaTeX to keep # running if errors occur, instead of asking the user for help. # This option is also used when generating formulas in HTML. LATEX_BATCHMODE = NO # If LATEX_HIDE_INDICES is set to YES then doxygen will not # include the index chapters (such as File Index, Compound Index, etc.) # in the output. LATEX_HIDE_INDICES = NO # If LATEX_SOURCE_CODE is set to YES then doxygen will include # source code with syntax highlighting in the LaTeX output. # Note that which sources are shown also depends on other settings # such as SOURCE_BROWSER. LATEX_SOURCE_CODE = NO # The LATEX_BIB_STYLE tag can be used to specify the style to use for the # bibliography, e.g. plainnat, or ieeetr. The default style is "plain". See # http://en.wikipedia.org/wiki/BibTeX for more info. LATEX_BIB_STYLE = plain #--------------------------------------------------------------------------- # configuration options related to the RTF output #--------------------------------------------------------------------------- # If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output # The RTF output is optimized for Word 97 and may not look very pretty with # other RTF readers or editors. GENERATE_RTF = NO # The RTF_OUTPUT tag is used to specify where the RTF docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `rtf' will be used as the default path. RTF_OUTPUT = rtf # If the COMPACT_RTF tag is set to YES Doxygen generates more compact # RTF documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_RTF = NO # If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated # will contain hyperlink fields. The RTF file will # contain links (just like the HTML output) instead of page references. # This makes the output suitable for online browsing using WORD or other # programs which support those fields. # Note: wordpad (write) and others do not support links. RTF_HYPERLINKS = NO # Load style sheet definitions from file. Syntax is similar to doxygen's # config file, i.e. a series of assignments. You only have to provide # replacements, missing definitions are set to their default value. RTF_STYLESHEET_FILE = # Set optional variables used in the generation of an rtf document. # Syntax is similar to doxygen's config file. RTF_EXTENSIONS_FILE = #--------------------------------------------------------------------------- # configuration options related to the man page output #--------------------------------------------------------------------------- # If the GENERATE_MAN tag is set to YES (the default) Doxygen will # generate man pages GENERATE_MAN = NO # The MAN_OUTPUT tag is used to specify where the man pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `man' will be used as the default path. MAN_OUTPUT = man # The MAN_EXTENSION tag determines the extension that is added to # the generated man pages (default is the subroutine's section .3) MAN_EXTENSION = .3 # If the MAN_LINKS tag is set to YES and Doxygen generates man output, # then it will generate one additional man file for each entity # documented in the real man page(s). These additional files # only source the real man page, but without them the man command # would be unable to find the correct page. The default is NO. MAN_LINKS = NO #--------------------------------------------------------------------------- # configuration options related to the XML output #--------------------------------------------------------------------------- # If the GENERATE_XML tag is set to YES Doxygen will # generate an XML file that captures the structure of # the code including all documentation. GENERATE_XML = NO # The XML_OUTPUT tag is used to specify where the XML pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `xml' will be used as the default path. XML_OUTPUT = xml # The XML_SCHEMA tag can be used to specify an XML schema, # which can be used by a validating XML parser to check the # syntax of the XML files. XML_SCHEMA = # The XML_DTD tag can be used to specify an XML DTD, # which can be used by a validating XML parser to check the # syntax of the XML files. XML_DTD = # If the XML_PROGRAMLISTING tag is set to YES Doxygen will # dump the program listings (including syntax highlighting # and cross-referencing information) to the XML output. Note that # enabling this will significantly increase the size of the XML output. XML_PROGRAMLISTING = YES #--------------------------------------------------------------------------- # configuration options related to the DOCBOOK output #--------------------------------------------------------------------------- # If the GENERATE_DOCBOOK tag is set to YES Doxygen will generate DOCBOOK files # that can be used to generate PDF. GENERATE_DOCBOOK = NO # The DOCBOOK_OUTPUT tag is used to specify where the DOCBOOK pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be put in # front of it. If left blank docbook will be used as the default path. DOCBOOK_OUTPUT = docbook #--------------------------------------------------------------------------- # configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- # If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will # generate an AutoGen Definitions (see autogen.sf.net) file # that captures the structure of the code including all # documentation. Note that this feature is still experimental # and incomplete at the moment. GENERATE_AUTOGEN_DEF = NO #--------------------------------------------------------------------------- # configuration options related to the Perl module output #--------------------------------------------------------------------------- # If the GENERATE_PERLMOD tag is set to YES Doxygen will # generate a Perl module file that captures the structure of # the code including all documentation. Note that this # feature is still experimental and incomplete at the # moment. GENERATE_PERLMOD = NO # If the PERLMOD_LATEX tag is set to YES Doxygen will generate # the necessary Makefile rules, Perl scripts and LaTeX code to be able # to generate PDF and DVI output from the Perl module output. PERLMOD_LATEX = NO # If the PERLMOD_PRETTY tag is set to YES the Perl module output will be # nicely formatted so it can be parsed by a human reader. # This is useful # if you want to understand what is going on. # On the other hand, if this # tag is set to NO the size of the Perl module output will be much smaller # and Perl will parse it just the same. PERLMOD_PRETTY = YES # The names of the make variables in the generated doxyrules.make file # are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. # This is useful so different doxyrules.make files included by the same # Makefile don't overwrite each other's variables. PERLMOD_MAKEVAR_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the preprocessor #--------------------------------------------------------------------------- # If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will # evaluate all C-preprocessor directives found in the sources and include # files. ENABLE_PREPROCESSING = YES # If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro # names in the source code. If set to NO (the default) only conditional # compilation will be performed. Macro expansion can be done in a controlled # way by setting EXPAND_ONLY_PREDEF to YES. MACRO_EXPANSION = YES # If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES # then the macro expansion is limited to the macros specified with the # PREDEFINED and EXPAND_AS_DEFINED tags. EXPAND_ONLY_PREDEF = NO # If the SEARCH_INCLUDES tag is set to YES (the default) the includes files # pointed to by INCLUDE_PATH will be searched when a #include is found. SEARCH_INCLUDES = YES # The INCLUDE_PATH tag can be used to specify one or more directories that # contain include files that are not input files but should be processed by # the preprocessor. INCLUDE_PATH = # You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard # patterns (like *.h and *.hpp) to filter out the header-files in the # directories. If left blank, the patterns specified with FILE_PATTERNS will # be used. INCLUDE_FILE_PATTERNS = # The PREDEFINED tag can be used to specify one or more macro names that # are defined before the preprocessor is started (similar to the -D option of # gcc). The argument of the tag is a list of macros of the form: name # or name=definition (no spaces). If the definition and the = are # omitted =1 is assumed. To prevent a macro definition from being # undefined via #undef or recursively expanded use the := operator # instead of the = operator. PREDEFINED = __attribute__(x)= # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then # this tag can be used to specify a list of macro names that should be expanded. # The macro definition that is found in the sources will be used. # Use the PREDEFINED tag if you want to use a different macro definition that # overrules the definition found in the source code. EXPAND_AS_DEFINED = # If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then # doxygen's preprocessor will remove all references to function-like macros # that are alone on a line, have an all uppercase name, and do not end with a # semicolon, because these will confuse the parser if not removed. SKIP_FUNCTION_MACROS = NO #--------------------------------------------------------------------------- # Configuration::additions related to external references #--------------------------------------------------------------------------- # The TAGFILES option can be used to specify one or more tagfiles. For each # tag file the location of the external documentation should be added. The # format of a tag file without this location is as follows: # # TAGFILES = file1 file2 ... # Adding location for the tag files is done as follows: # # TAGFILES = file1=loc1 "file2 = loc2" ... # where "loc1" and "loc2" can be relative or absolute paths # or URLs. Note that each tag file must have a unique name (where the name does # NOT include the path). If a tag file is not located in the directory in which # doxygen is run, you must also specify the path to the tagfile here. TAGFILES = # When a file name is specified after GENERATE_TAGFILE, doxygen will create # a tag file that is based on the input files it reads. GENERATE_TAGFILE = # If the ALLEXTERNALS tag is set to YES all external classes will be listed # in the class index. If set to NO only the inherited external classes # will be listed. ALLEXTERNALS = NO # If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed # in the modules index. If set to NO, only the current project's groups will # be listed. EXTERNAL_GROUPS = YES # If the EXTERNAL_PAGES tag is set to YES all external pages will be listed # in the related pages index. If set to NO, only the current project's # pages will be listed. EXTERNAL_PAGES = YES # The PERL_PATH should be the absolute path and name of the perl script # interpreter (i.e. the result of `which perl'). PERL_PATH = /usr/bin/perl #--------------------------------------------------------------------------- # Configuration options related to the dot tool #--------------------------------------------------------------------------- # If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will # generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base # or super classes. Setting the tag to NO turns the diagrams off. Note that # this option also works with HAVE_DOT disabled, but it is recommended to # install and use dot, since it yields more powerful graphs. CLASS_DIAGRAMS = YES # You can define message sequence charts within doxygen comments using the \msc # command. Doxygen will then run the mscgen tool (see # http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the # documentation. The MSCGEN_PATH tag allows you to specify the directory where # the mscgen tool resides. If left empty the tool is assumed to be found in the # default search path. MSCGEN_PATH = # If set to YES, the inheritance and collaboration graphs will hide # inheritance and usage relations if the target is undocumented # or is not a class. HIDE_UNDOC_RELATIONS = NO # If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is # available from the path. This tool is part of Graphviz, a graph visualization # toolkit from AT&T and Lucent Bell Labs. The other options in this section # have no effect if this option is set to NO (the default) HAVE_DOT = YES # The DOT_NUM_THREADS specifies the number of dot invocations doxygen is # allowed to run in parallel. When set to 0 (the default) doxygen will # base this on the number of processors available in the system. You can set it # explicitly to a value larger than 0 to get control over the balance # between CPU load and processing speed. DOT_NUM_THREADS = 0 # By default doxygen will use the Helvetica font for all dot files that # doxygen generates. When you want a differently looking font you can specify # the font name using DOT_FONTNAME. You need to make sure dot is able to find # the font, which can be done by putting it in a standard location or by setting # the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the # directory containing the font. DOT_FONTNAME = Helvetica # The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. # The default size is 10pt. DOT_FONTSIZE = 10 # By default doxygen will tell dot to use the Helvetica font. # If you specify a different font using DOT_FONTNAME you can use DOT_FONTPATH to # set the path where dot can find it. DOT_FONTPATH = # If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen # will generate a graph for each documented class showing the direct and # indirect inheritance relations. Setting this tag to YES will force the # CLASS_DIAGRAMS tag to NO. CLASS_GRAPH = YES # If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen # will generate a graph for each documented class showing the direct and # indirect implementation dependencies (inheritance, containment, and # class references variables) of the class with other documented classes. COLLABORATION_GRAPH = YES # If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen # will generate a graph for groups, showing the direct groups dependencies GROUP_GRAPHS = YES # If the UML_LOOK tag is set to YES doxygen will generate inheritance and # collaboration diagrams in a style similar to the OMG's Unified Modeling # Language. UML_LOOK = NO # If the UML_LOOK tag is enabled, the fields and methods are shown inside # the class node. If there are many fields or methods and many nodes the # graph may become too big to be useful. The UML_LIMIT_NUM_FIELDS # threshold limits the number of items for each type to make the size more # manageable. Set this to 0 for no limit. Note that the threshold may be # exceeded by 50% before the limit is enforced. UML_LIMIT_NUM_FIELDS = 10 # If set to YES, the inheritance and collaboration graphs will show the # relations between templates and their instances. TEMPLATE_RELATIONS = NO # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT # tags are set to YES then doxygen will generate a graph for each documented # file showing the direct and indirect include dependencies of the file with # other documented files. INCLUDE_GRAPH = YES # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and # HAVE_DOT tags are set to YES then doxygen will generate a graph for each # documented header file showing the documented files that directly or # indirectly include this file. INCLUDED_BY_GRAPH = YES # If the CALL_GRAPH and HAVE_DOT options are set to YES then # doxygen will generate a call dependency graph for every global function # or class method. Note that enabling this option will significantly increase # the time of a run. So in most cases it will be better to enable call graphs # for selected functions only using the \callgraph command. CALL_GRAPH = YES # If the CALLER_GRAPH and HAVE_DOT tags are set to YES then # doxygen will generate a caller dependency graph for every global function # or class method. Note that enabling this option will significantly increase # the time of a run. So in most cases it will be better to enable caller # graphs for selected functions only using the \callergraph command. CALLER_GRAPH = YES # If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen # will generate a graphical hierarchy of all classes instead of a textual one. GRAPHICAL_HIERARCHY = YES # If the DIRECTORY_GRAPH and HAVE_DOT tags are set to YES # then doxygen will show the dependencies a directory has on other directories # in a graphical way. The dependency relations are determined by the #include # relations between the files in the directories. DIRECTORY_GRAPH = YES # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images # generated by dot. Possible values are svg, png, jpg, or gif. # If left blank png will be used. If you choose svg you need to set # HTML_FILE_EXTENSION to xhtml in order to make the SVG files # visible in IE 9+ (other browsers do not have this requirement). DOT_IMAGE_FORMAT = png # If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to # enable generation of interactive SVG images that allow zooming and panning. # Note that this requires a modern browser other than Internet Explorer. # Tested and working are Firefox, Chrome, Safari, and Opera. For IE 9+ you # need to set HTML_FILE_EXTENSION to xhtml in order to make the SVG files # visible. Older versions of IE do not have SVG support. INTERACTIVE_SVG = YES # The tag DOT_PATH can be used to specify the path where the dot tool can be # found. If left blank, it is assumed the dot tool can be found in the path. DOT_PATH = # The DOTFILE_DIRS tag can be used to specify one or more directories that # contain dot files that are included in the documentation (see the # \dotfile command). DOTFILE_DIRS = # The MSCFILE_DIRS tag can be used to specify one or more directories that # contain msc files that are included in the documentation (see the # \mscfile command). MSCFILE_DIRS = # The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of # nodes that will be shown in the graph. If the number of nodes in a graph # becomes larger than this value, doxygen will truncate the graph, which is # visualized by representing a node as a red box. Note that doxygen if the # number of direct children of the root node in a graph is already larger than # DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note # that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. DOT_GRAPH_MAX_NODES = 100 # The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the # graphs generated by dot. A depth value of 3 means that only nodes reachable # from the root by following a path via at most 3 edges will be shown. Nodes # that lay further from the root node will be omitted. Note that setting this # option to 1 or 2 may greatly reduce the computation time needed for large # code bases. Also note that the size of a graph can be further restricted by # DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. MAX_DOT_GRAPH_DEPTH = 0 # Set the DOT_TRANSPARENT tag to YES to generate images with a transparent # background. This is disabled by default, because dot on Windows does not # seem to support this out of the box. Warning: Depending on the platform used, # enabling this option may lead to badly anti-aliased labels on the edges of # a graph (i.e. they become hard to read). DOT_TRANSPARENT = YES # Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output # files in one run (i.e. multiple -o and -T options on the command line). This # makes dot run faster, but since only newer versions of dot (>1.8.10) # support this, this feature is disabled by default. DOT_MULTI_TARGETS = YES # If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will # generate a legend page explaining the meaning of the various boxes and # arrows in the dot generated graphs. GENERATE_LEGEND = YES # If the DOT_CLEANUP tag is set to YES (the default) Doxygen will # remove the intermediate dot files that are used to generate # the various graphs. DOT_CLEANUP = YES clsync-0.4.1/.gitignore000066400000000000000000000016461252417542300150130ustar00rootroot00000000000000# dynamical headers: /revision.h /compilerflags.h # complied: *.o *.lo *.la /gencompilerflags /clsync /clsync-debug .libs/ #debian: /debian/clsync/ /debian/*clsync*.log /debian/files /debian/*.ex /debian/*.debhelper /.pc/ /debian/*.substvars /debian/clsync-dev/ /debian/clsync-doc/ /debian/libclsync-dev/ /debian/libclsync0/ /debian/patches/ # other: test.c build test test0 test1 test2 .gdbcommands log* from/ to/ .clsync-list.* /examples/rules /examples/testdir/ /examples/*.so /doc/ *.swp tmp /compilerflags.h /gencompilerflags # autotools build debris .deps/ Makefile Makefile.in /aclocal.m4 /autom4te.cache/ /compile /config.guess /config.h* /config.log /config.status /config.sub /config.cache /configure /depcomp /install-sh /libtool /ltmain.sh /m4/ /missing /pkgconfig/libclsync.pc /stamp-h1 /debian/autoreconf.after /debian/autoreconf.before /autoscan.log /configure.scan # coverage test files /*.gcda /*.gcno /*.gcov clsync-0.4.1/.travis.sh000077500000000000000000000110071252417542300147400ustar00rootroot00000000000000#!/bin/sh -x # configuration TIMEOUT_SYNC=15 case $(uname -s) in Linux) MAKE='make' ;; *) MAKE='gmake' ;; esac # test aggressive optimizations export CFLAGS="$CFLAGS -O3 -march=native" autoreconf -if # Build unit test build_test() { $MAKE clean echo ">>> Testing with \"$@\"" # make sure we test paralled build as they tend to fail when single works #./configure -C $@ >/dev/null || rm -f config.cache && ./configure -C $@ >/dev/null && $MAKE -j5 >/dev/null || { ./configure $@ >/dev/null && $MAKE -j5 >/dev/null || { echo "!!! test with \"$@\" configure options failed" exit 1 } } # Cleanup functions for run_example() run_example_cleanup_success() { rm -rf "examples/testdir"/{to,from}/* sudo pkill -F "$CLSYNC_PIDFILE" } run_example_cleanup_failure() { sudo pkill -F "$CLSYNC_PIDFILE" 2>/dev/null echo "$@" >&2 exit 1 } # Run example script run_example_counter=0 run_example() { MODE="$1"; shift; export CLSYNC_PIDFILE="/tmp/clsync-example-$MODE.$$.${run_example_counter}.pid" run_example_counter=$(( $run_example_counter + 1 )) rm -rf "examples/testdir"/*/* mkdir -p "examples/testdir/to" "examples/testdir/from" trap run_example_cleanup_failure INT TERM ( cd examples bash -x clsync-start-"$MODE".sh --background --pid-file "$CLSYNC_PIDFILE" --config-file '/NULL/' -w1 -t1 -d0 $@ ) sleep 1 mkdir -p examples/testdir/from/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/DIR touch examples/testdir/from/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/a touch examples/testdir/from/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/b touch examples/testdir/from/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/c touch examples/testdir/from/a/b/c/d/e/f/g/h/7 touch examples/testdir/from/test mkdir examples/testdir/from/dontSync i=0 while [ "$i" -le "$TIMEOUT_SYNC" ]; do if [ -f "examples/testdir/to/test" -a -f "examples/testdir/to/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/a" -a -d "examples/testdir/to/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/DIR" ]; then sleep 1 break fi sleep 1 i=$(( $i + 1 )) done if [ "$i" -gt "$TIMEOUT_SYNC" ]; then run_example_cleanup_failure "$MODE" "timed out on initial syncing" fi if ! [ -f "$CLSYNC_PIDFILE" ]; then run_example_cleanup_failure "$MODE" "no pidfile" fi touch "examples/testdir/from/file" "examples/testdir/from/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/DIR/file" rm -rf "examples/testdir/from/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/DIR" i=0 while [ "$i" -le "$TIMEOUT_SYNC" ]; do if ! [ -f "$CLSYNC_PIDFILE" ]; then run_example_cleanup_failure "$MODE" "premature exit" fi if [ -f "examples/testdir/to/file" -a ! -d "examples/testdir/to/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/DIR" ]; then sleep 1 run_example_cleanup_success return fi sleep 1 i=$(( $i + 1 )) done run_example_cleanup_failure "$MODE" "no successful sync" } if true; then # Test all possible package-specific configure options. # Do not test empty cases save as no options at all. build_test # clsync enabled a0="--enable-clsync" for a1 in "--enable-cluster" "--disable-cluster"; do # for a2 in "--enable-debug" "--disable-debug"; do for a3 in "--enable-paranoid=0" "--enable-paranoid=1" "--enable-paranoid=2" ; do for a4 in "--enable-capabilities" "--disable-capabilities"; do for a5 in "--enable-socket" "--disable-socket"; do for a6 in "--enable-socket-library" "--disable-socket-library"; do for a7 in "--enable-highload-locks" ""; do # for a8 in "--with-libcgroup" "--without-libcgroup"; do # for a9 in "--enable-seccomp" "--disable-seccomp"; do arg="$a0 $a1 $a2 $a3 $a4 $a5 $a6 $a7 $a8 $a9" build_test "$arg" done done done done done done # done # done # done # clsync disabled, libclsync enabled a0="--disable-clsync --enable-socket-library" # for a2 in "--enable-debug" "--disable-debug"; do for a3 in "--enable-paranoid=0" "--enable-paranoid=1" "--enable-paranoid=2" ; do arg="$a0 $a1 $a2" build_test "$arg" # done done # clsync disabled, libclsync disabled build_test "--disable-clsync --disable-socket-library" fi if true; then export PATH="$(pwd):$PATH" # Functionality test build_test --enable-cluster --enable-debug --enable-paranoid=0 --enable-capabilities --without-mhash run_example rsyncdirect run_example rsyncdirect --splitting=thread --threading=off run_example rsyncdirect --splitting=process --threading=off run_example rsyncdirect --threading=safe # run_example rsyncshell # run_example rsyncso #run_example so #run_example cluster fi exit 0 clsync-0.4.1/.travis.yml000066400000000000000000000014221252417542300151240ustar00rootroot00000000000000language: c before_install: - sudo apt-get update - sudo apt-get install libcap-dev libglib2.0-dev libmhash-dev libkqueue.* libcgroup-dev libseccomp-dev linux-libc-dev libkqueue-dev ccache - sudo pip install cpp-coveralls --use-mirrors - sudo ln -s /usr/bin/ccache /usr/local/bin/gcc - sudo ln -s /usr/bin/ccache /usr/local/bin/cc script: - ./.travis.sh after_success: - coveralls -x c --exclude examples --exclude debian --exclude gentoo --exclude freebsd --exclude m4 --exclude autom4te.cache --exclude man --exclude conf*.dir --exclude cluster.c --exclude calc.c --exclude mon_bsm.c --exclude mon_gio.c --exclude mon_kqueue.c --exclude mon_dtracepipe.c --exclude cgroup.c --exclude fileutils.c --exclude error.c --exclude pthreadex.c --exclude privileged.c --exclude malloc.c clsync-0.4.1/CONTRIB000066400000000000000000000007671252417542300140510ustar00rootroot00000000000000 I want to say thanks to next people: 1. Andrew A Savchenko 0x76B176E4 For: - adapting clsync for gentoo - good programming advices - fixing a lot of bugs 2. Barak A Pearlmutter (https://github.com/barak) For adapting clsync to autotools 3. Artyom A Anikeev 0xB5385841 For preparing first deb packages. 4. oldlaptop (https://github.com/oldlaptop) For fixing spelling and grammar. 5. Alexander M Gladtsin For testing clsync-0.4.1/DEVELOPING000066400000000000000000000056401252417542300144000ustar00rootroot00000000000000 This documentation may be outdated. Sorry if it's so :( First of all, I recommend you to read the manpage with "man ./man/man1/clsync.1". After that, I recommend you to run command "make doc" and look at images "./doc/html/main_8c_a0ddf1224851353fc92bfbff6f499fa97_cgraph.png" and "./doc/html/sync_8c_a320ae70a547c80b9b3ae897f955b2862_icgraph.png" The syncing is proceeding on "exec_argv()" on this image. ;) Here's very short description of how the program works: Program execution starts with main() function in "main.c". In turn, main() sets default configuration and executes: - parse_arguments() to parse "argv" - main_rehash() to parse file with filter-rules - becomedaemon() to fork() to daemonize - and sync_run() of "sync.c" to proceed to syncing process First of all, sync_run prepares hashtables and other things to be used in future.. and then sync_run() executes: - sync_notify_init() to initialize file system monitoring kernel interface - sync_mark_walk() to mark all directories in the monitoring interface - sync_notify_loop() to run infinite loop of checking and processing of the program sync_notify_loop() runs sync_fanotify_loop() or sync_inotify_loop() depending on selected FS monitoring kernel interface ("inotify" or "fanotify"). However, at the moment fanotify is not supported, so we will suppose, that the sync_inotify_loop() is used. sync_inotify_loop() waits for events with sync_inotify_wait(), handling them with sync_inotify_handle() and executes sync_idle() to process background things. Waiting and handling the events is proceeded with using of 3 queues: 1.) for normal files and directories 2.) for big files 3.) for immidiate syncing To be able to do that, the events are separated and stored in 3 hashtables (indexes_p->fpath2ei_coll_ht[queue_id]). sync_inotify_handle() is: - managing watch descriptors - executes rules_check() to filter events, then aggregating them into "indexes_p->fpath2ei_ht" - marking and syncing newly created directories - executing sync_inotify_handle_dosync() for every event to queue the syncing sync_inotify_handle_dosync() is just executes sync_queuesync() with appropriate arguments to queue the syncing. When the execution will back to sync_inotify_loop() code it will proceed to sync_idle(). sync_idle() executes _sync_exec_idle() to cleanup after previous executions of sync_exec_thread() and executes sync_idle_dosync_collectedevents() to process new events. sync_idle_dosync_collectedevents() prepares a lists of exclusion and inclusion to be synced and executes sync_idle_dosync_collectedevents_commitpart() to commit the lists. In turn, sync_idle_dosync_collectedevents_commitpart() executes sync_exec_thread() or sync_exec() depending on "argv" arguments. sync_exec_thread and sync_exec executes exec_argv that calls execvp to run external syncer process. The external syncer process is supposed to be rsync or any script that is able to handle with the task. clsync-0.4.1/LICENSE000066400000000000000000000020321252417542300140160ustar00rootroot00000000000000Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: clsync Source: git://git.mephi.ru/clsync Files: * Copyright: 2013 Dmitry Yu Okunev License: GPL-3+ This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. . This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. . You should have received a copy of the GNU General Public License along with this package; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA . On Debian systems, the full text of the GNU General Public License version 3 can be found in the file `/usr/share/common-licenses/GPL-3'. clsync-0.4.1/Makefile.am000066400000000000000000000062511252417542300150540ustar00rootroot00000000000000ACLOCAL_AMFLAGS = -I m4 SUBDIRS = examples if CLSYNC bin_PROGRAMS = clsync gencompilerflags gencompilerflags_SOURCES = gencompilerflags.c clsync_SOURCES = calc.c cluster.c error.c fileutils.c glibex.c \ indexes.c main.c malloc.c rules.c stringex.c sync.c \ posix-hacks.c privileged.c pthreadex.c calc.h cluster.h \ fileutils.h glibex.h main.h port-hacks.h posix-hacks.h \ pthreadex.h stringex.h sync.h common.h control.h privileged.h \ rules.h syscalls.h clsync_CFLAGS = $(AM_CFLAGS) clsync_LDFLAGS = $(AM_LDFLAGS) if HAVE_KQUEUE clsync_CFLAGS += -DKQUEUE_SUPPORT clsync_SOURCES += mon_kqueue.c mon_kqueue.h endif if HAVE_INOTIFY clsync_CFLAGS += -DINOTIFY_SUPPORT clsync_SOURCES += mon_inotify.c mon_inotify.h if INOTIFY_OLD clsync_CFLAGS += -DINOTIFY_OLD endif endif if HAVE_FANOTIFY clsync_CFLAGS += -DFANOTIFY_SUPPORT clsync_SOURCES += mon_fanotify.c mon_fanotify.h endif if HAVE_BSM clsync_CFLAGS += -DBSM_SUPPORT clsync_SOURCES += mon_bsm.c mon_bsm.h endif if HAVE_GIO clsync_CFLAGS += -DGIO_SUPPORT $(GIO_CFLAGS) clsync_LDFLAGS += $(GIO_LIBS) clsync_SOURCES += mon_gio.c mon_gio.h endif if HAVE_DTRACEPIPE clsync_CFLAGS += -DDTRACEPIPE_SUPPORT clsync_SOURCES += mon_dtracepipe.c mon_dtracepipe.h endif if HAVE_BACKTRACE clsync_CFLAGS += -DBACKTRACE_SUPPORT endif if HAVE_CAPABILITIES clsync_CFLAGS += -DCAPABILITIES_SUPPORT if HAVE_SECCOMP clsync_CFLAGS += -DSECCOMP_SUPPORT endif endif if HAVE_GETMNTENT clsync_CFLAGS += -DGETMNTENT_SUPPORT endif if HAVE_UNSHARE clsync_CFLAGS += -DUNSHARE_SUPPORT if HAVE_PIVOTROOT clsync_CFLAGS += -DPIVOTROOT_OPT_SUPPORT endif endif #if HAVE_TRE #clsync_CFLAGS += -DTRE_SUPPORT #endif if HAVE_LIBCGROUP clsync_CFLAGS += -DCGROUP_SUPPORT clsync_SOURCES += cgroup.c cgroup.h endif if HLLOCKS clsync_CFLAGS += -DHL_LOCKS endif if SOCKET clsync_SOURCES += socket.c control.c program.h endif gencompilerflags_CFLAGS = $(clsync_CFLAGS) main.o: compilerflags.h compilerflags.h: gencompilerflags ./gencompilerflags > compilerflags.h dist_man_MANS = man/man1/clsync.1 endif dist_doc_DATA = CONTRIB DEVELOPING LICENSE PROTOCOL README.md TODO EXTRA_DIST = .doxygen .travis.sh .travis.yml NOTES SHORTHANDS freebsd debian gentoo if LIBCLSYNC lib_LTLIBRARIES = libclsync.la libclsync_la_SOURCES = malloc.c libclsync.c socket.c error.c libclsync_la_LDFLAGS = -version-info 0:0:0 endif REVISION=$(shell [ -d .git ] &&\ (echo -n \ '\".'$$(($$(git log 2>/dev/null \ | grep -c ^commit \ | tr -d "\n" \ )-1083 \ ))'\"' \ ) || echo -n '\"-release\"' ) AM_CFLAGS := -DREVISION=$(REVISION) clsync_includedir = $(includedir)/clsync libclsync_includedir = $(includedir)/libclsync if CLSYNC clsync_include_HEADERS = \ clsync.h \ port-hacks.h \ configuration.h \ ctx.h \ error.h \ indexes.h \ malloc.h \ compilerflags.h if SOCKET clsync_include_HEADERS += \ socket.h endif endif if LIBCLSYNC libclsync_include_HEADERS = \ clsync.h \ ctx.h \ libclsync.h \ malloc.h \ socket.h endif doc: doxygen .doxygen if LIBCLSYNC pkgconfig_DATA = pkgconfig/libclsync.pc endif CLEANFILES = compilerflags.h gencompilerflags if CLSYNC CLEANFILES += examples/rules clean-local: -rm -rf examples/testdir examples/*.o examples/*.so examples/*.xz doc/doxygen endif clsync-0.4.1/NOTES000066400000000000000000000012331252417542300136260ustar00rootroot00000000000000v0.1 -> v0.2: * Renamed "--dir-destination" to "--destination-dir"; "--dir-lists" -> "--lists-dir" * Renamed shorthand "-d" to "-L" * Renamed shorthand "-D" to "-d" * You cannot use "-ddd" anymore, you should use "-d3" or "-d -d -d" instead. * Paths are relative, now. You may want to fix "rules" and "sync-handler" files. * Paths to "watch-dir", "sync-handler", "rules" and "dest-dir" are passed as options (see manpage) * sync-handler mode is selectable via "--mode" option (not "-R", "-d", "-M", "-S") * Removed shorthands for "--synclist-simplify", "--rsync-inclimit", "--rsync-prefer-include" * Option "--initialsync-enable" renamed to "--have-recursive-sync" clsync-0.4.1/PROTOCOL000066400000000000000000000034701252417542300142040ustar00rootroot00000000000000Unfortunetaly, on L2 all messages are sending to all members of the cluster. unicast: dst_node_id == node_id broadcast: dst_node_id == NOID session example of two nodes (A and B): "A" appears (getting node_id): A -> hello (serial: 0; src: NOID ; dst: NOID; name: A) | cluster_init() A -> register (serial: 1; src: 0 ; dst: NOID; name: A) | Trying to sync with somebody: A -> updtree (serial: 2; src: 0 ; dst: NOID; [A modtree]) "B" appears (getting node_id): B -> hello (serial: 0; src: NOID ; dst: NOID; name: B) | cluster_init() A -> welcome (serial: 3; src: 0 ; dst: NOID; my_name: A; to_name: B) B -> register (serial: 1; src: 1 : dst: NOID; name: B) | A -> ack (serial: 4; src: 0 ; dst: 1; ack_serial: 1) Initial syncing A <-> B: B -> updtree (serial: 2; src: 1 ; dst: NOID; [B modtree]) A -> updtree (serial: 5; src: 0 ; dst: NOID; [A modtree]) A -> lock (serial: 6: src: 0 ; dst: NOID; [paths list]) B -> ack (serial: 2; src: 1 ; dst: 0; ack_serial: 6) B -> lock (serial: 3: src: 1 ; dst: NOID; [paths list]) A -> ack (serial: 7; src: 0 ; dst: 1; ack_serial: 3) A -> unlockall (serial: 8: src: 0 ; dst: NOID) B -> ack (serial: 4; src: 1 ; dst: 0; ack_serial: 8) B -> unlockall (serial: 5: src: 1 ; dst: NOID) A -> ack (serial: 9; src: 0 ; dst: 1; ack_serial: 5) Just syncing A -> B: A -> lock (serial: 10: src: 0 ; dst: NOID; [paths list]) B -> ack (serial: 6; src: 1 ; dst: 0; ack_serial: 10) A -> unlockall (serial: 11: src: 0 ; dst: NOID) B -> ack (serial: 7; src: 1 ; dst: 0; ack_serial: 11) "A" disappers (shutdown) "A" appears (registering with old node_id) A -> hello (serial: 0; src: NOID ; dst: NOID; name: A) B -> welcome (serial: 4; src: 1 ; dst: 0; my_name: B; to_name: A) A -> register (serial: 1; src: 0 ; dst: NOID; name: A) B -> ack (serial: 5; src: 1 ; dst: 0; ack_serial: 1) [...] clsync-0.4.1/README.md000066400000000000000000000353231252417542300143010ustar00rootroot00000000000000[![Build Status](https://travis-ci.org/xaionaro/clsync.png?branch=master)](https://travis-ci.org/xaionaro/clsync) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/xaionaro/clsync?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) clsync ====== Contents -------- 1. Name 2. Motivation 3. inotify vs fanotify 4. Installing 5. How to use 6. Example of usage 7. Other uses 8. Clustering 9. Known building issues 10. FreeBSD support 11. Support 12. Developing 13. Articles 14. See also 1. Name ------- Why "clsync"? The first name of the utility was "insync" (due to inotify) but then I suggested to use "fanotify" instead of "inotify" and utility has been renamed to "fasync". After that I started to intensively write the program and I faced with some problems in "fanotify". So I was have to temporary fallback to "inotify" then I decided that the best name is "Runtime Sync" or "Live Sync" but "rtsync" is a name of some corporation and "lsync" is busy by "[lsyncd](https://github.com/axkibe/lsyncd)". So I called it "clsync" that should be interpreted as "lsync but on c" due to "lsyncd" that written on "LUA" and may be used for the same purposes. UPD: Also I was have to add somekind of clustering support. It's a multicast notifing subsystem to prevent loops on bidirection syncing. So "clsync" also can be interpreted as "cluster live sync". ;) 2. Motivation ------------- This utility has been written for two purposes: - for making high availability clusters - for making backups of them To do a HA cluster I've tried a lot of different solutions, like "simple rsync by cron", "glusterfs", "ocfs2 over drbd", "common mirrorable external storage", "incron + perl + rsync", "inosync", "lsyncd" and so on. When I started to write the utility we were using "lsyncd", "ceph" and "ocfs2 over drbd". However all of this solutions doesn't arrange me, so I was have to write own utility for this purpose. To do backups we also tried a lot of different solution, and again I was have to write own utility for this purpose. The best known (for me) replacement for this utility is "lsyncd", however: - It's code is `>½` on LUA. There a lot of problems connected with it, for example: - It's more difficult to maintain the code with ordinary sysadmin. - It really eats 100% CPU sometimes. - It requires LUA libs, that cannot be easily installed to few of our systems. - It's a little buggy. That may be easily fixed for our cases, but LUA. :( - It doesn't support pthread or something like that. It's necessary to serve huge directories with a lot of containers right. - It cannot run rsync for a pack of files. It runs rsync for every event. :( - Sometimes, it's too complex in configuration for our situation. - It can't set another event-collecting delay for big files. We don't want to sync big files (`>1GiB`) so often as ordinary files. - Shared object (.so file) cannot be used as rsync-wrapper. - It doesn't support kqueue/bsm Sorry, if I'm wrong. Let me know if it is, please :). "lsyncd" - is really interesting and useful utility, just it's not appropriate for us. UPD.: Also clsync had been used to replace incron/csync2/etc in HPC-clusters for syncing /etc/{passwd,shadow,group,shells} files. 3. inotify vs fanotify: ----------------------- It's said that fanotify is much better than inotify. So I started to write this program with using of fanotify. However I encountered the problem, that fanotify was unable to catch some important events at the moment of writing the program, like "directory creation" or "file deletion". So I switched to "inotify", leaving the code for "fanotify" in the safety... So, don't use "fanotify" in this utility ;). 4. Installing ------------- Debian/ubuntu-users can try to install it directly with apt-get: apt-get install clsync If it's required to install clsync from the source, first of all, you should install dependencies to compile it. On debian-like systems you should execute something like: apt-get install libglib2.0-dev autoreconf gcc Next step is generating Makefile. To do that usually it's enought to execute: autoreconf -i && ./configure Next step is compiling. To compile usually it's enough to execute: make Next step is installing. To install usually it's enough to execute: su -c 'make install' 5. How to use ------------- How to use is described in "man" ;). What is not described, you can ask me personally (see "Support"). 6. Example of usage ------------------- Example of usage, that works on my PC is in directory "examples". Just run "clsync-start-rsyncdirect.sh" and try to create/modify/delete files/dirs in "example/testdir/from". All modifications should appear (with some delay) in directory "example/testdir/to" ;) For dummies: pushd /tmp git clone https://github.com/xaionaro/clsync cd clsync autoreconf -fi ./configure make export PATH_OLD="$PATH" export PATH="$(pwd):$PATH" cd examples ./clsync-start-rsyncdirect.sh export PATH="$PATH_OLD" Now you can try to make changes in directory "/tmp/clsync/examples/testdir/from" (in another terminal). Wait about 7 seconds after the changes and check directory "/tmp/clsync/examples/testdir/to". To finish the experiment press ^C (control+c) in clsync's terminal. cd ../.. rm -rf clsync popd Note: There's no need to change PATH's value if clsync is installed system-wide, e.g. with make install For dummies, again (with "make install"): pushd /tmp git clone https://github.com/xaionaro/clsync cd clsync autoreconf -fi ./configure make sudo make install cd examples ./clsync-start-rsyncdirect.sh Directory "/tmp/clsync/examples/testdir/from" is now synced to "/tmp/clsync/examples/testdir/to" with 7 seconds delay. To terminate the clsync press ^C (control+c) in clsync's terminal. cd .. sudo make uninstall cd .. rm -rf clsync popd For really dummies or/and lazy users, there's a video demonstration: [http://ut.mephi.ru/oss/clsync](http://ut.mephi.ru/oss/clsync) 7. Other uses ------------- For example, command ionice -c 3 clsync -L /dev/shm/clsync --exit-on-no-events -x 23 -x 24 -M rsyncdirect -S $(which rsync) -W /path/from -D /path/to -d1 may be used to copy "/path/from" into "/path/to" with sync up of changes made (in "/path/from") while the copying. It will copy new changes over and over until there will be no changes, and then clsync will exit. It may be used as atomicity-like recursive copy. Or command clsync -w5 -t5 -T5 -x1 -W /var/www/site.example.org/root -Mdirect -Schown --uid 0 --gid 0 -Ysyslog -b1 -- --from=root www-data:www-data %INCLUDE-LIST% may be used to fix files owner in runtime. This may be used as a temporary solution for fixing file privileges of misconfigured web-servers (it's well-known problem of apache users). 8. Clustering ------------- I've started to implement support of bi-directional syncing with using multicast notifing of other nodes. However it became a long task, so it was suspended for next releases. However let's solve next hypothetical problem. For example, you're using LXC and trying to replicate containers between two servers (to make failover and load balancing). In this case you have to sync containers in both directions. However, if you just run clsync to sync containers to neighboring node on both of them, you'll get sync-loop [file-update on A causes file-update on B causes file-update on A causes ...]. Well, in this case I with my colleagues were using separate directories for every node of cluster (e.g. "`/srv/nodes//containers/`") and syncing every directory only in one direction. That was failover with load-balancing, but very unconvenient. So I've started to write code for bi-directional syncing, however it's no time to complete it :(. So Andrew Savchenko proposed to run one clsync-instance per container. And this's really good solution. It's just need to start clsync-process when container starts and stop the process when containers stops. The only problem is split-brain, that can be solved two ways: - by human every time; - by scripts that chooses which variant of container to save. Example of the script is just a script that calls "find" on both sides to determine which side has the latest changes :) UPD: I've added option "--modification-signature" that helps to prevent syncing file, that is not changed. You can easily use it to prevent sync-loops for bi-directional syncing. 9. Known building issues ------------------------ May be problems with "configuring" or compilation. In this case just try next command: echo '#define REVISION "-custom"' > revision.h; gcc -std=gnu99 -D\_FORTIFY\_SOURCE=2 -DPARANOID -pipe -Wall -ggdb3 --param ssp-buffer-size=4 -fstack-check -fstack-protector-all -Xlinker -zrelro -pthread $(pkg-config --cflags glib-2.0) $(pkg-config --libs glib-2.0) -ldl \*.c -o /tmp/clsync 10. FreeBSD support ------------------- clsync has been ported to FreeBSD. FreeBSD doesn't support inotify, so there're 3.5 ways to use clsync on it: * using [libinotify](https://github.com/dmatveev/libinotify-kqueue); * using BSM API (with or without a prefetcher thread); * using kqueue/kevent directly. Here's an excerpt from the manpage: Possible values: inotify inotify(7) [Linux, (FreeBSD via libinotify)] Native, fast, reliable and well tested Linux FS monitor subsystem. There's no essential performance profit to use "inotify" instead of "kevent" on FreeBSD using "libinotify". It backends to "kevent" any†way. FreeBSD users: The libinotify on FreeBSD is still not ready and unus†able for clsync to sync a lot of files and directories. kqueue kqueue(2) [FreeBSD, (Linux via libkqueue)] A *BSD kernel event notification mechanism (inc. timer, sockets, files etc). This monitor subsystem cannot determine file creation event, but it can determine a directory where something happened. So clsync is have to rescan whole dir every time on any content change. Moreover, kqueue requires an open() on every watched file/dir. But FreeBSD doesn't allow to open() symlink itself (without following) and it's highly invasively to open() pipes and devices. So clsync just won't call open() on everything except regular files and directories. Con†sequently, clsync cannot determine if something changed in sym†link/pipe/socket and so on. However it still can determine if it will be created or deleted by watching the parent directory and res†caning it on every appropriate event. Also this API requires to open every monitored file and directory. So it may produce a huge amount of file descriptors. Be sure that kern.maxfiles is big enough (in FreeBSD). CPU/HDD expensive way. Not well tested. Use with caution! Linux users: The libkqueue on Linux is not working. He-he :) bsm bsm(3) [FreeBSD] Basic Security Module (BSM) Audit API. This is not a FS monitor subsystem, actually. It's just an API to access to audit information (inc. logs). clsync can setup audit to watch FS events and report it into log. After that clsync will just parse the log via auditpipe(4) [FreeBSD]. Reliable, but hacky way. It requires global audit reconfiguration that may hopple audit analysis. Warning! FreeBSD has a limit for queued events. In default FreeBSD kernel it's only 1024 events. So choose one of: - To patch the kernel to increase the limit. - Don't use clsync on systems with too many file events. - Use bsm_prefetch mode (but there's no guarantee in this case anyway). See also option --exit-on-sync-skip. Not well tested. Use with caution! Also file /etc/secu†rity/audit_control will be overwritten with: #clsync dir:/var/audit flags:fc,fd,fw,fm,cl minfree:0 naflags:fc,fd,fw,fm,cl policy:cnt filesz:1M unless it's already starts with "#clsync\n" ("\n" is a new line char†acter). bsm_prefetch The same as bsm but all BSM events will be prefetched by an addi†tional thread to prevent BSM queue overflow. This may utilize a lot of memory on systems with a high FS events frequency. However the thread may be not fast enough to unload the kernel BSM queue. So it may overflow anyway. The default value on Linux is "inotify". The default value on FreeBSD is "kqueue". I hope you will send me bugreports to make me able to improve the FreeBSD support :) 11. Support ----------- To get support, you can contact with me this ways: - Official IRC channel of "clsync": irc.freenode.net#clsync - Where else can you find me: IRC:SSL+UTF-8 irc.campus.mephi.ru:6695#mephi,xaionaro,xai - And e-mail: , ; PGP pubkey: 0x8E30679C 12. Developing -------------- I started to write "DEVELOPING" and "PROTOCOL" files. You can look there if you wish. ;) I'll be glad to receive code contribution :) 13. Articles ------------ Russian: - [HA clustering](https://gitlab.ut.mephi.ru/ut/articles/blob/master/clsync/ha) - [syncing to many nodes](https://gitlab.ut.mephi.ru/ut/articles/blob/master/clsync/inotify-to-many-nodes) - [atomic sync](https://gitlab.ut.mephi.ru/ut/articles/blob/master/clsync/atomicsync) LVEE (Russian): - [clsync - live sync utility (abstract)](http://lvee.org/en/abstracts/118) [presentation](http://lvee.org/uploads/image_upload/file/337/winter_2014_15_clsync.pdf) - [clsync progress: security and porting to freebsd](http://lvee.org/en/abstracts/138) 14. See also ------------ - [lrsync](https://github.com/xaionaro/lrsync) -- Dmitry Yu Okunev 0x8E30679C clsync-0.4.1/SHORTHANDS000066400000000000000000000000161252417542300144110ustar00rootroot00000000000000ctx - context clsync-0.4.1/TODO000066400000000000000000000011371252417542300135060ustar00rootroot000000000000000! [SECURITY] Drop privilegies. Preserve access to files via "capabilities". 1. Remove pthreads metainfo GC 2. Replace g_hash_table* with "tsearch" and so on. 3. Remove all code with "#ifdef DOXYGEN" 4. Find-out is "pthread_tryjoin_np()" really required? Can we remove all this "threadsinfo" mechanism? There's a memleak if not pthread_*join*() is done. 5. Deduplicate code from functions, that calls sync_exec() and sync_exec_thread() 6. Fix variables' names 7. [CLUSTER] Replace node_id value with his ipv4 address (8bits -> 32bits) 8. In daemon mode stdout/stderr should be redirected to system log. clsync-0.4.1/calc.c000066400000000000000000000035341252417542300140670ustar00rootroot00000000000000/* clsync - file tree sync utility based on inotify/kqueue Copyright (C) 2014 Dmitry Yu Okunev 0x8E30679C This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "common.h" #include "calc.h" #include "error.h" #ifdef HAVE_MHASH #include #endif #ifndef HAVE_MHASH /** * @brief Calculated Adler32 value for char array * * @param[in] date Pointer to data * @param[in] len Length of the data * * @retval uint32_t Adler32 value of data * */ // Copied from http://en.wikipedia.org/wiki/Adler-32 uint32_t adler32_calc(const unsigned char *const data, uint32_t len) { // where data is the location of the data in physical // memory and len is the length of the data in bytes if (len&3) warning("len [%i] & 3 == %i != 0. Wrong length (not a multiple of 4).", len, len&3); debug(70, "%p, %i", data, len); const int MOD_ADLER = 65521; uint32_t a = 1, b = 0; int32_t index; // Process each byte of the data in order for (index = 0; index < len; ++index) { debug(80, "%5i: %02x %02x %02x", index, data[index], a, b); a = (a + data[index]) % MOD_ADLER; b = (b + a) % MOD_ADLER; } return (b << 16) | a; } #endif clsync-0.4.1/calc.h000066400000000000000000000023301252417542300140650ustar00rootroot00000000000000/* clsync - file tree sync utility based on inotify/kqueue Copyright (C) 2014 Dmitry Yu Okunev 0x8E30679C This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #ifdef HAVE_MHASH #include #include "error.h" static inline uint32_t adler32_calc(const unsigned char *const data, uint32_t len) { uint32_t adler32; debug(70, "%p, %i -> mhash", data, len); MHASH td = mhash_init(MHASH_ADLER32); mhash(td, data, len); mhash_deinit(td, &adler32); return adler32; } #else extern uint32_t adler32_calc(const unsigned char *const data, uint32_t len); #endif clsync-0.4.1/cgroup.c000066400000000000000000000057141252417542300144660ustar00rootroot00000000000000/* clsync - file tree sync utility based on inotify/kqueue/bsm Copyright (C) 2014 Dmitry Yu Okunev 0x8E30679C This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "common.h" #include "error.h" #include static struct cgroup *cgroup = NULL; int clsync_cgroup_init(ctx_t *ctx_p) { debug(2, "cgroup_name == \"%s\"", ctx_p->cg_groupname); SAFE( cgroup_init(), return -1; ); SAFE( (cgroup = cgroup_new_cgroup(ctx_p->cg_groupname)) == NULL, return -1; ); return 0; } int clsync_cgroup_forbid_extra_devices() { int rc; char *allowed_devices[] = CG_ALLOWED_DEVICES, **allowed_device_i; /* * Unfortunately, libcgroup doesn't allow multiple values for one key, and cgroups doesn't allow multiple devices for one set. So I was been have to write this hack. It adds character '/' to start of "devices.allow" for every new entry. So libclsync thinks that it's different keys, "/sys/fs/cgroup/devices/clsync/123/devices.allow" == "/sys/fs/cgroup/devices/clsync/123//devices.allow". */ char control_name_buf[BUFSIZ+BUFSIZ]={[0 ... BUFSIZ-1] = '/', 'd', 'e', 'v', 'i', 'c', 'e', 's', '.', 'a', 'l', 'l', 'o', 'w'}, *control_name = &control_name_buf[BUFSIZ]; debug(2, ""); struct cgroup_controller *cgc; SAFE( (cgc = cgroup_add_controller(cgroup, "devices")) == NULL, return -1; ); debug(8, "Deny device: \"a\""); SAFE( cgroup_add_value_string(cgc, "devices.deny", "a"), return -1; ); allowed_device_i = allowed_devices; while (*allowed_device_i != NULL) { critical_on (control_name < control_name_buf); debug(8, "Allow device: \"%s\" (\"%s\" = \"%s\")", *allowed_device_i, control_name, *allowed_device_i); SAFE( cgroup_add_value_string(cgc, control_name, *allowed_device_i),return -1; ); control_name--; allowed_device_i++; } if ((rc=cgroup_create_cgroup(cgroup, 1))) { error("Got error while cgroup_create_cgroup(): %s", cgroup_strerror(rc)); return -1; } return 0; } int clsync_cgroup_attach(ctx_t *ctx_p) { int rc; debug(2, ""); if ((rc=cgroup_attach_task_pid(cgroup, ctx_p->pid))) { error("Got error while cgroup_attach_task_pid(): %s", cgroup_strerror(rc)); return -1; } return 0; } int clsync_cgroup_deinit(ctx_t *ctx_p) { debug(2, ""); error_on(cgroup_delete_cgroup_ext(cgroup, CGFLAG_DELETE_IGNORE_MIGRATION | CGFLAG_DELETE_RECURSIVE)); cgroup_free(&cgroup); debug(15, "end"); return 0; } clsync-0.4.1/cgroup.h000066400000000000000000000017551252417542300144740ustar00rootroot00000000000000/* clsync - file tree sync utility based on inotify/kqueue/bsm Copyright (C) 2014 Dmitry Yu Okunev 0x8E30679C This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ extern int clsync_cgroup_init(struct ctx *ctx_p); extern int clsync_cgroup_forbid_extra_devices(); extern int clsync_cgroup_attach(struct ctx *ctx_p); extern int clsync_cgroup_deinit(struct ctx *ctx_p); clsync-0.4.1/clsync.h000066400000000000000000000065551252417542300144730ustar00rootroot00000000000000/* clsync - file tree sync utility based on inotify Copyright (C) 2013 Dmitry Yu Okunev 0x8E30679C This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef __CLSYNC_CLSYNC_H #define __CLSYNC_CLSYNC_H #include #include #include #ifndef CLSYNC_ITSELF # include #endif #define CLSYNC_API_VERSION 2 enum eventobjtype { EOT_UNKNOWN = 0, // Unknown EOT_DOESNTEXIST = 1, // Doesn't exists (not created yet or already deleted) EOT_FILE = 2, // File EOT_DIR = 3, // Directory // The value cannot be higher than "65535". It's due to recognize_event() function of mon_*.c }; typedef enum eventobjtype eventobjtype_t; struct api_eventinfo { uint32_t evmask; // event mask, see /usr/include/linux/inotify.h uint32_t flags; // flags, see "enum eventinfo_flags" size_t path_len; // strlen(path) const char *path; // path eventobjtype_t objtype_old; // type of object by path "path" before the event eventobjtype_t objtype_new; // type of object by path "path" after the event }; typedef struct api_eventinfo api_eventinfo_t; struct ctx; struct indexes; typedef int(*api_funct_init) (struct ctx *, struct indexes *); typedef int(*api_funct_sync) (int n, api_eventinfo_t *); typedef int(*api_funct_rsync) (const char *inclist, const char *exclist); typedef int(*api_funct_deinit)(); enum eventinfo_flags { EVIF_NONE = 0x00000000, // No modifier EVIF_RECURSIVELY = 0x00000001, // Need to be synced recursively EVIF_CONTENTRECURSIVELY = 0x00000002, // Affects recursively only on content of this dir }; typedef enum eventinfo_flags eventinfo_flags_t; /** * @brief Writes the list to list-file for "--include-from" option of rsync using array of api_eventinfo_t * * @param[in] indexes_p Pointer to "indexes" * @param[in] listfile File identifier to write to * @param[in] n Number of records in apievinfo * @param[in] apievinfo Pointer to api_eventinfo_t records * * @retval zero Successful * @retval non-zero If got error while deleting the message. The error-code is placed into returned value. * */ extern int apievinfo2rsynclist(struct indexes *indexes_p, FILE *listfile, int n, api_eventinfo_t *apievinfo); // Not tested, yet /** * @brief Returns currect API version * * @retval api_version Version of clsync's API * */ extern int clsyncapi_getapiversion(); /** * @brief clsync's wrapper for function "fork()". Should be used instead of "fork()" directly, to notify clsync about child's pid. * * @param[in] ctx_p Pointer to "ctx" * * @retval -1 If error (see "man 2 fork", added error code "ECANCELED" if too many children) * @retval 0 If child * @retval pid Pid of child of parent. (see "man 2 fork") * */ extern pid_t clsyncapi_fork(struct ctx *ctx_p); #endif clsync-0.4.1/cluster.c000066400000000000000000001400101252417542300146350ustar00rootroot00000000000000/* clsync - file tree sync utility based on inotify Copyright (C) 2013 Dmitry Yu Okunev 0x8E30679C This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ /* Hello, dear developer. You can ask me directly by e-mail or IRC, if something seems too hard. -- 0x8E30679C */ #ifdef CLUSTER_SUPPORT #include "common.h" #include "indexes.h" #include "error.h" #include "cluster.h" #include "sync.h" #include "calc.h" #include "malloc.h" // Global variables. They will be initialized in cluster_init() #define CLUSTER_RECV_PROC_ERRLIMIT (1<<8) #define NODES_ALLOC (MAX(MAXNODES, NODEID_NOID)+1) int sock_i = -1; struct sockaddr_in sa_i = {0}; int sock_o = -1; struct sockaddr_in sa_o = {0}; ctx_t *ctx_p = NULL; indexes_t *indexes_p = NULL; pthread_t pthread_cluster = 0; nodeinfo_t nodeinfo[NODES_ALLOC]= {{0}}; nodeinfo_t *nodeinfo_my = NULL; uint8_t node_id_my = NODEID_NOID; uint8_t node_ids[NODES_ALLOC] = {0}; unsigned int cluster_timeout = 0; uint8_t node_count = 0; uint8_t node_online = 0; cluster_recvproc_funct_t recvproc_funct[COUNT_CLUSTERCMDID] = {NULL}; window_t window_i = {0}; window_t window_o = {0}; /** * @brief Adds command (message) to window_p->buffer * * @param[in] clustercmd_p Pointer to cluster cmd to put into window * * @retval zero Successful * @retval non-zero If got error while deleting the message. The error-code is placed into returned value. * */ static inline int clustercmd_window_add(window_t *window_p, clustercmd_t *clustercmd_p, GHashTable *serial2queuedpacket_ht) { #ifdef PARANOID if (clustercmd_p->h.src_node_id >= MAXNODES) { error("Invalid src_node_id: %i.", clustercmd_p->h.src_node_id); return EINVAL; } #endif // Checking if there enough window_p->cells allocated if (window_p->packets_len >= window_p->size) { window_p->size += ALLOC_PORTION; # define CXREALLOC(a, size) \ (typeof(a))xrealloc((char *)(a), (size_t)(size) * sizeof(*(a))) window_p->packets_id = CXREALLOC(window_p->packets_id, window_p->size); window_p->occupied_sides = CXREALLOC(window_p->occupied_sides, window_p->size); # undef CXREALLOC } // Calculating required memory space in buffer for the message size_t clustercmd_size = CLUSTERCMD_SIZE(clustercmd_p); size_t required_space = sizeof(clustercmdqueuedpackethdr_t) + clustercmd_size; // Searching occupied boundaries in the window_p->buffer size_t occupied_left = SIZE_MAX, occupied_right=0; int i; i = 0; while (i < window_p->packets_len) { unsigned int window_id; window_id = window_p->packets_id[ i++ ]; occupied_left = MIN(occupied_left, window_p->occupied_sides[window_id].left); occupied_right = MAX(occupied_right, window_p->occupied_sides[window_id].right); } debug(3, "w.size == %u, b_left == %u; b_right == %u; w.buf_size == %u; r_space == %u", window_p->size, occupied_left, occupied_right, window_p->buf_size, required_space); // Trying to find a space in the buffer to place message size_t buf_coordinate = SIZE_MAX; if (window_p->packets_len) { // Free space from left (start of buffer) size_t free_left = occupied_left; // Free space from right (end of buffer) size_t free_right = window_p->buf_size - occupied_right; if (free_left > required_space) buf_coordinate = free_left - required_space; else if (free_right > required_space) buf_coordinate = occupied_right; else { // Not enough space in the window_p->buffer; window_p->buf_size += MAX(CLUSTER_WINDOW_BUFSIZE_PORTION, required_space); window_p->buf = xrealloc(window_p->buf, window_p->buf_size); buf_coordinate = occupied_right; } debug(3, "f_left == %u; f_right == %u; b_coord == %u; w.buf_size == %u", free_left, free_right, buf_coordinate, window_p->buf_size); } else { buf_coordinate = 0; if (window_p->buf_size <= required_space) { window_p->buf_size += MAX(CLUSTER_WINDOW_BUFSIZE_PORTION, required_space); window_p->buf = xrealloc(window_p->buf, window_p->buf_size); } } unsigned int window_id; // packet id in window window_id = window_p->packets_len; // reserving the space in buffer window_p->occupied_sides[window_id].left = buf_coordinate; window_p->occupied_sides[window_id].right = buf_coordinate + required_space; // placing information into buffer clustercmdqueuedpacket_t *queuedpacket_p; debug(3, "b_coord == %u", buf_coordinate); queuedpacket_p = (clustercmdqueuedpacket_t *)&window_p->buf[buf_coordinate]; memset(&queuedpacket_p->h, 0, sizeof(queuedpacket_p->h)); memcpy(&queuedpacket_p->cmd, clustercmd_p, clustercmd_size); queuedpacket_p->h.window_id = window_id; // remembering new packet g_hash_table_insert(serial2queuedpacket_ht, GINT_TO_POINTER(clustercmd_p->h.serial), queuedpacket_p); window_p->packets_id[window_p->packets_len++] = window_id; return 0; } /** * @brief Removes command (message) from window_p->buffer * * @param[in] queuedpacket_p Pointer to queuedpacket structure of the command (message) * * @retval zero Successful * @retval non-zero If got error while deleting the message. The error-code is placed into returned value. * */ static inline int clustercmd_window_del(window_t *window_p, clustercmdqueuedpacket_t *queuedpacket_p, GHashTable *serial2queuedpacket_ht) { #ifdef PARANOID if(!window_p->size) { error("window not allocated."); return EINVAL; } if(!window_p->packets_len) { error("there already no packets in the window."); return EINVAL; } #endif unsigned int window_id_del = queuedpacket_p->h.window_id; unsigned int window_id_last = --window_p->packets_len; // Forgeting the packet // Moving the last packet into place of deleting packet, to free the tail in "window_p->packets_id" and "window_p->occupied_sides" if(window_id_del != window_id_last) { debug(3, "%i -> %i", window_id_last, window_id_del); window_p->packets_id[window_id_del] = window_p->packets_id[window_id_last]; memcpy(&window_p->occupied_sides[window_id_del], &window_p->occupied_sides[window_id_last], sizeof(window_p->occupied_sides[window_id_del])); } // Removing from hash table g_hash_table_remove(serial2queuedpacket_ht, GINT_TO_POINTER(queuedpacket_p->cmd.h.serial)); return 0; } /** * @brief Calculates Adler32 for clustercmd * * @param[in] clustercmd_p Pointer to clustercmd * @param[out] clustercmdadler32_p Pointer to structure to return value(s) * * @retval zero On successful calculation * @retval non-zero On error. Error-code is placed into returned value. * */ int clustercmd_adler32_calc(clustercmd_t *clustercmd_p, clustercmdadler32_t *clustercmdadler32_p, adler32_calc_t flags) { debug(15, "(%p, %p, 0x%x)", clustercmd_p, clustercmdadler32_p, flags); if (flags & ADLER32_CALC_DATA) { uint32_t adler32; uint32_t size = clustercmd_p->h.data_len; char *ptr = clustercmd_p->data.p; // Calculating adler32 = adler32_calc((unsigned char *)ptr, CLUSTER_PAD(size)); debug(20, "dat: 0x%x", adler32); // Ending clustercmdadler32_p->dat = adler32 ^ 0xFFFFFFFF; } if (flags & ADLER32_CALC_HEADER) { uint32_t adler32; clustercmdadler32_t adler32_save; // Preparing memcpy(&adler32_save.hdr, &clustercmd_p->h.adler32.hdr, sizeof(clustercmd_p->h.adler32.hdr)); memset(&clustercmd_p->h.adler32.hdr, 0, sizeof(clustercmd_p->h.adler32.hdr)); adler32 = 0xFFFFFFFF; uint32_t size = sizeof(clustercmdhdr_t); char *ptr = (char *)&clustercmd_p->h; // Calculating adler32 = adler32_calc((unsigned char *)ptr, size); debug(20, "hdr: 0x%x", adler32); // Ending memcpy(&clustercmd_p->h.adler32.hdr, &adler32_save.hdr, sizeof(clustercmd_p->h.adler32.hdr)); clustercmdadler32_p->hdr = adler32 ^ 0xFFFFFFFF; } return 0; } /** * @brief Sends message to another nodes of the cluster. * * @param[in] clustercmd_p Command structure pointer. * * @retval zero Successfully send. * @retval non-zero Got error, while sending. * */ int _cluster_send(clustercmd_t *clustercmd_p) { debug(10, "{h.dst_node_id: %u, h.src_node_id: %u, cmd_id: %u, data_len: %u}", clustercmd_p->h.dst_node_id, clustercmd_p->h.src_node_id, clustercmd_p->h.cmd_id, clustercmd_p->h.data_len); clustercmd_p->h.src_node_id = node_id_my; SAFE (clustercmd_adler32_calc(clustercmd_p, &clustercmd_p->h.adler32, ADLER32_CALC_ALL), return _SAFE_rc ); debug(3, "Sending: " "{h.dst_node_id: %u, h.src_node_id: %u, cmd_id: %u, adler32.hdr: %p, adler32.dat: %p, data_len: %u}", clustercmd_p->h.dst_node_id, clustercmd_p->h.src_node_id, clustercmd_p->h.cmd_id, (void *)(long)clustercmd_p->h.adler32.hdr, (void *)(long)clustercmd_p->h.adler32.dat, clustercmd_p->h.data_len); nodeinfo_t *nodeinfo_p; nodeinfo_p = &nodeinfo[clustercmd_p->h.dst_node_id]; // Checking if the node online switch (nodeinfo_p->status) { case NODESTATUS_DOESNTEXIST: case NODESTATUS_OFFLINE: debug(1, "There's no online node with id %u. Skipping sending.", clustercmd_p->h.dst_node_id); return EADDRNOTAVAIL; default: break; } // Putting the message into an output window if (nodeinfo_my != NULL) clustercmd_window_add(&window_o, clustercmd_p, nodeinfo_my->serial2queuedpacket_ht); // Sending the message debug(10, "sendto(%p, %p, %i, 0, %p, %i)", sock_o, clustercmd_p, CLUSTERCMD_SIZE_PADDED(clustercmd_p), &sa_o, sizeof(sa_o)); debug(50, "clustercmd_p->data.p[0] == 0x%x", clustercmd_p->data.p[0]); critical_on (sendto(sock_o, clustercmd_p, CLUSTERCMD_SIZE_PADDED(clustercmd_p), 0, &sa_o, sizeof(sa_o)) == -1); // Finishing return 0; } static inline int cluster_send(clustercmd_t *clustercmd_p) { int rc; rc = _cluster_send(clustercmd_p); debug(10, "__________________sent___________________ %i", rc); return rc; } /** * @brief Changes information about node's status in nodeinfo[] and updates connected information. * * @param[in] node_id node_id of the node. * @param[in] node_status New node status. * * @retval zero Successful * @retval non-zero If got error while changing the status. The error-code is placed into returned value. * */ int node_update_status(uint8_t node_id, uint8_t node_status) { debug(3, "%i, %i", node_id, node_status); uint8_t node_status_old = nodeinfo[node_id].status; nodeinfo_t *nodeinfo_p = &nodeinfo[node_id]; if ((node_status == NODESTATUS_DOESNTEXIST) && (node_status_old != NODESTATUS_DOESNTEXIST)) { node_count--; node_ids[nodeinfo_p->num] = node_ids[node_count]; g_hash_table_destroy(nodeinfo_p->modtime_ht); g_hash_table_destroy(nodeinfo_p->serial2queuedpacket_ht); #ifdef VERYPARANOID memset(nodeinfo_p, 0, sizeof(*nodeinfo_p)); #endif return 0; } if (node_status == node_status_old) return 0; switch (node_status_old) { case NODESTATUS_DOESNTEXIST: nodeinfo_p->id = node_id; nodeinfo_p->num = node_count; nodeinfo_p->last_serial = -1; nodeinfo_p->modtime_ht = g_hash_table_new_full(g_str_hash, g_str_equal, free, 0); nodeinfo_p->serial2queuedpacket_ht = g_hash_table_new_full(g_direct_hash, g_direct_equal, 0, 0); node_ids[node_count] = node_id; node_count++; #ifdef PARANOID if (node_status == NODESTATUS_OFFLINE) break; // In case of NODESTATUS_DOESNTEXIST -> NODESTATUS_OFFLINE, node_online should be increased #endif case NODESTATUS_OFFLINE: nodeinfo_p->last_serial = -1; node_online++; break; default: if (node_status == NODESTATUS_OFFLINE) node_online--; break; } nodeinfo[node_id].status = node_status; return 0; } /** * @brief Changes information about node's name and updates connected information. * * @param[in] node_id node_id of the node. * @param[in] node_name New node name * * @retval zero Successful * @retval non-zero If got error while changing the status. The error-code is placed into returned value. * */ int node_update_name(uint8_t node_id, const char *node_name) { debug(3, "%i, \"%s\"", node_id, node_name); nodeinfo_t *nodeinfo_p = &nodeinfo[node_id]; void *ret = g_hash_table_lookup(indexes_p->nodenames_ht, node_name); if (ret != NULL) { int rc; uint8_t node_old_id = GPOINTER_TO_INT(ret); nodeinfo_t *nodeinfo_old_p = &nodeinfo[node_old_id]; debug(5, "nodename \"%s\" had been used by node_id == %i", node_name, node_old_id); if (node_old_id == node_id) { debug(10, "node_old_id [%i] == node_id [%i]", node_old_id, node_id); return 0; } { debug(15, "Sending a DIE command to node_id (%i)", node_old_id); clustercmd_t *clustercmd_p = CLUSTER_ALLOCA(clustercmd_die_t, 0); clustercmd_p->h.cmd_id = CLUSTERCMDID_DIE; clustercmd_p->h.dst_node_id = node_old_id; if ((rc = cluster_send(clustercmd_p))) return rc; } nodeinfo_old_p->node_name = NULL; debug(3, "changing status of node_id == %i to NODESTATUS_OFFLINE", node_old_id); node_update_status(node_old_id, NODESTATUS_OFFLINE); } char *node_name_dup = strdup(node_name); nodeinfo_p->node_name = node_name_dup; g_hash_table_replace(indexes_p->nodenames_ht, node_name_dup, GINT_TO_POINTER(node_id)); return 0; } /** * @brief Sets message processing functions for cluster_recv_proc() function for specified command type * * @param[in] cmd_id The command type * @param[in] procfunct The processing function for messages with specified cmd_id * * @retval zero Successful * @retval non-zero If got error while setting processing function. The error-code is placed into returned value. * */ static inline int cluster_recv_proc_set(clustercmd_id_t cmd_id, cluster_recvproc_funct_t procfunct) { recvproc_funct[cmd_id] = procfunct; return 0; } /** * @brief Safe wrapper for recvfrom() function * * @param[in] sock The socket descriptor * @param[out] _buf_p Pointer to pointer to buffer * @param[in] flags Flags... * * @retval zero Successful * @retval non-zero If got error while read()-ing. The error-code is placed into returned value. "-1" means that message is too short. * */ static inline int cluster_read(int sock, clustercmd_t **cmd_pp, cluster_read_flags_t flags) { static char buf[CLUSTER_PACKET_MAXSIZE]; static clustercmd_t *cmd_p = (void *)buf; struct sockaddr_in sa_in; size_t sa_in_len = sizeof(sa_in); *cmd_pp = cmd_p; debug(20, "%i, %p, 0x%x", sock, buf, flags); int readret = recvfrom(sock, buf, CLUSTER_PACKET_MAXSIZE, MSG_WAITALL, (struct sockaddr *)&sa_in, (socklen_t * restrict)&sa_in_len); debug(30, "recvfrom(%i, %p, 0x%x, %p, %p) -> %i", sock, buf, MSG_WAITALL, &sa_in, &sa_in_len, readret); #ifdef PARANOID if (!readret) { error("recvfrom() returned 0. This shouldn't happend. Exit."); return EINVAL; } #endif if (readret < 0) { error("recvfrom() returned %i. " "Seems, that something wrong with network socket.", readret); return errno != -1 ? errno : -2; } debug(2, "Got message from %s (len: %i).", inet_ntoa(sa_in.sin_addr), readret); if (readret < sizeof(clustercmdhdr_t)) { // Too short message error("Warning: cluster_read(): Got too short message from node (no header [or too short]). Ignoring it."); return -1; } if (readret < CLUSTERCMD_SIZE(cmd_p)) { // Too short message error("Warning: cluster_read(): Got too short message from node (no data [or too short]). Ignoring it."); return -1; } // Incorrect size? if (readret & 0x3) { error("Warning: cluster_recv(): Received packet of size %i (not a multiple of 4). Ignoring it.", readret); return 0; } return 0; } /** * @brief Sends packet-reject notification * * @param[in] clustercmd_p Pointer to clustercmd that will be rejected * @param[in] reason Reason why the clustercmd is denied * * @retval zero Successful * @retval non-zero If got error while read()-ing. The error-code is placed into returned value. "-1" means that message is too short. * */ static inline int clustercmd_reject(clustercmd_t *clustercmd_p, uint8_t reason) { clustercmd_t *clustercmd_rej_p = CLUSTER_ALLOCA(clustercmd_rej_t, 0); clustercmd_rej_p->h.dst_node_id = clustercmd_p->h.src_node_id; clustercmd_rej_p->data.rej.serial = clustercmd_p->h.serial; clustercmd_rej_p->data.rej.reason = reason; return cluster_send(clustercmd_rej_p); } #define CLUSTER_RECV_RETURNMESSAGE(clustercmd_p) {\ last_serial = (clustercmd_p)->h.serial;\ last_src_node_id = (clustercmd_p)->h.src_node_id;\ if(clustercmd_pp != NULL)\ *clustercmd_pp = (clustercmd_p);\ return 1;\ } /** * @brief Receives message from another nodes of the cluster. (not thread-safe) * * @param[out] clustercmd_pp Pointer to command structure pointer. It will be re-allocated every time when size is not enough. Allocated space will be reused on next calling. * @param[i] timeout Timeout (in milliseconds). * * @retval 1 If there's new message. * @retval 0 If there's no new messages. * @retval -1 If got error while receiving. The error-code is placed into "errno". * */ static int _cluster_recv(clustercmd_t **clustercmd_pp, struct timeval *timeout) { clustercmd_t *clustercmd_p; static uint8_t last_src_node_id = NODEID_NOID; static uint32_t last_serial = 0; // Checking if there message is waiting in the window if (last_src_node_id != NODEID_NOID) { nodeinfo_t *nodeinfo_p = &nodeinfo[last_src_node_id]; if (nodeinfo_p->serial2queuedpacket_ht != NULL) { clustercmdqueuedpacket_t *clustercmdqueuedpacket_p = (clustercmdqueuedpacket_t *) g_hash_table_lookup(nodeinfo_p->serial2queuedpacket_ht, GINT_TO_POINTER(last_serial+1)); if (clustercmdqueuedpacket_p != NULL) CLUSTER_RECV_RETURNMESSAGE(&clustercmdqueuedpacket_p->cmd); } } // Checking if there any event on read socket // select() fd_set rfds; FD_ZERO(&rfds); FD_SET(sock_i, &rfds); debug(3, "select() with timeout {%u, %u}", timeout->tv_sec, timeout->tv_usec); int selret = select(sock_i+1, &rfds, NULL, NULL, timeout); // processing select()'s retuned value if (selret < 0) { error("got error while select()."); return -1; } if (selret == 0) { debug(3, "no new messages."); return 0; } debug(3, "got new message(s)."); debug(10, "Reading new message's header"); clustercmdadler32_t adler32; //clustercmd_t *clustercmd_p = (clustercmd_t *)mmap(NULL, sizeof(clustercmdhdr_t), PROT_NONE, // MAP_PRIVATE, sock, 0); int ret; if ((ret=cluster_read(sock_i, clustercmd_pp, CLREAD_NONE))) { if(ret == -1) return 0; // Invalid message? Skipping. error("Got error from cluster_read()."); errno = ret; return -1; } clustercmd_p = *clustercmd_pp; debug(3, "Received: {h.dst_node_id: %u, h.src_node_id: %u, cmd_id: %u," " serial: %u, adler32: {0x%x, 0x%x}, data_len: %u}", clustercmd_p->h.dst_node_id, clustercmd_p->h.src_node_id, clustercmd_p->h.cmd_id, clustercmd_p->h.serial, clustercmd_p->h.adler32.hdr, clustercmd_p->h.adler32.dat, clustercmd_p->h.data_len); // Checking adler32 of packet headers. clustercmd_adler32_calc(clustercmd_p, &adler32, ADLER32_CALC_HEADER); if (adler32.hdr != clustercmd_p->h.adler32.hdr) { debug(1, "hdr-adler32 mismatch: %p != %p.", (void*)(long)clustercmd_p->h.adler32.hdr, (void*)(long)adler32.hdr); if((ret=clustercmd_reject(clustercmd_p, REJ_ADLER32MISMATCH)) != EADDRNOTAVAIL) { error("Got error while clustercmd_reject()."); errno = ret; return -1; } } // Checking src_node_id and dst_node_id uint8_t src_node_id = clustercmd_p->h.src_node_id; uint8_t dst_node_id = clustercmd_p->h.dst_node_id; // Packet from registering node? if (src_node_id == NODEID_NOID) { // Wrong command from registering node? if (clustercmd_p->h.cmd_id != CLUSTERCMDID_HELLO) { error("Warning: cluster_recv(): Got non hello packet from NOID node. Ignoring the packet."); return 0; } if (clustercmd_p->h.serial != 0) { error("Warning: cluster_recv(): Got packet with non-zero serial from NOID node. Ignoring the packet."); return 0; } } else // Wrong src_node_id? if (src_node_id >= MAXNODES) { error("Warning: cluster_recv(): Invalid h.src_node_id: %i >= "XTOSTR(MAXNODES)"", src_node_id); return -1; } // Is this broadcast message? if (dst_node_id == NODEID_NOID) { // CODE HERE } else // Wrong dst_node_id? if (dst_node_id >= MAXNODES) { error("Warning: cluster_recv(): Invalid h.dst_node_id: %i >= "XTOSTR(MAXNODES)"", dst_node_id); return -1; } // Seems, that headers are correct. Continuing. // Paranoid routines // The message from us? Something wrong if it is. if ((clustercmd_p->h.src_node_id == node_id_my) && (node_id_my != NODEID_NOID)) critical("node_id collision"); nodeinfo_t *nodeinfo_p = &nodeinfo[src_node_id]; // Not actual packet? long serial_diff = clustercmd_p->h.serial - nodeinfo_p->last_serial; debug(10, "serial_diff == %i", serial_diff); if (serial_diff <= 0 || serial_diff > CLUSTER_WINDOW_PCKTLIMIT) { debug(1, "Ignoring packet (serial %i) from %i due to serial_diff: 0 <= || > %i", clustercmd_p->h.serial, src_node_id, serial_diff, CLUSTER_WINDOW_PCKTLIMIT); return -1; } // Is this misordered packet? if (clustercmd_p->h.serial != nodeinfo_p->last_serial + 1) { clustercmd_window_add(&window_i, clustercmd_p, nodeinfo_p->serial2queuedpacket_ht); return -1; } // Is this the end of packet (packet without data) if (clustercmd_p->h.data_len == 0) CLUSTER_RECV_RETURNMESSAGE(clustercmd_p); // Too big data? if (clustercmd_p->h.data_len > CLUSTER_PACKET_MAXSIZE) { error("Warning: cluster_recv(): Got too big message from node %i. Ignoring it.", src_node_id); return -1; } /* // Need more space for this packet? if (CLUSTERCMD_SIZE(clustercmd_p) > size) { size = CLUSTERCMD_SIZE(clustercmd_p); clustercmd_p = (clustercmd_t *)xrealloc((char *)clustercmd_p, size); } debug(10, "Reading the data"); if ((ret=cluster_read(sock_i, (void *)clustercmd_p->data.p, CLUSTER_PAD(clustercmd_p->h.data_len), CLREAD_CONTINUE))) { if (ret == -1) return 0; error("Got error from cluster_read()."); errno = ret; return -1; } */ // Checking adler32 of packet data. clustercmd_adler32_calc(clustercmd_p, &adler32, ADLER32_CALC_DATA); if (adler32.dat != clustercmd_p->h.adler32.dat) { debug(1, "dat-adler32 mismatch: %p != %p.", (void*)(long)clustercmd_p->h.adler32.dat, (void*)(long)adler32.dat); if ((ret=clustercmd_reject(clustercmd_p, REJ_ADLER32MISMATCH)) != EADDRNOTAVAIL) { error("Got error while clustercmd_reject()."); errno = ret; return -1; } } CLUSTER_RECV_RETURNMESSAGE(clustercmd_p); } static inline int cluster_recv(clustercmd_t **clustercmd_pp, struct timeval *timeout) { int rc; rc = _cluster_recv(clustercmd_pp, timeout); debug(10, "__________________recv___________________ %i", rc); return rc; } /** * @brief (hsyncop) Reads messages for time "_timeout" and proceeding them to recvproc_funct[] functions * * @param[in] _timeout How long to wait messages (totally) * * @retval zero Successful * @retval non-zero If got error while reading or processing messages. The error-code is placed into returned value. * */ int cluster_recv_proc(unsigned int timeout_rel_int) { struct timeval timeout_rel, timeout_abs, tv_abs; int error_count, iteration; debug(3, "cluster_recv_proc(%i)", timeout_rel_int); clustercmd_t *clustercmd_p = NULL; int ret; timeout_rel.tv_sec = timeout_rel_int / 1000; timeout_rel.tv_usec = timeout_rel_int % 1000; gettimeofday(&tv_abs, NULL); timeradd(&tv_abs, &timeout_rel, &timeout_abs); error_count = 0; iteration = 0; while (1) { if (iteration++) { gettimeofday(&tv_abs, NULL); if (timercmp(&timeout_abs, &tv_abs, >)) timersub(&timeout_abs, &tv_abs, &timeout_rel); else memset(&timeout_rel, 0, sizeof(timeout_rel)); debug(5, "timeout_abs == {%u, %u}; tv_abs == {%u, %u}; timeout_rel == {%u, %u}", timeout_abs.tv_sec, timeout_abs.tv_usec, tv_abs.tv_sec, tv_abs.tv_usec, timeout_rel.tv_sec, timeout_rel.tv_usec ); } ret = cluster_recv(&clustercmd_p, &timeout_rel); if (!ret) break; // Exit if error if (ret == -1) { warning("Got error while cluster_recv(). error_count == %i", error_count); error_count++; critical_on (error_count >= CLUSTER_RECV_PROC_ERRLIMIT); } // If we have appropriate callback function, then call it! :) if (recvproc_funct[clustercmd_p->h.cmd_id] != NULL) { debug(2, "Calling function by pointer %p", recvproc_funct[clustercmd_p->h.cmd_id]); if ((ret = recvproc_funct[clustercmd_p->h.cmd_id](clustercmd_p))) { error("Got error from recvproc_funct[%i]: %s (%i)", clustercmd_p->h.cmd_id); return ret; } continue; } // We didn't found an appropriate callback function debug(2, "There's no appropriate callback function for cmd_id == %i", clustercmd_p->h.cmd_id); } return 0; } /** * @brief recvproc-function for DIE-messages * * @param[in] clustercmd_p Pointer to clustercmd * * @retval zero Successfully initialized. (never happens) * @retval non-zero Got error, while initializing. * */ static int cluster_recvproc_die(clustercmd_t *clustercmd_p) { critical("Got DIE message from node_id == %i,", clustercmd_p->h.src_node_id); return 0; } /** * @brief recvproc-function for ACK-messages * * @param[in] clustercmd_p Pointer to clustercmd * * @retval zero Successfully initialized. * @retval non-zero Got error, while initializing. * */ static int cluster_recvproc_ack(clustercmd_t *clustercmd_p) { uint32_t cmd_serial_ack = clustercmd_p->data.ack.serial; clustercmdqueuedpacket_t *queuedpacket_p = (clustercmdqueuedpacket_t *)g_hash_table_lookup(nodeinfo_my->serial2queuedpacket_ht, GINT_TO_POINTER(cmd_serial_ack)); if (queuedpacket_p == NULL) return 0; uint8_t node_id_from = clustercmd_p->h.src_node_id; if (! queuedpacket_p->h.w.o.ack_from[node_id_from]) { queuedpacket_p->h.w.o.ack_count++; queuedpacket_p->h.w.o.ack_from[node_id_from]++; if (queuedpacket_p->h.w.o.ack_count == node_count-1) clustercmd_window_del(&window_o, queuedpacket_p, nodeinfo_my->serial2queuedpacket_ht); } return 0; } /** * @brief Sets message processing functions for cluster_recv_proc() function for specified command type * * @param[in] cmd_id The command type * @param[in] procfunct The processing function for messages with specified cmd_id * * @retval zero Successful * @retval non-zero If got error while setting processing function. The error-code is placed into returned value. * */ int cluster_io_init() { cluster_recv_proc_set(CLUSTERCMDID_ACK, cluster_recvproc_ack); cluster_recv_proc_set(CLUSTERCMDID_DIE, cluster_recvproc_die); return 0; } /** * @brief Antagonist of cluster_recv_proc_init() function. Freeing everything what was allocated in cluster_recv_proc_init() * * @retval zero Successfully initialized * @retval non-zero Got error, while initializing * */ int cluster_io_deinit() { if(window_i.buf_size) { #ifdef PARANOID if(window_i.buf == NULL) { error("window_i.buf_size != 0, but window_i.buf == NULL."); } else #endif free(window_i.buf); } if(window_o.buf_size) { #ifdef PARANOID if(window_o.buf == NULL) { error("window_o.buf_size != 0, but window_o.buf == NULL."); } else #endif free(window_o.buf); } return 0; } /** * @brief recvproc-function for welcome-messages * * @param[in] clustercmd_p Pointer to clustercmd * * @retval zero Successfully initialized. * @retval non-zero Got error, while initializing. * */ static int cluster_recvproc_welcome(clustercmd_t *clustercmd_p) { debug(20, "%p", clustercmd_p); // static time_t updatets = 0; clustercmd_welcome_t *data_welcome_p = &clustercmd_p->data.welcome; /* // Is this the most recent information? Skipping if not. if (!(data_welcome_p->updatets > updatets)) return 0; */ // Is the node name length in message equals to our node name length? Skipping if not. uint32_t recv_nodename_len; recv_nodename_len = welcome_to_node_name_len(clustercmd_p); if (recv_nodename_len != ctx_p->cluster_nodename_len) { debug(9, "recv_nodename_len [%i] != ctx_p->cluster_nodename_len [%i]", recv_nodename_len, ctx_p->cluster_nodename_len); return 0; } // Is the node name equals to ours? Skipping if not. if (memcmp(welcome_to_node_name(data_welcome_p), ctx_p->cluster_nodename, recv_nodename_len)) { debug(9, "to_node_name != ctx_p->cluster_nodename"); return 0; } // Remembering the node that answered us node_update_status(clustercmd_p->h.src_node_id, NODESTATUS_SEEMSONLINE); { char *node_name; node_name = alloca(data_welcome_p->from_node_name_len); memcpy(node_name, data_welcome_p->from_node_name, data_welcome_p->from_node_name_len); node_update_name(clustercmd_p->h.src_node_id, node_name); } // Seems, that somebody knows our node id, remembering it. node_id_my = clustercmd_p->h.dst_node_id; // updatets = data_welcome_p->updatets; return 0; } /** * @brief recvproc-function for hello-messages * * @param[in] clustercmd_p Pointer to clustercmd * * @retval zero Successfully initialized. * @retval non-zero Got error, while initializing. * */ static int cluster_recvproc_hello(clustercmd_t *clustercmd_p) { clustercmd_hello_t *data_hello_p = &clustercmd_p->data.hello; // Sending information to the new node { int ret; uint8_t node_id; debug(15, "Preparing a welcome message for nodename \"%s\" from nodename == \"%s\"", data_hello_p->node_name, ctx_p->cluster_nodename); size_t data_len = sizeof(clustercmd_welcome_t) + clustercmd_p->h.data_len+ctx_p->cluster_nodename_len; clustercmd_t *answer_p = CLUSTER_ALLOCA(void, data_len); clustercmd_welcome_t *answer_data_p = &answer_p->data.welcome; answer_data_p->from_node_name_len = ctx_p->cluster_nodename_len; memcpy(answer_data_p->from_node_name, ctx_p->cluster_nodename, ctx_p->cluster_nodename_len); memcpy(welcome_to_node_name(answer_data_p), data_hello_p->node_name, clustercmd_p->h.data_len); { void *ptr; char *node_name = alloca(clustercmd_p->h.data_len+1); memcpy(node_name, data_hello_p->node_name, clustercmd_p->h.data_len+1); ptr = g_hash_table_lookup(indexes_p->nodenames_ht, node_name); node_id = (ptr == NULL ? NODEID_NOID : GPOINTER_TO_INT(ptr)); debug(3, "\"%s\" -> %i", node_name, node_id); } answer_p->h.data_len = data_len; answer_p->h.cmd_id = CLUSTERCMDID_WELCOME; answer_p->h.dst_node_id = node_id; // broadcast if ((ret = cluster_send(answer_p))) return ret; } return 0; } /** * @brief recvproc-function for register-messages * * @param[in] clustercmd_p Pointer to clustercmd * * @retval zero Successfully initialized. * @retval non-zero Got error, while initializing. * */ static int cluster_recvproc_register(clustercmd_t *clustercmd_p) { clustercmd_reg_t *data_register_p = &clustercmd_p->data.reg; char *node_name = alloca(clustercmd_p->h.data_len+1); memcpy(node_name, data_register_p->node_name, clustercmd_p->h.data_len+1); node_update_name(clustercmd_p->h.src_node_id, node_name); return 0; } extern int cluster_loop(); /** * @brief Initializes cluster subsystem. * * @param[in] _ctx_p Pointer to "glob" variable, defined in main(). * @param[in] _indexes_p Pointer to "indexes" variable, defined in sync_run(). * * @retval zero Successfully initialized. * @retval non-zero Got error, while initializing. * */ int cluster_init(ctx_t *_ctx_p, indexes_t *_indexes_p) { int ret; // Preventing double initializing if (ctx_p != NULL) { error("cluster subsystem is already initialized."); return EALREADY; } // Initializing global variables, pt. 1 ctx_p = _ctx_p; indexes_p = _indexes_p; cluster_timeout = ctx_p->cluster_timeout; indexes_p->nodenames_ht = g_hash_table_new_full(g_str_hash, g_str_equal, free, 0); node_update_status(NODEID_NOID, NODESTATUS_ONLINE); { int i=0; while (i < MAXNODES) { nodeinfo[i].last_serial = -1; i++; } } // Initializing network routines // Input socket // Creating socket sock_i = socket(AF_INET, SOCK_DGRAM, 0); if(sock_i < 0) { error("Cannot create socket for input traffic"); return errno; } // Enable SO_REUSEADDR to allow multiple instances of this application to receive copies // of the multicast datagrams. int reuse = 1; if(setsockopt(sock_i, SOL_SOCKET, SO_REUSEADDR,(char *)&reuse, sizeof(reuse)) < 0) { error("Got error while setsockopt()"); return errno; } // Binding sa_i.sin_family = AF_INET; sa_i.sin_port = htons(ctx_p->cluster_mcastipport); sa_i.sin_addr.s_addr = INADDR_ANY; if(bind(sock_i, (struct sockaddr*)&sa_i, sizeof(sa_i))) { error("Got error while bind()"); return errno; } // Joining to multicast group struct ip_mreq group; group.imr_interface.s_addr = inet_addr(ctx_p->cluster_iface); group.imr_multiaddr.s_addr = inet_addr(ctx_p->cluster_mcastipaddr); if(setsockopt(sock_i, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&group, sizeof(group)) < 0) { error("Cannot setsockopt() to enter to membership %s -> %s", ctx_p->cluster_iface, ctx_p->cluster_mcastipaddr); return errno; } // Output socket // Creating socket sock_o = socket(AF_INET, SOCK_DGRAM, 0); if(sock_o < 0) { error("Cannot create socket for output traffic"); return errno; } // Initializing the group sockaddr structure sa_o.sin_family = AF_INET; sa_o.sin_port = htons(ctx_p->cluster_mcastipport); sa_o.sin_addr.s_addr = inet_addr(ctx_p->cluster_mcastipaddr); // Disable looping back output datagrams { char loopch = 0; if(setsockopt(sock_o, IPPROTO_IP, IP_MULTICAST_LOOP, (char *)&loopch, sizeof(loopch))<0) { error("Cannot disable loopback for output socket."); return errno; } } // Setting local interface for output traffic { struct in_addr addr_o; addr_o.s_addr = inet_addr(ctx_p->cluster_iface); if(setsockopt(sock_o, IPPROTO_IP, IP_MULTICAST_IF, &addr_o, sizeof(addr_o)) < 0) { error("Cannot set local interface for outbound traffic"); return errno; } } // Initializing another routines cluster_io_init(); // Getting my ID in the cluster // Trying to preserve my node_id after restart. :) // Asking another nodes about my previous node_id { debug(15, "Preparing a message with my nodename == \"%s\" (%i)", ctx_p->cluster_nodename, ctx_p->cluster_nodename_len); clustercmd_t *clustercmd_p = CLUSTER_ALLOCA(clustercmd_hello_t, ctx_p->cluster_nodename_len); clustercmd_p->h.data_len = ctx_p->cluster_nodename_len; memcpy(clustercmd_p->data.hello.node_name, ctx_p->cluster_nodename, clustercmd_p->h.data_len+1); clustercmd_p->h.cmd_id = CLUSTERCMDID_HELLO; clustercmd_p->h.dst_node_id = NODEID_NOID; // broadcast if ((ret = cluster_send(clustercmd_p))) return ret; } // Processing answers cluster_recv_proc_set(CLUSTERCMDID_WELCOME, cluster_recvproc_welcome); if ((ret=cluster_recv_proc(cluster_timeout))) return ret; // Ignore next welcome messages cluster_recv_proc_set(CLUSTERCMDID_WELCOME, NULL); debug(3, "After communicating with others, my node_id is %i.", node_id_my); // Getting free node_id if nobody said us the certain value (see above). if (node_id_my == NODEID_NOID) { int i=0; while (icluster_nodename_len); clustercmd_reg_t *data_reg_p = &clustercmd_p->data.reg; memcpy(data_reg_p->node_name, ctx_p->cluster_nodename, ctx_p->cluster_nodename_len+1); clustercmd_p->h.data_len = ctx_p->cluster_nodename_len+1; clustercmd_p->h.cmd_id = CLUSTERCMDID_REG; clustercmd_p->h.dst_node_id = NODEID_NOID; // broadcast if((ret=cluster_send(clustercmd_p))) return ret; } // Setting process functions cluster_recv_proc_set(CLUSTERCMDID_HELLO, cluster_recvproc_hello); cluster_recv_proc_set(CLUSTERCMDID_REG, cluster_recvproc_register); // Getting answers if ((ret=cluster_recv_proc(cluster_timeout))) return ret; node_update_status(node_id_my, NODESTATUS_ONLINE); // Running thread, that will process background communicating routines with another nodes. // The process is based on function cluster_loop() [let's use shorthand "cluster_loop()-thread"] ret = pthread_create(&pthread_cluster, NULL, (void *(*)(void *))cluster_loop, NULL); return ret; } /** * @brief (syncop) Sends signal to cluster_loop()-thread * * @param[in] signal Signal number * * @retval zero Successfully send the signal * @retval non-zero Got error, while sending the signal * */ static inline int cluster_signal(int signal) { if (pthread_cluster) return pthread_kill(pthread_cluster, signal); return 0; } extern int cluster_modtime_exchange_cleanup(); /** * @brief Antagonist of cluster_init() function. Kills cluster_loop()-thread and cleaning up * * @retval zero Successfully initialized * @retval non-zero Got error, while initializing * */ int cluster_deinit() { int ret = 0; cluster_signal(SIGTERM); ret = pthread_join(pthread_cluster, NULL); cluster_io_deinit(); node_update_status(NODEID_NOID, NODESTATUS_DOESNTEXIST); #ifdef VERYPARANOID int i=0; #endif while(node_count) { #ifdef VERYPARANOID if(i++ > MAXNODES) { error("cluster_deinit() looped. Forcing break."); break; } #endif node_update_status(0, NODESTATUS_DOESNTEXIST); } close(sock_i); close(sock_o); #ifdef VERYPARANOID memset(nodeinfo, 0, sizeof(nodeinfo_t) * NODES_ALLOC); nodeinfo_my = NULL; node_count = 0; node_online = 0; node_id_my = NODEID_NOID; memset(&sa_i, 0, sizeof(sa_i)); memset(&sa_o, 0, sizeof(sa_o)); #endif cluster_modtime_exchange_cleanup(); g_hash_table_destroy(indexes_p->nodenames_ht); return ret; } /** * @brief (syncop) Forces anothes nodes to ignore events about the file or directory * * @param[in] fpath Path to the file or directory * * @retval zero Successfully initialized * @retval non-zero Got error, while initializing * */ int cluster_lock(const char *fpath) { return 0; } /** * @brief (syncop) Forces anothes nodes to ignore events about all files and directories listed in queues of "indexes_p" * * @retval zero Successfully initialized * @retval non-zero Got error, while initializing * */ int cluster_lock_byindexes() { return 0; } /** * @brief (syncop) Returns events-handling on another nodes about all files and directories, locked by cluster_lock() and cluster_lock_byindexes() from this node * * @retval zero Successfully initialized * @retval non-zero Got error, while initializing * */ int cluster_unlock_all() { return 0; } #define CLUSTER_LOOP_CHECK(a) {\ int ret = a;\ if(ret) {\ sync_term(ret);\ return ret;\ }\ } /** * @brief Processes background communicating routines with another nodes. cluster_init() function create a thread for this function. * * @retval zero Successfully initialized * @retval non-zero Got error, while initializing * */ int cluster_loop() { int ret = 0; sigset_t sigset_cluster; // Ignoring SIGINT signal sigemptyset(&sigset_cluster); sigaddset(&sigset_cluster, SIGINT); CLUSTER_LOOP_CHECK(pthread_sigmask(SIG_BLOCK, &sigset_cluster, NULL)); // Don't ignoring SIGTERM signal sigemptyset(&sigset_cluster); sigaddset(&sigset_cluster, SIGTERM); CLUSTER_LOOP_CHECK(pthread_sigmask(SIG_UNBLOCK, &sigset_cluster, NULL)); // Starting the loop debug(3, "cluster_loop() started."); while (1) { int _ret; // Waiting for event fd_set rfds; FD_ZERO(&rfds); FD_SET(sock_i, &rfds); debug(3, "select()"); _ret = select(sock_i+1, &rfds, NULL, NULL, NULL); // Exit if error if ((_ret == -1) && (errno != EINTR)) { ret = errno; sync_term(ret); break; } // Breaking the loop, if there's SIGTERM signal for this thread debug(3, "sigpending()"); if (sigpending(&sigset_cluster)) if(sigismember(&sigset_cluster, SIGTERM)) break; // Processing new messages debug(3, "cluster_recv_proc()"); if ((ret=cluster_recv_proc(0))) { sync_term(ret); break; } } debug(3, "cluster_loop() finished with exitcode %i.", ret); return ret; #ifdef DOXYGEN sync_term(0); #endif } /** * @brief Updating information about modification time of a directory. * * @param[in] path Canonized path to updated file/dir * @param[in] dirlevel Directory level provided by fts (man 3 fts) * @param[in] st_mode st_mode value to detect is it directory or not (S_IFDIR or not) * * @retval zero Successfully initialized * @retval non-zero Got error, while initializing * */ int cluster_modtime_update(const char *path, short int dirlevel, mode_t st_mode) { // "modtime" is incorrent name-part of function. Actually it updates "change time" (man 2 lstat64). int ret; // Getting relative directory level (depth) short int dirlevel_rel = dirlevel - ctx_p->watchdir_dirlevel; if ((st_mode & S_IFMT) == S_IFDIR) dirlevel_rel++; // Don't remembering information about directories with level beyond the limits if ((dirlevel_rel > ctx_p->cluster_scan_dl_max) || (dirlevel_rel < ctx_p->cluster_hash_dl_min)) return 0; // Getting directory/file-'s information (including "change time" aka "st_ctime") stat64_t stat64; ret=lstat64(path, &stat64); if (ret) { error("Cannot lstat64()", path); return errno; } // Getting absolute directory path const char *dirpath; if ((st_mode & S_IFMT) == S_IFDIR) { dirpath = path; } else { char *path_dup = strdup(path); dirpath = (const char *)dirname(path_dup); free(path_dup); } // Getting relative directory path // Initializing size_t dirpath_len = strlen(dirpath); char *dirpath_rel_p = xmalloc(dirpath_len+1); char *dirpath_rel = dirpath_rel_p; const char *dirpath_rel_full = &dirpath[ctx_p->watchdirlen]; size_t dirpath_rel_full_len = dirpath_len - ctx_p->watchdirlen; // Getting coodinate of the end (directory path is already canonized, so we can simply count number of slashes to get directory level) int slashcount=0; size_t dirpath_rel_end=0; while (dirpath_rel_full[dirpath_rel_end] && (dirpath_rel_end < dirpath_rel_full_len)) { if (dirpath_rel_full[dirpath_rel_end] == '/') { slashcount++; if (slashcount >= ctx_p->cluster_hash_dl_max) break; } dirpath_rel_end++; } // Copy the required part of path to dirpath_rel memcpy(dirpath_rel, dirpath_rel_full, dirpath_rel_end); // Updating "st_ctime" information. We should check current value for this directory and update it only if it less or not set. // Checking current value char toupdate = 0; gpointer ctime_gp = g_hash_table_lookup(nodeinfo_my->modtime_ht, dirpath_rel); if (ctime_gp == NULL) toupdate++; else if (GPOINTER_TO_INT(ctime_gp) < stat64.st_ctime) toupdate++; // g_hash_table_replace() will replace existent information about the directory or create it if it doesn't exist. if (toupdate) g_hash_table_replace(nodeinfo_my->modtime_ht, strdup(dirpath_rel), GINT_TO_POINTER(stat64.st_ctime)); // Why I'm using "st_ctime" instead of "st_mtime"? Because "st_ctime" also updates on updating inode information. return 0; } /** * @brief Puts entry to list to be send to other nodes. To be called from cluster_modtime_exchange() * * @param[in] pushrentry_arg_p Pointer to pushentry_arg structure * */ void cluster_modtime_exchange_pushentry(gpointer dir_gp, gpointer modtype_gp, void *pushentry_arg_gp) { struct pushdoubleentry_arg *pushentry_arg_p = (struct pushdoubleentry_arg *)pushentry_arg_gp; char *dir = (char *)dir_gp; time_t ctime = (time_t)GPOINTER_TO_INT(modtype_gp); size_t size = strlen(dir)+1; // TODO: strlen should be already prepared // but not re-calculated here if(pushentry_arg_p->allocated <= pushentry_arg_p->total) { pushentry_arg_p->allocated += ALLOC_PORTION; pushentry_arg_p->entry = (struct doubleentry *) xrealloc( (char *)pushentry_arg_p->entry, pushentry_arg_p->allocated * sizeof(*pushentry_arg_p->entry) ); } pushentry_arg_p->entry[pushentry_arg_p->total].dat0 = dir; pushentry_arg_p->entry[pushentry_arg_p->total].size0 = size; pushentry_arg_p->entry[pushentry_arg_p->total].dat1 = (void *)ctime; // Will be problems if sizeof(time_t) > sizeof(void *) pushentry_arg_p->entry[pushentry_arg_p->total].size1 = sizeof(ctime); pushentry_arg_p->size += size; pushentry_arg_p->total++; return; } static struct pushdoubleentry_arg cluster_modtime_exchange_pushentry_arg = {0}; /** * @brief Clean up after the last run of cluster_modtime_exchange. * * @retval zero Successfully initialized * @retval non-zero Got error, while initializing * */ int cluster_modtime_exchange_cleanup() { struct pushdoubleentry_arg *pushentry_arg_p = &cluster_modtime_exchange_pushentry_arg; int i=0; while(i < pushentry_arg_p->allocated) { if(pushentry_arg_p->entry[i].alloc0) free(pushentry_arg_p->entry[i].dat0); if(pushentry_arg_p->entry[i].alloc1) free(pushentry_arg_p->entry[i].dat1); i++; } free(pushentry_arg_p->entry); #ifdef VERYPARANOID memset(pushentry_arg_p, 0, sizeof(*pushentry_arg_p)); #endif return 0; } /** * @brief Exchanging with "modtime_ht"-s to be able to compare them. * * @retval zero Successfully initialized * @retval non-zero Got error, while initializing * */ int cluster_modtime_exchange() { struct pushdoubleentry_arg *pushentry_arg_p = &cluster_modtime_exchange_pushentry_arg; // Getting hash table entries pushentry_arg_p->size=0; pushentry_arg_p->total=0; g_hash_table_foreach(nodeinfo_my->modtime_ht, cluster_modtime_exchange_pushentry, (void *)pushentry_arg_p); if(!pushentry_arg_p->total) { // !!! } // Calculating required RAM to compile clustercmd size_t toalloc = 0; int i = 0; while(i < pushentry_arg_p->total) { toalloc += 4; // for size header toalloc += pushentry_arg_p->entry[i].size0; // for path toalloc += pushentry_arg_p->entry[i].size1; // for ctime } // Allocating space for the clustercmd clustercmd_t *clustercmd_p = (clustercmd_t *)xmalloc(sizeof(clustercmdhdr_t) + toalloc); memset(clustercmd_p, 0, sizeof(clustercmdhdr_t)); // Setting up clustercmd clustercmd_p->h.dst_node_id = NODEID_NOID; clustercmd_p->h.cmd_id = CLUSTERCMDID_HT_EXCH; clustercmd_p->h.data_len = toalloc; // Filing clustercmd with hash-table entriyes i = 0; clustercmd_ht_exch_t *clustercmd_ht_exch_p = &clustercmd_p->data.ht_exch; while(i < pushentry_arg_p->total) { // Setting the data clustercmd_ht_exch_p->ctime = (time_t)pushentry_arg_p->entry[i].dat1; clustercmd_ht_exch_p->path_length = (time_t)pushentry_arg_p->entry[i].size0; memcpy( clustercmd_ht_exch_p->path, pushentry_arg_p->entry[i].dat0, clustercmd_ht_exch_p->path_length ); // Pointing to space for next entry: size_t offset = sizeof(clustercmd_ht_exch_t)-1+pushentry_arg_p->entry[i].size0; clustercmd_ht_exch_p = (clustercmd_ht_exch_t *) (&((char *) clustercmd_ht_exch_p)[offset] ); } // Sending cluster_send(clustercmd_p); // Cleanup free(clustercmd_p); return 0; } /** * @brief (syncop) Syncing file tree with another nodes with using of directories' modification time as a recent-detector. * * @param[in] dirpath Path to the directory * * @retval zero Successfully initialized * @retval non-zero Got error, while initializing * */ int cluster_initialsync() { cluster_modtime_exchange(); return 0; } /** * @brief (syncop) "Captures" right to update the file or directory to another nodes. It just removes events about the file of directory from another nodes * * @param[in] dirpath Path to the directory * * @retval zero Successfully initialized * @retval non-zero Got error, while initializing * */ int cluster_capture(const char *path) { return 0; } #endif clsync-0.4.1/cluster.h000066400000000000000000000165451252417542300146610ustar00rootroot00000000000000/* clsync - file tree sync utility based on inotify Copyright (C) 2013 Dmitry Yu Okunev 0x8E30679C This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifdef CLUSTER_SUPPORT // Macros for reading messages #define CLUSTER_RESTDATALEN(clustercmd_p, data_type) \ ((clustercmd_p)->h.data_len - sizeof(data_type) + sizeof(char *)) #define CLUSTER_LOOP_EXPECTCMD(clustercmd_p, clustercmd_id, ret) {\ /* Exit if error */ \ if(ret == -1) { \ error("CLUSTER_LOOP_EXPECTCMD()"); \ return errno; \ }\ \ /* Is that the command we are expecting? Skipping if not. */\ if(clustercmd_p->h.cmd_id != clustercmd_id)\ continue;\ } // Macros for writing messages // calculated required memory for clustercmd packet #define CLUSTER_REQMEM(data_type, restdata_len) \ (sizeof(clustercmdhdr_t) + sizeof(data_type) + (restdata_len) + 2) // calculated required memory for clustercmd packet with padding #define CLUSTER_REQMEM_PADDED(data_type, restdata_len) \ CLUSTER_PAD(CLUSTER_REQMEM(data_type, restdata_len)) // allocated memory for clustercmd packet with padding #define CLUSTER_ALLOC(data_type, restdata_len, alloc_funct)\ (clustercmd_t *)memset((alloc_funct)(CLUSTER_REQMEM_PADDED(data_type, restdata_len)), 0, CLUSTER_REQMEM_PADDED(data_type, restdata_len)) // allocated memory for clustercmd packet with padding with alloca() #define CLUSTER_ALLOCA(data_type, restdata_len)\ CLUSTER_ALLOC(data_type, restdata_len, alloca) // allocated memory for clustercmd packet with padding with xmalloc() #define CLUSTER_MALLOC(data_type, restdata_len)\ CLUSTER_ALLOC(data_type, restdata_len, xmalloc) // Common macros #define CLUSTER_PAD(size) ((((size) + 3) >> 2) << 2) #define CLUSTERCMD_SIZE(clustercmd_p) (sizeof(clustercmdhdr_t) + (*(clustercmd_p)).h.data_len) #define CLUSTERCMD_SIZE_PADDED(clustercmd_p) (sizeof(clustercmdhdr_t) + CLUSTER_PAD((*(clustercmd_p)).h.data_len)) // Types enum adler32_calc { ADLER32_CALC_NONE = 0x00, ADLER32_CALC_HEADER = 0x01, ADLER32_CALC_DATA = 0x02, ADLER32_CALC_ALL = 0x03, }; typedef enum adler32_calc adler32_calc_t; enum cluster_read_flags { CLREAD_NONE = 0x00, CLREAD_ALL = 0xff }; typedef enum cluster_read_flags cluster_read_flags_t; enum nodestatus { NODESTATUS_DOESNTEXIST = 0, NODESTATUS_OFFLINE, NODESTATUS_SEEMSONLINE, NODESTATUS_ONLINE, NODESTATUS_BANNED }; typedef enum nodestatus nodestatus_t; enum nodeid { NODEID_NOID = MAXNODES }; typedef enum nodeid nodeid_t; struct packets_stats { uint64_t tot; uint64_t rej; }; typedef struct packets_stats packets_stats_t; struct nodeinfo { uint8_t id; uint8_t num; nodestatus_t status; uint32_t updatets; GHashTable *modtime_ht; GHashTable *serial2queuedpacket_ht; packets_stats_t packets_in; packets_stats_t packets_out; uint32_t last_serial; char *node_name; }; typedef struct nodeinfo nodeinfo_t; enum clustercmd_id { CLUSTERCMDID_PING = 0, CLUSTERCMDID_ACK = 1, CLUSTERCMDID_REG = 2, CLUSTERCMDID_HELLO = 3, CLUSTERCMDID_WELCOME = 4, CLUSTERCMDID_DIE = 5, CLUSTERCMDID_HT_EXCH = 6, COUNT_CLUSTERCMDID }; typedef enum clustercmd_id clustercmd_id_t; struct clustercmd_hello { char node_name[0]; }; typedef struct clustercmd_hello clustercmd_hello_t; #define welcome_to_node_name_len(cmd_p) ((cmd_p)->h.data_len-(((clustercmd_welcome_t *)&(cmd_p)->data)->from_node_name_len)-sizeof(clustercmd_welcome_t)) #define welcome_to_node_name(cmddata_p) (&cmddata_p->from_node_name[cmddata_p->from_node_name_len]) struct clustercmd_welcome { size_t from_node_name_len; char from_node_name[0]; // to_node_name == my_node_name+my_node_name_len }; typedef struct clustercmd_welcome clustercmd_welcome_t; struct clustercmd_reg { char node_name[0]; }; typedef struct clustercmd_reg clustercmd_reg_t; struct clustercmd_ack { uint32_t serial; }; typedef struct clustercmd_ack clustercmd_ack_t; enum reject_reason { REJ_UNKNOWN = 0, REJ_ADLER32MISMATCH, }; typedef enum reject_reason reject_reason_t; struct clustercmd_rej { uint32_t serial; uint8_t reason; }; typedef struct clustercmd_rej clustercmd_rej_t; struct clustercmd_ht_exch { time_t ctime; size_t path_length; char path[0]; }; typedef struct clustercmd_ht_exch clustercmd_ht_exch_t; struct clustercmdadler32 { uint32_t hdr; // 32 uint32_t dat; // 64 }; typedef struct clustercmdadler32 clustercmdadler32_t; struct clustercmdhdr { // bits uint8_t dst_node_id; // 8 uint8_t src_node_id; // 16 uint8_t flags; // 24 (for future compatibility) uint8_t cmd_id; // 32 clustercmdadler32_t adler32; // 96 uint32_t data_len; // 128 uint32_t ts; // 160 uint32_t serial; // 192 }; typedef struct clustercmdhdr clustercmdhdr_t; typedef char clustercmd_die_t; struct clustercmd { clustercmdhdr_t h; union data { char p[0]; clustercmd_welcome_t welcome; clustercmd_reg_t reg; clustercmd_ack_t ack; clustercmd_rej_t rej; clustercmd_hello_t hello; clustercmd_ht_exch_t ht_exch; clustercmd_die_t die; } data; }; typedef struct clustercmd clustercmd_t; struct clustercmdqueuedpackethdri { char dummy; // anti-warning }; typedef struct clustercmdqueuedpackethdri clustercmdqueuedpackethdri_t; struct clustercmdqueuedpackethdro { char ack_from[MAXNODES]; uint8_t ack_count; }; typedef struct clustercmdqueuedpackethdro clustercmdqueuedpackethdro_t; struct clustercmdqueuedpackethdr { unsigned int window_id; union w { clustercmdqueuedpackethdri_t i; clustercmdqueuedpackethdro_t o; } w; }; typedef struct clustercmdqueuedpackethdr clustercmdqueuedpackethdr_t; struct clustercmdqueuedpacket { clustercmdqueuedpackethdr_t h; clustercmd_t cmd; }; typedef struct clustercmdqueuedpacket clustercmdqueuedpacket_t; struct window_occupied_sides { size_t left; size_t right; }; typedef struct window_occupied_sides window_occupied_sides_t; struct window { unsigned int size; // Allocated cells unsigned int packets_len; // Count of packets (are waiting for ACK-s) unsigned int *packets_id; // Array of cells' id-s with packets window_occupied_sides_t *occupied_sides; // Array of structures with coordinates in buffer of occupied space by cell ida (aka window_id) size_t buf_size; // Allocated space of the buffer char *buf; // Pointer to the buffer }; typedef struct window window_t; typedef int (*cluster_recvproc_funct_t)(clustercmd_t *clustercmd_p); // Externs extern int cluster_init(struct ctx *ctx_p, struct indexes *indexes_p); extern int cluster_deinit(); extern int cluster_lock(const char *fpath); extern int cluster_lock_byindexes(); extern int cluster_unlock_all(); extern int cluster_capture(const char *fpath); extern int cluster_modtime_update(const char *dirpath, short int dirlevel, mode_t st_mode); extern int cluster_initialsync(); #endif clsync-0.4.1/common.h000066400000000000000000000126161252417542300144630ustar00rootroot00000000000000/* clsync - file tree sync utility based on inotify Copyright (C) 2013 Dmitry Yu Okunev 0x8E30679C This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef __CLSYNC_COMMON_H #define __CLSYNC_COMMON_H #ifndef __linux__ # ifdef HAVE_CAPABILITIES # undef HAVE_CAPABILITIES # warning Capabilities support can be built only on Linux # endif #endif #define _GNU_SOURCE //#define _XOPEN_SOURCE 700 #define _LARGEFILE64_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef KQUEUE_SUPPORT # include #endif #ifdef INOTIFY_SUPPORT # include #endif #ifdef FANOTIFY_SUPPORT # include #endif #include #include #include #include #include #include #include #include #include #include #define CLSYNC_ITSELF #include "configuration.h" #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "clsync.h" #include "port-hacks.h" #include "posix-hacks.h" #include "ctx.h" #include "program.h" #include #ifndef IN_CREATE_SELF # define IN_CREATE_SELF IN_CREATE #endif #ifdef _DEBUG # define DEBUGV(...) __VA_ARGS__ #else # define DEBUGV(...) #endif #ifdef PARANOID # define PARANOIDV(...) __VA_ARGS__ #else # define PARANOIDV(...) #endif #ifdef _GNU_SOURCE # ifndef likely # define likely(x) __builtin_expect(!!(x), 1) # endif # ifndef unlikely # define unlikely(x) __builtin_expect(!!(x), 0) # endif #else # ifndef likely # define likely(x) (x) # endif # ifndef unlikely # define unlikely(x) (x) # endif #endif #ifndef offsetof # define offsetof(a, b) __builtin_offsetof(a, b) #endif // clang defines "__GNUC__", but not compatible with gnuc. Fixing. #ifdef __clang__ # ifdef __GNUC__ # undef __GNUC__ # endif #endif #define TOSTR(a) # a #define XTOSTR(a) TOSTR(a) #define COLLECTDELAY_INSTANT ((unsigned int)~0) #define MSG_SECURITY_PROBLEM(a) "Security problem: "a". Don't use this application until the bug will be fixed. Report about the problem to: "AUTHOR #define require_strlen_le(str, limit) \ if (strlen(str) >= limit)\ critical("length of "TOSTR(str)" (\"%s\") >= "TOSTR(limit));\ #define SAFE(code, onfail) ({\ long _SAFE_rc;\ if ((_SAFE_rc = code)) {\ error("Got error while "TOSTR(code));\ onfail;\ } \ _SAFE_rc;\ }) enum paramsource_enum { PS_UNKNOWN = 0, PS_ARGUMENT, PS_CONFIG, PS_CONTROL, PS_DEFAULTS, // PS_REHASH, PS_CORRECTION, }; typedef enum paramsource_enum paramsource_t; enum notifyengine_enum { NE_UNDEFINED = 0, NE_FANOTIFY, NE_INOTIFY, NE_KQUEUE, NE_BSM, NE_BSM_PREFETCH, NE_DTRACEPIPE, NE_GIO, }; typedef enum notifyengine_enum notifyengine_t; enum threadingmode { PM_OFF = 0, PM_SAFE, PM_FULL }; typedef enum threadingmode threadingmode_t; enum splittingmode_enum { SM_OFF = 0, SM_THREAD, SM_PROCESS, }; typedef enum splittingmode_enum splittingmode_t; /* struct excludeinfo { unsigned int seqid_min; unsigned int seqid_max; eventobjtype_t objtype_old; eventobjtype_t objtype_new; uint32_t flags; }; typedef struct eventinfo eventinfo_t; */ struct eventinfo { uint32_t evmask; unsigned int seqid_min; unsigned int seqid_max; eventobjtype_t objtype_old; eventobjtype_t objtype_new; int wd; size_t fsize; uint32_t flags; }; typedef struct eventinfo eventinfo_t; enum pthread_mutex_id { PTHREAD_MUTEX_STATE, PTHREAD_MUTEX_SELECT, PTHREAD_MUTEX_THREADSINFO, PTHREAD_MUTEX_MAX }; struct dosync_arg { int evcount; char excf_path[PATH_MAX+1]; char outf_path[PATH_MAX+1]; FILE *outf; ctx_t *ctx_p; struct indexes *indexes_p; void *data; int linescount; api_eventinfo_t *api_ei; int api_ei_count; char buf[BUFSIZ+1]; // for be read by sync_parameter_get(): const char *include_list[MAXARGUMENTS+2]; size_t include_list_count; const char *list_type_str; const char *evmask_str; }; struct doubleentry { size_t size0; size_t size1; size_t alloc0; size_t alloc1; void *dat0; void *dat1; }; struct pushdoubleentry_arg { int allocated; int total; size_t size; struct doubleentry *entry; }; struct myentry { size_t size; size_t alloc; void *dat; }; struct pushentry_arg { int allocated; int total; size_t size; struct myentry *entry; }; enum initsync { INITSYNC_UNKNOWN = 0, INITSYNC_FULL, INITSYNC_SUBDIR }; typedef enum initsync initsync_t; struct sighandler_arg { ctx_t *ctx_p; // indexes_t *indexes_p; pthread_t pthread_parent; int *exitcode_p; sigset_t *sigset_p; }; typedef struct sighandler_arg sighandler_arg_t; #endif clsync-0.4.1/configuration.h000066400000000000000000000161601252417542300160400ustar00rootroot00000000000000#ifndef __CONFIGURATION_H #define __CONFIGURATION_H #ifndef BUFSIZ #define BUFSIZ (1<<16) #endif // don't do to much rules, it will degrade performance #define MAXRULES (1<<8) // there's no need in more than 256 arguments while running action-script, IMHO :) #define MAXARGUMENTS (1<<8) // clsync should be used, if there's more than 5-10 nodes. So the limit in 255 is quite enough. :) #define MAXNODES ((1<<8)-1) #define MAXSIGNALNUM (1<<9) // max user/group lengths #define USER_LEN (1<<8) #define GROUP_LEN USER_LEN // control socket listen backlog (man 2 listen) #define SOCKET_BACKLOG 2 // control socket connections limit in clsync #define SOCKET_MAX_CLSYNC 8 // control socket connections limit in libclsync #define SOCKET_MAX_LIBCLSYNC (1<<16) // children count limit #define MAXCHILDREN (1<<8) #define MAXMOUNTPOINTS (1<<8) #define MAXPERMITTEDHOOKFILES (1<<8) #ifdef __CLSYNC_COMMON_H # ifndef DEFAULT_NOTIFYENGINE # ifdef __linux__ # ifdef INOTIFY_SUPPORT # define DEFAULT_NOTIFYENGINE NE_INOTIFY # endif # endif # endif # ifndef DEFAULT_NOTIFYENGINE # ifdef __FreeBSD__ # ifdef KQUEUE_SUPPORT # define DEFAULT_NOTIFYENGINE NE_KQUEUE # endif # endif # endif # ifndef DEFAULT_NOTIFYENGINE # ifdef INOTIFY_SUPPORT # define DEFAULT_NOTIFYENGINE NE_INOTIFY # endif # endif # ifndef DEFAULT_NOTIFYENGINE # ifdef GIO_SUPPORT # define DEFAULT_NOTIFYENGINE NE_GIO # endif # endif # ifndef DEFAULT_NOTIFYENGINE # ifdef KQUEUE_SUPPORT # define DEFAULT_NOTIFYENGINE NE_KQUEUE # endif # endif # ifndef DEFAULT_NOTIFYENGINE # ifdef BSM_SUPPORT # define DEFAULT_NOTIFYENGINE NE_BSM # endif # endif # ifndef DEFAULT_NOTIFYENGINE # warning No default monitor subsystem is set # define DEFAULT_NOTIFYENGINE NE_UNDEFINED # endif #endif #define DEFAULT_RULES_PERM RA_ALL #define DEFAULT_COLLECTDELAY 30 #define DEFAULT_SYNCDELAY (DEFAULT_COLLECTDELAY) #define DEFAULT_BFILETHRESHOLD (128 * 1024 * 1024) #define DEFAULT_BFILECOLLECTDELAY 1800 #define DEFAULT_LABEL "nolabel" #define DEFAULT_RSYNCINCLUDELINESLIMIT 20000 #define DEFAULT_SYNCTIMEOUT (3600 * 24) #define DEFAULT_CLUSTERTIMEOUT 1000 #define DEFAULT_CLUSTERIPADDR "227.108.115.121" #define DEFAULT_CLUSTERIPPORT 40079 #define DEFAULT_CLUSTERHDLMIN 1 #define DEFAULT_CLUSTERHDLMAX 16 #define DEFAULT_CLUSTERSDLMAX 32 #define DEFAULT_CONFIG_BLOCK "default" #define DEFAULT_RETRIES 1 #define DEFAULT_VERBOSE 3 #define DEFAULT_DUMPDIR "/tmp/clsync-dump-%label%" #define DEFAULT_DETACH_IPC 1 #define FANOTIFY_FLAGS (FAN_CLOEXEC|FAN_UNLIMITED_QUEUE|FAN_UNLIMITED_MARKS) #define FANOTIFY_EVFLAGS (O_LARGEFILE|O_RDONLY|O_CLOEXEC) #define FANOTIFY_MARKMASK (FAN_OPEN|FAN_MODIFY|FAN_CLOSE|FAN_ONDIR|FAN_EVENT_ON_CHILD) #define INOTIFY_FLAGS (IN_CLOEXEC) #define INOTIFY_MARKMASK (IN_ATTRIB|IN_CLOSE_WRITE|IN_CREATE|IN_DELETE|IN_DELETE_SELF|IN_MOVE_SELF|IN_MOVED_FROM|IN_MOVED_TO|IN_MODIFY|IN_DONT_FOLLOW) #define COUNTER_LIMIT (1<<10) #define SLEEP_SECONDS 1 #define KILL_TIMEOUT 60 #define ALLOC_PORTION (1<<10) /* 1 KiX */ #define CLUSTER_WINDOW_BUFSIZE_PORTION (1<<20) /* 1 MiB */ #define CLUSTER_PACKET_MAXSIZE (1<<20) /* 1 MiB */ #define CLUSTER_WINDOW_PCKTLIMIT (1<<20) /* 1 Ki packets */ #define CONFIG_PATHS { ".clsync.conf", "/etc/clsync/clsync.conf", "/etc/clsync.conf", "/usr/local/etc/clsync/clsync.conf", "/usr/local/etc/clsync.conf", NULL } /* "~/.clsync.conf", "/etc/clsync/clsync.conf" ... */ #define API_PREFIX "clsyncapi_" #define DUMP_DIRMODE 0750 #define DUMP_FILEMODE 0644 #define DEFAULT_CP_PATH "cp" #define DEFAULT_RSYNC_PATH "rsync" // size of event chain size to be processes at a time #define KQUEUE_EVENTLISTSIZE 256 #define AUDITPIPE_PATH "/dev/auditpipe" #define AUDIT_CONTROL_PATH "/etc/security/audit_control" #define AUDIT_CONTROL_INITSCRIPT "/etc/rc.d/auditd" #define AUDIT_CONTROL_HEADER "#clsync\n" #define AUDIT_CONTROL_CONTENT "\n\ dir:/var/audit\n\ flags:fc,fd,fw,fm,cl\n\ minfree:0\n\ naflags:fc,fd,fw,fm,cl\n\ policy:cnt\n\ filesz:1M\n\ expire-after:20M\n\ " #define DTRACE_PATH "dtrace" #define PIVOT_AUTO_DIR "/dev/shm/clsync-rootfs" #define TMPDIR_TEMPLATE "/tmp/clsync-XXXXXX" #define SYSLOG_BUFSIZ (1<<16) #define SYSLOG_FLAGS (LOG_PID|LOG_CONS) #define SYSLOG_FACILITY LOG_DAEMON #define CLSYNCSOCK_WINDOW (1<<8) #define DEFAULT_SYNCHANDLER_ARGS_SIMPLE "sync \%label\% \%EVENT-MASK\% \%INCLUDE-LIST\%" #define DEFAULT_SYNCHANDLER_ARGS_DIRECT "\%INCLUDE-LIST\% \%destination-dir\%/" #define DEFAULT_SYNCHANDLER_ARGS_SHELL_NR "synclist \%label\% \%INCLUDE-LIST-PATH\%" #define DEFAULT_SYNCHANDLER_ARGS_SHELL_R "initialsync \%label\% \%INCLUDE-LIST\%" #define DEFAULT_SYNCHANDLER_ARGS_RDIRECT_E "-aH --delete --exclude-from \%EXCLUDE-LIST-PATH\% --include-from \%INCLUDE-LIST-PATH\% --exclude=* \%watch-dir\%/ \%destination-dir\%/" #define DEFAULT_SYNCHANDLER_ARGS_RDIRECT_I "-aH --delete --include-from \%INCLUDE-LIST-PATH\% --exclude=* \%watch-dir\%/ \%destination-dir\%/" #define DEFAULT_SYNCHANDLER_ARGS_RSHELL_E "rsynclist \%label% \%INCLUDE-LIST-PATH\% %EXCLUDE-LIST-PATH%" #define DEFAULT_SYNCHANDLER_ARGS_RSHELL_I "rsynclist \%label% \%INCLUDE-LIST-PATH\%" #define RSYNC_ARGS_E { \ "-aH", \ "--delete", \ "--exclude-from", \ "\%EXCLUDE-LIST-PATH\%",\ "--include-from", \ "\%INCLUDE-LIST-PATH\%",\ "--exclude=*", \ NULL } #define RSYNC_ARGS_I { \ "-aH", \ "--delete", \ "--include-from", \ "\%INCLUDE-LIST-PATH\%",\ "--exclude=*", \ NULL } #define DEFAULT_PRESERVE_CAPABILITIES ( CAP_TO_MASK(CAP_DAC_READ_SEARCH) | CAP_TO_MASK(CAP_SETUID) | CAP_TO_MASK(CAP_SETGID) | CAP_TO_MASK(CAP_KILL) ) #define DEFAULT_USER "nobody" #define DEFAULT_GROUP "nogroup" #define DEFAULT_UID 65534 #define DEFAULT_GID 65534 #define DEFAULT_CAPS_INHERIT CI_EMPTY #define DEFAULT_PIVOT_MODE (PW_OFF) #define DEVZERO "/dev/zero" // How long to wait on highloaded locks before fallback to mutexes // See: doc/devel/thread-splitting/highload-locks/clsync-graph-comma.odc // But optimal value can be very different on different systems #define HL_LOCK_TRIES_INITIAL (1<<13) // Enable run-time auto-adjustment #define HL_LOCK_TRIES_AUTO // Iterations delay between adjustments (power of 2; 2^x) #define HL_LOCK_AUTO_INTERVAL 7 /* 128 */ // Initial adjustment factor #define HL_LOCK_AUTO_K 1.1 // Delay detection error threshold #define HL_LOCK_AUTO_THREADHOLD 0.2 // Adjustment factor denominator #define HL_LOCK_AUTO_DECELERATION 1.1 // Don't adjust if the factor is less than #define HL_LOCK_AUTO_K_FINISH 0.001 // Upper limit #define HL_LOCK_AUTO_LIMIT_HIGH (1<<20) //#define READWRITE_SIGNALLING #define CG_DEV_CONSOLE "c 5:1" #define CG_DEV_ZERO "c 1:5" #define CG_DEV_RANDOM "c 1:8" #define CG_DEV_URANDOM "c 1:9" #define CG_DEV_NULL "c 1:3" #define CG_ALLOWED_DEVICES { \ CG_DEV_CONSOLE " rw", \ CG_DEV_ZERO " r", \ CG_DEV_URANDOM " r", \ CG_DEV_RANDOM " r", \ CG_DEV_NULL " w", \ NULL \ } #define DEFAULT_CG_GROUPNAME "clsync/%PID%" // In nanoseconds #define OUTPUT_LOCK_TIMEOUT (100*1000*1000) #define WAITPID_TIMED_GRANULARITY (30*1000*1000) #define BSM_QUEUE_LENGTH_MAX (1024*1024) #define GIO_QUEUE_LENGTH_MAX BSM_QUEUE_LENGTH_MAX #endif clsync-0.4.1/configure.ac000066400000000000000000000274621252417542300153150ustar00rootroot00000000000000# -*- Autoconf -*- # Process this file with autoconf to produce a configure script. AC_PREREQ([2.59]) AC_INIT([clsync],[0.4.1],[Dmitry Yu Okunev ],,[https://github.com/xaionaro/clsync]) AC_CONFIG_SRCDIR([sync.c]) AC_CONFIG_MACRO_DIR([m4]) AM_INIT_AUTOMAKE([1.11 foreign -Wall -Wno-portability]) AC_CONFIG_HEADERS([config.h]) AC_PROG_CC([gcc cc]) AM_PROG_CC_C_O AC_PROG_CC_STDC AC_CONFIG_MACRO_DIR([m4]) AC_PROG_LIBTOOL AM_PROG_LIBTOOL LT_INIT PKG_INSTALLDIR AC_CANONICAL_HOST m4_include(m4/ax_pthread.m4) AX_PTHREAD( [ CC="$PTHREAD_CC" LIBS="$LIBS $PTHREAD_LIBS" CFLAGS="$CFLAGS $PTHREAD_CFLAGS" LDFLAGS="$LDFLAGS $PTHREAD_LDFLAGS" ], [ AC_MSG_ERROR([Pthread library not found. Please set PTHREAD_CFLAGS and PTHREAD_LIBS correctly for your setup]) ] ) HAVE_BACKTRACE=1 case $host_os in *bsd*) AC_SEARCH_LIBS([backtrace], [execinfo], [], [HAVE_BACKTRACE=]) ;; esac dnl --enable-clsync AC_ARG_ENABLE(clsync, AS_HELP_STRING(--disable-clsync, [do not build clsync binary, default: build clsync])) AM_CONDITIONAL([CLSYNC], [test "x$enable_clsync" != "xno"]) dnl check for glibc only if clsync is being build AS_IF([test "x$enable_clsync" != "xno"], [ PKG_CHECK_MODULES(GLIB, [glib-2.0]) ]) dnl --enable-cluster AC_ARG_ENABLE(cluster, AS_HELP_STRING(--enable-cluster, [enable clustering support (not yet implemented), default: no])) AS_IF([test "x$enable_cluster" = "xyes"], [CPPFLAGS="${CPPFLAGS} -DCLUSTER_SUPPORT" dnl mhash check AC_ARG_WITH(mhash, AS_HELP_STRING(--with-mhash, [use mhash instead of internal version of hash algo, default: enabled])) AS_IF([test "x$with_mhash" = "xno"], [], [ AC_CHECK_HEADER([mhash.h], [], [AC_MSG_ERROR("Unable to find mhash.h")]) AC_SEARCH_LIBS([mhash_init], [mhash], [CPPFLAGS="${CPPFLAGS} -DHAVE_MHASH"], [AC_MSG_ERROR("Unable to find libmhash")]) ]) ]) dnl --enable-socket AC_ARG_ENABLE(socket, AS_HELP_STRING(--enable-socket, [enable control socket support, default: no])) AS_IF([test "x$enable_socket" = "xyes"], [CPPFLAGS="${CPPFLAGS} -DENABLE_SOCKET"]) AM_CONDITIONAL([SOCKET], [test "x$enable_socket" = "xyes"]) dnl --enable-libclsync AC_ARG_ENABLE(socket-library, AS_HELP_STRING(--enable-socket-library, [build libclsync socket library, default: no])) AM_CONDITIONAL([LIBCLSYNC], [test "x$enable_socket_library" = "xyes"]) dnl --enable-unshare AC_ARG_ENABLE(unshare, AS_HELP_STRING(--disable-unshare, [disable support of unshare(), default: enabled])) AS_IF([ test "x$enable_unshare" != "xno" ], [ AC_CHECK_FUNC([unshare], [HAVE_UNSHARE=1]) ]) dnl --enable-highload-locks AC_ARG_ENABLE(highload-locks, AS_HELP_STRING(--disable-highload-locks, [disable locks for high loaded instances with --splitting={thread,process} [which are not enough tested and can cause deadlocks with 100% CPU utilization]])) AM_CONDITIONAL([HLLOCKS], [test "x$enable_highload_locks" != "xno"]) dnl --enable-debug AC_ARG_ENABLE(debug, AS_HELP_STRING(--enable-debug, [enable debugging support, default: yes; value: no, yes, force]), [case "${enableval}" in (0|"no") debug=0 ;; (1|"yes") debug=1 ;; (2|"force") debug=2 ;; (*) AC_MSG_ERROR([bad value ${enableval} for --enable-debug]) ;; esac], [debug=1]) AS_IF([ test "$debug" -ge 1 ], [CFLAGS="${CFLAGS} -pipe -Wall -O0 -ggdb3" CPPFLAGS="${CPPFLAGS} -D_DEBUG_SUPPORT"]) AS_IF([ test "$debug" -ge 2 ], [CPPFLAGS="${CPPFLAGS} -D_DEBUG_FORCE"]) dnl --paranoid AC_ARG_ENABLE(paranoid, AS_HELP_STRING([--enable-paranoid], [set paranoid level of code security, default: 1, values: 0, 1, 2]), [case "${enableval}" in (0|"no") paranoid=0 ;; (1|"yes") paranoid=1 ;; (2) paranoid=2 ;; (*) AC_MSG_ERROR([bad value ${enableval} for --enable-paranoid]) ;; esac], [paranoid=1]) AS_IF( [test $paranoid -ge 1], [ CPPFLAGS="${CPPFLAGS} -D_FORTIFY_SOURCE=2 -DPARANOID" CFLAGS="${CFLAGS} -fstack-protector-all -Wall --param ssp-buffer-size=4" LDFLAGS="${LDFLAGS} -Xlinker -zrelro" AX_CHECK_COMPILE_FLAG([-fstack-check], [CFLAGS="${CFLAGS} -fstack-check"]) ] ) AS_IF([test $paranoid -eq 2], [CPPFLAGS="${CPPFLAGS} -DVERYPARANOID"]) dnl searching for seccomp AC_ARG_ENABLE(seccomp, AS_HELP_STRING(--enable-seccomp, [Enable seccomp support be able to forbid extra syscalls; values: no, check, yes; default: check]), , [enable_seccomp=check] ) case "$enable_seccomp" in yes) AC_CHECK_TYPES([struct seccomp_data], [HAVE_SECCOMP=1], [AC_MSG_FAILURE([Cannot find valid linux/seccomp.h])], [[#include ]]) ;; check) AC_CHECK_TYPES([struct seccomp_data], [HAVE_SECCOMP=1], , [[#include ]]) ;; esac dnl capabilities check AC_ARG_ENABLE(capabilities, AS_HELP_STRING(--enable-capabilities, [Enable linux capabilities support; values: no, check, yes; default: check]), , [enable_capabilities=check] ) case "$enable_capabilities" in yes) AC_CHECK_FUNC([capset], [ AC_CHECK_HEADER(sys/capability.h, [HAVE_CAPABILITIES=2], [AC_MSG_FAILURE([Cannot find sys/capability.h])]) ], [ AC_MSG_FAILURE([There is no capabilities support on this system]) ] ) ;; check) AC_CHECK_FUNC([capset], [ AC_CHECK_HEADER(sys/capability.h, [HAVE_CAPABILITIES=2]) ] ) ;; esac # Checks for programs. AC_PROG_CC_C99 AC_PROG_INSTALL PKG_PROG_PKG_CONFIG([0.20]) # Checks for libraries. dnl libdl AC_CHECK_HEADER([dlfcn.h], [], [AC_MSG_ERROR("Unable to find dlfcn.h")]) AC_SEARCH_LIBS([dlopen], [dl], [LDFLAGS="${LDFLAGS} -rdynamic"], [AC_MSG_ERROR("Unable to find libdl")]) dnl -lrt is needed on < glibc-2.17 AC_SEARCH_LIBS([clock_getres], [rt], [], [AC_MSG_ERROR("Unable to find librt; clock_getres() is needed")]) dnl searching for getmntent AC_CHECK_FUNC([getmntent], [HAVE_GETMNTENT=1]) dnl searching for pivot_root AC_CHECK_FUNC([pivot_root], [HAVE_PIVOTROOT=1]) dnl libcgroup check AC_ARG_WITH(libcgroup, AS_HELP_STRING(--with-libcgroup, [Enable cgroup support via libcgroup; values: no, check, yes; default: check]), , [with_libcgroup=check] ) case "$with_libcgroup" in yes) AC_CHECK_LIB([cgroup], [cgroup_init], [ AC_CHECK_HEADER(libcgroup.h, [], [AC_MSG_FAILURE([Cannot find libcgroup.h])]) LDFLAGS="${LDFLAGS} -lcgroup" HAVE_LIBCGROUP=1 ], [ AC_MSG_FAILURE( [Cannot find libcgroup]) ] ) ;; check) AC_CHECK_LIB([cgroup], [cgroup_init], [ AC_CHECK_HEADER(libcgroup.h, [], [AC_MSG_FAILURE([Cannot find libcgroup.h])]) LDFLAGS="${LDFLAGS} -lcgroup" HAVE_LIBCGROUP=1 ] ) ;; esac dnl tre check #AC_ARG_WITH(tre, # AS_HELP_STRING(--with-tre, # [Enable tre support be able to predict which directories should be scanned for excludes; values: no, check, yes; default: check]), # [], # [with_tre=check] #) # #case "$with_tre" in # yes) # AC_CHECK_LIB([tre], [tre_regaexec], # [ # AC_CHECK_HEADER(tre/tre.h, [], [AC_MSG_FAILURE([Cannot find tre/tre.h])]) # LDFLAGS="${LDFLAGS} -ltre" # HAVE_TRE=1 # ], # [ # AC_MSG_FAILURE( # [Cannot find libtre]) # ] # ) # ;; # check) # AC_CHECK_LIB([tre], [tre_regaexec], # [ # AC_CHECK_HEADER(tre/tre.h, [], [AC_MSG_FAILURE([Cannot find tre/tre.h])]) # LDFLAGS="${LDFLAGS} -ltre" # HAVE_TRE=1 # ], # [] # ) # ;; #esac dnl kqueue/inotify/gio/bsm AC_ARG_WITH(kqueue, AS_HELP_STRING(--with-kqueue, [Enable kqueue support; values: no, native, lib, check; default: check]), [], [with_kqueue=check] ) AC_ARG_WITH(inotify, AS_HELP_STRING(--with-inotify, [Enable inotify support; values: no, native, lib, check; default: check]), [], [with_inotify=check] ) AC_ARG_WITH(gio, AS_HELP_STRING(--with-gio, [Enable GIO support as FS monitor subsystem; values: no, lib, check; default: check]), [], [with_gio=check] ) AC_ARG_WITH(bsm, AS_HELP_STRING(--with-bsm, [Enable BSM (Sun/*BSD audit) support as FS monitor subsystem; values: no, lib, check; default: check]), [], [with_bsm=check] ) case "$with_kqueue" in check) AC_CHECK_FUNC([kqueue], [ HAVE_KQUEUE=2 ], [ AC_CHECK_LIB([kqueue], [kqueue], [ AC_CHECK_HEADER(sys/event.h, [ LDFLAGS="${LDFLAGS} -lkqueue" HAVE_KQUEUE=1 ]) ] ) ] ) ;; native) AC_CHECK_FUNC([kqueue], [ AC_CHECK_HEADER(sys/event.h, , [AC_MSG_FAILURE([Cannot find sys/event.h])]) HAVE_KQUEUE=2 ], [ AC_MSG_FAILURE( [There is no kqueue native support on this system]) ] ) ;; lib) AC_CHECK_LIB([kqueue], [kqueue], [ AC_CHECK_HEADER(sys/event.h, , [AC_MSG_FAILURE([Cannot find sys/event.h])]) LDFLAGS="${LDFLAGS} -lkqueue" HAVE_KQUEUE=1 ], [ AC_MSG_FAILURE( [Cannot find libkqueue]) ] ) ;; esac case "$with_inotify" in check) AC_CHECK_FUNC([inotify_init], [ HAVE_INOTIFY=2 ], [ AC_CHECK_LIB([inotify], [inotify_init], [ AC_CHECK_HEADER(sys/inotify.h, [ LDFLAGS="${LDFLAGS} -linotify" HAVE_INOTIFY=1 ]) ] ) ] ) ;; native) AC_CHECK_FUNC([inotify_init], [ AC_CHECK_HEADER(sys/inotify.h, [], [AC_MSG_FAILURE([Cannot find sys/inotify.h])]) HAVE_INOTIFY=2 ], [ AC_MSG_FAILURE( [There is no inotify native support on this system]) ] ) ;; lib) AC_CHECK_LIB([inotify], [inotify_init], [ AC_CHECK_HEADER(sys/inotify.h, [], [AC_MSG_FAILURE([Cannot find sys/inotify.h])]) LDFLAGS="${LDFLAGS} -linotify" HAVE_INOTIFY=1 ], [ AC_MSG_FAILURE( [Cannot find libinotify]) ] ) ;; esac case "$with_gio" in check) PKG_CHECK_MODULES(GIO, [gio-2.0], [ HAVE_GIO=1 AC_SUBST([GIO_CFLAGS]) AC_SUBST([GIO_LIBS]) ]) ;; lib) PKG_CHECK_MODULES(GIO, [gio-2.0], [ HAVE_GIO=1 AC_SUBST([GIO_CFLAGS]) AC_SUBST([GIO_LIBS]) ], [ AC_MSG_FAILURE([Cannot find libgio-2.0]) ]) ;; esac case "$with_bsm" in check) AC_CHECK_FUNC([au_fetch_tok], [ HAVE_BSM=2 ], [ AC_CHECK_LIB([bsm], [au_fetch_tok], [ AC_CHECK_HEADER(bsm/libbsm.h, [ LDFLAGS="${LDFLAGS} -lbsm" HAVE_BSM=1 ]) ] ) ] ) ;; lib) AC_CHECK_LIB([bsm], [au_fetch_tok], [ AC_CHECK_HEADER(bsm/libbsm.h, [], [AC_MSG_FAILURE([Cannot find bsm/libbsm.h])]) LDFLAGS="${LDFLAGS} -lbsm" HAVE_BSM=1 ], [ AC_MSG_FAILURE( [Cannot find libbsm]) ] ) ;; esac #AC_CHECK_PROG([HAVE_DTRACEPIPE], [dtrace], [found]) AS_IF([test "$HAVE_INOTIFY" != ""], [AC_CHECK_FUNC([inotify_init1], [], [INOTIFY_OLD=1])]) AM_CONDITIONAL([HAVE_KQUEUE], [test "x$HAVE_KQUEUE" != "x"]) AM_CONDITIONAL([HAVE_INOTIFY], [test "x$HAVE_INOTIFY" != "x"]) AM_CONDITIONAL([INOTIFY_OLD], [test "x$INOTIFY_OLD" != "x"]) AM_CONDITIONAL([HAVE_FANOTIFY], [test "x$HAVE_FANOTIFY" != "x"]) AM_CONDITIONAL([HAVE_BSM], [test "x$HAVE_BSM" != "x"]) AM_CONDITIONAL([HAVE_GIO], [test "x$HAVE_GIO" != "x"]) AM_CONDITIONAL([HAVE_DTRACEPIPE], [test "x$HAVE_DTRACEPIPE" != "x"]) AM_CONDITIONAL([HAVE_BACKTRACE], [test "x$HAVE_BACKTRACE" != "x"]) AM_CONDITIONAL([HAVE_CAPABILITIES], [test "x$HAVE_CAPABILITIES" != "x"]) AM_CONDITIONAL([HAVE_GETMNTENT], [test "x$HAVE_GETMNTENT" != "x"]) AM_CONDITIONAL([HAVE_PIVOTROOT], [test "x$HAVE_PIVOTROOT" != "x"]) AM_CONDITIONAL([HAVE_UNSHARE], [test "x$HAVE_UNSHARE" != "x"]) AM_CONDITIONAL([HAVE_SECCOMP], [test "x$HAVE_SECCOMP" != "x"]) AM_CONDITIONAL([HAVE_TRE], [test "x$HAVE_TRE" != "x"]) AM_CONDITIONAL([HAVE_LIBCGROUP], [test "x$HAVE_LIBCGROUP" != "x"]) AS_IF([test "$HAVE_KQUEUE" = '' -a "$HAVE_INOTIFY" = '' -a "$HAVE_FANOTIFY" = '' -a "$HAVE_BSM" = '' -a "$HAVE_GIO" = ''], [AC_MSG_FAILURE([At least one monitoring engine must be enabled! Available (depending on system): inotify, kqueue, gio, bsm])]) LIBS="${GLIB_LIBS} ${LIBS}" AM_CPPFLAGS="${GLIB_CFLAGS}" AC_SUBST(AM_CPPFLAGS) AC_CONFIG_FILES([Makefile examples/Makefile pkgconfig/libclsync.pc]) AC_OUTPUT clsync-0.4.1/control.c000066400000000000000000000143621252417542300146460ustar00rootroot00000000000000/* clsync - file tree sync utility based on inotify Copyright (C) 2013 Dmitry Yu Okunev 0x8E30679C This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "common.h" #include // for "struct sockaddr_un" #include // mkdir() #include // mkdir() #include // mkdirat() #include // g_hash_table_foreach() #include "indexes.h" #include "main.h" #include "ctx.h" #include "error.h" #include "sync.h" #include "control.h" #include "socket.h" static pthread_t pthread_control; static inline int control_error(clsyncsock_t *clsyncsock_p, sockcmd_t *sockcmd_p, const char *const funct, const char *const args) { debug(3, "%s(%s): %u: %s", funct, args, errno, strerror(errno)); return socket_reply(clsyncsock_p, sockcmd_p, SOCKCMD_REPLY_ECUSTOM, funct, args, errno, strerror(errno)); } int control_dump(ctx_t *ctx_p, clsyncsock_t *clsyncsock_p, sockcmd_t *sockcmd_p) { sockcmd_dat_dump_t *dat = sockcmd_p->data; debug(3, "%s", dat->dir_path); return (sync_dump(ctx_p, dat->dir_path)) ? control_error(clsyncsock_p, sockcmd_p, "sync_dump", dat->dir_path) : socket_reply(clsyncsock_p, sockcmd_p, SOCKCMD_REPLY_DUMP); } int control_procclsyncsock(socket_sockthreaddata_t *arg, sockcmd_t *sockcmd_p) { int rc; clsyncsock_t *clsyncsock_p = arg->clsyncsock_p; ctx_t *ctx_p = (ctx_t *)arg->arg; switch(sockcmd_p->cmd_id) { case SOCKCMD_REQUEST_DUMP: rc = control_dump(ctx_p, clsyncsock_p, sockcmd_p); break; case SOCKCMD_REQUEST_INFO: rc = socket_reply(clsyncsock_p, sockcmd_p, SOCKCMD_REPLY_INFO, ctx_p->config_block, ctx_p->label, ctx_p->flags, ctx_p->flags_set); break; case SOCKCMD_REQUEST_SET: { sockcmd_dat_set_t *dat = sockcmd_p->data; rc = ctx_set(ctx_p, dat->key, dat->value); if (rc) { control_error(clsyncsock_p, sockcmd_p, "ctx_set", dat->key); break; } rc = socket_reply(clsyncsock_p, sockcmd_p, SOCKCMD_REPLY_SET); break; } case SOCKCMD_REQUEST_DIE: rc = sync_term(SIGTERM); break; default: return EINVAL; } debug(3, "rc == %u", rc); return rc; } static inline void closecontrol(ctx_t *ctx_p) { if(ctx_p->socket) { close(ctx_p->socket); ctx_p->socket = 0; } } int control_loop(ctx_t *ctx_p) { // Starting debug(1, "started (ctx_p->socket == %u)", ctx_p->socket); int s; while((s=ctx_p->socket)) { // Check if the socket is still alive if(socket_check_bysock(s)) { error("Control socket closed [case 0]"); closecontrol(ctx_p); continue; } // Waiting for event debug(3, "waiting for events on the socket"); fd_set rfds; FD_ZERO(&rfds); FD_SET(s, &rfds); int count = select(s+1, &rfds, NULL, NULL, NULL); // Processing the events debug(2, "got %i events with select()", count); // Processing the events: checks if(count == 0) { debug(2, "select() timed out."); continue; } if(count < 0) { debug(1, "Got negative events count. Closing the socket."); closecontrol(ctx_p); continue; } if(!FD_ISSET(s, &rfds)) { error("Got event, but not on the control socket. Closing the socket (cannot use \"select()\")."); closecontrol(ctx_p); continue; } // Processing the events: accepting new clsyncsock clsyncsock_t *clsyncsock_p = socket_accept(s); if(clsyncsock_p == NULL) { if(errno == EUSERS) // Too many connections. Just ignoring the new one. continue; // Got unknown error. Closing control socket just in case. error("Cannot socket_accept()"); closecontrol(ctx_p); continue; } debug(2, "Starting new thread for new connection."); socket_sockthreaddata_t *threaddata_p = socket_thread_attach(clsyncsock_p); if (threaddata_p == NULL) { error("Cannot create a thread for connection"); closecontrol(ctx_p); continue; } threaddata_p->procfunct = control_procclsyncsock; threaddata_p->clsyncsock_p = clsyncsock_p; threaddata_p->arg = ctx_p; threaddata_p->running = &ctx_p->socket; threaddata_p->authtype = ctx_p->flags[SOCKETAUTH]; threaddata_p->flags = 0; if (socket_thread_start(threaddata_p)) { error("Cannot start a thread for connection"); closecontrol(ctx_p); continue; } #ifdef DEBUG // To prevent too often connections sleep(1); #endif } // Cleanup debug(1, "control_loop() finished"); return 0; } int control_run(ctx_t *ctx_p) { if(ctx_p->socketpath != NULL) { int ret = 0; int s = -1; // initializing clsync-socket subsystem if ((ret = socket_init())) error("Cannot init clsync-sockets subsystem."); if (!ret) { clsyncsock_t *clsyncsock = socket_listen_unix(ctx_p->socketpath); if (clsyncsock == NULL) { ret = errno; } else { s = clsyncsock->sock; socket_cleanup(clsyncsock); } } // fixing privileges if (!ret) { if(ctx_p->flags[SOCKETMOD]) if(chmod(ctx_p->socketpath, ctx_p->socketmod)) { error("Error, Cannot chmod(\"%s\", %o)", ctx_p->socketpath, ctx_p->socketmod); ret = errno; } if(ctx_p->flags[SOCKETOWN]) if(chown(ctx_p->socketpath, ctx_p->socketuid, ctx_p->socketgid)) { error("Error, Cannot chown(\"%s\", %u, %u)", ctx_p->socketpath, ctx_p->socketuid, ctx_p->socketgid); ret = errno; } } // finish if (ret) { close(s); return ret; } ctx_p->socket = s; debug(2, "ctx_p->socket = %u", ctx_p->socket); ret = pthread_create(&pthread_control, NULL, (void *(*)(void *))control_loop, ctx_p); } return 0; } int control_cleanup(ctx_t *ctx_p) { if(ctx_p->socketpath != NULL) { unlink(ctx_p->socketpath); closecontrol(ctx_p); // TODO: kill pthread_control and join // pthread_join(pthread_control, NULL); socket_deinit(); } return 0; } clsync-0.4.1/control.h000066400000000000000000000017001252417542300146430ustar00rootroot00000000000000/* clsync - file tree sync utility based on inotify Copyright (C) 2013 Dmitry Yu Okunev 0x8E30679C This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef __CLSYNC_CONTROL_H #define __CLSYNC_CONTROL_H extern int control_run(struct ctx *ctx_p); extern int control_cleanup(struct ctx *ctx_p); #endif clsync-0.4.1/ctx.h000066400000000000000000000226341252417542300137720ustar00rootroot00000000000000/* clsync - file tree sync utility based on inotify Copyright (C) 2013 Dmitry Yu Okunev 0x8E30679C This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef __CLSYNC_CTX_H #define __CLSYNC_CTX_H #include #ifdef CAPABILITIES_SUPPORT # include // __u32 #endif #define MAX_BLOCKTHREADS (1<<4) #define register_blockthread(thread) {\ critical_on (ctx_p->blockthread_count >= MAX_BLOCKTHREADS);\ ctx_p->blockthread[ctx_p->blockthread_count++] = pthread_self();\ debug(3, "register_blockthread(): ctx_p->blockthread_count -> %i", ctx_p->blockthread_count);\ } #define OPTION_FLAGS (1<<10) #define OPTION_LONGOPTONLY (1<<9) #define OPTION_CONFIGONLY (1<<8) #define NOTOPTION (3<<8) enum flags_enum { WATCHDIR = 'W', SYNCHANDLER = 'S', RULESFILE = 'R', DESTDIR = 'D', SOCKETPATH = 's', HELP = 'h', CONFIGFILE = 'H', CONFIGBLOCK = 'K', BACKGROUND = 'b', UID = 'u', GID = 'g', CAP_PRESERVE = 'C', THREADING = 'p', RETRIES = 'r', OUTPUT_METHOD = 'Y', EXCLUDEMOUNTPOINTS= 'X', PIDFILE = 'z', CLUSTERIFACE = 'c', CLUSTERMCASTIPADDR='m', CLUSTERMCASTIPPORT='P', CLUSTERTIMEOUT = 'G', CLUSTERNODENAME = 'n', CLUSTERHDLMIN = 'o', CLUSTERHDLMAX = 'O', DELAY = 't', BFILEDELAY = 'T', SYNCDELAY = 'w', BFILETHRESHOLD = 'B', DEBUG = 'd', QUIET = 'q', VERBOSE = 'v', OUTLISTSDIR = 'L', AUTORULESW = 'A', MODE = 'M', IGNOREEXITCODE = 'x', DONTUNLINK = 'U', INITFULL = 'F', SYNCTIMEOUT = 'k', LABEL = 'l', SHOW_VERSION = 'V', HAVERECURSIVESYNC = 0|OPTION_LONGOPTONLY, RSYNCINCLIMIT = 1|OPTION_LONGOPTONLY, RSYNCPREFERINCLUDE = 2|OPTION_LONGOPTONLY, SYNCLISTSIMPLIFY = 3|OPTION_LONGOPTONLY, ONEFILESYSTEM = 4|OPTION_LONGOPTONLY, STATUSFILE = 5|OPTION_LONGOPTONLY, SKIPINITSYNC = 6|OPTION_LONGOPTONLY, ONLYINITSYNC = 7|OPTION_LONGOPTONLY, EXITONNOEVENTS = 8|OPTION_LONGOPTONLY, STANDBYFILE = 9|OPTION_LONGOPTONLY, EXITHOOK = 10|OPTION_LONGOPTONLY, CLUSTERSDLMAX = 11|OPTION_LONGOPTONLY, PREEXITHOOK = 12|OPTION_LONGOPTONLY, SOCKETAUTH = 13|OPTION_LONGOPTONLY, SOCKETMOD = 14|OPTION_LONGOPTONLY, SOCKETOWN = 15|OPTION_LONGOPTONLY, MAXITERATIONS = 16|OPTION_LONGOPTONLY, IGNOREFAILURES = 17|OPTION_LONGOPTONLY, DUMPDIR = 18|OPTION_LONGOPTONLY, CONFIGBLOCKINHERITS = 19|OPTION_LONGOPTONLY, MONITOR = 20|OPTION_LONGOPTONLY, SYNCHANDLERARGS0 = 21|OPTION_LONGOPTONLY, SYNCHANDLERARGS1 = 22|OPTION_LONGOPTONLY, CUSTOMSIGNALS = 23|OPTION_LONGOPTONLY, CHROOT = 24|OPTION_LONGOPTONLY, MOUNTPOINTS = 25|OPTION_LONGOPTONLY, SPLITTING = 26|OPTION_LONGOPTONLY, SYNCHANDLERUID = 27|OPTION_LONGOPTONLY, SYNCHANDLERGID = 28|OPTION_LONGOPTONLY, CAPS_INHERIT = 29|OPTION_LONGOPTONLY, CHECK_EXECVP_ARGS = 30|OPTION_LONGOPTONLY, PIVOT_ROOT = 31|OPTION_LONGOPTONLY, DETACH_NETWORK = 32|OPTION_LONGOPTONLY, DETACH_MISCELLANEA = 33|OPTION_LONGOPTONLY, ADDPERMITTEDHOOKFILES = 34|OPTION_LONGOPTONLY, SECCOMP_FILTER = 35|OPTION_LONGOPTONLY, FORGET_PRIVTHREAD_INFO = 36|OPTION_LONGOPTONLY, SECURESPLITTING = 37|OPTION_LONGOPTONLY, FTS_EXPERIMENTAL_OPTIMIZATION = 38|OPTION_LONGOPTONLY, FORBIDDEVICES = 39|OPTION_LONGOPTONLY, CG_GROUPNAME = 40|OPTION_LONGOPTONLY, PERMIT_MPROTECT = 41|OPTION_LONGOPTONLY, SHM_MPROTECT = 42|OPTION_LONGOPTONLY, MODSIGN = 43|OPTION_LONGOPTONLY, CANCEL_SYSCALLS = 44|OPTION_LONGOPTONLY, EXITONSYNCSKIP = 45|OPTION_LONGOPTONLY, DETACH_IPC = 46|OPTION_LONGOPTONLY, PRIVILEGEDUID = 47|OPTION_LONGOPTONLY, PRIVILEGEDGID = 48|OPTION_LONGOPTONLY, }; typedef enum flags_enum flags_t; enum detachnetwork_way { DN_OFF = 0, DN_NONPRIVILEGED, DN_EVERYWHERE, }; typedef enum detachnetwork_way detachnetwork_way_t; enum pivotroot_way { PW_OFF = 0, PW_DIRECT, PW_AUTO, PW_AUTORO, }; typedef enum pivotroot_way pivotroot_way_t; enum capsinherit { CI_DONTTOUCH = 0, CI_PERMITTED, CI_CLSYNC, CI_EMPTY, }; typedef enum capsinherit capsinherit_t; enum mode_id { MODE_UNSET = 0, MODE_SIMPLE, MODE_DIRECT, MODE_SHELL, MODE_RSYNCSHELL, MODE_RSYNCDIRECT, MODE_RSYNCSO, MODE_SO, }; typedef enum mode_id mode_id_t; enum queue_id { QUEUE_NORMAL, QUEUE_BIGFILE, QUEUE_INSTANT, QUEUE_LOCKWAIT, QUEUE_MAX, QUEUE_AUTO }; typedef enum queue_id queue_id_t; enum ruleactionsign_enum { RS_REJECT = 0, RS_PERMIT = 1 }; typedef enum ruleactionsign_enum ruleactionsign_t; enum ruleaction_enum { RA_NONE = 0x00, RA_MONITOR = 0x01, RA_WALK = 0x02, RA_ALL = 0x0f, }; typedef enum ruleaction_enum ruleaction_t; // signals (man 7 signal) enum sigusr_enum { SIGUSR_THREAD_GC = 10, SIGUSR_INITSYNC = 12, SIGUSR_BLOPINT = 16, SIGUSR_DUMP = 29, }; struct rule { int num; regex_t expr; mode_t objtype; ruleaction_t perm; ruleaction_t mask; }; typedef struct rule rule_t; struct queueinfo { unsigned int collectdelay; time_t stime; }; typedef struct queueinfo queueinfo_t; struct api_functs { api_funct_init init; api_funct_sync sync; api_funct_rsync rsync; api_funct_deinit deinit; }; typedef struct api_functs api_functs_t; struct notifyenginefuncts { int (*wait)(struct ctx *ctx_p, struct indexes *indexes_p, struct timeval *tv_p); int (*handle)(struct ctx *ctx_p, struct indexes *indexes_p); int (*add_watch_dir)(struct ctx *ctx_p, struct indexes *indexes_p, const char *const accpath); }; enum shflags { SHFL_NONE = 0x00, SHFL_RSYNC_ARGS = 0x01, SHFL_INCLUDE_LIST = 0x02, SHFL_INCLUDE_LIST_PATH = 0x04, SHFL_EXCLUDE_LIST_PATH = 0x08, }; typedef enum shflags shflags_t; enum shargsid { SHARGS_PRIMARY = 0, SHARGS_INITIAL, SHARGS_MAX, }; struct synchandler_args { char *v[MAXARGUMENTS]; int c; char isexpanded[MAXARGUMENTS]; }; typedef struct synchandler_args synchandler_args_t; #define STATE_STARTING(state_p) (state_p == NULL) enum state_enum { STATE_EXIT = 0, STATE_STARTING, STATE_RUNNING, STATE_SYNCHANDLER_ERR, STATE_REHASH, STATE_PREEXIT, STATE_TERM, STATE_THREAD_GC, STATE_INITSYNC, STATE_HOLDON, STATE_UNKNOWN }; typedef enum state_enum state_t; enum stat_fields { STAT_FIELD_RESET = 0x0000, STAT_FIELD_DEV = 0x0001, STAT_FIELD_INO = 0x0002, STAT_FIELD_MODE = 0x0004, STAT_FIELD_NLINK = 0x0008, STAT_FIELD_UID = 0x0010, STAT_FIELD_GID = 0x0020, STAT_FIELD_RDEV = 0x0040, STAT_FIELD_SIZE = 0x0080, STAT_FIELD_BLKSIZE = 0x0100, STAT_FIELD_BLOCKS = 0x0200, STAT_FIELD_ATIME = 0x0400, STAT_FIELD_MTIME = 0x0800, STAT_FIELD_CTIME = 0x1000, STAT_FIELD_ALL = 0x1ff7, }; enum syscall_bitmask { CSC_RESET = 0x00, CSC_MON_STAT = 0x01, }; #define CAP_PRESERVE_TRY (1<<16) struct ctx { #ifndef LIBCLSYNC volatile state_t state; pid_t pid; char pid_str[65]; size_t pid_str_len; uid_t uid; gid_t gid; uid_t privileged_uid; gid_t privileged_gid; uid_t synchandler_uid; gid_t synchandler_gid; #ifdef CAPABILITIES_SUPPORT __u32 caps; #endif pid_t child_pid[MAXCHILDREN]; // Used only for non-pthread mode int children; // Used only for non-pthread mode uint32_t iteration_num; rule_t rules[MAXRULES]; size_t rules_count; dev_t st_dev; #endif char *flags_values_raw[OPTION_FLAGS]; int flags[OPTION_FLAGS]; int flags_set[OPTION_FLAGS]; #ifndef LIBCLSYNC char *config_path; const char *config_block; char *customsignal[MAXSIGNALNUM+1]; char *label; char *watchdir; char *pidfile; char *standbyfile; char *exithookfile; char *preexithookfile; char *destdir; char *destproto; char *watchdirwslash; char *destdirwslash; char *statusfile; char *socketpath; char *dump_path; #ifdef CGROUP_SUPPORT char *cg_groupname; #endif int socket; mode_t socketmod; uid_t socketuid; gid_t socketgid; #ifdef CLUSTER_SUPPORT char *cluster_iface; char *cluster_mcastipaddr; char *cluster_nodename; uint32_t cluster_nodename_len; uint16_t cluster_mcastipport; uint16_t cluster_hash_dl_min; uint16_t cluster_hash_dl_max; uint16_t cluster_scan_dl_max; unsigned int cluster_timeout; #endif size_t watchdirlen; size_t destdirlen; size_t watchdirsize; size_t destdirsize; size_t watchdirwslashsize; size_t destdirwslashsize; short int watchdir_dirlevel; char *handlerfpath; void *handler_handle; api_functs_t handler_funct; char *rulfpath; size_t rulfpathsize; char *listoutdir; struct notifyenginefuncts notifyenginefunct; int retries; size_t bfilethreshold; unsigned int syncdelay; queueinfo_t _queues[QUEUE_MAX]; // TODO: remove this from here unsigned int rsyncinclimit; time_t synctime; unsigned int synctimeout; sigset_t *sigset; char isignoredexitcode[(1<<8)]; pthread_t blockthread[MAX_BLOCKTHREADS]; size_t blockthread_count; char *chroot_dir; #ifdef CAPABILITIES_SUPPORT char *permitted_hookfile[MAXPERMITTEDHOOKFILES+1]; int permitted_hookfiles; #endif #ifdef GETMNTENT_SUPPORT char *mountpoint[MAXMOUNTPOINTS+1]; int mountpoints; #endif synchandler_args_t synchandler_args[SHARGS_MAX]; shflags_t synchandler_argf; #endif // ifndef LIBCLSYNC void *indexes_p; void *fsmondata; }; typedef struct ctx ctx_t; #endif clsync-0.4.1/debian/000077500000000000000000000000001252417542300142365ustar00rootroot00000000000000clsync-0.4.1/debian/changelog000066400000000000000000000035251252417542300161150ustar00rootroot00000000000000clsync (0.4-1) unstable; urgency=medium [ Dmitry Yu Okunev ] * A lot of fixes [ Barak A. Pearlmutter ] * Merge upstream, in particular debian/ tweaks * don't install compile-time stuff that shouldn't be * bump debian standards version, no changes necessary * update debian/watch file, mangle upstream -rc * update .gitignore * correct some spelling errors * man dash hyphen slash patch -- Barak A. Pearlmutter Fri, 08 May 2015 10:43:41 +0100 clsync (0.3-1) unstable; urgency=medium [ Dmitry Yu Okunev ] * Added support of control socket [ Barak A. Pearlmutter ] * New upstream version * Bump standards version * Remove -*- makefile -*- in debian/rules: defaults to GNUmakefile-mode * Must generate library: ./configure --enable-socket * Fold clsync-dev into libclsync-dev * Address lintian issues - Add appropriate per-binary-package sections - Remove extra LICENSE file - Rename lib package w/ .so suffix "0" - Expand and diversify package descriptions. * Add dependency of lib-dev package upon lib package * Print list of debian/tmp files not installed into any binary package * Install some stray uninstalled files - the actual clsync executable (!) - its man page - some *.h header files * Fold small (14k) clsync-doc package into clsync -- Barak A. Pearlmutter Fri, 06 Jun 2014 09:44:28 +0100 clsync (0.2.1-1) unstable; urgency=low * New upstream version -- Barak A. Pearlmutter Thu, 24 Oct 2013 17:14:15 +0100 clsync (0.1-2) unstable; urgency=low * Tweak debian/watch to ignore debian releases -- Barak A. Pearlmutter Fri, 11 Oct 2013 10:45:20 +0100 clsync (0.1-1) unstable; urgency=low * Initial release (Closes: #718769 ) -- Barak A. Pearlmutter Sat, 07 Sep 2013 12:16:00 +0100 clsync-0.4.1/debian/clsync.init000077500000000000000000000044701252417542300164260ustar00rootroot00000000000000#! /bin/sh ### BEGIN INIT INFO # Provides: clsync # Required-Start: $remote_fs # Required-Stop: $remote_fs # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: clsync daemon init script # Description: This script launches the clsync daemon. ### END INIT INFO # bugtrack: https://github.com/xaionaro/clsync/issues PATH=/sbin:/usr/sbin:/bin:/usr/bin DESC="syncer daemon" NAME=clsync DAEMON=/usr/bin/$NAME PIDFILE=/var/run/$NAME.pid ARGS="--pid-file ${PIDFILE} --background" SCRIPTNAME=/etc/init.d/$NAME # Exit if the package is not installed [ -x "$DAEMON" ] || exit 0 # Read configuration variable file if it is present [ -r /etc/default/$NAME ] && . /etc/default/$NAME [ "$CLSYNC_ENABLE" != "true" ] && exit 0 IONICE="/usr/bin/ionice" if [ "$CLSYNC_IONICE_CLASS" != "" ]; then IONICE_ARGS="-c $CLSYNC_IONICE_CLASS" else IONICE_ARGS="-c 3" fi . /lib/lsb/init-functions do_start() { start-stop-daemon --start --quiet --pidfile "$PIDFILE" \ --exec "$DAEMON" --test > /dev/null \ || return 1 start-stop-daemon --start --quiet --pidfile "$PIDFILE" \ --exec $IONICE -- $IONICE_ARGS "$DAEMON" $ARGS $CLSYNC_ARGS \ || return 2 } do_stop() { start-stop-daemon --stop --quiet --pidfile "$PIDFILE" --name "$NAME" RC="$?" [ "$RC" = 2 ] && return 2 start-stop-daemon --stop --quiet --oknodo --exec "$DAEMON" [ "$?" = 2 ] && return 2 rm -f "$PIDFILE" return "$RC" } do_reload() { start-stop-daemon --stop --signal 1 --quiet --pidfile "$PIDFILE" --name "$NAME" return 0 } case "$1" in start) log_daemon_msg "Starting $DESC" "$NAME" do_start case "$?" in 0|1) log_end_msg 0 ;; 2) log_end_msg 1 ;; esac ;; stop) log_daemon_msg "Stopping $DESC" "$NAME" do_stop case "$?" in 0|1) log_end_msg 0 ;; 2) log_end_msg 1 ;; esac ;; status) status_of_proc $DAEMON $NAME && exit 0 || exit $? ;; restart|force-reload) log_daemon_msg "Restarting $DESC" "$NAME" do_stop case "$?" in 0|1) do_start case "$?" in 0) log_end_msg 0 ;; 1) log_end_msg 1 ;; # Old process is still running *) log_end_msg 1 ;; # Failed to start esac ;; *) # Failed to stop log_end_msg 1 ;; esac ;; *) echo "Usage: $SCRIPTNAME {start|stop|restart|force-reload}" >&2 exit 3 ;; esac exit 0 clsync-0.4.1/debian/clsync.install000066400000000000000000000000711252417542300171170ustar00rootroot00000000000000usr/share/man/man1/*.1* usr/bin/* usr/share/doc/clsync/* clsync-0.4.1/debian/compat000066400000000000000000000000021252417542300154340ustar00rootroot000000000000009 clsync-0.4.1/debian/control000066400000000000000000000037421252417542300156470ustar00rootroot00000000000000Source: clsync Section: admin Priority: optional Maintainer: Artyom A Anikeev Uploaders: Barak A. Pearlmutter , Dmitry Yu Okunev Build-Depends: debhelper (>= 9), libglib2.0-dev (>= 2.0.0), dh-autoreconf Standards-Version: 3.9.6 Homepage: http://ut.mephi.ru/oss Vcs-Git: git://anonscm.debian.org/collab-maint/clsync.git Vcs-Browser: http://anonscm.debian.org/gitweb/?p=collab-maint/clsync.git Package: clsync Recommends: rsync Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends} Description: live sync tool based on inotify, written in GNU C Clsync recursively watches for source directory and executes external program to sync the changes. Clsync is adapted to use together with rsync. This utility is much more lightweight than competitors and supports such features as separate queue for big files, regex file filter, multi-threading. Package: libclsync0 Architecture: any Pre-Depends: ${misc:Pre-Depends} Depends: ${shlibs:Depends}, ${misc:Depends} Description: clsync control socket library Clsync recursively watches for source directory and executes external program to sync the changes. Clsync is adapted to use together with rsync. This utility is much more lightweight than competitors and supports such features as separate queue for big files, regex file filter, multi-threading. . This package contains a shared library to control clsync via socket. Package: libclsync-dev Section: libdevel Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends}, libclsync0 (= ${binary:Version}) Description: development files for libclsync Clsync recursively watches for source directory and executes external program to sync the changes. Clsync is adapted to use together with rsync. This utility is much more lightweight than competitors and supports such features as separate queue for big files, regex file filter, multi-threading. . This package contains development files for controlling clsync via socket. clsync-0.4.1/debian/copyright000066400000000000000000000020321252417542300161660ustar00rootroot00000000000000Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: clsync Source: git://git.mephi.ru/clsync Files: * Copyright: 2013 Dmitry Yu Okunev License: GPL-3+ This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. . This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. . You should have received a copy of the GNU General Public License along with this package; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA . On Debian systems, the full text of the GNU General Public License version 3 can be found in the file `/usr/share/common-licenses/GPL-3'. clsync-0.4.1/debian/default000066400000000000000000000007301252417542300156050ustar00rootroot00000000000000# # clsync is not enabled by default. To enable it replace "false" with "true": CLSYNC_ENABLE=false CLSYNC_IONICE_CLASS=3 # clsync's arguments: # proof of concept: CLSYNC_ARGS='-M rsyncdirect --output=syslog -L /dev/shm/clsync -x 23 -W /var/www -S /usr/bin/rsync -D /mnt/backup' # production example: #CLSYNC_ARGS='-M rsyncshell --output=syslog -L /dev/shm/clsync -x 24 -W /srv/lxc -S /opt/backup_engine/bin/sync-handler.sh -R /opt/backup_engine/etc/clsync.filter' clsync-0.4.1/debian/libclsync-dev.install000066400000000000000000000001321252417542300203600ustar00rootroot00000000000000usr/include/*/*.h usr/lib/*/libclsync*.a usr/lib/*/libclsync*.so usr/lib/*/pkgconfig/*.pc clsync-0.4.1/debian/rules000077500000000000000000000010721252417542300153160ustar00rootroot00000000000000#!/usr/bin/make -f # Uncomment this to turn on verbose mode. #export DH_VERBOSE=1 %: dh $@ --parallel --with autoreconf override_dh_auto_configure: dh_auto_configure -- --enable-socket --enable-socket-library --enable-highload-locks override_dh_auto_install: dh_auto_install sed -i "/dependency_libs/ s/'.*'/''/" $$(find debian/tmp -name '*.la') -rm --verbose debian/tmp/usr/share/doc/clsync/LICENSE -rm --verbose debian/tmp/usr/bin/gencompilerflags -rm --verbose debian/tmp/usr/include/clsync/compilerflags.h override_dh_install: dh_install --list-missing clsync-0.4.1/debian/source/000077500000000000000000000000001252417542300155365ustar00rootroot00000000000000clsync-0.4.1/debian/source/format000066400000000000000000000000141252417542300167440ustar00rootroot000000000000003.0 (quilt) clsync-0.4.1/debian/source/local-options000066400000000000000000000000241252417542300202400ustar00rootroot00000000000000single-debian-patch clsync-0.4.1/debian/watch000066400000000000000000000001561252417542300152710ustar00rootroot00000000000000version=3 opts="uversionmangle=s/-rc/~rc/" \ https://github.com/xaionaro/clsync/tags (?:.*/)?v(.+)\.tar\.gz clsync-0.4.1/doc/000077500000000000000000000000001252417542300135615ustar00rootroot00000000000000clsync-0.4.1/doc/devel/000077500000000000000000000000001252417542300146605ustar00rootroot00000000000000clsync-0.4.1/doc/devel/thread-splitting/000077500000000000000000000000001252417542300201425ustar00rootroot00000000000000clsync-0.4.1/doc/devel/thread-splitting/benchmark-synchandler.c000066400000000000000000000010061252417542300245450ustar00rootroot00000000000000 #include #include // Required header: #include // Optional headers: #include #include #include struct ctx *ctx_p = NULL; int clsyncapi_init(struct ctx *_ctx_p, struct indexes *_indexes_p) { ctx_p = _ctx_p; return 0; } int clsyncapi_rsync(const char *inclistfile, const char *exclistfile) { return 0; } int clsyncapi_sync(int n, api_eventinfo_t *ei) { return 0; } int clsyncapi_deinit() { return 0; } clsync-0.4.1/doc/devel/thread-splitting/benchmark.sh000077500000000000000000000041231252417542300224330ustar00rootroot00000000000000#!/bin/bash RUN_TIMES=27 configuration() { git checkout -- configuration.h for regex in "$@"; do sed -i configuration.h -e "$regex" done } configure() { ./configure -C $@ >/dev/null 2>/dev/null || ./configure $@ || exit -1 } run() { time ./clsync -Mso -S'doc/devel/thread-splitting/benchmark-synchandler.so' --have-recursive-sync --max-iterations 1 -W ~/clsync-test $@ || exit -1 } benchmark() { make clean all HL_INITIAL=$(awk '{if ($2 == "HL_LOCK_TRIES_INITIAL") print $3}' < configuration.h) HL_AUTO=$(gcc -x c - -o /tmp/hl_auto.bin.$$ << 'EOF' #include #include "configuration.h" int main() { #ifdef HL_LOCK_TRIES_AUTO printf("auto\n"); #else printf("noauto\n"); #endif return 0; } EOF /tmp/hl_auto.bin.$$ rm -f /tmp/hl_auto.bin.$$ ) CONFIGURE=$(awk '{if ($2 == "./configure") {$1=""; $2="";print $0; exit}}' < config.log) hash="$@|$CONFIGURE" if [[ "$HL_AUTO" == "auto" ]]; then hash="$hash|$HL_AUTO" else hash="$hash|$HL_INITIAL" fi rm -f /tmp/benchmark.{,err}log-"$hash" i=0 while [[ "$i" -lt "$RUN_TIMES" ]]; do run -d1 $@ >>/tmp/benchmark.log-"$hash" 2>> /tmp/benchmark.errlog-"$hash" i=$[ $i + 1 ] done } gcc -I. -shared -o doc/devel/thread-splitting/benchmark-synchandler.so -fPIC -D_DEBUG_SUPPORT doc/devel/thread-splitting/benchmark-synchandler.c configuration 's|SLEEP_SECONDS.*$|SLEEP_SECONDS 0|g' for args in "" "--thread-splitting"; do #for args in "--thread-splitting"; do configure --enable-debug=yes benchmark $args configure --enable-highload-locks --enable-debug=no benchmark $args configure --enable-highload-locks --enable-debug=yes benchmark $args configure --enable-highload-locks --enable-debug=force benchmark $args done configure --enable-highload-locks --enable-debug=yes benchmark interval=1; while [[ "$interval" -le "2147483648" ]]; do configuration 's|SLEEP_SECONDS.*$|SLEEP_SECONDS 0|g' 's|#define HL_LOCK_TRIES_AUTO|//#define HL_LOCK_TRIES_AUTO|g' "s|HL_LOCK_TRIES_INITIAL.*$|HL_LOCK_TRIES_INITIAL $interval|g" benchmark --thread-splitting interval=$[ $interval * 2 ] done rm -f doc/devel/thread-splitting/benchmark-synchandler.so clsync-0.4.1/doc/devel/thread-splitting/highload-locks/000077500000000000000000000000001252417542300230325ustar00rootroot00000000000000clsync-0.4.1/doc/devel/thread-splitting/highload-locks/clsync-graph-comma.csv000066400000000000000000000042461252417542300272410ustar00rootroot00000000000000tryes real user sys 1 21,71 24,30 14,52 1 16,79 18,60 11,52 1 20,39 22,50 14,29 2 19,16 21,64 12,88 2 19,22 21,04 13,21 2 19,61 21,94 13,72 4 16,96 18,89 11,44 4 19,37 21,38 13,43 4 18,84 20,74 13,00 8 18,50 20,53 12,64 8 21,53 23,61 14,62 8 22,18 24,82 14,85 16 18,13 19,90 12,45 16 20,93 23,18 14,35 16 19,98 22,34 13,57 32 15,15 17,42 10,47 32 17,38 19,56 11,84 32 16,08 18,12 11,47 64 11,99 13,61 8,93 64 12,02 14,00 8,58 64 12,81 14,95 9,16 128 12,20 14,23 8,53 128 8,83 10,42 6,29 128 8,76 10,03 6,42 256 10,02 12,96 6,48 256 7,89 10,39 4,86 256 6,13 7,84 3,91 512 6,76 8,57 4,36 512 9,42 12,30 5,81 512 10,75 14,06 6,69 1024 10,48 13,62 6,67 1024 8,04 10,60 4,90 1024 7,04 9,25 4,41 2048 7,54 9,81 4,66 2048 9,26 12,09 5,81 2048 10,60 13,64 6,80 4096 10,31 13,74 6,40 4096 10,49 13,86 6,44 4096 6,49 8,12 4,26 8192 8,80 11,82 5,31 8192 9,94 13,30 6,06 8192 10,58 14,04 6,54 16384 6,79 8,88 4,23 16384 8,10 10,52 5,30 16384 10,30 13,77 6,32 32768 11,11 14,00 7,58 32768 10,03 13,23 6,25 32768 7,53 10,12 4,55 65536 8,82 11,46 5,56 65536 9,68 12,91 5,98 65536 10,23 13,66 6,32 131072 8,02 10,34 5,21 131072 10,50 14,02 6,47 131072 9,27 12,10 5,80 262144 9,25 12,22 5,82 262144 8,01 10,65 4,95 262144 8,40 11,08 5,26 524288 9,69 12,92 5,99 524288 8,14 10,70 5,15 524288 10,95 14,23 7,01 1048576 9,82 12,96 6,23 1048576 6,94 8,43 4,86 1048576 8,38 10,84 5,30 2097152 10,67 14,06 6,62 2097152 8,45 11,05 5,43 2097152 14,89 18,81 10,34 4194304 6,97 8,34 5,06 4194304 11,72 15,33 7,53 4194304 14,72 18,54 10,30 8388608 15,66 19,91 10,98 8388608 10,16 13,56 6,25 8388608 9,33 12,36 5,91 16777216 10,51 14,07 6,56 16777216 6,96 8,66 4,79 16777216 7,83 10,20 5,13 33554432 10,39 14,02 6,43 33554432 6,89 8,48 4,87 33554432 11,13 14,90 7,03 67108864 7,69 10,03 5,13 67108864 10,40 13,88 6,55 67108864 9,11 12,38 5,66 134217728 10,47 14,28 6,51 134217728 12,31 16,32 8,15 134217728 8,50 11,77 5,11 268435456 9,08 12,33 5,71 268435456 14,51 18,90 9,97 268435456 6,81 8,72 4,79 536870912 6,28 8,49 3,98 536870912 7,47 10,14 4,70 536870912 8,86 11,82 5,77 1073741824 8,59 11,59 5,48 1073741824 6,78 8,74 4,76 1073741824 23,68 28,16 19,03 2147483648 14,60 18,86 10,17 2147483648 14,53 18,80 10,09 2147483648 6,83 8,77 4,82 clsync-0.4.1/doc/devel/thread-splitting/highload-locks/clsync-graph-comma.odc000066400000000000000000001440051252417542300272110ustar00rootroot00000000000000PKÒiñD«¸²l((mimetypeapplication/vnd.oasis.opendocument.chartPKÒiñD"SÁj ¯ ¯Thumbnails/thumbnail.png‰PNG  IHDRØÿë4¯gIDATxœìu EÿÆç\x¤C¤A )¥$Dn¥»Dº•îîîî’–î)éîRîþ>w¿ï™ßÜ=çž ÂËyãÎçîÝyæù~ŸÙ‰ð/^¼PJ(;ê9öÈ.;=NÊ¿Ä .œÜ+¿f‚ÜÈU~åªÎ¡ÎŒþ•tô³$)¦NÇ‘7IYgFçV§ÆdF2¦³ô×_ØA§o–gA9 Ðñf9;â8ªÀkzV“øŽ§ècÏWx›u©KÐÇqH<ƒï˜+Ê@ž„þWñàÁƒ¨Q£Ê& ïß¿ÿçŸr)R¤ˆ#jzæÖLS0ĽQ¢D‘²Ó ”{ù÷Þ½{Ñ¢ESÁë@ƒUÇ—K…gÜÎq¬X±¸t÷îÝèѣ뜛™yÕ2÷ZŒ¡ÖE¨ñJg|¿ÂßË!!¼kÿÒ lñüùó)S¦,[¶lñâÅï¾û®A¸pÓ¦MsçÎgéÒ¥+[¶ìˆ#vîܹhÑ"‰³nݺãÇÿãÿغuë˜1c¦Nºÿþyóæ ¤¼>W@?xðàÛ·oƒÅúõë§M›öðáì^½šì~ýú=zôˆ«5J:µäDßNÊ<´U«V•íÀ¥7vïÞÌR›4i'Nœ"dÉ’åçŸ~çwhE­[·¼ú®ÿÙ^[ æ¿^CŠà|Ç:|øða’$I¨›ÇSsÚPJÎß¼y³Aƒ 0oÞ¼ùóçO™2å–-[€/‘‰öÖ¯_ˆ©æ¤I“îØ±CS¦g¶å¡Ož<>ûôé3nܸéÓ§0`âĉOŸ>|‘#G&'0YÇŽ{÷î½dÉ’4iÒhXK"üKÌ)R\¹r…3ÞÌ™3É),üñGçÎãÆ{àÀ<É’Ð9r$WAª`×L˜™3gÞ³gÏ;w83kÖ¬5jh˜O|GÞkìØ±çÎûá‡@!üýñÇÓÞ´¿ëY>!Y´lbH¥êã¼×h>*4Ôª³9ô›i6UHõk/^·6ñÞøÞ{ï5mÚTÙè„ü Cå.¶/C† âÏq•¤LÎÑ ]nõsùòåž={¶mÛË‹ý­R¥ óæÍ_ý5| FA*è4hÐèÑ£½6e’Š;6©c²·{÷îíÛ·çÊ• ˜Ò®h`°)@œóàR¹Íú¿¾8ÿ#Ã?M³. ó_S–êó¦ÝôA¶®àªÖñø³gÏ@ÀéÓ§ñÏrç΋&.¿²« ŠÂ™Ûµkˆ!VrÛ¶m§NÂ:£ %\1n¬]»6·`åñÕ¸Š¿ˆs†ã¨“ҙѭU«˜ãßcÇŽá\â¾c‡µk×fÍšµyóæuêÔÁ°âÛéV!·ËOá*˜Ë™3'd paY¤1 “a… €æ­[·`V¼C2LkA™ÙÐåãYPŽt¼…YÎŽ8Ž*ðZ§žÕä#¾ã)fQ¼Á*mš=û¼æÒ3C*„`:j>Jº¼té5Gá]á¢iÚ>m5ÄÂó£>ºqãÆ÷ßõêUð‡¥þàƒêÕ«GÝxN4Äùóç?ûì3€èȶ<]d8ãàÈ‘#Ÿ|òI¦L™83lØ0¼:hOô < KøÌìš‘Dh ò/bŽ9¸:|øpxæÃ)lÓ¦ (Äå€OØhˆ“Lzm>Ð`Fð,Rß7:¯uêYM>â;ž¢zž÷š®gÌPoÿã?&Mš´|ùrª»f~Ž”cjtâĉà§ èYb®Zµ ’–Ì]»v $]¸pÑ*Uª-Z¦ã»¶þ£ù믿¢NÐ4H „ 2yÆ ¨œ–-[[¼xqTÔÁƒ‰ ?^+·ëùË/¿p;º;}úô+V¬à¸R¥J@MHqÊ”)íÛ·G©ìرcÍš58Á¨1Þ"~üøŽ‚ ©œ½ ×"õQ> ßG¼Ì¿fNÞTýlšcĈѭ[·S§Ni_ÊêìѣǙ3gø—êG8_¼x±H‘"âêñ/qºvíŠÈà·sçΰÔСC/ZÛë˜ PÔ>ÿüófÍšmݺ•tÀ42œ HjÜ‹Î%yC³[†~R6Os{¾|ù®\¹"Úd†ÛŸ>}*yÆA|ðà,Nó ÍFÙžÀ¿º<ÿÓƒ?U³ø| €ZôôŸjDÀP¢$HáÖ­[ñìдiÓ©S§~ðÁœÜ³gÏ7R¤Híîß¿?†zuäß³ÇdEgð@óPÍ‚$€ I¹cÇŽ¤©¿ˆè%WòRr,ÌU$y“&Mh??ýô[£Fúõë׫W/A‚ŽtÂLs0Óì©T°óš!Bðêã›YÑ÷ÂàLä§ç "es¸ ۾ܹs÷îÝ›Î1WçÍ›‡ÆØhŒ,€ápøÌa8ŽR“ ø]¹råÞ½{÷ïßߦM›råÊá8bC3eÊÄ¥Áƒ; ¬ß‚«4!9#=8.»[ôîÝ»ü‹ë ¸,X7nܘæ!.gEz-gG:ÞÂQ—^¹:õ¬&ñOQÞZõëçPùýËʽ{÷F…ê4hPÆ ãĉ£í©D@IšéÕ«WõêÕ!Bü-p ©Zœ¼ˆ#¦I“†˜ð%®$ڛ˯9¼À,5éýÁ×ܲe  AXà A.A]x‡C† q9U:tpºÜŽS{èÐ!Žñ8~¢9Á1$ ¬wîÜ%J”\¹ríÛ·o÷îÝxŠ$NËÑîf#:á·ñˆr2räÈh \{Ì"æÒ33˜ÈV­Z,"ð‹C)cgfÏž jy7,ƒ¡Õ„ ~øá‡PšCçŠ3š6mZPˆ qÙ’ÎûüóÏùw»Ü¥KÈžFG#öÉ39¤!•/_^†J„ùˆŽ'*ÿÎ⣎aATUhzšá‰&MšÛ·o7hКcP*@jJ‰‰5=cÆŒ)\¸0à âAáõë×…ÉLú1ËôƒÂåË—oذüÅŽ{éÒ¥;wî:t¨ŒÊ;7D»dɘÈjWO 4)HGŒeËè°^½z¨²ù2š ò?þøi?xð`‡G§›îïË©gVÏG]„Z¯tÆ«]~ͪ‡©2¾&FŒ®àS„Ïà6H¥U«V™3g†ó¨]ê—ºrÙó°°sæÌÁ¶mÛ:ä,u̘1=›¦rƒ›G€B|D€â²Ç(lÛ¶mæÌ™‡Véf̘ñèÑ£íÚµkÖ¬™×lËŒ’÷¤°{÷n`q'} 1Ù8pàãÇëÔ©%J”¸qãJ†ÃìrHÁŸãůŸ>}úåË—±ªµjÕŠ+–veªÀèÑ£‰í¼dÉ’¤ÌÓÑXjÎcÙÉ4ì´$»~ýú_ýúL–,Í¿ÎmäûË/Y5jÄGľÃÜø^³$ømô²ù ^Á˜¬‡J›¾*x-[¶ÄÙ:ÄpèÚµk)R¤€o°ÑeÊ”AU`»eTعsçÀ.îåîÐöìkpÙŸƒ,X@²`=zôè ‰,»ÿ½ÌU€xòäÉXvÐÚ¦çš:ujxZFàÊ×EÀ‡œ‡MIææu”ÍͼÆÄ;œl=8! ×"õQ¾ ßó¤ïø¡öà¼fÃëò•ÿßÖ’#Êí®QI汞ÊݧÃrF‰bà-ÃAl;ˆÇFÅ+{ø´î˜4«_wR¬¸vÐÏ'Žb,ûƒµ$¥Q¨ßȲçè{åA8»_J(Q"}5bĈBÏb—õ †-9âx„ßÄŠT‰Ìq¹—›ñb2¦_¹;®¥F9 ö.{ò€`KzÜKÕxf[Àª‹FÆ6;«…áäj {)eøæ:Û¢`tÑóh=N[KcIÇ\¬Ç‘Ú«–ù³Xñc÷åž §lœ™Þ¡r7íà[Á{à¤ú]Á?iÈwS){m©r`þëȉük®Ãh,줯þ³‡ÊSô—óY!õ€x-gG¾f爣N=«ÉG|ÇS™S9Tþ¡M>Nœ81yòdŽsæÌ‰;eºóbat ‚tâĉ—.]BRŒgÉ%Žg̘±gÏ™›úè#ˆM;8^™6óÙgŸYvANâqÊ®0FôzÞÏkßè¥m¤gÎôõ[Iq­¤—‘*Wî©tr;dÁIÊQ@âk Âtï·²…S(Iéõnm0¢ø¦že-ëõØkßH×7Çøh Ë’#|Ìí’=‰é»éþÎYQî¡7r|…`ˆ¹Þ>Ö=ŽúŒåmÍ$3½Þ¤¯§›è¯5Žõndº“åîÄ6߈3z±Psu˽”ôiÚ“co§Ó·ÂT³§jväÆ+ì¼fH…L#R)(»FwìØÌÄSÔ#´µ"“z½uëÖáÇsçÎ-Ò7oÞ,\¸°ô3Ø ©K/4gP¦=⪈Gß8 ®Y³ºâYD@Ù uô—î{áxngŽœ ]²gÏ.I™E¯³'N4 ÂeóæÍø HuÉ?g¶lÙ’"E |PÄǸ2;̬~ZHh0#x©™”òÖÈCªSÏjòßñGæßT•QÆL›6m×®]9rähРÁäÉ“5»(·oK¥öíÛˆìÛ·oÒ¤I¤vñGŒAä!C†ÉÒ©S'D7ò™c¼Æ‘#G ø$)Qè2v¸ƒ0¼ÉFåÉ“) 4!¿qãÆ¡vQ-íÛ·ïÓ§™iÕª²W¾>Kó°Üë}ýòË/íÚµC‚Ô©S‡œã\"üË•+WªT©«W¯6iÒÿ²_¿~œ'K¤öóÏ?§J•Êë{høßbDÇû;þ é¼×t=cú¸][´h¥Æ/ÄYÄ©'¿ýö[±bŬà㥫 ÂɹsçàààfÈB‰óæÍ«Zµ*¸üòË/ix x·ä™ÈæðP‹Ô3‚«ç£.|WÄ«žñj—_3‡ÊŒ(î ÕP}øð!Ү̧£-ªT©R¿~}â$L˜ðÎ;Øhäêùóç©ÔŽ;"™±}ýAŽõjÕ:)¡=(êþýû˜TàN|˜X( $Yööè ®ÆŒS<€«ûƒÄÔD–H¡P¡B—/_Ü´–Å‹éHD 1bÄH”(QÉ’%il²‘Œ NÂÑŸ uRýò½˜ÊSn¨Ë݉(#ï¡Cð!Ó8hذ¡H‘d™a æ@Iê=;èz—IßOšò…Z÷PJ·¢|¡NI °êtäó´²×%W.{á(=½AÒtl <ƒŸ'O‰RVn7άrË=…ÀrokÞ(1e4ƒ ¾³®véIéÞ½i­\’ .^]i¯&É̶eôVúXÖüTèµ|B²h!ÙD¥Òy¯Ñ|Th¨UÿfsèçÕÀ¨¼7nà«ÉJè–ÑÇá2f¸råJ¼xñ€1±ÎØP‰ÃíÁ‹/ê}M²’¬ N®âÛa…Áì}‚WÇ1\%(!)Ù™GVw°',cp¼-²Hd²Á%1¸‡B!IRHi$”e¯ÆI®¢ëy;—‡L¶ÂTó¿ƒi–m´rdÌž=»GfÎwïÞíß¿?RfëÖ­kÖ¬Y°`z™:nß¾=Ž;†h£«V­ªY³f—.]PDs‡< 1ËUîE4 ,¾ûî»üùó£3dqÄéÓ§#wˆöñÇã’®\¹;Kú¦éWný}ûvÙ&­F¤“.]:°Ø¨Q£lٲ᧒U< î¥ýàu=ÜM¤½H1Çà”° ÁoªY¬'~UïÞ½©ª/¾øB¶Üq4Pþ-W®•J|Tm›6mP … ®X±b²dÉ8“5kÖܹsË'ÝÊ•+K§‰C™JݧL™„ÁsµjÕ*Z´(¢‘a¹Çé ÀiÀ¥\¢D‰Õ«WƒH„Y2{%…\QK¥J•âª<½Òºuë“'O’ÜGÌùÎ;M€˜:ujϳHPº?}Øeßeþ÷4©ïŠxÕ3^íòkæPùq„¶˜¼Ï?ÿœcØ…ê”*wÌÁ£^£D‰‚˜…ÕЪ³fÍ‚r®^½*[Äc”]öiLö AƒÒ¤IšÅt(;’Ê—/wàˆÇìb‹eü8ƸC®}âÀß~û-˜6¿ô(·ŠlÇ|£u0î0(ìÓ§ÏÑ£G4h ÓLe%fâк¸ FÏž=;ÿjäC2û.s‚ÔG]ø®ˆW=ã[õÿ½ª‡ì{õê¥Éà?‡È@½¢vÁàøå—_?~ ƒÊvã8(P @Þ¼y/\¸ðᇂ -_‚/£ƒ3ʳ +A¤H‘¤O[¤tË–-wìØŽÁ%6˜ò/êõì©lM-Âmh×®¾Ô>aÂrõäÉ“ýû÷sÌC‡E8ƒ~exWo£|ÿÓ‚ßFhË‚Á#GŽÄìâZÍ›7ï믿Ö#¯”»ÚÐ"‹-:uêÔܹsñ‘Ô+†Ó™+W.P…u&ÔçÇúôi’*Y²¤ÌÀ×@4;väÓ h$H@ gÏžF8ޏz‹/æ*€/qx(ÄÔ ZÆmvîܹiÓ&RNŸ>==~ü8Ï…eçÌ™S©R¥#GŽÈ×r¾téR >sæLò,ûayÞ6BÛo¦YzdÊ–- ™6*ɹú.¢á Nš4 𩇆±°tx`€n‹7.\Ø´iÓ§OŸbX'Nœ(³ïÌçêetÀWy†Äp¸ j1Ǥ°eË”¯,üÊy( Ùë0Í–Ý—D4ü?Îàâ-üúë¯xà˜§gvä GŽI“&Å‚ó8Ùoз] µÌÿ›M³£îßZ÷ØÍí ãH³× @ÈRÄôhê-ZÈš2Ií`Ù}ãæžººÉ*wK5Ù!Èv„¶mÛJµ¸tz«$ky[éÁ±^䈣¥”Ý}M°ì%rÌUrÌ[ºoðg‡¶ro•##øÂÚªÊê6zª€²¥?±`âõLŽå^Ç6¹fRÊXFGŽqÅaÕÙü驟ž:Ñœ*`Ù“´¯)II‰’šžEêµ|B›!ÉUߥêõ¼×h>*4Ôª³9ôÛ·fi(B0ò1-0øF²úØœ àyo®AcyLp¼…˽…žD"W壎™”½ÏŠCêZÁ×¾QÆÎš2u£ÒŸp¼¾]¨ÍÇ-^KÕÇy¯Ñ|Th¨Uÿfsè7Ó,'…uF…Ä×ccͻ֮]‹Ö­[7þ6mÚï¿ÿž'O|2þ½}û6båÑ£GÈ‹4iÒŒ;öâÅ‹ ÄF›ªY¹‰ õÀíP ê'J”(µkׯÄáæÍ›xx8šõêÕ»ví"Ò­S§Ž=.cÀ"¾&r˜§|õÕW·nÝ>|x¾|ùä¡8ÔÕôéÓÉ3yøñljCRæ‡Á0Óìx„?÷â¢7ûôéÓ¤I€|‹._¾¼gÏžuëÖuïÞòoݺu·mÛ&}rœD[*ThàÀp;õM|¼u (%ûì›7oz~ùånìÕ«qø•e*T¨?~ü=z€ÚaƵk×NVi²Ü½ÙÊ^FgïÞ½Øb”ÖöíÛy"¨ÅÅÔô9xðà 6= سgÏ 2È>Ž ©œhHHHò¬‹êÔ³š|Äw<Å‘ù7•CåÇoÍr•:^¾|9zVÝrä;A‚@açÎó D.\˜9sfý ºtéÒ@ðêÕ«‚~ÐªŽ¼ 5&Ož¼uëÖß}÷ËÞÃlæÌ™üñÚY8 ýé§Ÿà(q ›"EŠÏ>û ªÖo¡²L¿ºté ã|ñâÅi(e;»h&Ø4kÖ¬âq.X°Š•©žƈúØoÚB‡ÐU Oœ8Á’¨ì\ Á`.+÷ïßÿôÓOK–,Y¥JØ1Q¢D:uÂ=z”»¸Ô‚B®2ÙŠGW¿$…çY ƒÀ¡'ެK;vl®H‘"XÕ‡FŽ9$çÉ“'Zp,ßr@¡ìtÞ¬Y3ó ¦A69¼sçŽ,ZçÃŒü/?G̘1#Ô"#IŸ={&@4x…xnܸAER£8g¢OÁP¼xñ¨u¼Cbâ“á®A€ÃssI;$…[ }‚?»bÅ þ7.æXãhvíÚµFå˗lj4ûƒtRMRàOˆÿ’ç’2m†W€¤Ožø›:u*VæÃªj¿  ÁÈÇŒH‘"5mÚTöÀ˜r:4çaIR£‰Í]¶l„7hРΓø”#FŒ˜1cƪU«ÆŒƒ ™7o ‚È@÷bîZ¬€¹5kÖÀ—oݺ•óòapüøñׯ_Ç;”¥K¸Ú A@3p$5ý­9Ì4;ž¨þMÖЖ¿ò!δ§üÆŠ«C‡ú.ü-9€{dQ%‘ô:8Vðmr¥€€BçÎud$Ž”-[V¶B åŒ,0§Üüg¦#H*j'¿²€eo±v6l(= Iì`.¦m¾ ‚òŒ’Ÿàõ’ï4ÿö3óo*‡Ê¿»“Zî®DÝÏlyë# ´×s—D½÷¬¬$ëÎXîU¶DXvw£#)9Ð+¿ë3<½páÂ’”¹j| {»]冣™šdIú-»ŠÚŒ,¦(†hm³]'â5o/SæžÅèãÒKVÄ«ž13ÿ¦r¨ü¾;©œ1ÇË8â nôtýÉD÷k€r¯Ù¡í`2eô‡ëÏÊÝ—)tw·ËîßÖÉzå½tŽî×M"нYn ±/î×ôÍ(> Ðk‘z=ãõ¼×h>*4Ôª³9ôÿ~ÍTÎ>U.;ÒëÈî"`Ù’Jå ˜%KÑß~ûíÞ½{Ù²eCŽËž¡œ:uj!E3Û$uäȈ é­ìIüÊ2‡ÊÆåÁƒÉ\ݵkv_¯7¬3¦Ÿ‹îŽ!Âûï¿O‚DþОj­ç¾ðKV?úè#YÒ3S¦Lz?Ž0FôüɈRm²K@¥J•Z·nwe®ý¥lÇKÖ¾AçþôÓOˆ\ªœ™W0nܸãÇãü!bJ”(qæÌRC¾,X°@†ÈÅ?CF ²#FŒ¸qãFD™*€jAj€’Þ½{Ë’ ëׯçaŽÇDùʧd”,×Ô²eKR¨]»võêձȣG®[·.ºDVùæß và¡\9r$*J²êØØ 𿜽Æx Aˆ ž9s&H2 C"h«M}ƒ0êoöìÙ(ÜtéÒáÕ;wºBMW¬X‘ãþýûa”/ Z¸6‹R÷9rä@É·™Ê•+תU ÒRnêÍ›7/¼µnݺµk×>zô¨M›6K–,‘àfS–ÕK :R¯\mÖ¬âÚƒü”m²9>tèPš4i@3z|x£FHŸ4N«V­¬à+=Èq„ 1ýÒ=Î 8yò$ø–µ@Éè„kÁ"9ç‰XmfšC ~}³yófŒ/–Ž_˜O×–y—lHK•—+Wnûöí2å^fjÙ>ŒbÜ1ܸ÷BÓ¦YÒ„Þúõë§ÊÜÓ(Q¢q=ŽfÕªUØk ÈâÆ»gÏ\Æ.]ºàXÁ;ƒ$H–8Xºt)x͘1#Þ|Lc Qq×áÇ(–£ IÏŸ?–qéžåfšýƈÒeˆ§gLœ8ñÎ;‚B=úÆrsÁ"Ÿ={vÊ”)xpUäÈ‘K—.M½,XÁ½¬T©R,™ÓiïqUîJHL/L'P^¾|ù… V¯^‚©W¯Þ±cÇÀq‚ °¹àxàÀ\• í•AJÚŽ&Çp˜ƒ/]º”6mÚI“&62L>÷ïßwíÚ5gΜ¼E®\¹4¡†1¢gðóÆá"r¿ýö[Ý-§Ñã²ÐACTØÁ 2@6 „*Ç÷zøða•*Uð aSMd‡Œµ6¿©(wô žkX8¶ôãh¬1ìéêq©8j.÷ÌÙZn—Èϵx̤äŒ~„D¦ÍèÌè–`n|?/Â$СÚôàSóª sD…`^emVeƒ†ïÝ»'gÄ,zMJ †542$›tÎ$+´wóæMY–«2ßÙ6³L<§9k9첿ÊXöDt‰L`•ÉM·Õ‡›Rz-Rá»ð=OúŽª›øš9ôóö‚ÄGþüù4h •h,ñÓ6räÈ 6¬\¹ò·ß~=ztÔ¨Q‘Dæ*:cüøñP>¢;Ð^Îù";îj*’œÌ›7ï×_%… æÞõë׃<î’õŸPÍ—/_([·nEìÞ½›Gà>š‹ÓyNãÕÇ&µã2b£7nŒdFPƒlPÞ¦M26¯Ùk•ß7…+èVô©lU§IE¹ù â® 2½nݺøñã·mÛ½\§NÈ ô 'š6mZ¨P!”ïœ9sPßøj–;8 t·nÝ(¸ÇA\")&L˜@¨Zµ*qH¡bÅŠpêÔ©S¥§0B„Úsµ\îñDéÒ¥»}û6éà Ö¨QP‚i(üñãÇ8² ô“rçÎ=Ù",èàÏ©T3µN…aabÍš5±bžÚ.{$ &rذaà¬H‘"eÊ”¹téR¢D‰ú÷ï4óæÍ[»víåË—çÊ•kÏž=Øîo¾ùÆœu µŸ>} &ræÌ |‹/Ž–Ï€âhÂÍóçÏ/Q¢h†+T¨°k×.8øë¯¿þ"5Y%–fÖ¥K—Y³f 4¨_¿~ÒÓþ¦Êð¿)øm„¶è €… ›2e •D0;´%>Æv9}ú4.0ݼy³ ˆ€'ì2ÄØúöíË-ÇŽ Žñˆ’ì÷ß¡X–:uêI“&át–,YgÏž=FŒÛ·oâ xâĉȚɓ'“¾)ÃM·Á,%óXÆøÈ¼> 4Ч‘ðÄ)R˜ÒÄ‘š§$òú8OzvÄqT×:õ¬&ñOqdþMåPùw:)ÿƱC³fÍdL`ð ëä1”Ù½{wðŠw¸cÇŽ«W¯Ž3.ÄÞuèÐrF”)ô1bDY['%2š|ðàÁ¹sçØ vÙ²eð_îܹq.qCcƌپ}û„ Êj;Ü5jÔ(ÙäÑ|w”_ÍSdSRY ‡¦‚Wúå—_ZîmWð?!¡ÁóqžßÇQ^ëÔ³š|ÄwyòmÔªU«ñãÇ_»v­yóæÒ¹£“’®u458K›6-²‰eЊ+ò˽Mš4Ù·oŸLµá‘.@ô×$øm<¢Ô‡L‰:zôhÊ”)=g ¨à¦ IÑ£GÊ•+sŒÎ…ÑÚ2j‹h÷ïß7+W®Ì‘#‡¬7¢oúá‡>ðº8-Z4UªT(qIé Ž¹]Æ>|øÑ£GŸ~ú©å1B<,XðÊ•+4ìû´iÓÐé´±õ'Nœà<9‰)0å* tºÜA‹¡P‹Ô3‚Wo5Ôºð]¯zÆÌü›Ê¡òã'> X±hÑ¢ÙùGÆ.x½Qü}èJÉä@º¬¹Wj·téÒgÏžjðÕÿþûï›.wqiùòå‹/†Ãz÷îm>ž«^½:@ä „ïܹ=zt™†¢³!HR6²©°#í§C‡(2 l«Í]ü‹qÿÊéÒ¥ÃR›+ûèÔ^µÌ=‹ÑÇ¥—¬ˆW=cfþMåPù±C[Ì%N[Μ9kÕªÇÀpqâÄlæAß"\òôéS*<É4ƒråÊɶ¦Ç‡áR¤HLå˵¾]ƒ3 Âd™èóúõë¸w8”²;øo¿ý–;wî˜1c¶k×®Y³f˜lp3hÐ Œµ¢>6{F4 )÷¨IËX]"Ȥ^ug²Ëîa–ÛŒ¥4Ú$3z‘s x tÇ:5î]-tQh3­·çÕ% t/o²©™¦®ª0FT^ñím4§NÊ—/Οgó °·yÂ;ÌŸ??ă˜EUdÏž]®òïŽ;°Œ¨D^àÇ ( 05+^þݸq#žˆ[pàp³dÉ¢s‚aEðæÍ›írðàA¼ÆíY|¦Ûª#ïÛ·/›šCçy„zW®\‰wÈr{òäIY‹Öñ1=,˜Áo>¢ØÖ 6L™2¥òÝwßq WÒVîïñãÇ=z«eË–8ˆè‰]»vmݺU ѶªU«F .Q¦õêÕ“ tö$)”(Ä›‚(\Àžpß~û-q,X°ÿ~lëüùóÓ¤IÓ°aCr…2gßiÎ[³f j¦FMš4á_ÄÓ€d°-ñ@Ÿ}öH¥ ñ›'Ož 'FŒ漪0F ƈŽÛ¼ZOó8¤žÁwLí"AÒ§O¿hÑ"M<Y üT·n]D„÷Þ{¯]»v-Z´to)ÝÀ7]ºtT3$´}ûvùâçHJÙ r"hΞ=Û¨Q£#F@Q(_mÙúé'|͘1c¦NšŽl¢«]FåöS8«]»ö£Gø÷ðáè¥/¾øB†dCðçΫY³fÊ”)ÑÑU«V=úìÙ³ïݻǛš\B-RÏžÅèãÒKVÄ«žqÈÉ¡ò»/^¼Û·oO˜0ç½,.ZŠ)®]»Ö¬Y³ï¿ÿ^fßAf2âÆ²—šÝ¶mx‚~üñGè*Ï”)ø6ÇOÈçPJ„V?úè£iÓ¦9r KžFe)ö©S§Ê@Á<ËåVÍü)ÙåŠß1cÆ4nܸk×®2èþýû/^<}úôÒ¥Ke_iȵT©RÉ’% [º8¤*?ªfqä±§TRéÒ¥‹/Ž‹–+W.= U@ÙôîÝ»sçβ`MÔ¨Q©K=ƒ˜ƒ¦M›¾óÎ; °oß¾Øwà9‘š,m(Ñ$MLüp;$H7 V­Z<®~ýúØYâÀ¦¸¡<…”=o¢•I¥.㻜ïÚÁe!‡M÷ìÙ³iÓ¦’%KBÌ8¬¼Yв‘Œ}4{”åãY˜žèµH}T„Â÷zÒwürò¦rè7Q¨7}útùÖœ!CsôôÂ@–Ïž=Ã)„ùJ”(J0Ç£GÆüÁCpØÐ¡C'N †ømݺ5Ì É%L˜00ø›@jذawˆ ræÌ™N:AÆ‚äÆ5j—ðüxjãÁƒD.V¬˜¶§–{"^æ–-[ CÔ YÂì’Ú§OœÔRûöí!TΠ¥0÷$Y°æ#zÍ¡òãTQ°þÖõë×?´÷(5ï’` @à~áùál•/_÷ Í‹¶Àq„x€JYÔëW_}u÷î]©‚ÿ“¤ b€5<ŠëvéÒ%( p@¥DFáÊöõ €)ñÍÏ3.w Íà ˆ`šÐ'N'äЉּysü‹ÅWRïmáøõ3‚g‘êøž¸Ñ)x­SÏjòßñe í æPùq‚½dšZŒ)(4—øÐYâ Šô]º÷Ô¥²¥s;†ä uT;èÑVfRö24:)âÈ:8À…ÒC.8•œtÎóÉIøšx턼AÒÊî׋ì W< 'l‚½ã‰ÊŒ¨Ïk'Ìkó’Ïfß²¶­#(w—6ÇÂ^ʽ~¢,a¹»Ä¥Œ´{*…Ͷá Ñ%¢ë]îo6æçœ@÷¦‚–»ï]ç'Œ=s¨ü«š-÷"2ú[³Ë)Æ~³Æ"IrR׫ƒTWÍ–j®d¬­YÓ\çI¹whSè [ƹÝ1cA¨ZgX†™[H›鵜h~HHòÚȽ֩g5ùˆïxŠ#óo*‡ÊýˆRµ·nÝBCÈøÖJ•*™c˜åxß¾}3fÌàí‰Ú?~<ùË/¿D¸PÙÈ…É“'£Xù=þ<"†dóäÉ#:’Ú°aÃ’%K8à^â /PܳgÏ–• ;F"DÆïyò$J¿ÈcÆŒßÙ²e«^½zÉ’%{öì‰Ô8qb—.]ô®šÏ._¾LV#ÿ’ÒOs"3©‘#GnÞ¼™x/ÜͶmÛjÊt¹=×0Ft<Âo uŠ+P €l“ Q-æ B‰ ¨éŒ3Êw >sÙŽb¹FK—.ÖàЀ-€(WM[L„FqÜ£G¨wÓ¦M`Ž6 ¾ `¯áÇÓ0b­ZµPâWÖfÕo çÊ•ëøñ㲭˼yóàE—{‡$9â*½ÿ>çG"Ag§Nd.„9&#T" ‰ŠB*Uç½FóQ¡¡Vý›Í¡Ÿ;´gΜ‰ÍÅV¶oßþÈ‘#Ÿ|ò‰éíIœqãÆÁ|²¢Æ—êL:™ÙÄ-0"¨Íš5k«V­dÃQ³-Ê£ûôé-Z4YÆ©víÚû÷ïß¶mL¬ìuÀPâ »{÷îPã AƒêÔ©“#Gósˆù.Ð$lÇÓy(bïÞ½<ø^¸p­^½zÇŽsçÎ=:vyáÂ…@\&V{–gaz ×"õQ> ßëIßñCÊɛʡ?7WöÞŽøv`ëÊ•+Ž©–BicÇŽ]¿~=¤rúôé+V€Píݹs'uêÔ˜B€‰¤ú9sñâÅ$I’ܸqÃу Ê£k×®´M›6 ^v¯€±°;wîÌœ93ÇÜN‚ä"¬\¹2 ÄË*úä¥8Odßg’âÑ ìÞ½{d^çäõë×y©íÛ·ÓròçÏ+üÑGYÞ„³×rvàÿ„iv¼¿ãß#ЍìСâE‹½{÷Xf/”DJáÂ…×®]+V,Ž¡O˜æý÷ߘÚĉGR.w/´¼¼tb‹™!Cé ’È"_tµ£½É±LiÕƒ±%‡1í ßHn×Ɇ­}ã;¼í~DÝhtw±^)Æë¾÷4*x·¶†”Ü.÷ê %u+tèô€à3ª4^uftšr‹ž¶¢ó#Çötfý pÁ·ÉåŒÎ‰^aGç’—)RÏ>©ºð]e¯zÆ«d~ͪ·ÿ­Ùr¸ûå—_ðÀd¶%ê5wîܲ]ŠŠN…qàÀB… q¼eˤ@ñâÅeO Î\ºtéèÑ£øŽ€fÕªU>Ě˚Ÿ:‡æ·Qe‚ct–:Ddp#*±,Ñä Þá_|aÞb¾;~-^àÇL~ýõ×äÉ“ËtXÉ<Ò×qÉp$W2ÇÊå^Ç'ì[s°oÍê-q@áÌ™3ñèg̘ñÇ >¼téÒÓ§O—½œtdÉ7(ìß¿ÿÆ‘,óæÍ㯠y1{öl®4 ˜"2,{~ê·ß~ËUy–gÄŽtR®\¹Ò¥KW¯^½R¥J-\¸ýŽ— †PÍœ!Ô1.¬Ù»)Å L;vìˆH¢ñàV­ZuäÈ‘Ä,Z´èóçÏeÞ¹]²dÉäÉ“»uë&Ä}{Õÿãám›æ{K•òåËCr£aȨT©’6”æÑ£Gïܹ3,hÙ‹v¢E‹ÆI!W|²®]»6nܬT©R%räÈ-D’É3JäO?ýrìØ±gÏžužŽ@éÒ¥ @äY7oÞUˆ¡Aƒ‰ÄÑ·‹¯™3gN”ìNJCJ”(ÑöíÛ+ʶÅ4Œ'Ož ¦I™37nÜH’$‰ôf;2óªeþßlš}\{ãÁå7€M¤9sýúu Z´h1jÔ(jÔt¡äC¿Ô+¤ÎîÞ½ ʼn‡3T9&ø©däÕñãÇ7oÞL­“¦¹¢g°ìPäG"3f„Àdáe¯ E~† êéÏž·Ëνö¾|C† Qöœ|Ë^ jüøñM›6íÞ½;—ðxP›6mÚ¶m›-[6sìEX0Ã[] LE»€€€Z§FK–,‰ ƒŠô®zÊh@  @àþ1¢~ýú0MÞ¼yÏž=+¤‚B’š5kVöìÙ‰]aëÍ©:fç Êú°XaXc6mZ!CªT©àéß~ûíóÏ?·ÜáeŠÔ3‚.:uá»Ê^õŒ×NÄ×Ì¡z˦YËU 5‚<ÈJ“Ý.0Ö @›Ž2L‰øˆˆžÕ«W“8 :tHúYÊ”)uÁd+V¬šð%ÄfŽâÑ·“gl1g¦NJ~"ýúõ#3×®]k×®d ²Ç¯Å…%cØe×ofš=ƒú©Hd/ƒ…xP¦ød±cÇVF‡rÓ'N!Þ†íIMcépÅ>øàƒ &P»\EX´jÕ Ðpò›o¾‘Y›ÊcÏV(PÀG„ÿ¤7‘»ÐLÜŽÝÇ¿„ÏRz=ósˆrïÉnÁ%N$,Èy|Á;vL“7üKÙH†ó2¢Û|»°à~øÖ,"ƒP¨ìš…Ò?|+Ëå 8MÕ‚ÆY_÷ :Fmé ¸Ü£ õ£%_> rR>p¬ÈÓYrŸdb«²ã®¬Fg¹GæJï&êeâCú,á((G¾æw Gá{V“øŽ§(£9½Áª·ü‰OÂS|v}`JfÓM4ï׉ë~i=^ßìy6=QíʱÊIdÝ&Ð1 (œ{ƒRåAÕz¶vøÌ:ÁºÇìhòYż¿D²ž s',w_ºo`ýûÑqÛËû+ŽcÏàÕ±°ÜŸ".\¸{ABÈÌ'N ]Í•étdbž;wÛ‡1Uö4x‘µ»yþüyLª,`lnAo›Ù ø—É:qœ”Åk´<")|áÂ…¤f¦#É‚*˜²|ùòè'þ%'(hÚÒãÇÅõD?eË–¦²gÏ /‹p6g‡½#ÊØÊéÓ§?~œ¢@×óh2ÛÚ¹sgd–9›Ö¬¯¬ažôß3¦ùëy>ÔG;Îû¡C;œ½ÙeR‡nݺřC‡!3EhôPåÔ"À­i‡U«V¦ù'É‘#С>@X¤n:uêT²dIm‹å¹b.Ež7oÞ¼cÇŽ p̘1kÖ¬‘š#2¼‚ }úô)%ÙöíÛƒ3~Ít”Û……¡©{YNœ8±aÆ _6$oÜ.£wÁýÌ™3—-[©Ë,ª×ßjE”>Æö¹mÛ6D^ 2jԨŋ—õÀUpÏDçÜk„!T†r4'¯g|<Úqþmwßè–*å˜dT 0Â5Äzò˜lÈ I“&h¬¹tÇèçJRX.0qêÔ)èëðáÃ@jÁ™ÃXs;öîÈ‘#ä'iÒ¤˜fR檣øÄG„Š~ýõWj€=sÃŽeÊ”kÉ$ £ ÜÓ¥Kh`Ç7ò}O8•LÒŒy#rNžaz蜢—Ä!'dòÁƒÊ^Mê?ÃG|›bEþRÓØ\¬Õ矅POZTjË¿%’™2m8y8gèÓ,Y² @'L‰a"A*›[0¸²ù”cÜ—$…_ö½téÒ[ȬÀ‘@ ÷._¾|2fy¦†_áÝÀà‹¹Kjýûï¿—Š',õرcÉ'y˜cnÇ‘åíÀ(nhݺue¦lHTñJb…_ 'Òœ2gÎüÃ? á8~Ò/îðGUöñÔ4ëb•píµ u4ÐDv3A<;ÚÛòÈDéÖƒžÌb§m†˜Tž~"—2eÊ$7ââ`TGëÊ=3ßLJrˆ‘Õ1ë@3zôè­Zµ²ìÎÙq—Û?°ƒr3™'a¼jDh“8Ç4EñªÅ˜À÷º Mñ?ƒÿv‰ü ³«Ç÷»Œmf=Ç÷+÷dËÝÛ¬ö¹*cñ.r첇zì*³´¼“º´ìQ’zt§ÏÛuR2U@>–º×Í‘-÷þ’%ɰõ*›>‡Z†.÷'"r®ç$(cö…Y€ÿ)á­ÎâÓ-C¯|QOg9À½‰®£•KMèt$èO#–1qS¹1¤a/Ü{+›™×ÝICf£7Ÿë9’ÙLÊÁL¦yU!—³×4#›6Ýåžçê…¾qé£BC­úrÒ»ø~ºFhSp(†aÆ5mÚa1iÒ$þÅבñ)&I@K÷þß„ pòJ–,)» Qe[P¼:tÃСC>|(K”xäA!,8®Y³æÅ‹¹ømõêÕär_sÑ¢ED®\¹rªT©PH}ûöEEé^h3)ÄÁ€¾úê«‚ Ι3¹;vlq‰CV=z„S‹r?}ú4̓|"§ð†c‚Þxñšÿ¾¦ð–ƒazþü9µ~èÐ!\œ•+Wž=þ€²}YC¶jÕª?ýôÓ¥K—`Öß~ûíøñã¢èÍ2•ìÁv2g”à`zݺuÉ“''©Ñ£GC‡  ÝÊÙkz-Ò*B7ß–ÑG…†Zõ¯™CÇyÿthã„aO÷íÛ÷õ×_S=“'OnÒ¤‰^ Ì|Õ‰ÂÈ™3'µË/ÎêÑ£nY­Zµ` Î# œ"Œ;‚ÄìR÷æ2ÆB¨p0*m9rdh˜Ä‡ž#GŽY³fÁ¬@gîܹ;wîœ2eŠ $÷+\¸°åVÍ.CœnÛ¶Mrˆ5¯S§Ï…€I/–4å)@ÓŒ$’Ï3ʺ`ߎiö¬SÏjrÜâÓü’œçµa½ )šÇúLõêÕõ%˜I–DrÌÍ–ÈTpÛ¶mõI½·­x]2T_Õ1/„ÏyÍ'‘q7A3¤(g¤ó¯víÚâó™ã娱§®¬ƒƒç* äéý{ ÈjeÌ7•}—¹²ñQ¾«ìUÏø~…¿—Cå—meÛJmï¤C[¤wD¶ìþá@÷ηºÏ¹ªÜk)IϳËÝ{¬<š‘c½¢Ë˜3à²wYÓW-cywLHkßHw·ËîÈ”üsL«€³-w‡¶e÷–ëÑàz"„ïúøŸ ~˜Njö-ËæËÊèbTîfÙrSqT¡Ô½@A¾£x¬}ãr÷¶¸ÜA˳ÏYï ŒaÏG=!ÐXxD"ȳ¸’š|äåˆfæ*L¬x o{éb©Ë7n pÅðÕÐ×®]ûâ‹/cud”ÇÉ“'±à>Ä'Chã~eΜYÀ‡'‡dI›6탸Jd¬¹th›IId$ºçã?Þºu+.£ EK“&vþxP‚ 0Ó›6mŠ?¾ zP†ï/9”%£D‰"Ã|ìè’ígÄÏŸ?®BzØ+6¡‡xÙ0±Òù·jšE@€¼.]º 0gΜ‰È½téRêÔ©çÏŸ?bÄÓ± iQ7nÜ»wïš5k-ZT¹reð$\‚»wïŽèÙ±cÇ’%KЭeÊ”Aß8Ÿ$uñâE1j}ÅŠÜșÇ“Ï7P9Á]¸páâÅ‹ÉØÔ©S‘Õ… 2»$Ð<yT£F¦M›âz™w©Y³&ÒG’"ÍñãÇ/]º”Çkdu›6mdfàkŽs¨F߸¼É…ÿˆà‡~ÄhÑ¢!lá-à•,Y²V­ZaÚpÔðÉ ‡ˆƒ´ÔçÎØÅ‹‚¾–^F˜ˆFê 3®Ê0ËQ&Õ)R¤-Z ·ì¥Û9¿ÿ~ )\DÏ_~ùå£GêÕ«Å8Šá¬k]{ÓrªV­J¶(ä ØnÑõ <}ú´,% ò’…òµÓ¬]ý‚žß ‰¼²i¶Ïú®ÊÿxÓLÁɪԠñ›o¾á·N:õë×—™Ë¦D%rÔ¨Qñ1¸À„]¹r;Ø»wï1cÆ`Q»Êî[&‘O?ý”K€»ÿþcÇŽ•EsLÓŒ˜d™3“'OÖØVl=‘'Nœˆõöì™eRûé§Ÿ@á°aæ”ÓqãÆ•oݺ…q'oä'!Ð^¦gÒ¤I 6„ªÿ²Ã!C uËè ÷,ŸW2ÍVðYæ]ޝˆ¾á%}r˱NÜȹ#oÿ‘¦Yš/ÞÕ… úöí;`À€Î;CØ8QëhGÓ%ïØÐ`Óá!¬0^¦ 0räȰ&'&L(]9ùòåƒ;e\¾#Do¿~ýz,¦ŒSç>+iÊr†d[µU+=ès¿»ÈÛåË—cÇŽ-óJ9ƸïÞ½›DˆÆ BÕzŒÖkÚM(p§ÙàáðûóÏ?ÓÌ2fÌX¢D Å¿)V(ÿÀÈ:»wà…r9wTøW1¢×H!ÝüªvÙ3N ½Dì„ öìÙƒÿb¨EÙRªzõêæÚmR wîÜ™>}:îݸqã¨fà‹½Ãb )t¬IZ’pråÊ•$IÇ¢†.{+€iÓ¦ç/Ož<ÀFtÙc) dÌ«Ìk¦:GU¬X1žGâ5«—H²ð®LH•*Uþüù»uëvÿþý¯¾úŠD7n,Ÿžwî܉ËH"ØqÓó]‘> Ðr÷Ñ[8¦ Ç”WÆî—-[Öü#P€›Ÿ|Û1ç+P„³N 8}X®„Å€p¡Òa¨Ôîõ’Þê—¡„;uìØ1\öœ&H8ê öúYè †ûú믉ƒkXªT)°Âl Çp®\¹rrX¤®rƒÏÔÎ0(éT©R T›˜c©)ÎS@ ½ðˆù.–»ŸÊd„_nÁ%Õôø» Cë}„$|öÙgÿ¼ªÿk÷:×’y*^Ì ‹¼ï5wˆj=Æõâ¯ZßàP0L3f"<$j%Oouž°u…jÜOýõ''5”‚~ìc+d/öoñ%¯W#ò2ÖÙ<œAåË—/T¨P5póÛµk'0jÔ¨‘çä_jI¸oß>¸ªˆÖ¯_O"²2|¦¹Ô¥KèZhêÞS Èë ,?ÿü3élÙ²| *ˆsæÌòÀ½8ˆß|óÍèÑ£i²|¨Ùµ©ÜÂkˆ Qö×dÔ1(—˜ˆwø ï­C‡'Nœ—ÇG‰›µâÃ.û.sýR2®B =J(¨íÑlò|cµê0¢"㫹÷'Ó€Òk…ýÿ¹ F Táþá /©ŽíU*¨O²á›Ñ¡­¦ƒb‰ŽUæBõ1BEË[+RdÛ¡Y³f0DÇŽ9˜d³f“$2Ò4@`â,Þ½{ßh̘1RÅ‹‡œ€rÕªUÁ+P†Ÿ ,èòØ&W`‹3fD°üG3¸råŠx]U"¿:p.œK}ž*þ?~aE‹åšÚ[ExO YáJ™”]Hìàü÷29 éüÛ^Qt¼‰Ò+Åȸ|GÌ@{Ò±tD‹æP6³Š?'úå*'9¶ÜC¦¦Y~‰ Ü]ÙÒ)MLYû:нŒŽ,eäX\Б” «Ñ“ äXv<¤ävɆåžÌàÛ†¼‘à²FÀùß‚ü¹Ë§Tø¸êæTÝfX_–èX¢rÈÞmpù‚ÚzÇnΆ£mz-QÑßS/ƒø/Ò»Wæ5÷î;/¢ÄÇ-ïFPþ†û·ý°˜2©Qî„”ñÌá§êÑöÒobFvÒªàãò媙”Ëýñ#нæ“ÄÔŸõ§siO/yÖÙp/v#æ[hOçDÔŒ9¯þõŠ0”ä† xxñü³K×Þ‹ƒ©U÷îÙ ¢-À_§ÊªhUõEI%&=ZlPçN€8‘ï„ðÞ»á#½û@|)bø€¬aÍ]‡©¸1ÕÃûªn.5zƒJùÙ›ÕÎ~XûFÙâw*K–,ÖÙ³gã#–(QŸÏü&«ëwpÛ¶m¸ƒ/^¼ø·ß~C¦p¯t/Ï;÷?þ(W®\âĉ'OžŒ"Aÿbv¾‹€`íÚµ¨×ï¾ûîòåËsæÌÁ­ÄÓ‹Šà^½zåÄq7±×æV•:)e€œ2eÊgŸ}öùçŸ?zôˆcÜA%’ºxñ"zýÄit€NËüðÎÝ{OU :Þ¹~3H8Oíe¸±Õ³§jP •8¥Ê^X¾°põölP™óÁØŠ$©,[h?}bÿ60|DÕ±ŠŠÛê97 ^<*\œ7Ù}ó’rÄ«[ý2zÅ<–_\û¡C‡.[¶,R¤H˜ –Bç{<éqü»uëvôèQŽýõW"ƒB¸GˆŠšÍ3fœ8q"šàÚµk›7o®àcèŶ~øá‡`˜ò/"âçË—OÙÖ=AfÚ·oOc€Y±­p­,lbêéÐFÊÀ²T5ˆt?~¬lß1bĈkÖ¬¡UäÎ[ÆO:½•*U* hJÅw™ûÐî4m×"|8W€ÞzÄm.èpAßöÞu… zUÇ ž> Š.¼kl5®·jÚSÕl/²Nýÿ·– úp!¥±á…+«wéxIUü¤AKÀ?×ËzÕ†Þö"LÚâE¨'êÝ£GÞ½{ËÇTØ…8Ô½Ë$‹ÄÆÞAŸÔn„ µ"¡‚Asüøñ7ña2Y;ZW¹NÊe¯ç ™i,ì… grU 9²±bÅÂpkcjzœRÐåX¼LP§Ž?þ‡~ÀýUš L­=Q]°Ö¿¨Ñ]n”¸d”µë‹’ÖæŸƒFå¼øSeÌ©°°ÏŸºF¶µfS±c¨Õ±=®Ó)Ê  ²ÚvÁ¸?û³t·E7oÞsE‹`]~qcBÍÒYkJÿ×_”LÀkäЪY*uïÞ½K—.…ç°³²^öQÆTk hþÊÆÊ  ¸@-24áœ2eJˆ Ö$©aâa¸1cÆÀU ÜaDbã®Ý½{wݺu}úôPɶ”ÏÄ÷gÏžuèÐüà•š£ËtR–=z Ô‚cQñ¹—§ÐTÈÕÉ“'Ï;‡kAó Ã2âõõŠƒ4¦ p¼@ªÿõâÏ¿l<ýõ⯶/˜½°•(…ëÈ6õçŸ*ËW*ñÇêñCuù´Šh/9%’ºrVýõ,è;rI¶=@ü—‹ÔÖí?wÿêîlLï>Î‘í£ ¶ õ¾Î›oÕG”ÏÇ ç½÷ÞÃG@˜Hh#ȋ鱮&+ >FE]â"#fÎœÉømøsÔnË–-Œ¡oÖ¬YìØ±GŒÂpï’&Mj®}#†ü¦OŸŽ€LáF¬0x0`@›6mºví ˜ÈÆûï¿ß·o_¤F¢D‰ºwïþÅ_äÉ“Ç|wËî`_±bÅùóçyÚ8I6h4*'2‡·‹ØhË–öš>¢¤£Çãüóó‰ î W¶z¥J+w–d*¢½8þ +áQqlƒ†mÙh%ˆ¤KFôPÉÒªüåU×™VùTAfg±Ë4+rì?·ÃÿðnP´pï<~$b¢Fz÷a䮄‡QÓÞ ¯qï¡^2ƒºo0šÔ½yÞì"Ñb£cÆŒ‰•Ô,Òºuk9C0"I‰ÔªäÀ2>¦)÷0}H@¹»o59oÞ¼t42î#K:)Óà·hѢŊÓÏ*P €²‡·8p@(œhdŒ»*T¨ Ücª}×ÄË—¡åžÅh¬™!þëó?_„S®ß¯ÞÝ~øbøhïùˆÏþLñèý šÊß[‡¶«3G‚TH…Z*[A×ãªmiõàŽŠ‰á;UT#ÖT)š%‡uÕ5¶}‡˜<]øÿkï=À¤(¶÷ÿê™Ù̲€HDPIW"ˆzI$@‚Š’T DE D…‹ŠJ*ˆ¢  d$ç°Ë¦éþºÏNÙÛ3» º{ï÷ÿüúñY›žîêꪷÞsÞ §úO(Q,>33’… {òÝ5*.F_^¯þÞת¨ÿ ]™¡½'<ïÒ‡nv2eÚm€d¸Eº‹Ý|#lÞ碲Bk“õý²ÆÙ Ú€z²ä¶†Ê¨DN9ç°œ)rÑÚßÔš>?7Q)˜2e é7lذfÍš'N”¦U»ö•<‘4³2ƒÁ,[¬¨Ì`¦cš­ Õ¬± T¯æêÖûTû64ƒÙFjÕ*!QJ3.¾R%=ÿÐÖógõU¾Ãöt›õ›ÕGI§zÍUv ¶=Â5«Ñª1-ãèM,ìU|Rš2¶¡±èžê1CrƒãsmΨ“R¡hL:@¼Š48i¹fí¡…f(œ’å à®BŠ$¯Ña”6Ö*Ô*|®:nquý-ZÄ7œxàxÃ-Z´€˜%*NvfÖþcX¶„PiŸ“²*žn©óÕÌ”ßÖ·û¢}Ñv‡â¾jæ Õ©»ñØ$ûzfŠYù kù"ž±fªÖ5Bë] §TT¨$ÆÅ›\Ã,箚u3=›‡= ‘Oºy%«[ùƱ•Ú//òÂýj]¯  õ9fTCçàÁƒ( °F±ž:uªJ•*Rß.ÿä~¼I®lÚ´ ],ÖÊÞ*:†¤ gÖ鬥Òé„‘ä–ùüݤ¤$=ÞcáeÊtµ_ý…îVñá©åUá.h–@~ÿùϪU«F àŠ/Ö?Q¢xRvV¶èyCŠQYèúŽ/-úc÷#.Æ.‹ÔŒ›W´Í•öÓ£TÕZêî>FvfVff0®¨5l¦@+ÿ¯«2Fäkpcê‘cÒ4­slà{¤ŸNËZÑQ¸éÏfÞÒ)3)ÅpæÏ†[6¯‡£ØÿÂÈ å…8@4lݺÑ ªP»·ß~;"CO¦×72<¿¯¾újÙ²eGEI u‘Ê©’… ¢Zð#Q'h wß}`‘`¿~ýÌP<$âH`Šß¶gÏyä‘G€Î–-[Ú¶m+sйÔ+ìR½zõ:uêtíÚµuëÖd)¯}4cñ-<µmÛ6©]»6Ú,P3ݺu{ë­·@!Xìß¿ÿ¥—^úľßãí·ß^·n ‰OЦ *:šÊÍÈʶ2²²2£l¿-#+=3‹‚ývý®½[ª˜€m[¥U¿¨ÌžÃ'o~ì=#: RüŸ½LM®÷j–Ëöbž~Çwt_våZþãGƒŽk(Ȇ ¡XªÉ’¥ ÿ°ŽjîLsùÂìñŸA FV–•ÇL•/íaÒ³4¾ÈÙXg÷¹pÚ½{wŸ>}öíÛhÞÿ}ÀçºpÛ j®U«VT¤Ì“¯›7oæœGøuòäÉ#FŒ¸ä’KÐo¼ñƬY³`Y€uÏ=÷Hè-÷7óOÜÁ©S§’òâ–[n3fÌ?þ(3º§M›F"U«V•ÈÞ¼Tz ÝYò‚dï‡~à$P^¹r%DË£Á_yå•x ~ýú½{÷†#b>©M™k"ügÏžM³üý÷ß¡ÃéÓ§ÃÇpyb‘²4¬S“[ÖMO? ÆÅÅÇÆDÑì‹%ÄíñÅF¦ìÌ`r±"ÑqE6n=ª¢yÈå;q:KE-–ò|‰ET©ó’Ì J*îÏàvÓÊÌ‚¸l§ótV &®H|tÜ“ýKgZJÆn\;öA5`’ŠŽÍùÿ…‰± pçÎå/¿ü ÒÒÒºwïNå¹}yêëÁz-ZäeddÈŽ÷2ÅFˆªV­Z¤sà 7€éÊ•+S÷×\s çV ®•¯¨(PÜ\:P8iÒ¤ùóçÃ(ÂRh^¸ö믿æWH–ݾ}»Þ*ÕóEÚåfäBÏž=Oœ8޹Ÿl@ŠPøªU«bccAáã?Î×I Ïdb,Ä\ã-ÑÑу ‚ãy)mR9Ÿ\©Lÿ™Î¤0!sÇvl„)úÂÊÆ«6ƒ¾Ø€ DÀ­ f”#Cüþp©€ýò%ã6úìhÑ„ 5´o‡‘i¯Š±²‚ÖÞ*3ÃðG¹—âë/ÊŸs€x–^`DoælÜD÷¹"`:ÿüóûöíûæ›ovìØ‘r„-0»:u¢™akÑ!?YÙ©œˆ3²?­ü‚1Ó8vx`O<ñÄÏ?ÿ|äÈ‘²eËÊ®fFX¡ð¬ÌïÇ÷èÑ£Q£FC† ¡•3sÅ¿¬W¯@ýÜ©cu†‚°;o¾~øá’%K E¸óE4ì5í.sæÌ5jÚ–+Z¾œ±H#¾W|Dé0ÒG»víäDJ/˜€ÊÞð#)pY]#+Ó‡ìPŽÈu4‡Òý‘–`OfÎ|ÄÔô¬ÃÇRýöòèœL–NŠ_>®½ã¦Ûã˾@tÀ ¼¢&.QãQÓ_5šß¤žû•-Ãùè•|®vÈJªI“&Ÿ~ú)„´|ùòE‹a 1—²¿¡î=Ño‡iæÍ›‡‚ù®»îºÏ?ÿü·ß~£^e#zß»w/ð‚Ö®]»k×®˜˜îtwhKR8õ˜3™ÜÀ –lРÁŒ3xüþûï?~<–½qãÆPõâÅ‹Ñ.ß|óľQ‘(Šë²Ëf]ô 6ºiÓ¦¸ª$Âá Œ9’ï…u·¶ÎϹ1¢ ¹úÓdƸê‹°× ÆÁ=±Ý¦²³ÌW¾ðU¬nÚ¬ï @|[L; ª>I‡™VR‘Ø9Ë6v{jN ¹HÎ4°„˜ Óº•JJ°ïŒÿs–mÜbãÍÇ&Õ몦mìE-A'tSü2¢åDá7n(Ä>Ê<í^½z‰Ûç¡CÃѤTö«¯¾ŠEæ†råÊáxQîß~û-X¼ï¾ûð8eÁ )s‘{dg”p:ä'„´Äãh2€Kÿ¡H@N!MðA“ˆRæqhØÂ+¼@ü”)SV¬X!‘9GÏ"VÖ¯_/¿qð»Ä¥ýû“äܳŠïÏY·29ëÈ~5¢³up=¿¡ßÍjÔ,£ú•‡§f9¥â¢m±räÔÉÓ> ÓäïE¦Úà/#=33#Ër€è ø3³ƒŸ®Ü’–žéP¹affW¯\ºz…d3;›«ëÛª¸D{Ñ´,6Íù£¥Pû5Àa:„:¼…C¦òˆ2#Û(ë$*’r¦ðhn¸öÚkUhQŸ«Üìb:{8”„ƒç:ÖYTÂñXÎ.¸:"™;Òƒ»däºÎ!âfàÀ\‘žmÉ >ƒ¼4¯‘ô¼ 3¼#©÷ŠeÚ‹ä×­P_-R)IÊ0~Ûf-œ¡.©û|×&ÇNf('”H0=«ö¥åN¦eâ*ÊsÙÀ1Ã>ágŸÏïóe;V;*àKÏ̾kÄÜS{ò…É>rj@¿ŸíÒÌ;_¹ÄðoÕó)ëÎ ‰¡iˆ%/¨¸¯ªXÑ'2!J÷ŒHäLŸ+”Œ~»L»wÿÊ W@• ÅO7B]Ür¸ßn„†%´{§ûüpüåH×Ú#7×’õÜZÁ  …ÝD*׸‘йÌuÐŽ¦Û(Ÿ³XñÔe.Ã-Q&×Ýf ›êÙÅ&¿6|ŽrlC-O­ß~°|¹dåZ4‚ 1q1¶^± ¸_z!8†‘”{:5.g¬9;0¬ì˜/¨WÙJùÅÞƒjÓ;‡Ts÷v­iÎ IqèÊ£nPy²rO˜šaaŠ :~¿ä¸È²#´K‚ZïZ%&Þó:]…9^TÐr›¬s¯4ð –Dlr’ h+ß"´ÇlWNˆQ÷¯á0úç§ÃÙ»ky¿™zBeeí®Êš!Z·ï£¥\\>yã›Ý­Ð€+óÅÚÙÇӲΎi>êˆmþãi[Ú œ³MyêÄ®·&¢K1Ç4ë­WK7½76Ù‰VN/T±¢B ï~óæÍ²{|RRR—.]pò:wî,0rßL-¾þúëhtR@¥" Ú´iï¨ †röÕAëàùH„‚N '3÷_ï‚ô=z4Ú'’+ëÖ­Ãwüøã%´R8‡©PË!Kè$Ù=$ë°ẍ ÇE;}ûʾÜ1Ä"–³:gF)cVVVjkˆJ%dfqÉðœ(L¡¤rv¹÷{ûê[Ô­’8¬­/Æ^~ c¢Åb‚6õ+U‘(cá`ݬN¾Ÿ Æ(+Ã÷Ŭ†Éáfù/2báøˆf(‚Ñ_|ñÚk¯q2aÈêÐvøÀ“·;vtìØOîúë¯GçYPX³fͶmÛ¶kן¯gÏžT6mÙ²¥»„»ež!WÈ`„<çŸP,ME‚ÉŠ–ˆŠ“0yò䯾úêé§ŸþðÃÁ"-ñŽß™’’BKCôðýû÷—pnO9ç•Ï|î”C¾ ŠœI23œ\欓Æçv{Žž<½xõ6a0ûOÐlX³B­*¥ùÏ~jzfFfPeó¤i;ŽÙ(t0Ìâ± ÎÂ+Cef Ì-;}3ÿæu½°gßÎ ïÑ£G_~ùeþ"Sà||ε–´\}=çwÞÝwßýÞ{ïQÁ²µØ˜1cn¸áᕽ{÷Êê',éÍ7ߌÂ9r$'Vh"FÔY•]xá…¤õrömÑ¢oôÌÄñp˜¬*„Œ{ôèjŸ{î9ñÁ1´ Ÿ|òIÒéÕ«×úõëg|¤¨ ¯î¥ë¾ùa›°G™ýÊW*9qè}×¸ë °|¾íûŽß=ð=%¤˜Ž§ÍœÐ±íuÕ3²²y†£=ŒØèÀ²1í0Ñ‹ìÌìòe’~Ýy}voådK Ìݦ&b= ùˆžÇÜÿŒxž× áG>I!x ëÕ«Gmýúë¯@ªC‡:‚çK¨o‘cÇŽ…„žxâ ùIw8cÓ!W’Û¨r÷´wÜÃÓÙÄÀp6èÔ©Ó?ü™ÝxãæŸ+4ruhË‚¬ 6ð!Þ›o¾9|øpr›BÌ‹-ºë®»àiÚÆ¼yó¦N:tèPwÆÎX¤á7„çÜó“4‘ Vmyç­eªDÛRžÎ(uáyOµ¿V¹0a8ç Í_4N€h¬(ÃÃ>Ñ$¹ðtYÅÏ{³·9K[ gJXN’v&”7ðÝYwßœ¥ñhDÎÆ:»Ï…Æ®¹æ¬ØÒ¥Kq­ž}öÙ:uêàÛáù‰wè!]hiÕªUÔ7v|É’%kÖ¬¸( T»vmø²ÄS¬X±âŒ3„\þùçË.»Ì »3ѺÉK?þý÷ßÿöÛoàn“ 5põŒÜÍói8‚ä,nݺ•“Õ«W—+W»,¹Aƒäm“sHg“r¹g,Òðò±znÖLDý–(â/g›Éبä¢qáMÇù³ô¹•G]ì€è©òJû ÷W…Ž|r˜×ñ_P͸b#FŒˆ˜ZéM,UªÔéÓ§!3ݽ¢BøÔ"Ömýúõ g—-[†ûõÙgŸ¡r°ÚT0l:lØ0*¾lÙ²ü庬}6‡p@D©ÀÊ`Q|€_|QúÉ#:!rñ‚ .€†¿üòË5jÜtÓMS¦LÁ(c©A3Š 'lËð úI¹:¢ ô c6t HGÔßLÐç2¸~gh¦x‘ØFWT4b¢,‡­Œl4:íåsö|D©¿ÊΡBr®½:÷³¢©ÃÓG²T¨PG¨~¹‚ãè¾Á³ 0â_9„§IM¿Hzmš5k&í;â|D+Ô­s‘sp(‹#XÇ9$KC…õÝäcó*À¼ü ÷{ù¨áË@O¤geÙËM,•žu:#g‡Tíö˜¡P8NÜDûÁ ²£t†ZýkÞUïˆÈÔŒ¯×îðÅŘÎXŽ•‘u"-Ó¹'BU^PÉå#F¼£€m ÀŸ,Ü”îA#4N屃–³N@â¹[¡mryJê^Ǿ‘¾C=äªû õ{­P‡‹ M·B³ù奜ãÞ‰?*ÏBÆœçÓ‰h…:çå[Š9‡$+½Ü²M®46wïæß/FÏשܦùüäÄ*•Kûm”¨¬`¥r%<¯–æjåJ¬{£»¤"j¢|ébÊñó»ü…oÍ?KÖëoüÕ£°}D•ã&ûÝ¥'Ù ›.ÅêîgÖ‡nÜî­qõüP#÷,Mžµ:$ í‹›«h\Ýìî÷çXΜwï·4-݃­µ\âñïûˆV¨[@…@fçsX‡k‡u¸N:Õí™áÊò¤ %ƒ'yi˜ ±ó™s¿†£!ÙýóŸ9ç¶ZA3k…b_—2wÂxæ•ùˆGa‡¥‹†W÷é§ŸV«V wžÒüî»ïм—_~yø{þ¹cÇ\®V­ZA- ,8vìXË–-1Ù’ÔâÅ‹wïÞ}Ûm·•(Q÷nãÆÜÃU7¾y¾}ûv$ŽTäG}„e—.k¨ mËß¶mÛ"Ж3]µjU]Ùž/ÒÓ+QQUªT‘`Šò®ùóç_|ñÅ| öšdqj¯¿þzAdDáL³ž?a8«ýexI!7 gÍŠ}SÛ‚K÷“寍|xÀ€ݺuC¯PU”ÝwÞ‰ˆ²TÀM{öì7nÜ_|qûí·Oš4éСCµjÕB ¼ýöÛÔÄ´iÓ~ùåpÙ»woøK/½„pÖQft"‚Â-[¶¼ð À òÏéÓ§óRþ)Ã?þx™2eåСCÑ¿¤ ¶0µÊe…à üðêN:!ü9ÂSüôñÇ?ýôÓ| îÛ·o»víhHÈšK.¹Ä=ÞsÎŒh9+V)Ù³g/Z´ˆ“o¿ý¶R¥J¤/¡vl^´!f‡Æ³œNh¥|“ò†~ŸsjXÙÇTÖAeøCCu>]ÞW±Ì·þ$u› ]¸'à·NWß/VÍÚ*gJÎ_cD}äÅyÛîÙ¢û\ÂRj¡Q£F¥K—~ùå—Û´iƒ‰H¢ð•úÇPôˆY¨6Cò+ÿþûï$+KU¸³OŸ>ʵߘN“¢©¹áÑGåŸ<[·nZz€.\Ñ(´xîÔÊ L,ãÖ­[y»(â9sæD˜ òôXg‰CÓ¢ñÈÐQDÝsöen„ú´1»|Ž|#äÝ´iÓ;î¸Ã—³#¿;fÑooš Üô–¯’p –öÚÖÑò³ÃS"õU•÷stÇÿv>ÄžëíЭ=‰ÖpvpõYéÆÐ{Õ¢O¬QS–÷+ËM¥ùö#znrןÖãoäîäËãºÈÜKK:qâæŒê™9sæ”)SP»ÔVUfÙ¸Ó7 ,¨iéÕ…ÞSÎ!P£PÒÒÒV¯^-ó·}Îv÷á¹2B;LIàŽÑ£GwïÞ]—e€¸}ûö<òÈe—]&û5ëoœcĈ¢iÜ_2m&,Õ;–üëžj^D"22É—r`Ó¦Mp§ŽØžš» <è.|A Ob$Sh˜”Ï>ûŒæM1ÂÍ™ÙVððÜØŒÏbƒÑ¶á 3£j¦'Üã3L—{—«šrÎí(v~•g9(³i0˜LÌ8yZY¨ÉñQäQ~Ó:%Ö9”ïSééûvD=ÛÓZþ©*]ÔÿhvRÊéÚMm³Î 1±VV¦rGxrÁ¬°Wñ PsTsãÆAÕäÉ“k×®ýå—_òa2âgY^ýé:@ +ŒñÅR™ÒBMôïßÿçèÚµëyç'sñUÛÛFÛ‹Ü >s‚Üçì È!+ 8š7o^´hÑ_|1Ÿvl8Z:äsxP6ß“û}NÐ0òÉO??2ÿ¦{‡lÞöàƒÊŽÁ8©w¶m_4xúuø•¨@Šm2ƒGT±ö*áe[j¿käÃëÚ̆m…›¨SŸÛ„jD©”î6:ÿ R¿Wþ"ö#™‡UÕg•¯ ¢$sZ ‹Ž5öü¦¾ùÔÞqßõÕôV·n]lÁ^¤óÐC)R”áï…çI$‚Å7`̘1\GRð(#½eþÄ•W^I"â¶æ¶FÒäEˆ$¸:ÖB®x™ ž§¸¡çÌF\"}ÅHþi–€1‡(ù|X ešÙfì¥F±[ÔÑw•­êY‰-p÷Œô-JÏž†ýÅTTY/dK`´EšXÇ?4JÙýóÊ Z‰7ÅîRÇ?±Y³ÜpK%Ô­jlz¯ó˜#Mëü”D+ôÕ¿ÑB©lXgÜØÎœœe?l<Ý^=`ÍŸ®üQꡱ*&N…éŸèVˆú ¥&û-jgË3ÉrõÒ™Œ-¶ˆsp‚3¤BÓ`A¡Ç‘þd¹ßÝÿ''ú1dâ°òR*R9ýÒ²õ•ô?“=9Ñ]›áN›|Ãy Ã–³‚Æ9”¡KÚÝÃ]2çæ#*§Ü{コ´Ý rбT/u|¾ žT¥zçL£9ð‚:2Ë‚ÕðÍ“ªÒl]‘l)YEjÈ|E% $¹vZ%\aXA»·ú؇†<È߬ÝFÚO ñ—W-—«µÛf=3SMl¼7F‰SO¶3Æ|¢*^lÍxÁøn¹º ”ÁûíiªF}…ˆáÜï׌/, Õl„æèNl½—¶¬Еí& Q] fh§\}EÖ[I›®m ܳ­ÜIùB;[iON¹ú· g ·îËt‡Pʧ|¡píV("VÙ*4 âIêoªf9—­Ð²K¯â³‡Ü‚VìžĦ*c³J¼Þ0³•P%P'Û)ÓT‘&ªH•}Xm»×°N;†1h{„ß¶i’W$6†õlþ^F”ƒx`¬Š¾@™éÖîÇŒª‹M¸Ó̲TÎæöVÐYHµþ{ó—Œ"Ålq×VõÎ ¾A¯í³vl0¾øØˆñ«©îqº»Ôr¦Ÿ9Tà¦Y‚ ç)S¦ vìØ1”.ùã?¶mÛ&=ÛnŠõÔ–rª‚Ù²e‹lÔ½råJÈ…¡'ôÿú믘Ýzõê©ÜbE£#Ž-“@9\ܼy3Ý?þÈmØeþ._¾郓>-MV¨ëÅSÁ9ä 9D,sNRØz’âÒÛ’Ó=Ëò”÷€MîEg½AÕÉ=Uð°Ì“±ÿ;6ÇÑ+ÑʯÒ׫Sߨ¢$î2uh² ”´A™ÜYE•vî±lÿÒ_ÜF§¥ûÛv9xŠ5J´Ä0¥á XOŸ ªÄ¢¾¤b*ý~©¬R¥ùÙ,–ì5ÛÞÖ¯áM¹"ȇ†^¬JÈ¡œÙ³g?õÔSh‹råÊá#¢š§OŸŽ/µpáB 3cÆŒ‰'zƈ=¯@{Ž1bÅŠxèx{¥K—Æ-ÃMlß¾=¿¾üòËè€>þüaÆY¡C'âñ ÷îÝËy 2¿"F‘!ˆ?ûì3ÀZ½zõ©S§Þ~ûíˆbJŽXœôéÓ§Fo¾ù&¶’‚¨P'<… AC!fÑ[ýúõ»êª«<«­ÿj™‡x䟜¾»4ãR…RVŽ1Ó•™fbœRm8¬´UV „Í…ØÜÓ?ª¬}*ºœ+¡€cÖƒV ¸*ý¨ÚÕKùŠZ)Ý”3ÿ 2û3˜»£À÷žÈ:˜bEm—Ïw*êDà"0feYѱjÜB;YXÜ™aG 8~X z’]& C¬¯ŒŒ DeóæÍqqq:ìÒ¥Ë÷ß_¾|yêp¸Wׇbˆ{öì ’àEÔîÕW_^ž7o^‡¨`ª¹W¯^'NœhÓ¦•Çßâ½èÁ?<¸xñbR€AÅqDaðOpÉ=£G¾ä’KxÑòú(#4eÿþýO<ñ7ïÛ·‹|)rН£½áŒ‚?’Z»v-­H©éþcGŽšåX½èœ.šÅê|@Œ‚ õÕéu**Þfĸ+@¡JûY¥®T¾¸5bUÒmvNò}êà«*þ הּrmÕ§ÉkÆü’õ|Ú=*Ñ飉 –ú¹Ì¶ çàD'“-Ó0Åøb¶û0´Z´PíX§Æ|ªŠ%¸¨]l\NŸ>]Ò9^xál_çÎQÄC‡ÅÚúB‘ #¾BD†ìYÂm€fÍš5¯¿þºtÙpC:udÀM:J¬°°tÉÉÉHo }B‡p° MV AlРAPãå—_>a„իW‹œ×Y ÷z9jÖ¬Iþq6h| ¶˜+¸ /½ôÒ]wÝÕ²eËáÇS‰Àô÷GV"rØO9£Ë9?‰²-ÕW}ß°2Tð„Jn¯®´ÁTªŸqlž2OÛ½†¥ûɃÆîþ*ë°-zŠ·1ö WY»l žµÇØÖA•¸×(z=FÜÊj_VºÊNQN˜`m/ ñå¬FP¡=”²³Œg»«å UŵáÕ£‰» 0Q“•$vJ‚&©¡=zP[Ôw«V­¨U«V…ÉÙƒ/çÎ ¾±õ²Ìù+Ù<ÃõW9{äUTŠÍjûŸ·‰0¥§ã«e«èóU‰vjïSªÌ SY™*¾¦Jé¡M±}DIû:ã;î}REWPéi¶½>o¨tþ9¸e:ëVD|Ëa9Áàî©R®DÖk¨k4ïùj@ËÕ‰(…ˆñÅgÏÌÌ„Š5j„-ã*x!#/7s¿766öºë®8‚ÿú׿0²xc¨8 W‹|äÈŒ/éG4X¨õ-ZYY$ÊëÀ NO`Ø‹œ Æ=ˆzòÑ5íþ",iNš4 lÀvo½õþp§màu¤¤¤[š_»víðC¤‡ÅÓ•±œ#¾ÎS¤ú~O–lªól:“„§ì‰«1*Å(ýJlæèVC¦Ø1U•ù@ÓIņ¯}¥ØmÖá7ìÛ4 _´ÅùÎ.*ª‚‘ÜÁÙF(ÍÚÑÙÈÚcùP?8£ Öų-#“Ð @òV"16P¢ˆ/ÞŽ ŠR+V$Öþ i<0º¸®jt›´§gDý¸Ì©ÇkŠsH'œ‡”«“OÔf7<tk||<÷ËVÎúžmŒ2Då~µlæ5ÇÖóвeËêgeç[Ý%ÎaÊ5ÇGrˆ}ôQA›\‘Çå¥îÙàÉˆŽ =¹L¥ÿeÇ.4R ÃZìVŸ3•Ë,-û9Ø=&Ê<åËØl⺦´u>•­b*)_´Éí}Xðè²vŸŸ0-Ó—ÔJí@í!>x«k63¶øü ¸˜–¿‚£C¤‘2?WŽ ½ç‹é :Ý›ASâÂÛ5ƒ>P˜Ó•ã+¤ö†3*%=ÒºÏÙ3/!"#êsŸlSÛn÷\ktýR÷ÔkåâÙÁJ+Ɇ¬a•¤ôìyW>a“ô¹¼W[.Êø¡r­vuFL-¯~™ð×åÓƒ#ÿ² ÐÂ9â ¯DzÖoë’b-Tñ»¬K‹‘x©*ÙÝž;h¸6h¶O²U ÉHé­v?lwhgí5P03g’ΟãoöáÌó.v“|¤Í|¾8£Ô#jk§+ÕJécñé™ifZffL†='3ûäi;¶ol´?&&Êe3bLvL”crú"üú…ÁˆR¸ÿÏ?ÿ<Æ«R¥J8‹¬R¥ ®=ŽšD%”¥ìîšv§Æuœ9ôÍñãǹ³qãÆÜ|øða®Hß!ÎÙ´iÓ¸íÖ[omРÖ’Ž >ÜÖ­[Ÿ}öY ‚ [ß½{w4ж}<ðÀÃ?Œ‚Æïä×^½zé½Â9^¬íêÕ«çÌ™c8±oð¡C„”ô“?ôÐCóçÏç]%J” )÷â›dDËéÿCùž\ª‚'¬’]qí`!¾€ÃpFÎm;Z%îR‡'«Œ­*¦Š•ÜN9œD”ÎYè¡IÚöÐ3_±hcU´©u|J¸Ú(Ñšû*Ÿ—\犊þ¢ñö²ª¬`ÉäÄlÓú`h›ÌÌlÝâbíVj¯b°•¤i3¢pŠÇÕ ÷Ž=×Ý5‘µ?.ÿÄ&nذÁ»ÿ~HY0dÈjîûï¿ÇQ>|xÓ¦M"²äB§ ß+«ø€M —,YB½N:•ÛH 5€A™â&vêÔé‹/¾pgO,;*øÀȰB"ãÇ_¶lmcôèѲàcîܹóæÍC^¤Ïĉ‘烖%yî/Ò¥AÖ$òdÇI>ÙN–áxŸ|éK/½ôâ‹/¾þúë={ö”±u·^‰XΞt~¸¡È}Tæ·]´" } W0«x[Ë—áÁ±CІ?Á‚/1Ðe‡˜rgTÚrÌw®nÉ‚t{&vð´-‹Kv7Ž¢’;ŒlËÌîݪ.ÿym6”‰„¶"™?ÅMa˜fù JDÞV­ZäíÛ·¦¹ÿþûQÔÙ5×\#³TܽÐ:5©<ð7lØ0n¾þúë%2 yÆ9®ºêª=z€õ§žzêî»ïÖ¶Xÿò /äˆÖ³r¶0—ég›7o†¤¹"$åÌV¼ë®»tŸK¸³A®vìØü'‘Ý»wó,!EÀ™™ Fk×®-h?üðƒåá%±0ó2ÍÊÕ®å¼ä0g^gž}„eî1"6¼ü’\.Lp$µVI¨âm ñœÑ—<«ÞrÖ˜©Æ¶;í¡ç¨²êÀKæÁñF¥ÙVt9SOé§ì((ÙF J}2ݺ¤®ªTNòö=8ˆs^U­»©€=SHŒ('z•õë×C$O>ù$ÐDT¬ &üöÛozÉœ;ºÖ©ïfÍšÕ«W4hí>ú¨yóæW\qƒ‰ *`j!NÙÕ'¼OaKé9³VhÆç#FŒ€•#¸Ö!KÆôèp8EÉ $ݱcÇ÷Þ{øÂ Üï>þøãmÛ¶åqn—Û·o—­ŸõSç̈:3¼šIÉÈî­89^„|3-p ö§Îú’ÜÃåº\,¢ªø–ò'99ðI靖¹sh;{VTI#±©µw¤SA¥oT%;«˜ò2Ö’ ¿Ð(ühš1´‹Y¥ªzò £fC333Æ2¡¿1eœÚ°F=4.:.®À‡øô—c³þøã={ölÚ´©oß¾O<ñ^ÔÊ•+Ë—/h¶mÛF±æõ I”ÔªU —¿jÕ*@ÉS0W ¹çž{®~ýúMš4©Ïㆽ°nÙ²®Bá~ûí·+V¬¸òÊ+iä¡C‡xŸ¤ qâÛIRø V9"QëÐ*ϲ0=mŒÇ¹ššÐ!ï"'7ÝtŽì¿þõ¯³¡ÃüË\s!e…o›ûå—_~ýõ׳fÍ‘d¸eË–9S4l›à¬âS±âîE` K¢4Xö Š´Ä;týè¹÷ÏSÇ_´{Ì´‚Çr;ÅžBkx‚tÊ.÷Lò=ÛÓ*™¤öïT=š©1óþ¸¨Yƒn/÷G©Šû?O ~7~ÌÀÛ i†6~áÒ¥K/¹äØ‚ê§È~ùåPاOñæ›o4ȳ®Ù}ˆêjèn–“²eËŽ37‘s`ôá'Îa ~r[yA4ùÍ7ß@{ðè¨Q£¨EZe×®]±ø…'d/\¸ê$eDìÑtZÎXÜŒ>øz¾÷Þ{É «ð™8…°59ÈÁ¢H±$ö4*švæ¥çwàEâÎ:…–³Œ:îȹâZ§—¿0ÏiEv|í£tµó!uÞ #¶rŽ­wÏ«ð;ˤ‹—R±ñ¶5Ž1ýѾ¥)·=N¨¨’¶4OR‡Ž¦¥gø40ÍI÷ÜsOÄÛtGw„Ñ*W"ÍÞ½{Ëuäl`/}s¿~ýôSº#Z…º‚¡.½Ë.Gÿþýå¤K—.&ÇS²ZÍš5õmîŽqO!ˆ•Ä]õÿþ÷¿É¤½ñNèÐ]èîš|$sþen…FáÝk¯½ »Cº˜Š‚Ö.«&p[¹Í^hç¸CžŠp¾‘Ç ŽÊ >ó`H|»;ðÖxÿ«é6Á'œбÀÍy™*˜eùÓc¶¬IOS1ÅmÂ<~8}놱Qþ •m +3 ø,3XàËIu¡KŸ³Ï9ô"9—JŠ81Ö]g–ÓW¬‚¼úê«¥”uRz»‘ð¤ôãÝFæ„K·3OÉ^’ F§#/òåÌ]—Žã8ð_u&-×(*4…ÛS2çà#º gþؾ¿}ðu¸V(|taºßûÑù(kT™ðWõ9Ÿhdï³2¶F”’)d*ZEÕP×¶4L·öíR™™VݦÆe YYþ€ßYEm¯¿RÎ6§…Ѩ½l=à¡-”{H-S<„¡‹‰tWœÜìîL– /8w)ëmþ©Ïu7¡a£ÓqïZž+]+ ţøz²¤]OÓÊ«0Ã_çF?¦×ßtÁ‚uêÔA«!öiQä„"Õkµ”ãläSMÿÄq¹S¦-tNüªöÜ£ü‰Î…T{òbò"uQ-5z–êÖX5hªžÿˆæ»çHƱÓ2I–¬Ž¥f­Â[*`8ÑèÇÝ»w¯]»å EinݺM¶ÌÍ'*xݺu(ièkÖ¬Ù·oŸ ™ˆæýý÷ß±Y¢‹Ã— à&îÚµ •Í•Ï?ÿœœpn†V½à³bÚÈ Rpþùçc£óO×ßµdÉêD ÌŠŒwôèÑåË—W«VÏ¿éþÕ"‹øÊdµG”†¿B;Ú-ÑM¨@ÑþËÍN9cyXþÒ†•©Œ(ËÌPQÕѽÇ}²yoš‘ÒÏȪ|xV㺕zÝVgàýUÈã²Ò3ëV+SàýˆVèxï½÷ðëßxã À‡cwë­·2¤qãÆ€Õ­[·iÓ¦i+ãI_Æ0æÏŸÎ(Ý»wG3Š1¢>¨ ”5¶€Î+¯¼‚ÿ'/Õò–¿Hf4 8æ6´F Ñ veïc„Ð!o@©F™={ö 7ÜpçwêHž\I­<œ!ÅxX$K´ŠñãÇO™2JZ´h„êÛ·ïUW]¥õJ>ÂÙS€‹”F‹@Ö?iïÂà_‘…ž±êC]½†æIee9÷Ù#ÊVð†÷³µ»6­Ù¦’“ÕÞýêð‰è„˜AÿŽyæþ&žœ†jö9ñÍ©TîÉ“'qçÇŽ{ÁPO@ºÇµúðÃϘ€Cr³,:¡>¨Z®ËøÇܹsA!Â}ª«A¹Œª€EpðáãgžyfÆ èå¶mÛ*'*è)W®Ìú / ¡~ýõWñö"ÂÐp9Œ~ß}÷]zé¥2!®_¿¸KÞ稙Ÿ~ú‰/ůýû¤¨ ´Š>ùä“@ß u…5^& ㎡§…téÒeï޽ǎûàƒ~øá¼CüÀŠ˜P¹aôwLsÄ;Ã/–i–리곸šªRWšež°E|ʲ·e±£æúmLfý†:™–Ùjèl‡í9ŒfjÆ£í¯+¤øˆÀè?ÿùÌAÂaxi˜3 ´ì|‹u«R¥Š(#w'…»ÅÃ7ï¼ó>X¤¦q’0ÄA‰žß‰w¸lÙ2üÎJ•*…o“ËðÈÀ…êp¡Æ^½z1‚¿={öågñ©§ž‘ÀÑÊÝ ¥s%j bÅŠ7ÝtÎî©S§Ð |Q¿~ýp7:Äg–.]zàÀ$2nÜ8\[Ïž#æUÈyÕEa1¢r¬sÐòÅ«÷ÙÑ JÜ£œ¹‡IEb’’|Ñ©Ê0­øb ±™YÁÏ¿û-gQ½¿KjÛ// ±¢$Õ¯_añ¢Àr’‹ðŒ(¦\Š,â»n¾ùf·°éT­çÑ£GË>â5jÔxñÅAàP¹=w9¶÷Þ{/FЀ{p Ab¯?ýôSÌ4ê¤zõêxø¬xÛ·oçW «)ļ õ-ó·wïÞ€>†ùfÍše9=¦ü‚äKç͛ܭÐlpQ(#æu’׳í"1œ¡j#é6{q´?ÞîâVƇO·¶g0™iÅT±âëÆFûK‹*o:;Ùû»˜fLÀ_à“´n•­™õ!Þrõ ÝË<ïÒP.%œƒ›q cbb,gM»íð(t‡ÑÏšN|D‰#{âél‘Ø FУB{D šõ¬-+¬ Zç{pp•ÓI‰ç ¿B:óJ;‡.CwÁzþé.gwz^çÁ‡çOùG¬Ó¼n‹ˆÏ[<™÷æP¶¢ŠJQÅntæòØýÅœŽÌÄN:µ*-”ÖŸ9)¤måŠârX²îŸÌPTOÑH²$%"#üÕž²ÓI¹W¬N϶G'…×ç‹"º[ÛÝ„dír­Y‰˜Z^Ö0/›±Ä<=ÈËë¶¼Ò9cÕGÎaηÛvW ¶³D!çW.H¤žÌ“§T¶É?‚H˜©YÙ…!V¬Ð̹ßÿN"+PL3WÔжmÛ¤¿×ÊíÎë% ¦l?‹û]É÷Ë (Œµ?¡B*T¨ Â”ê彘c®lݺc]¬X1!cÑ.œËªÝ»w#_dòv^K$<ˆi&MIJ¯ÅÁ"£„øuË–-Héc/L±’×I^ÏþmÓì^M-kÃÞÕ>¬ÆÇÆü뺆¶”NOõÇ'Ó2.(“\àŒ('w¦råÊ@SøÈ#ÈT?~:u*µˆC¢zR°œ8Çx~_:ZgüøñT*÷#M¨ïµk×¢0ðädÀæ“O>Á¼¢Ç|ðAýjêÐæÕH={ö ´Ñ¸ä Ô A~EK!t8A’“14/èïСC^kŒÐ’(2°ÿ~’套]v™l'=sæLœ×Õ«WóF˜r×®]}úôÑ ·U#æu’׳ÿ#ºþ*}=íËLuV&Ø»÷ZQåÎ+^nÉ M¶ÐÎÎRöjè EaLŒõ9q|ÛµkøÚ·o¿dÉ@Ó¦M›””” &,4ž~ÄÊæÁo¼ñÇäW ‹@nݺ5ª…¿xf¯½öÚÝz»s<ÿüó²M)“=ÙÀÌÍHZмP$ÈgÄ2…H|Μ9xœè!ˆpòäÉp-yS!Íîqü-Z´H–Ýà[Îà2/^L{€¿W­ZõñÇÓi3h|÷¢Rëÿ>¢'‡ÎkïÓÆÉÏ”¯˜=÷1ë€Qî¹ìè‡ý¶XøÒˆ*nGpŒ®Z´f×7Í’9Ùó Ö¡¦AÃ_|AÅïܹûxÑEHeäÈ‘2B¯Ÿ5¨åðŸÌp9vìvÊá"¦ |%n6f´Q£FGDq—.]<ž¨áÌ‘áYà ’ÄuP¸aÆ… >œA>ÃŒ3j×®ÍuðzV¬X¸o¹å3ÀÄýÕr–Ý»wïìÙ³¿ÿþ{šyà†·Þz þ3f ¦¹^½zݺu#ç=zô°Âú# Ô4ŸñùW蹘fÏÇeT¥úÚáýE••aÅ\¢R:ÿq0ýŠáÍ‚YWÓ”ýHëSÆ+ƒ÷RX:éGüùçŸ_~ùe €…Lï¾û.v­_¿~-Z´hÕªÖŠÔ²C$'…1¦p †zÅ)4œ¥Ä8…ÀH6À¸ƒrѽ0Þ÷ËÜúuëÖMš4iܸqzQýàÁƒi=‘×2óðÃË®-á–TÒD‰“«ž={Ö¨QƒOìƒão¾ù†ñ±Ï<óÌöíÛÁå|ð /ðÕîÎX¤gc^ÏÆ¸ç_eõJD»9²P0±©J¸Z¥­¶Ìt«tOŸ¯„}šÊ£æ8Çí—øypÕO]ÊŽÆn“àNœ8ÎÃÅ¤â‘Ø½zõ2dçMš4)Uªj·Y³fëׯG>C™à)ÜÝ1œ¯¯T©ÒرcSSSi2sꂉ›6m «Áˆ›œE]sÍ5œ‡iP!ÈñÜsÏ}þùçÐ'숳ˆ5— *´1¼¾kåÊ•8 —_~¹Š}óO1¢áŠÇ,W"Mý×ÑÆ"ß´£íÄÕ°›¨èh ´ZÐïsV?Q<ýRΡ/J<$åôWt¶3Š(†O"ôËGVuNà-$°–ýQLgCZ÷{Ý©I…!¨=[éÊñØcn–_¥.7n¬\±o"Ö1Xd®ÐHÊ EÒ…òRÉ¿Èu¦Öû—y;Í“B£â]z;ªÿ•Ã&EgkªóG«¨²väŸ šFÆÉh•-c u"út¦¯À(åB•HÜåL}…ÂR²‘¬Lloy:*ášÚÖ’¬Ä3A#,ûFþ¢KTh•´ê‰,]º´ØY™ô%â¥kɽÝF^Ÿf9ƒ+ÒfôEIM¾T÷Sþã‘rƒ†ñ7Y˜w¤îÿ DZïñ§H}'d²=üœ\ÄÝ1MÖ¼ÚkõÓ3®©Vˆ»“ÊÀƒîè0z A^±o‡UBßhÉ }Å3`ãù #4§ßó"hñl“«Ï=ë¬Ã Ä ­ÐŽšNÊÊ'Ÿò9Q¿nçÎ8 ø£˜Ë%ÌÝÛ—†—ê9_ÉßÇÍçYûÿNèØâEûßò:µRùíÈÞ1¥U¹‡ C5‹µB¦à ¢3Сh©`ñÌðÒpÈðóòš}#勬A“"{ñ|ðAt7Véí·ßF[Ü}÷ÝåÊ•CqåÎ;ï¬\¹²»Z…ª¶lÙÒ§OŸ;vÌœ9É‚›h8[ bÈþ ’[ê•_…#ºDzR>–‘l 8PÊ}íÚµ}ôQùòå;vìˆÌš5k/âÜÞ:9¹_âÝsžqb¡J¨_aš¦0Ä ù¸ä’KrÊUK­PUøy«V­âÊÂ… ‘z?öðDä{ @ålcšew§  ùóçׯ_†êÔ©³gÏÒ¯è{Õyàæ*Uª <üA¨îꫯÆ;pâ6 ¤N:•¤à]D.·?žûóÊ’LhX²d “@€€<ÈÊÎ<ßCQ2ù(毢èÉ3È»š4i"›gɼ8é‘u{Dÿ+‡uJúÒŒ½È8þ‘[#*}‰Š»¼0VñÉJªdÀ€íÚµkÞ¼9¥F#ž>}ú¸qã F4ѧ»½9i”åË—ƒ!½7ôvá…’ÔÊ•+»téB•Pëáf„Æ…SSS%}Ï<{ù•\‘Ú‘#GpÀå‰'¤F#BH| .|ÿý÷1и4rˆh€É ‰àu¼ùæ›#œC¶jû§ VY¥X¤Yj…gÞ*¾¿hší]ÖjÏHuz­u¾:6Ç:±H]ü]/°—âØ¿?ª[·nÔ1ÖjÓ¦MõêÕ5jԠᦤ¤È¾ÌžÜ§‡g¨WΩo*û³Ï>Ã;lÙ²å+¯¼°ÑTÿÅ_ ×zæB á6íÛ·OV¡ã~ýõ×ÕªU;tè\;tèPœWœBª³G8š¼KÆo<_ä)%Ú.N'­")) 2Æñ€Jif¤Á@ÞP/¯Ó~x-zÊ9¯×…BŠW¹Ã<{²^žá•X(@t‚,úbÕyOªmwÛK§¬ UæiUº0fß Ø¢xñâTaëÖ­1UÐpùꫯ`‹Q£FÁ”z[ //‡6¯q>ì™gžéÞ½;¢‹}ÇçC[ 6°MC† Á¹'JYìÚµ }BœÜóÆo@xâ,’V­Z¼>ø†Þ¾};žèeu‡¶'WRè4!\¹‚‡ š'L˜Ð¹sgDOñi|2™úÒÅ1€qÄ /„ˆWäuˆ‹|”PÄúʧBÏXõg“ðg­œ½6ìq¿Ö©eö«â÷€Î7ÍRôW:‡¾èî|FÿʉéŠ1nØ7ÜpË-· ß9¬PÈxåêß1CQìÿ¥À!EËÞ’²dWU¼µ3èç+¤mrMW0w\Fà¢û~=n‡;BO?³–zôB¹Âü‡g[w¹ƒ6â’+­–pa%Wú©pQ’ m¥&! ô#Ô¿ífÖˆy;›2/ð¿úS>·åwæÏ=‡øT…—)uåì–[àbE…¦l U½zu<³mÛ¶>|˜_±†™Pãžê΃NDŽ?Žwùå—û\›Žàü•+WŽ{ø ïé­ÂÆšµk/³t%ŠPÃs“-±NŸ>$—›ÑCXÿ¼¢¹ÿ¹fÍ쾬+æñÓO?!td‡òà#JSÄÇÃË9üuž+® Ísb¬§šþ7ÄJÎ5{˜…ë¾(‘üZð¡‹…«€ÈàÁƒeÆÔ€(‘ ¡iÓ¦Q‹¨„§žzÊóUúÜ mÇŒ:¦‚?þøcR3]=?þø#útÊ”)èñ2eÊÌš5˳M®§Èx/Ä­[·‚l²¤œ]|¿ùæÀ$ †WàömܸQïž^î'OžìÛ·/­,¢Z®ºê*ÚHàÕÉâh®su¯R&#–§§š<<NçîÐMª0L³X+Ù°¸C‡Ë–-ƒ*åË—?~<2PZ.Eþ 1ÊÓ§OÊM›6Kþ¢ 䀮½óÎ;aG )´ä.mJ g:mÇŽùç“O>ùè£ MþòË/õëׯQ£Æ¸qãRSS.\5r.=Ãͺą•IªQ£F`wóæÍ¤€„ß»wïĉßyç4bwVÆÜûì³|K÷îÝI¹Zµj˜þE‹‘‰a‚ sp<霱HÃo/ð|~Ò×󯲿z%ÿO8‡ÊQÁÜE‘P»2ózÀtRCï¾û.ötРA²EÊÚµke©@ø©øøøxèðÖ[o½þúëup.RÓ iùòåøŽ˜ÔæÍ›ƒ*fæÓ¦ÿJú‹/&z@ì™gžyë­·p !EòtHpÁ‚ÿþ÷¿=+J•‹;†?€§òF…›AK㟇"ÙJÎ2ª)‹¯Ý<ýÿÑ}ø¦,(ðÕW_Å~͘1jœ4iRÆ AÞ{ï½÷É'Ÿ@‡ÔŸthkÚpËaá¶®]»ÊÜ;°²téRð!a¹B:Ä·ß~ûÒK/Å㔑•»gÇçÚ#ÓÈ ¯Ã€Þÿý`?9Á¸c”Á¦V[y_Ø.’[¾,ânâ¼’™›nº zÆûÄ}üòË/ùöbÅŠÁ”W^y¥{á³;5O9»óì)|ϼÏ=îòׯÄS§áÕ¤ÏÃ1ày‹>ÿ„¿šCÏ+ iÒÕùQ÷–Íòµ×^£òîºë®””H$áÑK]pá9®[·. —*øá‡‡Nj<(©x$Ô%›ö<ðÀ20bST!–ÇŒ#U…¡GsÐHh2wF9qÃȰꈲLÚ7£i­X±üñ:ñÞ{ï…h±Ô·Ür‹¬?2diižÞÍÿw¸âÓ¯·š—C‚œbõ*w‡Kø»„\Ë:‡å„>’ˆr’ŽÜs¡sˆWäFÛ¡ zèP®<þøãà‰Äeý”¬SÖ¾f¸W$×ù+c'4ÇsÊógtÃóœ×•|*"\«æu[^霱êÿf=× iÃ=²¢rÛ}Qž¥!_¡£\”.:3~I…ºÇ­°Ålît W¿ƒê©&)+÷°<".]>^.wÉ¿,ÊŸØtm¬B°p›Ë³)Ò¿ã…‹ô3¦p–Wþ¯úˆòO+iýrbö8*ÌgrãI…ºcÜçÊ5:läÞg%ܳñ0eÄ‚s{‡á.‘ûÓÜôés-¤w·]˜í#ªÐ@ÑÿñÿЬVÔË>8IEND®B`‚PKÒiñD´)Çuå-ê content.xmlí]QsÛ¸~ï¯È覂Aã›»Üô©×‡Þu¦¯´HËìQ¢JÒ±Ý__€¤d¬"‹rœt:Mfl/  ða±»Ø]\ýø¸)?|Î릨¶g‚ñÙ‡|»ª²b»þ8ûÇï™ëÙ׺ªno‹U¾ÌªÕý&ß¶óUµmÍïæém³ìï~œÝ×Ûe•6E³Ü¦›¼Y¶«eµË·û§–néeWW¥iŸÊ‹ï »O·ùc{éö,y6½¹¼æ®°ûtV§—>lËšNu¿­.}ø±)ç·•éõÍ.m‹£V<–Åö³»¶Ý-‹‡‡ö²ª^/D’$‹îî¡Á«C¹Ý}]v¥²Õ"/s[Y³L,öe7y›^Ú>[ÖmÒö~s“×wMÚ¦_Œjóy}1">¯_èšÕ]Z_Œ®0Þ0»|xÃÌ}v“¶w/Œ‰^üjnv?~ýë3êÍ¥uÙ²¤«Vu±»ø3ûÒîóUUšjè'h×Ü€s¹èi§ôÃÙâuÑæµS|u¶ø*-W‡¯6§:Í” Sbž¶0=ßvDóÂÁ¢¿}(Üd/¾úŸ¿þõ·Õ]¾IŸ ã…çŶiÓísÏÔv^üÒhQ绪ns{9Ã4£Úv×nÊ—§»½»/º®³ìdQÓœpa¦¾™xóÏEþðá‡çñ,ºBdŠ}BðEWÈåšcØ2‡ @žY|½>¬B·ÕýÖ|‚Y¹†îËwy]Ø[iÙ=¶$op1_V_ñÊaårÞ@Øy‘—{~qø¤“¯©ªù¦1ð1Ó¤Ú-§éêPo/{Uv{üÆ#6±jš°=…†ßÿ¾°÷ævq4ì¨É ‚Ùõ^HïÛÊð¶b5ï8vs}Õóúå÷W?ôÜܶõãìo|vTèÃ@mŠm×äµy.+ÖEk沘-®¯'Þy}Õ¿ôË Vwb6\¸M7Eùd/Y¼íY×éîδxgú&¯Û"o>X|™WÕÕæÛj›wÕ:5œ«.8_]G¹•õ ‹í¹ù®jжëÕ¶¾ï*}E?˜…n¸bÖ»²2 ì7¡ý?ëKße¹/{¸p(š+ûÿ¹N;Ün…·•Œ‘íšâ?¦FÁwíá;÷—ç†W¥Û—nÚ5¸Ì‡Û¯èÐð«:´yÚÜTå¼}Ú™—p9îÛUyŸåó»"Ëòí|•—¥×mZ6ùì̈¸·úŽp/·už¶ó|³kŸöo,ÖÛª>¨‹õ];O·ë2ÏæécÞ<ô¥!OwÆp±cÙÝŸsgzí§¬hveú4/Ó›¼¤ßSVëÔ¬Öw›bEotœäsZÎ7é¿,xÄásËr3_ë|Õ÷ÛЧhm„Í|~czéã¾~,§¯ùY迌ówÞhªñ"ýûÿq9õêEáìç]Zmü­á@Øœ}(›÷—f´@ÿ¾æß÷é3;n=™ÕF8 ¢ÕæèÞ]nYÖáæ×¯H‡J¸¶•œèL#íGZZšžï-*ÏÖù¼¶‚Xg„ˆþüÑ¥§æ¬H7Õ6{Ÿã|{+~Ry¾ó½Œs2í8§u]=̳êaû^‡: ~z¨÷w¾—¡ümjDS•Eöí„ô‹›ýzíç’&_¶hñ¢F8ܸ©²§ѵéúªÇi÷³kÅ€0¡öøÚc7±:Ûåò®Îo?ÎÛÓýÔi ;Ì{ä¯Ê´i†/_6«´í,PéÌO«+î[QÅs›}p GÊ·ÃXZ$…,ã}Mš‡ÝõÈð°ÑÊÓm¯3ß™¯8]`{²¿±++£+¡ìdA3½ÁÙ*óÚèøYVçöC»ËóV°ŸÄrøë—DšËYì6–3E{zßá ƒø¸ÓcI[rh`UUËUUÕfÒ¦­™¿ùÚ|Û¡OX˜§Áx$káLé/†V±‹ž/=Ë Ùw“]ø¸ï¿¾Cvu±Ië§ùãén•c¯{:ýº§Ó¯‹(Y×Ev²ˆ:Â^§)uÓä¹û—4yí¬ä5ñþ5Fۺϛù™Ñþù0Ú?'ǵSäïëÍÌÌ,¶¯ÁÑO‰zîÉnyÛUF °½¨ÐL“>Ô¨¯áT3å0ÍTwM5‘·fãî~P+1J[䌇„?Eà§ÆÐ‡BjÚÕî˜nÏ©¤Éq’f@}¾˜Ï—A‚&¸˜FÞDíTxÁELÜæbœ×-¸ØÉ´Óq–š(—6/¨PdëTÁvNcbpŒa>_!KµÊR“`´âG®0BY‘+L®ÐdÓJíY½O„„O„Óð ôƒ‰„Hf nÇ*p#Y-ê5b~‰Æ¬/ L„ ‹¦L˜¥=ŽÈJã¢û¸+iZÅûÑKÕ$ziˆ²òÀµs˜þˉÀÞ‘ šBêUŽyU¾1AÆ$;c°ÝLOÈÐ$ÌOã¢ü8 Eç¨Pté:Ê@&`Ö@?˜ˆ &b&D® ƒ²óH&… 9l‹ªqY»¥P¨ˆ*"*\I0íÃI¦ˆ¢&ÑBFüEß=*̬%{Œ(›œÝ Oˆ"ÂQúÛûÐD`^Ø~pák½·¦¤ò&ˆ/)FLÞ“X¼QẒ»–ÂÙ¢L³ I¬iG¢pầ°Iü6Q°q!é…Äùè¹› ÃÆ4È@íõxB†"ÀP0\P—^œG¯$ò’“—@¨¨èt‘¸½h(ÔhwG” #HÔiG¢dÎÈœ#«" (oeO¨HHv¢™œˆfyßp²ï7bcG¡5‡=¡Âµ}xG}M’Î:…ŒˆÈ#¶2`1ê!‰†²$ªc"] ±j’÷P¥=|‹tébßÞI÷;À…‘ÿˆ0ˆãLÔ3p$mî[ä "^L‚ XŒº/TXs5^àl4z<Ù} ‰ïÈn& °(õX9l·#aš‚DS@åzòM)ž(šl$Òõ{@…7?}›4—ª$¨øEölÕØž- °8u_؈IVŒË}Çi¬:Z¶¢È•=£iŠT"×iµ#aº±DÂ2 &Y³PëpDÒhDcY4@ȈPÑêÞ‘0—9Y „Œ€8íw$ nê§h,ó ¨¸uoÈ0l= ’,¾6$n3ù¿¬› 4r N3Ü[µ¢p²¢Àâ9ɯ֑(¾ý’iNŒ€Él±aNÔ…³Ól»(*’2IM”1 åé bWÞ@EVƒŠ0iƒÓÈ(O×ߤ§a¸ðâ·m³­%_ÙÇ]+Ó¹PØ@ù|zĆ‘.ÈÖ Çí(ºp)Xô@Bì#ÙšPØ@Eµ{Å 5Âåû#y ,dD|¹¢‰|¹PÞ Q w׺§a–Ä]QF¢<ß²¢$tEAyD$éd4–s… TŒ„Glh&Û€åÀ猦fq\šÖz4«5 éîfŒ* 2)úËÎù)“ŸPè€ImRG1 ê. VMrdÀvR<æÝ Hv iü»Pý~Ñ¡ˆ·°‚y kr>¡;žð«ëyÙ·lÛ'649b@ãNà4ï?‡åáz€¨ÈÌ€'±p=)‡ 8ËG2¼Eêð—e‡ìµMsʂ㚦ÇN ûztÆ]é×’(Þ! ó˜æ€]Ô>³gî!éiÙ—÷SÓŒö—Ñþµû¾(„ ¢ãE"C7²g¸“KÉþeú*MM÷ê9±’Nņò•ò‹ Ãäc‘ ó 02¢kñ°$Êâñ*OX>Pqò~ñ!)>$š„ v$nuyEÔ 1*b>ÔZ¹ûUÃج&Þ{Ì{ÏãéJ ̇Ԛ}i|ï4^¤1*zÞ/B894¨#A5…ôtÅvºâûˆYywzÅGBÄ‚&ØLÓÔ¦ŽòŽ£Š&ÊPÃbéã8è9Îýÿˆh†/”w½5‡ÅDÖá°(Ùˆäá˜&~•¿Ì7BÙ,S°½2MD “t$É!!ÇrH Ð‹®÷‹Ž˜œ¬#OV£a‘0? Al§#繠Њ°£HJrºóp§ ú9ËÈk¼ÙӟƺŽÊ0á!Š˜×̺®É vž‚d:vW=I4KŒŠ·÷Í?aÇ ãt›„ؤ˜ˆÇ2L ðŠº7ʬë§3\Aõ¦"gòÁü½eyò‡FÅÞ{ƇMLL† —aÒu¢íH˜nKlcÓdí@íLûÆGBÒ@%¸,PqPëHÿPd÷všS2`ž»¡ „ÑfÉ\.á8õCå¹²NꚈ ¨oRÄä§Æ,~(ŒÀ¢ñ½c$ §$¸Sh’ 1šeã«kÒ$êEOõË é#š‘îÄù’L”–‰2"+g4¶r¢ð‹Î×2Œ$9Az %‰p’+ ¶_P?¡¶#±˜ dd¿…XŒ¾o„€ìÔIÜN&¶²…$Äs2óœD!©ï!Š8+˜ÿ°&Nkæ³ö>vêPš|*ó„œ4½¿„ÂG@ŽDÃÉ:î@vPHɉüÈP;þñU7Æiºœ¤èH! d |$¨È}ïøÐ$bUÃNbóyŽ“«.EcÚ !0OÛ8Œ¥Ðôêý5˜žKx?*£‰…[QG¢0"Iº‘cF`þ1¢XLäÔnµ&GŠj؉¢fa!‘ü#Þ;(„À<¯ü#$IêìŽÕ¤‰ÿ|G‚ú1¡{ÈÉD{ÿ¨¼±Ô¡"çU®Ál"4à5vTô@ÙÇÃt¿IL#“ ,«Ó $¢Ù `™ ÉA§wÒ)gœúUòIl#(_ð)P¢ˆ'³‚y2k¢shØ$J”|}ŽÜã+ ½d¨Õ]Z·Ëî§¡†êÈ›*{z¦²ju¿É·í|Um[óûú¿PKÒiñDå¨á¸Ây styles.xml•Ûrã †ï÷)2îµCog&‡»>@§ûc‡)FÀ‡¾}Ç.î&)ÓK[ß/ Iˆýi¨ÅªcJs‡$[o’“ .«Còïõ9}JNÇ?{(KN.€¶5“&ÕæC0½²b©ñh<$­’ˆæKR3 ÅÐ09‰pHcjüãÅÊ=ª L¬Ø± -y‹ìáP](ÒÇŠkkÊKˆZ¤%¤ê†þ-‹Apù~HÎÆ4¡¾ï×}¾U¡l·Û!o¦3×´Jxª ˆ æ‚i”­34±²­ß˜Š>1ä¿Î變îjWÝ8=Ý_/[”ñ-Ê‹EúTñ&:ôH‡z˜Ëíãàû¢o7›¿hüèþ.Þ+n˜ pz§Dй P_ËeÈ)ë\û'Z¹Cßôüˆk@™9‘2þâÛêlç±=›ZÜ[gÐJÅUÔ¦“#;ÂvøÒ޳þaq¯ïׇ<´³»Šlƒ<ÞþŸŽ™#؆|­*UÍË´„VÚ#Ø|)¦¸3áexá!œ1¿pyÙÀ‡Å…Ó:7תýú‚œ-uKÔ®‰‹—àíØ&Çé¡ßtÜ£ë/ÇñPKÒiñD“±]Ÿ#Lmeta.xml’AOƒ0Çï~ Rw¥.[,ñài‰M¼-µ}cUhI)cßQdA³íû½þík¶=×UpBÛ*£sÓˆ¨…‘J—9y}y ×d[ÜeæpP™4¢«Q»°FǃK«nÙXÊIg53¼U-ӼƖ9ÁLƒzjasšù qç\)ý™“£s èûžöKjl ñf³_P)®\ÓÙÊSRV8$´Ó&v0ü¯ÔÀΕŒ1× ¥}\E)Œë‰.­”Õ­ \Ø%\ ¹ãáIa?uˆ#·îÏ8‘àûÁf#JH1Íc/2¯_¢F˱ÅN½[|ö¤4¡4Yì”îÎû·õj¿Jƒ°o¬ù@á M¢:Z 0x8E30679C This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ /* * This file implements way to output debugging information. It's supposed * to be slow but convenient functions. */ #include "common.h" #include #include #include #include #include #include #include #include /* pthread_self() */ #include "error.h" #include "pthreadex.h" /* pthread_*_shared() */ static int zero = 0; static int three = 3; static int *outputmethod = &zero; static int *debug = &zero; static int *quiet = &zero; static int *verbose = &three; pthread_mutex_t *error_mutex_p = NULL; static int printf_stderr(const char *fmt, ...) { va_list args; int rc; va_start(args, fmt); rc = vfprintf(stderr, fmt, args); va_end(args); return rc; } static int printf_stdout(const char *fmt, ...) { va_list args; int rc; va_start(args, fmt); rc = vfprintf(stdout, fmt, args); va_end(args); return rc; } static int vprintf_stderr(const char *fmt, va_list args) { return vfprintf(stderr, fmt, args); } static int vprintf_stdout(const char *fmt, va_list args) { return vfprintf(stdout, fmt, args); } static void flush_stderr(int level) { fprintf(stderr, "\n"); fflush(stderr); } static void flush_stdout(int level) { fprintf(stdout, "\n"); fflush(stdout); } static char _syslog_buffer[SYSLOG_BUFSIZ+1] = {0}; size_t _syslog_buffer_filled = 0; static int vsyslog_buf(const char *fmt, va_list args) { int len; size_t size; size = SYSLOG_BUFSIZ - _syslog_buffer_filled; #ifdef VERYPARANOID if ( ( size > SYSLOG_BUFSIZ) || (_syslog_buffer_filled + size > SYSLOG_BUFSIZ) || (_syslog_buffer_filled > SYSLOG_BUFSIZ) ) { fprintf(stderr, "Security problem while vsyslog_buf(): " "_syslog_buffer_filled == %lu; " "size == %lu; " "SYSLOG_BUFSIZ == "XTOSTR(SYSLOG_BUFSIZ)"\n", _syslog_buffer_filled, size); exit(ENOBUFS); } #endif if (!size) return 0; len = vsnprintf ( &_syslog_buffer[_syslog_buffer_filled], size, fmt, args ); if (len>0) { _syslog_buffer_filled += len; if (_syslog_buffer_filled > SYSLOG_BUFSIZ) _syslog_buffer_filled = SYSLOG_BUFSIZ; } return 0; } static int syslog_buf(const char *fmt, ...) { va_list args; int rc; va_start(args, fmt); rc = vsyslog_buf(fmt, args); va_end(args); return rc; } static void syslog_flush(int level) { syslog(level, "%s", _syslog_buffer); _syslog_buffer_filled = 0; } typedef int *( *outfunct_t)(const char *format, ...); typedef int *( *voutfunct_t)(const char *format, va_list ap); typedef void *(*flushfunct_t)(int level); static outfunct_t outfunct[] = { [OM_STDERR] = (outfunct_t)printf_stderr, [OM_STDOUT] = (outfunct_t)printf_stdout, [OM_SYSLOG] = (outfunct_t)syslog_buf, }; static voutfunct_t voutfunct[] = { [OM_STDERR] = (voutfunct_t)vprintf_stderr, [OM_STDOUT] = (voutfunct_t)vprintf_stdout, [OM_SYSLOG] = (voutfunct_t)vsyslog_buf, }; static flushfunct_t flushfunct[] = { [OM_STDERR] = (flushfunct_t)flush_stderr, [OM_STDOUT] = (flushfunct_t)flush_stdout, [OM_SYSLOG] = (flushfunct_t)syslog_flush, }; void _critical(const char *const function_name, const char *fmt, ...) { if (*quiet) return; struct timespec abs_time; clock_gettime(CLOCK_REALTIME , &abs_time); abs_time.tv_sec += 1; if (error_mutex_p != NULL) pthread_mutex_timedlock(error_mutex_p, &abs_time); outputmethod_t method = *outputmethod; { va_list args; pthread_t thread = pthread_self(); pid_t pid = getpid(); outfunct[method]("Critical (pid: %u; thread: %p): %s(): ", pid, thread, function_name); va_start(args, fmt); voutfunct[method](fmt, args); va_end(args); outfunct[method](" (current errno %i: %s)", errno, strerror(errno)); flushfunct[method](LOG_CRIT); } #ifdef BACKTRACE_SUPPORT { void *buf[BACKTRACE_LENGTH]; char **strings; int backtrace_len = backtrace((void **)buf, BACKTRACE_LENGTH); strings = backtrace_symbols(buf, backtrace_len); if (strings == NULL) { outfunct[method]("_critical(): Got error, but cannot print the backtrace. Current errno: %u: %s\n", errno, strerror(errno)); flushfunct[method](LOG_CRIT); pthread_mutex_unlock(error_mutex_p); exit(EXIT_FAILURE); } for (int j = 1; j < backtrace_len; j++) { outfunct[method](" %s", strings[j]); flushfunct[method](LOG_CRIT); } } #endif if (error_mutex_p != NULL) pthread_mutex_unlock(error_mutex_p); error_deinit(); exit(errno); return; } void _error(const char *const function_name, const char *fmt, ...) { va_list args; if (*quiet) return; if (*verbose < 1) return; if (error_mutex_p != NULL) pthread_mutex_reltimedlock(error_mutex_p, 0, OUTPUT_LOCK_TIMEOUT); pthread_t thread = pthread_self(); pid_t pid = getpid(); outputmethod_t method = *outputmethod; outfunct[method](*debug ? "Error (pid: %u; thread: %p): %s(): " : "Error: ", pid, thread, function_name); va_start(args, fmt); voutfunct[method](fmt, args); va_end(args); if (errno) outfunct[method](" (%i: %s)", errno, strerror(errno)); flushfunct[method](LOG_ERR); if (error_mutex_p != NULL) pthread_mutex_unlock(error_mutex_p); return; } void _info(const char *const function_name, const char *fmt, ...) { va_list args; if (*quiet) return; if (*verbose < 3) return; if (error_mutex_p != NULL) pthread_mutex_reltimedlock(error_mutex_p, 0, OUTPUT_LOCK_TIMEOUT); pthread_t thread = pthread_self(); pid_t pid = getpid(); outputmethod_t method = *outputmethod; outfunct[method](*debug ? "Info (pid: %u; thread: %p): %s(): " : "Info: ", pid, thread, function_name); va_start(args, fmt); voutfunct[method](fmt, args); va_end(args); flushfunct[method](LOG_INFO); if (error_mutex_p != NULL) pthread_mutex_unlock(error_mutex_p); return; } void _warning(const char *const function_name, const char *fmt, ...) { va_list args; if (*quiet) return; if (*verbose < 2) return; if (error_mutex_p != NULL) pthread_mutex_reltimedlock(error_mutex_p, 0, OUTPUT_LOCK_TIMEOUT); pthread_t thread = pthread_self(); pid_t pid = getpid(); outputmethod_t method = *outputmethod; outfunct[method](*debug ? "Warning (pid: %u; thread: %p): %s(): " : "Warning: ", pid, thread, function_name); va_start(args, fmt); voutfunct[method](fmt, args); va_end(args); flushfunct[method](LOG_WARNING); if (error_mutex_p != NULL) pthread_mutex_unlock(error_mutex_p); return; } #ifdef _DEBUG_SUPPORT void _debug(int debug_level, const char *const function_name, const char *fmt, ...) { va_list args; if (*quiet) return; if (debug_level > *debug) return; if (error_mutex_p != NULL) pthread_mutex_reltimedlock(error_mutex_p, 0, OUTPUT_LOCK_TIMEOUT); pthread_t thread = pthread_self(); pid_t pid = getpid(); outputmethod_t method = *outputmethod; outfunct[method]("Debug%u (pid: %u; thread: %p): %s(): ", debug_level, pid, thread, function_name); va_start(args, fmt); voutfunct[method](fmt, args); va_end(args); flushfunct[method](LOG_DEBUG); if (error_mutex_p != NULL) pthread_mutex_unlock(error_mutex_p); return; } #endif void error_init(void *_outputmethod, int *_quiet, int *_verbose, int *_debug) { outputmethod = _outputmethod; quiet = _quiet; verbose = _verbose; debug = _debug; openlog(NULL, SYSLOG_FLAGS, SYSLOG_FACILITY); return; } ipc_type_t ipc_type; void error_init_ipc(ipc_type_t _ipc_type) { static pthread_mutex_t error_mutex = PTHREAD_MUTEX_INITIALIZER; ipc_type = _ipc_type; switch (ipc_type) { case IPCT_SHARED: pthread_mutex_init_shared(&error_mutex_p); break; case IPCT_PRIVATE: error_mutex_p = &error_mutex; pthread_mutex_init(error_mutex_p, NULL); break; default: critical ("Unknown ipc_type: %i", ipc_type); } return; } void error_deinit() { switch (ipc_type) { case IPCT_SHARED: pthread_mutex_destroy_shared(error_mutex_p); error_mutex_p = NULL; break; case IPCT_PRIVATE: break; } return; } clsync-0.4.1/error.h000066400000000000000000000054541252417542300143260ustar00rootroot00000000000000/* clsync - file tree sync utility based on inotify/kqueue Copyright (C) 2013-2014 Dmitry Yu Okunev 0x8E30679C This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef __CLSYNC_ERROR_H #define __CLSYNC_ERROR_H #define BACKTRACE_LENGTH 256 #ifdef _DEBUG_FORCE # define DEBUGLEVEL_LIMIT 255 #else # define DEBUGLEVEL_LIMIT 9 #endif extern void _critical( const char *const function_name, const char *fmt, ...); #define critical(...) _critical(__FUNCTION__, __VA_ARGS__) #define critical_on(cond) {debug(30, "critical_on: checking: %s", TOSTR(cond)); if (unlikely(cond)) {critical("Assert: "TOSTR(cond));}} extern void _error(const char *const function_name, const char *fmt, ...); #define error(...) _error(__FUNCTION__, __VA_ARGS__) #define error_on(cond) {if (unlikely(cond)) {error("Error: ("TOSTR(cond)") != 0");}} extern void _warning(const char *const function_name, const char *fmt, ...); #define warning(...) _warning(__FUNCTION__, __VA_ARGS__) extern void _info(const char *const function_name, const char *fmt, ...); #define info(...) _info(__FUNCTION__, __VA_ARGS__) #ifdef _DEBUG_SUPPORT extern void _debug(int debug_level, const char *const function_name, const char *fmt, ...); # define debug(debug_level, ...) {if (debug_level < DEBUGLEVEL_LIMIT) _debug(debug_level, __FUNCTION__, __VA_ARGS__);} # define error_or_debug(debug_level, ...) ((debug_level)<0 ? _error(__FUNCTION__, __VA_ARGS__) : _debug(debug_level, __FUNCTION__, __VA_ARGS__)) #else # define debug(debug_level, ...) {} # define error_or_debug(debug_level, ...) ((debug_level)<0 ? _error(__FUNCTION__, __VA_ARGS__) : (void)0) #endif #define debug_call(debug_level, code) debug(debug_level, "%s -> %i", TOSTR(code), code) #define critical_or_warning(cond, ...) ((cond) ? _critical : _warning)(__FUNCTION__, __VA_ARGS__) enum ipc_type { IPCT_PRIVATE, IPCT_SHARED, }; typedef enum ipc_type ipc_type_t; extern void error_init(void *_outputmethod, int *_quiet, int *_verbose, int *_debug); extern void error_init_ipc(ipc_type_t ipc_type); extern void error_deinit(); enum outputmethod { OM_STDERR = 0, OM_STDOUT, OM_SYSLOG, OM_MAX }; typedef enum outputmethod outputmethod_t; #endif clsync-0.4.1/examples/000077500000000000000000000000001252417542300146325ustar00rootroot00000000000000clsync-0.4.1/examples/Makefile.am000066400000000000000000000021241252417542300166650ustar00rootroot00000000000000exampledir=$(docdir)/examples # example_DATA = \ # $(wildcard $(srcdir)/*.c) \ # $(wildcard $(srcdir)/*.sh) dist_example_DATA = clsync-synchandler-rsyncso.c clsync-synchandler-so.c dist_example_SCRIPTS = clsync-start-cluster.sh \ clsync-start-rsyncdirect.sh clsync-start-rsyncshell.sh \ clsync-start-rsyncso.sh clsync-start-so.sh \ clsync-synchandler-rsyncshell.sh # find production -type f -name '*.sh' nobase_dist_example_SCRIPTS = \ production/etc/clsync/hooks/lxc/exit-backup.sh \ production/etc/clsync/hooks/lxc/exit-brother.sh \ production/etc/clsync/synchandler/lxc/backup.sh \ production/etc/clsync/synchandler/lxc/brother.sh # find production -type f -not -name '*.sh' nobase_dist_example_DATA = production/etc/clsync/rules/hpc \ production/etc/clsync/rules/hpc-backup \ production/etc/clsync/rules/lxc \ production/etc/clsync/synchandler/hpc/handler-backup.c \ production/etc/clsync/synchandler/hpc/handler-pdcp.c \ production/etc/clsync/synchandler/hpc/Makefile \ production/etc/clsync/synchandler/lxc/rsync.exclude \ production/etc/clsync/clsync.conf clsync-0.4.1/examples/clsync-start-cluster.sh000077500000000000000000000012121252417542300212720ustar00rootroot00000000000000#!/bin/sh echo "Is not implemented, yet!" >&2 exit 1 IFACE="$1" if [ "$IFACE" = "" ]; then echo "syntax: $0 " >&2 echo "example: $0 eth0" >&2 exit 1 fi IPADDR=$(ip a s "$IFACE" | awk '{if($1=="inet") {gsub("/.*", "", $2); print $2}}') if [ "$IPADDR" = "" ]; then echo "Interface \"$IFACE\" doesn't exists or there's no IP-addresses assigned to it." >&2 exit 2 fi mkdir -m 700 -p testdir/from testdir/to testdir/listdir cat > rules < rules < rules < rules < rules < rules < #include // Required header: #include #include // Optional headers: #include #include #include static struct ctx *ctx_p = NULL; static struct indexes *indexes_p = NULL; static const char *argv[11] = {NULL}; // Optional function, you can erase it. int clsyncapi_init(struct ctx *_ctx_p, struct indexes *_indexes_p) { debug(1, "Hello world!"); ctx_p = _ctx_p; indexes_p = _indexes_p; if (ctx_p->destdir == NULL) { error("dest-dir is not set."); return EINVAL; } if (ctx_p->flags[RSYNCPREFERINCLUDE]) { error("clsync-synchandler-rsyncso.so cannot be used in conjunction with \"--rsync-prefer-include\" option."); return EINVAL; } if (ctx_p->flags[THREADING]) { error("this handler is not pthread-safe."); return EINVAL; } argv[0] = "/usr/bin/rsync"; argv[1] = ctx_p->flags[DEBUG] >= 4 ? "-avvvvvvH" : "-aH"; argv[2] = "--exclude-from"; argv[4] = "--include-from"; argv[6] = "--exclude=*"; argv[7] = "--delete-before"; argv[8] = ctx_p->watchdirwslash; argv[9] = ctx_p->destdirwslash; return 0; } int clsyncapi_rsync(const char *inclistfile, const char *exclistfile) { debug(1, "inclistfile == \"%s\"; exclistfile == \"%s\"", inclistfile, exclistfile); argv[3] = exclistfile; argv[5] = inclistfile; if (ctx_p->flags[DEBUG] >= 3) { int i=0; while (argv[i] != NULL) { debug(3, "argv[%i] == \"%s\"", i, argv[i]); i++; } } // Forking int pid = clsyncapi_fork(ctx_p); switch (pid) { case -1: error("Cannot fork()."); return errno; case 0: execvp(argv[0], (char *const *)argv); return errno; } int status; if (waitpid(pid, &status, 0) != pid) { error("Cannot waitid()."); return errno; } // Return int exitcode = WEXITSTATUS(status); debug(1, "Execution completed with exitcode %i.", exitcode); return exitcode; } // Optional function, you can erase it. int clsyncapi_deinit() { debug(1, "Goodbye cruel world!"); return 0; } clsync-0.4.1/examples/clsync-synchandler-so.c000066400000000000000000000042661252417542300212300ustar00rootroot00000000000000 #include #include // Required header: #include #include // Optional headers: #include #include #include struct ctx *ctx_p = NULL; struct indexes *indexes_p = NULL; char **argv = NULL; size_t argv_size = 0; // Optional function, you can erase it. int clsyncapi_init(struct ctx *_ctx_p, struct indexes *_indexes_p) { debug(1, "Hello world! API version is %i", clsyncapi_getapiversion()); ctx_p = _ctx_p; indexes_p = _indexes_p; if(ctx_p->destdir == NULL) { errno = EINVAL; error("dest-dir is not set."); return EINVAL; } if(ctx_p->flags[THREADING]) { errno = EINVAL; error("this handler is not pthread-safe."); return EINVAL; } argv_size = ALLOC_PORTION; argv = malloc(argv_size * sizeof(char *)); argv[0] = "/bin/cp"; argv[1] = "-pf"; return 0; } int clsyncapi_sync(int n, api_eventinfo_t *ei) { debug(1, "clsyncapi_sync(): n == %i", n, ei->path); if(n+4 > argv_size) { // "/bin/cp" + "-pf" + n paths + ctx_p->destdir + NULL --> n+4 argv_size = n+4 + ALLOC_PORTION; argv = realloc(argv, argv_size * sizeof(char *)); } int argv_i=2; int ei_i=0; while(ei_i < n) { if(ei[ei_i].path_len > 0) { debug(1, "ei[%i].path == \"%s\" (len == %i, type_o == %i, type_n == %i)", ei_i, ei[ei_i].path, ei[ei_i].path_len, ei[ei_i].objtype_old, ei[ei_i].objtype_new); argv[argv_i++] = (char *)ei[ei_i].path; } ei_i++; } if(argv_i == 2) { debug(1, "Nothing to sync."); return 0; } argv[argv_i++] = ctx_p->destdir; argv[argv_i++] = NULL; // Forking int pid = clsyncapi_fork(ctx_p); switch(pid) { case -1: error("Cannot fork()."); return errno; case 0: chdir(ctx_p->watchdir); execvp(argv[0], (char *const *)argv); return errno; } int status; if(waitpid(pid, &status, 0) != pid) { error("Cannot waitid()."); return errno; } // Return int exitcode = WEXITSTATUS(status); debug(1, "Execution completed with exitcode %i.", exitcode); return exitcode; } // Optional function, you can erase it. int clsyncapi_deinit() { debug(1, "Goodbye cruel world!"); if(argv != NULL) free(argv); return 0; } clsync-0.4.1/examples/production/000077500000000000000000000000001252417542300170205ustar00rootroot00000000000000clsync-0.4.1/examples/production/etc/000077500000000000000000000000001252417542300175735ustar00rootroot00000000000000clsync-0.4.1/examples/production/etc/clsync/000077500000000000000000000000001252417542300210665ustar00rootroot00000000000000clsync-0.4.1/examples/production/etc/clsync/clsync.conf000066400000000000000000000042211252417542300232270ustar00rootroot00000000000000# This configuration was used on Gentoo based HPC cluster # for live cluster-wide distribution of selected files. [hpc] watch-dir = /etc sync-handler = /etc/clsync/synchandler/hpc/handler-pdcp.so rules-file = /etc/clsync/rules/hpc delay-sync = 5 delay-collect = 1 auto-add-rules-w = 1 one-file-system = 1 mode = so retries = 0 # This configuration was used on Gentoo based HPC cluster # for a full cluster backup. [hpc-backup] watch-dir = / sync-handler = /etc/clsync/synchandler/hpc/handler-backup.so rules-file = /etc/clsync/rules/hpc-backup destination-dir = /mnt/backup/mirror mode = rsyncso lists-dir = /dev/shm/clsync exclude-mount-points = 1 delay-collect = 10 retries = 3 # This configuration was used on Debian/Gentoo based HA-cluster for LXC [lxc-rsyncshell] watch-dir = /srv/lxc/%label% background = 1 mode = rsyncshell debug = 1 syslog = 1 full-initialsync = 1 rules-path = /etc/clsync/rules/lxc retries = 3 ignore-exitcode = 23,24 [lxc-brother] config-block-inherits = lxc-rsyncshell delay-sync = 15 delay-collect = 15 sync-handler = /etc/clsync/synchandler/lxc/brother.sh lists-dir = /dev/shm/clsync-%label%-brother exit-hook = /etc/clsync/hooks/lxc/exit-brother.sh pid-file = /var/run/clsync-%label%-brother.pid status-file = /srv/lxc/%label%/clsync-brother.status dump-dir = /tmp/clsyncdump-%label%-brother threading = safe [lxc-brother-atomic-sync] config-block-inherits = lxc-brother threading = off background = 0 exit-on-no-events = 1 max-iterations = 10 [lxc-brother-initialsync] config-block-inherits = lxc-brother threading = off background = 0 only-initialsync = 1 [lxc-backup] config-block-inherits = lxc-rsyncshell sync-handler = /etc/clsync/synchandler/lxc/backup.sh delay-sync = 1800 delay-collect = 1800 delay-collect-bigfile = 43200 lists-dir = /dev/shm/clsync-%label%-backup exit-hook = /etc/clsync/hooks/lxc/exit-backup.sh pid-file = /var/run/clsync-%label%-backup.pid status-file = /srv/lxc/%label%/clsync-backup.status dump-dir = /tmp/clsyncdump-%label%-backup clsync-0.4.1/examples/production/etc/clsync/hooks/000077500000000000000000000000001252417542300222115ustar00rootroot00000000000000clsync-0.4.1/examples/production/etc/clsync/hooks/lxc/000077500000000000000000000000001252417542300227775ustar00rootroot00000000000000clsync-0.4.1/examples/production/etc/clsync/hooks/lxc/exit-backup.sh000077500000000000000000000001241252417542300255470ustar00rootroot00000000000000#!/bin/bash LABEL="$1" brotherssh rm -f /srv/lxc/"$LABEL"/clsync-backup.status clsync-0.4.1/examples/production/etc/clsync/hooks/lxc/exit-brother.sh000077500000000000000000000001251252417542300257500ustar00rootroot00000000000000#!/bin/bash LABEL="$1" brotherssh rm -f /srv/lxc/"$LABEL"/clsync-brother.status clsync-0.4.1/examples/production/etc/clsync/rules/000077500000000000000000000000001252417542300222205ustar00rootroot00000000000000clsync-0.4.1/examples/production/etc/clsync/rules/hpc000066400000000000000000000000571252417542300227170ustar00rootroot00000000000000+f^passwd +f^group +f^hosts$ +f^machines$ -*.* clsync-0.4.1/examples/production/etc/clsync/rules/hpc-backup000066400000000000000000000002771252417542300241660ustar00rootroot00000000000000-d^dev$ -d^home$ -d^lost+found$ -d^media$ -d^mnt$ -d^proc$ -d^run$ -d^sys$ -d^tmp$ -d^usr/portage$ -d^var/cache$ -d^var/lib/layman/ -d^var/lock$ -d^var/tmp$ -d/\.ccache$ -f\.lock$ -f\.swp$ clsync-0.4.1/examples/production/etc/clsync/rules/lxc000066400000000000000000000000661252417542300227330ustar00rootroot00000000000000-f/var/log/.* -f/bin\.000 -d/rootfs-readonly -f/dmesg clsync-0.4.1/examples/production/etc/clsync/synchandler/000077500000000000000000000000001252417542300234005ustar00rootroot00000000000000clsync-0.4.1/examples/production/etc/clsync/synchandler/hpc/000077500000000000000000000000001252417542300241525ustar00rootroot00000000000000clsync-0.4.1/examples/production/etc/clsync/synchandler/hpc/Makefile000066400000000000000000000010061252417542300256070ustar00rootroot00000000000000CC = gcc CFLAGS = -march=native -O2 -funswitch-loops -fpredictive-commoning -fgcse-after-reload -ftree-vectorize -ftree-loop-linear -ftree-loop-im -fweb -frename-registers -fomit-frame-pointer -fexcess-precision=fast -pipe CFLAGS += --std=gnu11 -Wall -fPIC -shared LDFLAGS = -Wl,-O1,--as-needed HANDLERS = handler-pdcp.so handler-backup.so all: $(HANDLERS) %.so: %.c $(CC) $(CFLAGS) $(LDFLAGS) $< -o $@ strip --strip-unneeded -R .comment -R .GCC.command.line -R .note.gnu.gold-version $@ clean: rm $(HANDLERS) clsync-0.4.1/examples/production/etc/clsync/synchandler/hpc/handler-backup.c000066400000000000000000000065311252417542300272030ustar00rootroot00000000000000/* This program is free software and is distributed by the terms of * GPL v3 license. Author: Andrew Savchenko * Based on clsync example. */ /* Adopted to clsync 0.3 by Dmitry Yu Okunev */ #include #include #include #include #include #include #include #include #include #include #include #define ARGV_SIZE 32 struct ctx *ctx_p; const char *const decrement = "decrement"; size_t decrement_size; int clsyncapi_init(struct ctx *_ctx_p, struct indexes *_indexes_p) { if (clsyncapi_getapiversion() != CLSYNC_API_VERSION) { error("handler: API version mistmatch: compiled for %i, but have %i", CLSYNC_API_VERSION, clsyncapi_getapiversion()); return -1; } decrement_size = strlen(decrement); ctx_p = _ctx_p; if(!ctx_p->destdir) { errno = EINVAL; error("handler: dest-dir is not set, aborting"); return EINVAL; } if(!ctx_p->destdir) { errno = EINVAL; error("handler: dest-dir is not set, aborting"); return EINVAL; } debug(1, "handler: Initialization OK"); return 0; } /* build backup directory name as dirname(destdir)/decrement */ static inline char* get_decrement_name(const char* path) { char *sep, *ret; size_t size; // length of "dirname(destdir)/" sep = strrchr(path,'/'); if (!sep) ret = strdup(decrement); else { size = sep - path + 1; ret = xmalloc(sizeof(char)*size + decrement_size); memcpy(ret, path, size); memcpy(ret+size, decrement, decrement_size); } return ret; } int clsyncapi_rsync(const char *incl_file, const char *excl_file) { char *argv[ARGV_SIZE]; size_t back_idx; // remember string to free int exitcode; debug(1, "handler: sync started for include file %s, exclude file %s", incl_file, excl_file); /* form rsync arguments */ int i = 0; argv[i++] = "/usr/bin/rsync"; if (ctx_p->flags[DEBUG] >= 4) argv[i++] = "-vvvvv"; argv[i++] = "--archive"; argv[i++] = "--hard-links"; argv[i++] = "--acls"; argv[i++] = "--sparse"; argv[i++] = "--del"; argv[i++] = "--partial-dir=.rsync-partial"; argv[i++] = "--backup"; argv[i++] = "--backup-dir"; back_idx = i; argv[i++] = get_decrement_name(ctx_p->destdir); if (!ctx_p->flags[RSYNCPREFERINCLUDE]) { argv[i++] = "--exclude-from"; argv[i++] = (char*)excl_file; } argv[i++] = "--include-from"; argv[i++] = (char*)incl_file; argv[i++] = "--exclude=*"; argv[i++] = ctx_p->watchdirwslash; argv[i++] = ctx_p->destdirwslash; argv[i++] = NULL; // Forking int pid = clsyncapi_fork(ctx_p); switch(pid) { case -1: error("handler: Can't fork()"); exitcode = errno; goto cleanup; case 0: execv(argv[0], argv); error("handler: Can't exec()"); exit(errno); } int status; if(waitpid(pid, &status, 0) != pid) { error("handler: Can't waitid()"); exitcode = errno; goto cleanup; } // Return exitcode = WEXITSTATUS(status); cleanup: free(argv[back_idx]); return exitcode; } clsync-0.4.1/examples/production/etc/clsync/synchandler/hpc/handler-pdcp.c000066400000000000000000000041751252417542300266660ustar00rootroot00000000000000/* This program is free software and is distributed by the terms of * GPL v3 license. Author: Andrew Savchenko * Based on clsync example. */ #include #include #include #include #include #include #include #include #include #include #include struct ctx *ctx_p; int clsyncapi_init(struct ctx *_ctx_p, struct indexes *_indexes_p) { if (clsyncapi_getapiversion() != CLSYNC_API_VERSION) { error("handler: API version mistmatch: compiled for %i, but have %i", CLSYNC_API_VERSION, clsyncapi_getapiversion()); return -1; } ctx_p = _ctx_p; debug(1, "handler: Initialization OK"); return 0; } int clsyncapi_sync(int n, api_eventinfo_t *ei) { size_t argv_size; char **argv; int exitcode; debug(1, "handler: Sync requested for %i objects.", n); argv_size = n+4; // "pdsh" + "-a" + n + "todir" + NULL argv = xmalloc(argv_size * sizeof(char *)); argv[0] = "/usr/bin/pdcp"; argv[1] = "-a"; int i=2; for (int j=0; j < n; j++) { if(ei[j].path_len) argv[i++] = (char*)ei[j].path; } if(i == 2) { debug(1, "handler: Nothing to sync."); exitcode = 0; goto cleanup; } argv[i++] = ctx_p->watchdir; argv[i++] = NULL; // Forking int pid = clsyncapi_fork(ctx_p); switch(pid) { case -1: error("handler: Can't fork()"); exitcode = errno; goto cleanup; case 0: if (chdir(ctx_p->watchdir) == -1) { error("handler: Can't chdir()"); exit(errno); } execv(argv[0], argv); error("handler: Can't exec()"); exit(errno); } int status; if(waitpid(pid, &status, 0) != pid) { error("handler: Can't waitid()"); exitcode = errno; goto cleanup; } // Return exitcode = WEXITSTATUS(status); cleanup: free(argv); return exitcode; // do not die on errors } clsync-0.4.1/examples/production/etc/clsync/synchandler/lxc/000077500000000000000000000000001252417542300241665ustar00rootroot00000000000000clsync-0.4.1/examples/production/etc/clsync/synchandler/lxc/backup.sh000077500000000000000000000024241252417542300257740ustar00rootroot00000000000000#!/bin/bash ACTION="$1" LABEL="$2" ARG0="$3" ARG1="$4" FROM="/srv/lxc/${LABEL}" CLUSTERNAME=$(clustername) BACKUPHOST=$(backuphost) BACKUPMNT="/mnt/backup" BACKUPDECR="/decrement/${LABEL}" BACKUPMIRROR="rsync://$CLUSTERNAME@$BACKUPHOST/$HOSTNAME/mirror/${LABEL}" if [ "$CLSYNC_STATUS" = "initsync" ]; then STATICEXCLUDE='' else STATICEXCLUDE='--exclude-from=/etc/clsync/synchandler/lxc/rsync.exclude' fi function rsynclist() { LISTFILE="$1" EXCLISTFILE="$2" excludefrom='' if [ "$EXCLISTFILE" != "" ]; then excludefrom="--exclude-from=${EXCLISTFILE}" fi # if ! mount | grep "$BACKUPMNT" > /dev/null; then # mount "$BACKUPMNT" # fi # if mount | grep "$BACKUPMNT" > /dev/null; then if ping -w 1 -qc 5 -i 0.1 $BACKUPHOST > /dev/null; then #if [ ! -d "$BACKUPDECR" ]; then # mkdir -p "$BACKUPDECR" #fi exec rsync --password-file="/etc/backup.pass" -aH --timeout=3600 --inplace --delete-before $STATICEXCLUDE "$excludefrom" --include-from="${LISTFILE}" --exclude='*' --backup --backup-dir="$BACKUPDECR"/ "$FROM"/ "$BACKUPMIRROR"/ 2>/tmp/clsync-rsync-"$LABEL"-backup.err else sleep $[ 3600 + $RANDOM % 1800 ] return 128 fi # else # sleep $[ 3600 + $RANDOM % 1800 ] # return 128 # fi } case "$ACTION" in rsynclist) rsynclist "$ARG0" "$ARG1" ;; esac exit 0 clsync-0.4.1/examples/production/etc/clsync/synchandler/lxc/brother.sh000077500000000000000000000021661252417542300261770ustar00rootroot00000000000000#!/bin/bash -x ACTION="$1" LABEL="$2" ARG0="$3" ARG1="$4" BROTHERMNT="/mnt/mirror" BROTHERNAME=$(brothername) CLUSTERNAME=$(clustername) FROM="/srv/lxc/${LABEL}" #TO="/mnt/mirror/${LABEL}" TO="rsync://${CLUSTERNAME}@${BROTHERNAME}/lxc/${LABEL}" if [ "$CLSYNC_STATUS" = "initsync" ]; then STATICEXCLUDE='' else STATICEXCLUDE='--exclude-from=/etc/clsync/synchandler/lxc/rsync.exclude' fi function rsynclist() { LISTFILE="$1" EXCLISTFILE="$2" excludefrom='' if [ "$EXCLISTFILE" != "" ]; then excludefrom="--exclude-from=${EXCLISTFILE}" fi # if mount | grep "$BROTHERMNT" > /dev/null; then if ping -w 1 -qc 5 -i 0.1 $BROTHERNAME > /dev/null; then #if [ ! -d "$TO" ]; then # mkdir -p "$TO" #fi exec rsync --password-file="/etc/rsyncd.pass" -aH --timeout=3600 --inplace --delete-before $STATICEXCLUDE "$excludefrom" --include-from="${LISTFILE}" --exclude='*' "$FROM"/ "$TO"/ 2>/tmp/clsync-rsync-"$LABEL"-brother.err else sleep $[ 3600 + $RANDOM % 1800 ] exit 128 fi # else # sleep $[ 3600 + $RANDOM % 1800 ] # exit 128 # fi } case "$ACTION" in rsynclist) rsynclist "$ARG0" "$ARG1" ;; esac exit 0 clsync-0.4.1/examples/production/etc/clsync/synchandler/lxc/rsync.exclude000066400000000000000000000003611252417542300266770ustar00rootroot00000000000000sess_* nanocacmail/*/*.nexus home/mrtg/*.html home/mrtg/*.log home/mrtg/*.old home/mrtg/*.png vim/spell/* tmp/* sys/* proc/* run/* var/tmp/* var/lock/* var/run/* radwtmp bitrix/cache/* *cache/*** access.log* error.log* rootfs-readonly dmesg clsync-0.4.1/examples/test.sh000077500000000000000000000006141252417542300161510ustar00rootroot00000000000000#!/bin/sh #mkdir -m 700 -p testdir/from testdir/to testdir/listdir cat > rules < 0x8E30679C This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "common.h" #include "error.h" #include "malloc.h" char *fd2fpath_malloc(int fd) { #if __linux__ stat64_t st64; if(fd <= 0) { error("Invalid file descriptor supplied: fd2fpath_malloc(%i).", fd); errno = EINVAL; return NULL; } char *fpath = xmalloc((1<<8) + 2); sprintf(fpath, "/proc/self/fd/%i", fd); if(lstat64(fpath, &st64)) { error("Cannot lstat64(\"%s\", st64).", fpath); return NULL; } ssize_t fpathlen = st64.st_size; if(fpathlen > (1<<8)) fpath = xrealloc(fpath, fpathlen+2); debug(3, "Getting file path from symlink \"%s\". Path length is: %i.", fpath, fpathlen); if((fpathlen = readlink(fpath, fpath, fpathlen+1)) < 0) { error("Cannot readlink(\"%s\", fpath, bufsize).", fpath); return NULL; } debug(3, "The path is: \"%s\"", fpath); fpath[fpathlen] = 0; return fpath; #else critical("Function fd2fpath_malloc() is not supported in this OS"); return NULL; #endif } /** * @brief Copies file * * @param[in] path_from Source file path * @param[in] path_to Destination file path * * @retval zero Successfully copied * @retval non-zero Got error, while copying * */ int fileutils_copy(const char *path_from, const char *path_to) { char buf[BUFSIZ]; FILE *from, *to; from = fopen(path_from, "r"); if(from == NULL) { error("fileutils_copy(\"%s\", \"%s\"): Cannot open file \"%s\" for reading", path_from, path_to, path_from); return errno; } to = fopen(path_to, "w"); if(to == NULL) { error("fileutils_copy(\"%s\", \"%s\"): Cannot open file \"%s\" for writing", path_from, path_to, path_to); return errno; } while(!feof(from)) { int err; size_t r, w; r = fread(buf, 1, BUFSIZ, from); if((err=ferror(from))) { error("fileutils_copy(\"%s\", \"%s\"): Cannot read from file \"%s\"", path_from, path_to, path_from); return errno; // CHECK: Is the "errno" should be used in fread() case? } w = fwrite(buf, 1, r, to); if((err=ferror(to))) { error("fileutils_copy(\"%s\", \"%s\"): Cannot write to file \"%s\"", path_from, path_to, path_to); return errno; // CHECK: is the "errno" should be used in fwrite() case? } if(r != w) { error("fileutils_copy(\"%s\", \"%s\"): Got error while writing to file \"%s\" (%u != %u)", path_from, path_to, path_to, r, w); return errno; // CHECK: is the "errno" should be used in case "r != w"? } } return 0; } /** * @brief Calculates directory level of a canonized path (actually it just counts "/"-s) * * @param[in] path Canonized path (with realpath()) * * @retval zero or prositive Direcory level * @retval negative Got error, while calculation. Error-code is placed to errno. * */ short int fileutils_calcdirlevel(const char *path) { short int dirlevel = 0; const char *ptr = path; if(path == NULL) { error("path is NULL."); errno=EINVAL; return -1; } if(*path == 0) { error("path has zero length."); errno=EINVAL; return -2; } if(*path != '/') { error("path \"%s\" is not canonized.", path); errno=EINVAL; return -3; } while(*(ptr++)) if(*ptr == '/') dirlevel++; return dirlevel; } /** * @brief Combination of mkdirat() and openat() * * @param[in] dir_path Path to directory to create and open @ @param[in] dirfd_parent File descriptor of directory for relative paths @ @param[in] dir_mode Modes for newly created directory (e.g. 750) * * @retval dirfd File descriptor to newly created directory * @retval NULL On error * */ int mkdirat_open(const char *const dir_path, int dirfd_parent, mode_t dir_mode) { int dirfd; debug(5, "mkdirat(%u, \"%s\", %o)", dirfd_parent, dir_path, dir_mode); if (mkdirat(dirfd_parent, dir_path, dir_mode)) return -1; debug(5, "openat(%u, \"%s\", %x)", dirfd_parent, dir_path, O_RDWR|O_DIRECTORY|O_PATH); dirfd = openat(dirfd_parent, dir_path, O_RDWR|O_DIRECTORY|O_PATH); if (dirfd == -1) return -1; return dirfd; } /** * @brief Opens a directory with open() * * @param[out] fd_p Pointer to the result file descriptor @ @param[in] dir_path Path to the directory * * @retval *fd_p On success * @retval -1 On error * * / int open_dir(int *fd_p, const char *const dir_path) { int fd = open(dir_path, O_RDONLY|O_DIRECTORY|O_PATH); if (fd == -1) { error("Got error while open(\"%s\", O_RDWR|O_DIRECTORY|O_PATH)", dir_path); return fd; } *fd_p = fd; return fd; } */ uint32_t stat_diff(stat64_t *a, stat64_t *b) { uint32_t difference; #ifdef PARANOID critical_on (a == NULL); critical_on (b == NULL); #endif difference = 0x0000; #define STAT_COMPARE(bit, field) \ if (a->field != b->field) \ difference |= bit; STAT_COMPARE(STAT_FIELD_DEV, st_dev); STAT_COMPARE(STAT_FIELD_INO, st_ino); STAT_COMPARE(STAT_FIELD_MODE, st_mode); STAT_COMPARE(STAT_FIELD_NLINK, st_nlink); STAT_COMPARE(STAT_FIELD_UID, st_uid); STAT_COMPARE(STAT_FIELD_GID, st_gid); STAT_COMPARE(STAT_FIELD_RDEV, st_rdev); STAT_COMPARE(STAT_FIELD_SIZE, st_size); STAT_COMPARE(STAT_FIELD_BLKSIZE,st_blksize); STAT_COMPARE(STAT_FIELD_BLOCKS, st_blocks); STAT_COMPARE(STAT_FIELD_ATIME, st_atime); STAT_COMPARE(STAT_FIELD_MTIME, st_mtime); STAT_COMPARE(STAT_FIELD_CTIME, st_ctime); #undef STAT_COMPARE return difference; } clsync-0.4.1/fileutils.h000066400000000000000000000021351252417542300151660ustar00rootroot00000000000000/* clsync - file tree sync utility based on inotify Copyright (C) 2013 Dmitry Yu Okunev 0x8E30679C This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ extern char *fd2fpath_malloc(int fd); extern int fileutils_copy(const char *path_from, const char *path_to); extern short int fileutils_calcdirlevel(const char *path); extern int mkdirat_open(const char *const dir_path, int dirfd_parent, mode_t dir_mode); extern uint32_t stat_diff(stat64_t *a, stat64_t *b); clsync-0.4.1/freebsd/000077500000000000000000000000001252417542300144265ustar00rootroot00000000000000clsync-0.4.1/freebsd/usr/000077500000000000000000000000001252417542300152375ustar00rootroot00000000000000clsync-0.4.1/freebsd/usr/local/000077500000000000000000000000001252417542300163315ustar00rootroot00000000000000clsync-0.4.1/freebsd/usr/local/etc/000077500000000000000000000000001252417542300171045ustar00rootroot00000000000000clsync-0.4.1/freebsd/usr/local/etc/clsync/000077500000000000000000000000001252417542300203775ustar00rootroot00000000000000clsync-0.4.1/freebsd/usr/local/etc/clsync/clsync.conf000066400000000000000000000046671252417542300225560ustar00rootroot00000000000000# Just a simple example [simple] mode = rsyncdirect watch-dir = /tmp/from destination-dir = /tmp/to lists-dir = /tmp/lists sync-handler = /usr/local/bin/rsync delay-sync = 5 delay-collect = 1 # This configuration was used on Gentoo based HPC cluster # for live cluster-wide distribution of selected files. [hpc] watch-dir = /usr/local/etc sync-handler = /usr/local/etc/clsync/synchandler/hpc/handler-pdcp.so rules-file = /usr/local/etc/clsync/rules/hpc delay-sync = 5 delay-collect = 1 auto-add-rules-w = 1 one-file-system = 1 mode = so retries = 0 # This configuration was used on Gentoo based HPC cluster # for a full cluster backup. [hpc-backup] watch-dir = / sync-handler = /usr/local/etc/clsync/synchandler/hpc/handler-backup.so rules-file = /usr/local/etc/clsync/rules/hpc-backup destination-dir = /mnt/backup/mirror mode = rsyncso lists-dir = /dev/shm/clsync exclude-mount-points = 1 delay-collect = 10 retries = 3 # This configuration was used on Debian/Gentoo based HA-cluster for LXC [lxc-rsyncshell] watch-dir = /srv/lxc/%label% background = 1 mode = rsyncshell debug = 1 syslog = 1 full-initialsync = 1 rules-path = /usr/local/etc/clsync/rules/lxc retries = 3 ignore-exitcode = 23,24 [lxc-brother] config-block-inherits = lxc-rsyncshell delay-sync = 15 delay-collect = 15 sync-handler = /usr/local/etc/clsync/synchandler/lxc/brother.sh lists-dir = /dev/shm/clsync-%label%-brother exit-hook = /usr/local/etc/clsync/hooks/lxc/exit-brother.sh pid-file = /var/run/clsync-%label%-brother.pid status-file = /srv/lxc/%label%/clsync-brother.status dump-dir = /tmp/clsyncdump-%label%-brother threading = safe [lxc-brother-atomic-sync] config-block-inherits = lxc-brother threading = off background = 0 exit-on-no-events = 1 max-iterations = 10 [lxc-brother-initialsync] config-block-inherits = lxc-brother threading = off background = 0 only-initialsync = 1 [lxc-backup] config-block-inherits = lxc-rsyncshell sync-handler = /usr/local/etc/clsync/synchandler/lxc/backup.sh delay-sync = 1800 delay-collect = 1800 delay-collect-bigfile = 43200 lists-dir = /dev/shm/clsync-%label%-backup exit-hook = /usr/local/etc/clsync/hooks/lxc/exit-backup.sh pid-file = /var/run/clsync-%label%-backup.pid status-file = /srv/lxc/%label%/clsync-backup.status dump-dir = /tmp/clsyncdump-%label%-backup clsync-0.4.1/freebsd/usr/ports/000077500000000000000000000000001252417542300164065ustar00rootroot00000000000000clsync-0.4.1/freebsd/usr/ports/sysutils/000077500000000000000000000000001252417542300203055ustar00rootroot00000000000000clsync-0.4.1/freebsd/usr/ports/sysutils/clsync/000077500000000000000000000000001252417542300216005ustar00rootroot00000000000000clsync-0.4.1/freebsd/usr/ports/sysutils/clsync/Makefile000066400000000000000000000054201252417542300232410ustar00rootroot00000000000000# Created by: Dmitry Yu Okunev dyokunev@ut.mephi.ru # $FreeBSD: head/sysutils/clsync/Makefile 357592 2014-06-12 12:48:11Z feld $ PORTNAME= clsync DISTVERSION= 0.4 PORTREVISION= 1 DIST_SUBDIR= ${PORTNAME}-${PORTVERSION}-${GH_COMMIT} CATEGORIES= sysutils MAINTAINER= dyokunev@ut.mephi.ru COMMENT= Live file sync daemon based on inotify, written in GNU C LICENSE= GPLv3 LIB_DEPENDS= libinotify.so:${PORTSDIR}/devel/libinotify \ libexecinfo.so:${PORTSDIR}/devel/libexecinfo GNU_CONFIGURE= yes USES= gmake libtool pkgconfig USE_GNOME= glib20 USE_LDCONFIG= yes USE_RC_SUBR= clsync SUB_FILES= pkg-message USE_GITHUB= yes GH_ACCOUNT= xaionaro GH_PROJECT= clsync GH_COMMIT= 12ea62c GH_TAGNAME= ${GH_COMMIT} USE_AUTOTOOLS= autoconf aclocal automake libtoolize OPTIONS_DEFINE= PARANOID VERYPARANOID SOCKET DEBUG LIBCLSYNC OPTIONS_DEFAULT=PARANOID PARANOID_DESC= Paranoid secure routines VERYPARANOID_DESC=Extra paranoid secure routines, less features SOCKET_DESC= Control socket support LIBCLSYNC_DESC= Build libclsync.so for control socket clients OPTIONS_MULTI= MONITOR OPTIONS_MULTI_MONITOR= BSM KQUEUE LIBINOTIFY GIO KQUEUE_DESC= "Kqueue" monitor support BSM_DESC= "BSM" monitor support LIBINOTIFY_DESC="Inotify" monitor support (via libinotify-kqueue) GIO_DESC= "GIO" monitor support OPTIONS_DEFAULT+=KQUEUE BSM LIBS+= -L${LOCALBASE}/lib CPPFLAGS+= -I${LOCALBASE}/include .include CONFIGURE_ARGS+= --bindir=${PREFIX}/sbin .if ${PORT_OPTIONS:MBSM} CONFIGURE_ARGS+= --with-bsm=lib .endif .if ${PORT_OPTIONS:MKQUEUE} CONFIGURE_ARGS+= --with-kqueue=native .endif .if ${PORT_OPTIONS:MLIBINOTIFY} CONFIGURE_ARGS+= --with-inotify=lib .endif .if ${PORT_OPTIONS:MGIO} CONFIGURE_ARGS+= --with-gio=check .endif .if ${PORT_OPTIONS:MVERYPARANOID} CONFIGURE_ARGS+= --enable-paranoid=2 .else .if ${PORT_OPTIONS:MPARANOID} CONFIGURE_ARGS+= --enable-paranoid=1 .else CONFIGURE_ARGS+= --enable-paranoid=0 .endif .endif .if ${PORT_OPTIONS:MSOCKET} CONFIGURE_ARGS+= --enable-socket PLIST_FILES+= include/clsync/socket.h .endif .if ${PORT_OPTIONS:MDEBUG} CONFIGURE_ARGS+= --enable-debug .endif .if ${PORT_OPTIONS:MLIBCLSYNC} CONFIGURE_ARGS+= --enable-socket-library PLIST_FILES+= include/libclsync/clsync.h \ include/libclsync/ctx.h \ include/libclsync/libclsync.h \ include/libclsync/malloc.h \ include/libclsync/socket.h \ lib/libclsync.a \ lib/libclsync.so \ lib/libclsync.so.0 \ lib/libclsync.so.0.0.0 \ lib/pkgconfig/libclsync.pc PLIST_DIRS+= include/libclsync \ lib/pkgconfig .endif .include pre-configure: cd "${WRKSRC}" && ${AUTORECONF} -fi post-stage: ${MKDIR} ${STAGEDIR}/${ETCDIR} ${INSTALL_DATA} ${WRKSRC}/freebsd/usr/local/etc/clsync/${PORTNAME}.conf ${STAGEDIR}/${ETCDIR}/${PORTNAME}.conf.sample .include clsync-0.4.1/freebsd/usr/ports/sysutils/clsync/distinfo000066400000000000000000000002001252417542300233320ustar00rootroot00000000000000SHA256 (clsync-0.4.tar.gz) = 6f0ce7a5f61fbb50db53b787b62cf5347870f3be315acb02c4aee6b76206d19e SIZE (clsync-0.4.tar.gz) = 253396 clsync-0.4.1/freebsd/usr/ports/sysutils/clsync/files/000077500000000000000000000000001252417542300227025ustar00rootroot00000000000000clsync-0.4.1/freebsd/usr/ports/sysutils/clsync/files/clsync.in000077500000000000000000000017121252417542300245310ustar00rootroot00000000000000#!/bin/sh # $FreeBSD: # PROVIDE: clsync # REQUIRE: FILESYSTEMS # KEYWORD: shutdown . /etc/rc.subr name=clsync rcvar=clsync_enable load_rc_config $name start_cmd=clsync_start start_precmd=clsync_prestart stop_cmd=clsync_stop restart_cmd=clsync_restart clsync_program="%%PREFIX%%/sbin/clsync" clsync_prestart() { if [ "$clsync_config" = "" ]; then clsync_config=/usr/local/etc/clsync/clsync.conf fi if [ "$clsync_config_groups" = "" ]; then clsync_config_groups=default fi } clsync_start() { for config_group in $clsync_config_groups; do echo "Running clsync (group ${config_group})." "${clsync_program}" -H "$clsync_config" -K "$config_group" -b \ --pid-file=/var/run/clsync-"${config_group}".pid done } clsync_stop() { for config_group in $clsync_config_groups; do echo "Stopping clsync (group ${config_group})." pkill -F /var/run/clsync-"${config_group}".pid done } clsync_restart() { clsync_stop clsync_start } run_rc_command "$1" clsync-0.4.1/freebsd/usr/ports/sysutils/clsync/files/pkg-message.in000066400000000000000000000007541252417542300254430ustar00rootroot00000000000000------------------------------------------------------------------------------ Author: Dmtiry Yu Okunev; e-mail: dyokunev@ut.mephi.ru; PGP: 0x8E30679C What's left: 1. Edit %%PREFIX%%/etc/clsync/clsync.conf 2. Set clsync_enable="YES" in /etc/rc.conf 3. Set required config groups to start in /etc/rc.conf: clsync_config_groups="www-backup etc-replicate" Start the program: %%PREFIX%%/etc/rc.d/clsync start ------------------------------------------------------------------------------ clsync-0.4.1/freebsd/usr/ports/sysutils/clsync/pkg-descr000066400000000000000000000001421252417542300233770ustar00rootroot00000000000000file live sync daemon based on inotify, written in GNU C WWW: https://github.com/xaionaro/clsync clsync-0.4.1/freebsd/usr/ports/sysutils/clsync/pkg-plist000066400000000000000000000047221252417542300234420ustar00rootroot00000000000000@sample %%ETCDIR%%/clsync.conf.sample @dirrmtry %%ETCDIR%% include/clsync/clsync.h include/clsync/configuration.h include/clsync/ctx.h include/clsync/error.h include/clsync/indexes.h include/clsync/malloc.h man/man1/clsync.1.gz sbin/clsync %%PORTDOCS%%%%DOCSDIR%%/CONTRIB %%PORTDOCS%%%%DOCSDIR%%/DEVELOPING %%PORTDOCS%%%%DOCSDIR%%/LICENSE %%PORTDOCS%%%%DOCSDIR%%/PROTOCOL %%PORTDOCS%%%%DOCSDIR%%/README.md %%PORTDOCS%%%%DOCSDIR%%/TODO %%PORTDOCS%%%%DOCSDIR%%/examples/clsync-start-cluster.sh %%PORTDOCS%%%%DOCSDIR%%/examples/clsync-start-rsyncdirect.sh %%PORTDOCS%%%%DOCSDIR%%/examples/clsync-start-rsyncshell.sh %%PORTDOCS%%%%DOCSDIR%%/examples/clsync-start-rsyncso.sh %%PORTDOCS%%%%DOCSDIR%%/examples/clsync-start-so.sh %%PORTDOCS%%%%DOCSDIR%%/examples/clsync-synchandler-rsyncshell.sh %%PORTDOCS%%%%DOCSDIR%%/examples/clsync-synchandler-rsyncso.c %%PORTDOCS%%%%DOCSDIR%%/examples/clsync-synchandler-so.c %%PORTDOCS%%%%DOCSDIR%%/examples/production/%%ETCDIR%%/clsync.conf %%PORTDOCS%%%%DOCSDIR%%/examples/production/%%ETCDIR%%/hooks/lxc/exit-backup.sh %%PORTDOCS%%%%DOCSDIR%%/examples/production/%%ETCDIR%%/hooks/lxc/exit-brother.sh %%PORTDOCS%%%%DOCSDIR%%/examples/production/%%ETCDIR%%/rules/hpc %%PORTDOCS%%%%DOCSDIR%%/examples/production/%%ETCDIR%%/rules/hpc-backup %%PORTDOCS%%%%DOCSDIR%%/examples/production/%%ETCDIR%%/rules/lxc %%PORTDOCS%%%%DOCSDIR%%/examples/production/%%ETCDIR%%/synchandler/hpc/Makefile %%PORTDOCS%%%%DOCSDIR%%/examples/production/%%ETCDIR%%/synchandler/hpc/handler-backup.c %%PORTDOCS%%%%DOCSDIR%%/examples/production/%%ETCDIR%%/synchandler/hpc/handler-pdcp.c %%PORTDOCS%%%%DOCSDIR%%/examples/production/%%ETCDIR%%/synchandler/lxc/backup.sh %%PORTDOCS%%%%DOCSDIR%%/examples/production/%%ETCDIR%%/synchandler/lxc/brother.sh %%PORTDOCS%%%%DOCSDIR%%/examples/production/%%ETCDIR%%/synchandler/lxc/rsync.exclude @dir include/clsync %%PORTDOCS%%@dir %%DOCSDIR%%/examples/production/%%ETCDIR%%/hooks/lxc %%PORTDOCS%%@dir %%DOCSDIR%%/examples/production/%%ETCDIR%%/hooks %%PORTDOCS%%@dir %%DOCSDIR%%/examples/production/%%ETCDIR%%/rules %%PORTDOCS%%@dir %%DOCSDIR%%/examples/production/%%ETCDIR%%/synchandler/hpc %%PORTDOCS%%@dir %%DOCSDIR%%/examples/production/%%ETCDIR%%/synchandler/lxc %%PORTDOCS%%@dir %%DOCSDIR%%/examples/production/%%ETCDIR%%/synchandler %%PORTDOCS%%@dir %%DOCSDIR%%/examples/production/%%ETCDIR%% %%PORTDOCS%%@dir %%DOCSDIR%%/examples/production/etc %%PORTDOCS%%@dir %%DOCSDIR%%/examples/production %%PORTDOCS%%@dir %%DOCSDIR%%/examples %%PORTDOCS%%@dir %%DOCSDIR%% clsync-0.4.1/gencompilerflags.c000066400000000000000000000021451252417542300165030ustar00rootroot00000000000000#include int main() { printf("%s", #ifdef _DEBUG_SUPPORT "#define _DEBUG_SUPPORT\n" #endif #ifdef _DEBUG_FORCE "#define _DEBUG_FORCE\n" #endif #ifdef KQUEUE_SUPPORT "#define KQUEUE_SUPPORT\n" #endif #ifdef INOTIFY_SUPPORT "#define INOTIFY_SUPPORT\n" #endif #ifdef INOTIFY_OLD "#define INOTIFY_OLD\n" #endif #ifdef FANOTIFY_SUPPORT "#define FANOTIFY_SUPPORT\n" #endif #ifdef BSM_SUPPORT "#define BSM_SUPPORT\n" #endif #ifdef GIO_SUPPORT "#define GIO_SUPPORT\n" #endif #ifdef DTRACEPIPE_SUPPORT "#define DTRACEPIPE_SUPPORT\n" #endif #ifdef BACKTRACE_SUPPORT "#define BACKTRACE_SUPPORT\n" #endif #ifdef CAPABILITIES_SUPPORT "#define CAPABILITIES_SUPPORT\n" #endif #ifdef SECCOMP_SUPPORT "#define SECCOMP_SUPPORT\n" #endif #ifdef GETMNTENT_SUPPORT "#define GETMNTENT_SUPPORT\n" #endif #ifdef UNSHARE_SUPPORT "#define UNSHARE_SUPPORT\n" #endif #ifdef PIVOTROOT_OPT_SUPPORT "#define PIVOTROOT_OPT_SUPPORT\n" #endif #ifdef CGROUP_SUPPORT "#define CGROUP_SUPPORT\n" #endif #ifdef TRE_SUPPORT "#define TRE_SUPPORT\n" #endif #ifdef HL_LOCKS "#define HL_LOCKS\n" #endif ); return 0; } clsync-0.4.1/glibex.c000066400000000000000000000054131252417542300144350ustar00rootroot00000000000000/* clsync - file tree sync utility based on inotify Copyright (C) 2013 Dmitry Yu Okunev 0x8E30679C This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "common.h" #include "glibex.h" struct keyvalue_copy_arg { union { GHashTable *ht_dst; GTree *bt_dst; }; GDupFunc k_dup_funct; GDupFunc v_dup_funct; }; void g_hash_table_dup_item(gpointer k, gpointer v, gpointer arg_gp) { GHashTable *ht_dst = ((struct keyvalue_copy_arg *)arg_gp)->ht_dst; GDupFunc k_dup_funct = ((struct keyvalue_copy_arg *)arg_gp)->k_dup_funct; GDupFunc v_dup_funct = ((struct keyvalue_copy_arg *)arg_gp)->v_dup_funct; g_hash_table_insert(ht_dst, k_dup_funct==NULL?NULL:k_dup_funct(k), v_dup_funct==NULL?NULL:v_dup_funct(v)); return; } GHashTable *g_hash_table_dup(GHashTable *src, GHashFunc hash_funct, GEqualFunc key_equal_funct, GDestroyNotify key_destroy_funct, GDestroyNotify value_destroy_funct, GDupFunc key_dup_funct, GDupFunc value_dup_funct) { GHashTable *dst = g_hash_table_new_full(hash_funct, key_equal_funct, key_destroy_funct, value_destroy_funct); struct keyvalue_copy_arg arg; arg.ht_dst = dst; arg.k_dup_funct = key_dup_funct; arg.v_dup_funct = value_dup_funct; g_hash_table_foreach(src, g_hash_table_dup_item, &arg); return dst; } gboolean g_tree_dup_item(gpointer k, gpointer v, gpointer arg_gp) { GTree *bt_dst = ((struct keyvalue_copy_arg *)arg_gp)->bt_dst; GDupFunc k_dup_funct = ((struct keyvalue_copy_arg *)arg_gp)->k_dup_funct; GDupFunc v_dup_funct = ((struct keyvalue_copy_arg *)arg_gp)->v_dup_funct; g_tree_replace(bt_dst, k_dup_funct==NULL?NULL:k_dup_funct(k), v_dup_funct==NULL?NULL:v_dup_funct(v)); return FALSE; } GTree *g_tree_dup(GTree *src, GCompareDataFunc key_compare_func, gpointer key_compare_data, GDestroyNotify key_destroy_func, GDestroyNotify value_destroy_func, GDupFunc key_dup_funct, GDupFunc value_dup_funct) { GTree *dst = g_tree_new_full(key_compare_func, key_compare_data, key_destroy_func, value_destroy_func); struct keyvalue_copy_arg arg; arg.bt_dst = dst; arg.k_dup_funct = key_dup_funct; arg.v_dup_funct = value_dup_funct; g_tree_foreach(src, g_tree_dup_item, &arg); return dst; } clsync-0.4.1/glibex.h000066400000000000000000000024401252417542300144370ustar00rootroot00000000000000/* clsync - file tree sync utility based on inotify Copyright (C) 2013 Dmitry Yu Okunev 0x8E30679C This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include typedef gpointer(*GDupFunc)(gpointer data); extern GHashTable *g_hash_table_dup(GHashTable *ht, GHashFunc hash_funct, GEqualFunc key_equal_funct, GDestroyNotify key_destroy_funct, GDestroyNotify value_destroy_funct, GDupFunc key_dup_funct, GDupFunc value_dup_funct); extern GTree *g_tree_dup(GTree *src, GCompareDataFunc key_compare_func, gpointer key_compare_data, GDestroyNotify key_destroy_func, GDestroyNotify value_destroy_func, GDupFunc key_dup_funct, GDupFunc value_dup_funct); clsync-0.4.1/indexes.c000066400000000000000000000015311252417542300146170ustar00rootroot00000000000000/* clsync - file tree sync utility based on inotify/kqueue Copyright (C) 2013-2014 Dmitry Yu Okunev 0x8E30679C This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "common.h" #include "error.h" clsync-0.4.1/indexes.h000066400000000000000000000162661252417542300146370ustar00rootroot00000000000000/* clsync - file tree sync utility based on inotify Copyright (C) 2013 Dmitry Yu Okunev 0x8E30679C This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef __CLSYNC_INDEXES_H #define __CLSYNC_INDEXES_H #include #include "common.h" #include "error.h" #include "malloc.h" struct fileinfo { stat64_t lstat; }; typedef struct fileinfo fileinfo_t; struct indexes { GHashTable *wd2fpath_ht; // watching descriptor -> file path GHashTable *fpath2wd_ht; // file path -> watching descriptor GHashTable *fpath2ei_ht; // file path -> event information GHashTable *exc_fpath_ht; // excluded file path GHashTable *exc_fpath_coll_ht[QUEUE_MAX]; // excluded file path aggregation hashtable for every queue GHashTable *fpath2ei_coll_ht[QUEUE_MAX]; // "file path -> event information" aggregation hashtable for every queue GHashTable *out_lines_aggr_ht; // output lines aggregation hashtable GHashTable *nonthreaded_syncing_fpath2ei_ht; // events that are synchronized in signle-mode (non threaded) GHashTable *fileinfo_ht; // to search "fileinfo" structures (that contains secondary sorts of things about any files/dirs) #ifdef CLUSTER_SUPPORT GHashTable *nodenames_ht; // node_name -> node_id #endif }; typedef struct indexes indexes_t; // Removes necessary rows from hash_tables if some watching descriptor closed // Return: 0 on success, non-zero on fail static inline int indexes_remove_bywd(indexes_t *indexes_p, int wd) { int ret=0; char *fpath = g_hash_table_lookup(indexes_p->wd2fpath_ht, GINT_TO_POINTER(wd)); ret |= g_hash_table_remove(indexes_p->wd2fpath_ht, GINT_TO_POINTER(wd)); if(fpath == NULL) { error("Cannot remove from index \"fpath2wd\" by wd %i.", wd); return -1; } ret |= g_hash_table_remove(indexes_p->fpath2wd_ht, fpath); return ret; } // Lookups file path by watching descriptor from hash_tables // Return: file path on success, NULL on fail static inline char *indexes_wd2fpath(indexes_t *indexes_p, int wd) { return g_hash_table_lookup(indexes_p->wd2fpath_ht, GINT_TO_POINTER(wd)); } // Lookups watching descriptor by file path from hash_tables // Return: watching descriptor on success, -1 on fail static inline int indexes_fpath2wd(indexes_t *indexes_p, const char *fpath) { gpointer gint_p = g_hash_table_lookup(indexes_p->fpath2wd_ht, fpath); if(gint_p == NULL) return -1; return GPOINTER_TO_INT(gint_p); } // Adds necessary rows to hash_tables if some watching descriptor opened // Return: 0 on success, non-zero on fail static inline int indexes_add_wd(indexes_t *indexes_p, int wd, const char *fpath_const, size_t fpathlen) { debug(4, "indexes_add_wd(indexes_p, %i, \"%s\", %i)", wd, fpath_const, fpathlen); char *fpath = xmalloc(fpathlen+1); memcpy(fpath, fpath_const, fpathlen+1); g_hash_table_insert(indexes_p->wd2fpath_ht, GINT_TO_POINTER(wd), fpath); g_hash_table_insert(indexes_p->fpath2wd_ht, fpath, GINT_TO_POINTER(wd)); return 0; } static inline eventinfo_t *indexes_fpath2ei(indexes_t *indexes_p, const char *fpath) { return (eventinfo_t *)g_hash_table_lookup(indexes_p->fpath2ei_ht, fpath); } static inline int indexes_fpath2ei_add(indexes_t *indexes_p, char *fpath, eventinfo_t *evinfo) { debug(5, "\"%s\"", fpath); g_hash_table_replace(indexes_p->fpath2ei_ht, fpath, evinfo); return 0; } static inline int indexes_queueevent(indexes_t *indexes_p, char *fpath, eventinfo_t *evinfo, queue_id_t queue_id) { g_hash_table_replace(indexes_p->fpath2ei_coll_ht[queue_id], fpath, evinfo); debug(3, "indexes_queueevent(indexes_p, \"%s\", evinfo, %i). It's now %i events collected in queue %i.", fpath, queue_id, g_hash_table_size(indexes_p->fpath2ei_coll_ht[queue_id]), queue_id); return 0; } static inline eventinfo_t *indexes_lookupinqueue(indexes_t *indexes_p, const char *fpath, queue_id_t queue_id) { return (eventinfo_t *)g_hash_table_lookup(indexes_p->fpath2ei_coll_ht[queue_id], fpath); } static inline int indexes_queuelen(indexes_t *indexes_p, queue_id_t queue_id) { return g_hash_table_size(indexes_p->fpath2ei_coll_ht[queue_id]); } static inline int indexes_removefromqueue(indexes_t *indexes_p, char *fpath, queue_id_t queue_id) { // debug(3, "indexes_removefromqueue(indexes_p, \"%s\", %i).", fpath, queue_id); g_hash_table_remove(indexes_p->fpath2ei_coll_ht[queue_id], fpath); debug(3, "indexes_removefromqueue(indexes_p, \"%s\", %i). It's now %i events collected in queue %i.", fpath, queue_id, g_hash_table_size(indexes_p->fpath2ei_coll_ht[queue_id]), queue_id); return 0; } static inline int indexes_addexclude(indexes_t *indexes_p, char *fpath, eventinfo_flags_t flags, queue_id_t queue_id) { g_hash_table_replace(indexes_p->exc_fpath_coll_ht[queue_id], fpath, GINT_TO_POINTER(flags)); debug(3, "indexes_addexclude(indexes_p, \"%s\", %i). It's now %i events collected in queue %i.", fpath, queue_id, g_hash_table_size(indexes_p->exc_fpath_coll_ht[queue_id]), queue_id); return 0; } static inline int indexes_addexclude_aggr(indexes_t *indexes_p, char *fpath, eventinfo_flags_t flags) { debug(3, "indexes_addexclude_aggr(indexes_p, \"%s\", %u).", fpath, flags); gpointer flags_gp = g_hash_table_lookup(indexes_p->exc_fpath_ht, fpath); if(flags_gp != NULL) flags |= GPOINTER_TO_INT(flags_gp); // Removing extra flags if((flags&(EVIF_RECURSIVELY | EVIF_CONTENTRECURSIVELY)) == (EVIF_RECURSIVELY | EVIF_CONTENTRECURSIVELY)) flags &= ~EVIF_CONTENTRECURSIVELY; g_hash_table_replace(indexes_p->exc_fpath_ht, fpath, GINT_TO_POINTER(flags)); debug(3, "indexes_addexclude_aggr(indexes_p, \"%s\", flags): %u.", fpath, flags); return 0; } static inline int indexes_outaggr_add(indexes_t *indexes_p, char *outline, eventinfo_flags_t flags) { gpointer flags_gp = g_hash_table_lookup(indexes_p->out_lines_aggr_ht, outline); if(flags_gp != NULL) flags |= GPOINTER_TO_INT(flags_gp); // Removing extra flags if((flags&(EVIF_RECURSIVELY | EVIF_CONTENTRECURSIVELY)) == (EVIF_RECURSIVELY | EVIF_CONTENTRECURSIVELY)) flags &= ~EVIF_CONTENTRECURSIVELY; g_hash_table_replace(indexes_p->out_lines_aggr_ht, outline, GINT_TO_POINTER(flags)); debug(3, "indexes_outaggr_aggr(indexes_p, \"%s\").", outline); return 0; } static inline fileinfo_t *indexes_fileinfo(indexes_t *indexes_p, const char *fpath) { return (fileinfo_t *)g_hash_table_lookup(indexes_p->fileinfo_ht, fpath); } static inline int indexes_fileinfo_add(indexes_t *indexes_p, const char *fpath_const, fileinfo_t *fi) { size_t fpathlen = strlen(fpath_const); debug(4, "indexes_add_wd(indexes_p, \"%s\", %p)", fpath_const, fpathlen); char *fpath = xmalloc(fpathlen+1); memcpy(fpath, fpath_const, fpathlen+1); g_hash_table_insert(indexes_p->fileinfo_ht, fpath, fi); return 0; } #endif clsync-0.4.1/libclsync.c000066400000000000000000000064301252417542300151450ustar00rootroot00000000000000/* libclsyncmgr - clsync control socket API Copyright (C) 2014 Dmitry Yu Okunev 0x8E30679C This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #define LIBCLSYNC #include "common.h" #include #include #include // for "struct sockaddr_un" #include "configuration.h" #include "error.h" #include "libclsync.h" #include "malloc.h" #include "socket.h" int libproc_procclsyncsock(socket_sockthreaddata_t *arg, sockcmd_t *sockcmd_p) { clsyncproc_t *proc_p = arg->arg; clsyncsock_t *clsyncsock_p = proc_p->sock_p; clsyncsock_procfunct_t procfunct = proc_p->procfunct; #ifdef PARANOID if (procfunct == NULL) { error("procfunct == NULL"); return 0; } #endif if(procfunct(arg, sockcmd_p)) switch(sockcmd_p->cmd_id) { default: socket_sendinvalid(clsyncsock_p, sockcmd_p); break; } return 0; } static inline int _clsync_connect_setthreaddata(socket_sockthreaddata_t *threaddata_p, clsyncproc_t *proc_p, sockprocflags_t flags) { threaddata_p->procfunct = libproc_procclsyncsock; threaddata_p->clsyncsock_p = proc_p->sock_p; threaddata_p->arg = proc_p; threaddata_p->running = NULL; threaddata_p->authtype = SOCKAUTH_NULL; threaddata_p->flags = flags; threaddata_p->freefunct_arg = free; return 0; } static inline clsyncproc_t *_clsync_x_unix( const char *const socket_path, clsyncsock_procfunct_t procfunct, sockprocflags_t flags, const char *const action, clsyncsock_t *(*socket_x_unix)(const char *const) ) { if (procfunct == NULL) { errno = EINVAL; return NULL; } clsyncproc_t *proc_p = xmalloc(sizeof(*proc_p)); memset(proc_p, 0, sizeof(*proc_p)); proc_p->sock_p = socket_x_unix(socket_path); if(proc_p->sock_p == NULL) { free(proc_p); if(errno == EUSERS) { error("clsync_%s_unix(): Too many connections.", action); return NULL; } error("clsync_%s_unix(): Cannot socket_%s_unix()", action, action); return NULL; } socket_sockthreaddata_t *threaddata_p = socket_thread_attach(proc_p->sock_p); if (threaddata_p == NULL) { socket_close(proc_p->sock_p); free(proc_p); return NULL; } _clsync_connect_setthreaddata(threaddata_p, proc_p, flags); proc_p->procfunct = procfunct; socket_thread_start(threaddata_p); return proc_p; } clsyncproc_t *clsync_listen_unix(const char *const socket_path, clsyncsock_procfunct_t procfunct, sockprocflags_t flags) { return _clsync_x_unix(socket_path, procfunct, flags, "listen", socket_listen_unix); } clsyncproc_t *clsync_connect_unix(const char *const socket_path, clsyncsock_procfunct_t procfunct, sockprocflags_t flags) { return _clsync_x_unix(socket_path, procfunct, flags, "connect", socket_connect_unix); } clsync-0.4.1/libclsync.h000066400000000000000000000024141252417542300151500ustar00rootroot00000000000000/* clsync - file tree sync utility based on inotify Copyright (C) 2014 Dmitry Yu Okunev 0x8E30679C This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "socket.h" struct clsyncproc { clsyncsock_t *sock_p; clsyncsock_procfunct_t procfunct; void *data; }; typedef struct clsyncproc clsyncproc_t; extern int libclsync_init(int quite, int verbosity, int debug); extern clsyncproc_t *clsync_listen_unix (const char *const socket_path, clsyncsock_procfunct_t procfunct, sockprocflags_t flags); extern clsyncproc_t *clsync_connect_unix(const char *const socket_path, clsyncsock_procfunct_t procfunct, sockprocflags_t flags); clsync-0.4.1/m4/000077500000000000000000000000001252417542300133345ustar00rootroot00000000000000clsync-0.4.1/m4/.gitignore000066400000000000000000000000261252417542300153220ustar00rootroot00000000000000* !.gitignore !pkg.m4 clsync-0.4.1/m4/ax_check_compile_flag.m4000066400000000000000000000064111252417542300200460ustar00rootroot00000000000000# =========================================================================== # http://www.gnu.org/software/autoconf-archive/ax_check_compile_flag.html # =========================================================================== # # SYNOPSIS # # AX_CHECK_COMPILE_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS], [INPUT]) # # DESCRIPTION # # Check whether the given FLAG works with the current language's compiler # or gives an error. (Warnings, however, are ignored) # # ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on # success/failure. # # If EXTRA-FLAGS is defined, it is added to the current language's default # flags (e.g. CFLAGS) when the check is done. The check is thus made with # the flags: "CFLAGS EXTRA-FLAGS FLAG". This can for example be used to # force the compiler to issue an error when a bad flag is given. # # INPUT gives an alternative input source to AC_COMPILE_IFELSE. # # NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this # macro in sync with AX_CHECK_{PREPROC,LINK}_FLAG. # # LICENSE # # Copyright (c) 2008 Guido U. Draheim # Copyright (c) 2011 Maarten Bosmans # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the # Free Software Foundation, either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General # Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program. If not, see . # # As a special exception, the respective Autoconf Macro's copyright owner # gives unlimited permission to copy, distribute and modify the configure # scripts that are the output of Autoconf when processing the Macro. You # need not follow the terms of the GNU General Public License when using # or distributing such scripts, even though portions of the text of the # Macro appear in them. The GNU General Public License (GPL) does govern # all other use of the material that constitutes the Autoconf Macro. # # This special exception to the GPL applies to versions of the Autoconf # Macro released by the Autoconf Archive. When you make and distribute a # modified version of the Autoconf Macro, you may extend this special # exception to the GPL to apply to your modified version as well. #serial 3 AC_DEFUN([AX_CHECK_COMPILE_FLAG], [AC_PREREQ(2.59)dnl for _AC_LANG_PREFIX AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [ ax_check_save_flags=$[]_AC_LANG_PREFIX[]FLAGS _AC_LANG_PREFIX[]FLAGS="$[]_AC_LANG_PREFIX[]FLAGS $4 $1" AC_COMPILE_IFELSE([m4_default([$5],[AC_LANG_PROGRAM()])], [AS_VAR_SET(CACHEVAR,[yes])], [AS_VAR_SET(CACHEVAR,[no])]) _AC_LANG_PREFIX[]FLAGS=$ax_check_save_flags]) AS_IF([test x"AS_VAR_GET(CACHEVAR)" = xyes], [m4_default([$2], :)], [m4_default([$3], :)]) AS_VAR_POPDEF([CACHEVAR])dnl ])dnl AX_CHECK_COMPILE_FLAGS clsync-0.4.1/m4/ax_pthread.m4000066400000000000000000000326761252417542300157330ustar00rootroot00000000000000# =========================================================================== # http://www.gnu.org/software/autoconf-archive/ax_pthread.html # =========================================================================== # # SYNOPSIS # # AX_PTHREAD([ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]]) # # DESCRIPTION # # This macro figures out how to build C programs using POSIX threads. It # sets the PTHREAD_LIBS output variable to the threads library and linker # flags, and the PTHREAD_CFLAGS output variable to any special C compiler # flags that are needed. (The user can also force certain compiler # flags/libs to be tested by setting these environment variables.) # # Also sets PTHREAD_CC to any special C compiler that is needed for # multi-threaded programs (defaults to the value of CC otherwise). (This # is necessary on AIX to use the special cc_r compiler alias.) # # NOTE: You are assumed to not only compile your program with these flags, # but also link it with them as well. e.g. you should link with # $PTHREAD_CC $CFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS # # If you are only building threads programs, you may wish to use these # variables in your default LIBS, CFLAGS, and CC: # # LIBS="$PTHREAD_LIBS $LIBS" # CFLAGS="$CFLAGS $PTHREAD_CFLAGS" # CC="$PTHREAD_CC" # # In addition, if the PTHREAD_CREATE_JOINABLE thread-attribute constant # has a nonstandard name, defines PTHREAD_CREATE_JOINABLE to that name # (e.g. PTHREAD_CREATE_UNDETACHED on AIX). # # Also HAVE_PTHREAD_PRIO_INHERIT is defined if pthread is found and the # PTHREAD_PRIO_INHERIT symbol is defined when compiling with # PTHREAD_CFLAGS. # # ACTION-IF-FOUND is a list of shell commands to run if a threads library # is found, and ACTION-IF-NOT-FOUND is a list of commands to run it if it # is not found. If ACTION-IF-FOUND is not specified, the default action # will define HAVE_PTHREAD. # # Please let the authors know if this macro fails on any platform, or if # you have any other suggestions or comments. This macro was based on work # by SGJ on autoconf scripts for FFTW (http://www.fftw.org/) (with help # from M. Frigo), as well as ac_pthread and hb_pthread macros posted by # Alejandro Forero Cuervo to the autoconf macro repository. We are also # grateful for the helpful feedback of numerous users. # # Updated for Autoconf 2.68 by Daniel Richard G. # # LICENSE # # Copyright (c) 2008 Steven G. Johnson # Copyright (c) 2011 Daniel Richard G. # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the # Free Software Foundation, either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General # Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program. If not, see . # # As a special exception, the respective Autoconf Macro's copyright owner # gives unlimited permission to copy, distribute and modify the configure # scripts that are the output of Autoconf when processing the Macro. You # need not follow the terms of the GNU General Public License when using # or distributing such scripts, even though portions of the text of the # Macro appear in them. The GNU General Public License (GPL) does govern # all other use of the material that constitutes the Autoconf Macro. # # This special exception to the GPL applies to versions of the Autoconf # Macro released by the Autoconf Archive. When you make and distribute a # modified version of the Autoconf Macro, you may extend this special # exception to the GPL to apply to your modified version as well. #serial 21 AU_ALIAS([ACX_PTHREAD], [AX_PTHREAD]) AC_DEFUN([AX_PTHREAD], [ AC_REQUIRE([AC_CANONICAL_HOST]) AC_LANG_PUSH([C]) ax_pthread_ok=no # We used to check for pthread.h first, but this fails if pthread.h # requires special compiler flags (e.g. on True64 or Sequent). # It gets checked for in the link test anyway. # First of all, check if the user has set any of the PTHREAD_LIBS, # etcetera environment variables, and if threads linking works using # them: if test x"$PTHREAD_LIBS$PTHREAD_CFLAGS" != x; then save_CFLAGS="$CFLAGS" CFLAGS="$CFLAGS $PTHREAD_CFLAGS" save_LIBS="$LIBS" LIBS="$PTHREAD_LIBS $LIBS" AC_MSG_CHECKING([for pthread_join in LIBS=$PTHREAD_LIBS with CFLAGS=$PTHREAD_CFLAGS]) AC_TRY_LINK_FUNC([pthread_join], [ax_pthread_ok=yes]) AC_MSG_RESULT([$ax_pthread_ok]) if test x"$ax_pthread_ok" = xno; then PTHREAD_LIBS="" PTHREAD_CFLAGS="" fi LIBS="$save_LIBS" CFLAGS="$save_CFLAGS" fi # We must check for the threads library under a number of different # names; the ordering is very important because some systems # (e.g. DEC) have both -lpthread and -lpthreads, where one of the # libraries is broken (non-POSIX). # Create a list of thread flags to try. Items starting with a "-" are # C compiler flags, and other items are library names, except for "none" # which indicates that we try without any flags at all, and "pthread-config" # which is a program returning the flags for the Pth emulation library. ax_pthread_flags="pthreads none -Kthread -kthread lthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config" # The ordering *is* (sometimes) important. Some notes on the # individual items follow: # pthreads: AIX (must check this before -lpthread) # none: in case threads are in libc; should be tried before -Kthread and # other compiler flags to prevent continual compiler warnings # -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h) # -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able) # lthread: LinuxThreads port on FreeBSD (also preferred to -pthread) # -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads) # -pthreads: Solaris/gcc # -mthreads: Mingw32/gcc, Lynx/gcc # -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it # doesn't hurt to check since this sometimes defines pthreads too; # also defines -D_REENTRANT) # ... -mt is also the pthreads flag for HP/aCC # pthread: Linux, etcetera # --thread-safe: KAI C++ # pthread-config: use pthread-config program (for GNU Pth library) case ${host_os} in solaris*) # On Solaris (at least, for some versions), libc contains stubbed # (non-functional) versions of the pthreads routines, so link-based # tests will erroneously succeed. (We need to link with -pthreads/-mt/ # -lpthread.) (The stubs are missing pthread_cleanup_push, or rather # a function called by this macro, so we could check for that, but # who knows whether they'll stub that too in a future libc.) So, # we'll just look for -pthreads and -lpthread first: ax_pthread_flags="-pthreads pthread -mt -pthread $ax_pthread_flags" ;; darwin*) ax_pthread_flags="-pthread $ax_pthread_flags" ;; esac # Clang doesn't consider unrecognized options an error unless we specify # -Werror. We throw in some extra Clang-specific options to ensure that # this doesn't happen for GCC, which also accepts -Werror. AC_MSG_CHECKING([if compiler needs -Werror to reject unknown flags]) save_CFLAGS="$CFLAGS" ax_pthread_extra_flags="-Werror" CFLAGS="$CFLAGS $ax_pthread_extra_flags -Wunknown-warning-option -Wsizeof-array-argument" AC_COMPILE_IFELSE([AC_LANG_PROGRAM([int foo(void);],[foo()])], [AC_MSG_RESULT([yes])], [ax_pthread_extra_flags= AC_MSG_RESULT([no])]) CFLAGS="$save_CFLAGS" if test x"$ax_pthread_ok" = xno; then for flag in $ax_pthread_flags; do case $flag in none) AC_MSG_CHECKING([whether pthreads work without any flags]) ;; -*) AC_MSG_CHECKING([whether pthreads work with $flag]) PTHREAD_CFLAGS="$flag" ;; pthread-config) AC_CHECK_PROG([ax_pthread_config], [pthread-config], [yes], [no]) if test x"$ax_pthread_config" = xno; then continue; fi PTHREAD_CFLAGS="`pthread-config --cflags`" PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`" ;; *) AC_MSG_CHECKING([for the pthreads library -l$flag]) PTHREAD_LIBS="-l$flag" ;; esac save_LIBS="$LIBS" save_CFLAGS="$CFLAGS" LIBS="$PTHREAD_LIBS $LIBS" CFLAGS="$CFLAGS $PTHREAD_CFLAGS $ax_pthread_extra_flags" # Check for various functions. We must include pthread.h, # since some functions may be macros. (On the Sequent, we # need a special flag -Kthread to make this header compile.) # We check for pthread_join because it is in -lpthread on IRIX # while pthread_create is in libc. We check for pthread_attr_init # due to DEC craziness with -lpthreads. We check for # pthread_cleanup_push because it is one of the few pthread # functions on Solaris that doesn't have a non-functional libc stub. # We try pthread_create on general principles. AC_LINK_IFELSE([AC_LANG_PROGRAM([#include static void routine(void *a) { a = 0; } static void *start_routine(void *a) { return a; }], [pthread_t th; pthread_attr_t attr; pthread_create(&th, 0, start_routine, 0); pthread_join(th, 0); pthread_attr_init(&attr); pthread_cleanup_push(routine, 0); pthread_cleanup_pop(0) /* ; */])], [ax_pthread_ok=yes], []) LIBS="$save_LIBS" CFLAGS="$save_CFLAGS" AC_MSG_RESULT([$ax_pthread_ok]) if test "x$ax_pthread_ok" = xyes; then break; fi PTHREAD_LIBS="" PTHREAD_CFLAGS="" done fi # Various other checks: if test "x$ax_pthread_ok" = xyes; then save_LIBS="$LIBS" LIBS="$PTHREAD_LIBS $LIBS" save_CFLAGS="$CFLAGS" CFLAGS="$CFLAGS $PTHREAD_CFLAGS" # Detect AIX lossage: JOINABLE attribute is called UNDETACHED. AC_MSG_CHECKING([for joinable pthread attribute]) attr_name=unknown for attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do AC_LINK_IFELSE([AC_LANG_PROGRAM([#include ], [int attr = $attr; return attr /* ; */])], [attr_name=$attr; break], []) done AC_MSG_RESULT([$attr_name]) if test "$attr_name" != PTHREAD_CREATE_JOINABLE; then AC_DEFINE_UNQUOTED([PTHREAD_CREATE_JOINABLE], [$attr_name], [Define to necessary symbol if this constant uses a non-standard name on your system.]) fi AC_MSG_CHECKING([if more special flags are required for pthreads]) flag=no case ${host_os} in aix* | freebsd* | darwin*) flag="-D_THREAD_SAFE";; osf* | hpux*) flag="-D_REENTRANT";; solaris*) if test "$GCC" = "yes"; then flag="-D_REENTRANT" else # TODO: What about Clang on Solaris? flag="-mt -D_REENTRANT" fi ;; esac AC_MSG_RESULT([$flag]) if test "x$flag" != xno; then PTHREAD_CFLAGS="$flag $PTHREAD_CFLAGS" fi AC_CACHE_CHECK([for PTHREAD_PRIO_INHERIT], [ax_cv_PTHREAD_PRIO_INHERIT], [ AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include ]], [[int i = PTHREAD_PRIO_INHERIT;]])], [ax_cv_PTHREAD_PRIO_INHERIT=yes], [ax_cv_PTHREAD_PRIO_INHERIT=no]) ]) AS_IF([test "x$ax_cv_PTHREAD_PRIO_INHERIT" = "xyes"], [AC_DEFINE([HAVE_PTHREAD_PRIO_INHERIT], [1], [Have PTHREAD_PRIO_INHERIT.])]) LIBS="$save_LIBS" CFLAGS="$save_CFLAGS" # More AIX lossage: compile with *_r variant if test "x$GCC" != xyes; then case $host_os in aix*) AS_CASE(["x/$CC"], [x*/c89|x*/c89_128|x*/c99|x*/c99_128|x*/cc|x*/cc128|x*/xlc|x*/xlc_v6|x*/xlc128|x*/xlc128_v6], [#handle absolute path differently from PATH based program lookup AS_CASE(["x$CC"], [x/*], [AS_IF([AS_EXECUTABLE_P([${CC}_r])],[PTHREAD_CC="${CC}_r"])], [AC_CHECK_PROGS([PTHREAD_CC],[${CC}_r],[$CC])])]) ;; esac fi fi test -n "$PTHREAD_CC" || PTHREAD_CC="$CC" AC_SUBST([PTHREAD_LIBS]) AC_SUBST([PTHREAD_CFLAGS]) AC_SUBST([PTHREAD_CC]) # Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: if test x"$ax_pthread_ok" = xyes; then ifelse([$1],,[AC_DEFINE([HAVE_PTHREAD],[1],[Define if you have POSIX threads libraries and header files.])],[$1]) : else ax_pthread_ok=no $2 fi AC_LANG_POP ])dnl AX_PTHREAD clsync-0.4.1/m4/pkg.m4000066400000000000000000000171671252417542300143730ustar00rootroot00000000000000# pkg.m4 - Macros to locate and utilise pkg-config. -*- Autoconf -*- # serial 1 (pkg-config-0.24) # # Copyright © 2004 Scott James Remnant . # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a # configuration script generated by Autoconf, you may include it under # the same distribution terms that you use for the rest of that program. # PKG_PROG_PKG_CONFIG([MIN-VERSION]) # ---------------------------------- AC_DEFUN([PKG_PROG_PKG_CONFIG], [m4_pattern_forbid([^_?PKG_[A-Z_]+$]) m4_pattern_allow([^PKG_CONFIG(_(PATH|LIBDIR|SYSROOT_DIR|ALLOW_SYSTEM_(CFLAGS|LIBS)))?$]) m4_pattern_allow([^PKG_CONFIG_(DISABLE_UNINSTALLED|TOP_BUILD_DIR|DEBUG_SPEW)$]) AC_ARG_VAR([PKG_CONFIG], [path to pkg-config utility]) AC_ARG_VAR([PKG_CONFIG_PATH], [directories to add to pkg-config's search path]) AC_ARG_VAR([PKG_CONFIG_LIBDIR], [path overriding pkg-config's built-in search path]) if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then AC_PATH_TOOL([PKG_CONFIG], [pkg-config]) fi if test -n "$PKG_CONFIG"; then _pkg_min_version=m4_default([$1], [0.9.0]) AC_MSG_CHECKING([pkg-config is at least version $_pkg_min_version]) if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then AC_MSG_RESULT([yes]) else AC_MSG_RESULT([no]) PKG_CONFIG="" fi fi[]dnl ])# PKG_PROG_PKG_CONFIG # PKG_CHECK_EXISTS(MODULES, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) # # Check to see whether a particular set of modules exists. Similar # to PKG_CHECK_MODULES(), but does not set variables or print errors. # # Please remember that m4 expands AC_REQUIRE([PKG_PROG_PKG_CONFIG]) # only at the first occurence in configure.ac, so if the first place # it's called might be skipped (such as if it is within an "if", you # have to call PKG_CHECK_EXISTS manually # -------------------------------------------------------------- AC_DEFUN([PKG_CHECK_EXISTS], [AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl if test -n "$PKG_CONFIG" && \ AC_RUN_LOG([$PKG_CONFIG --exists --print-errors "$1"]); then m4_default([$2], [:]) m4_ifvaln([$3], [else $3])dnl fi]) # _PKG_CONFIG([VARIABLE], [COMMAND], [MODULES]) # --------------------------------------------- m4_define([_PKG_CONFIG], [if test -n "$$1"; then pkg_cv_[]$1="$$1" elif test -n "$PKG_CONFIG"; then PKG_CHECK_EXISTS([$3], [pkg_cv_[]$1=`$PKG_CONFIG --[]$2 "$3" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes ], [pkg_failed=yes]) else pkg_failed=untried fi[]dnl ])# _PKG_CONFIG # _PKG_SHORT_ERRORS_SUPPORTED # ----------------------------- AC_DEFUN([_PKG_SHORT_ERRORS_SUPPORTED], [AC_REQUIRE([PKG_PROG_PKG_CONFIG]) if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi[]dnl ])# _PKG_SHORT_ERRORS_SUPPORTED # PKG_CHECK_MODULES(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND], # [ACTION-IF-NOT-FOUND]) # # # Note that if there is a possibility the first call to # PKG_CHECK_MODULES might not happen, you should be sure to include an # explicit call to PKG_PROG_PKG_CONFIG in your configure.ac # # # -------------------------------------------------------------- AC_DEFUN([PKG_CHECK_MODULES], [AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl AC_ARG_VAR([$1][_CFLAGS], [C compiler flags for $1, overriding pkg-config])dnl AC_ARG_VAR([$1][_LIBS], [linker flags for $1, overriding pkg-config])dnl pkg_failed=no AC_MSG_CHECKING([for $1]) _PKG_CONFIG([$1][_CFLAGS], [cflags], [$2]) _PKG_CONFIG([$1][_LIBS], [libs], [$2]) m4_define([_PKG_TEXT], [Alternatively, you may set the environment variables $1[]_CFLAGS and $1[]_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details.]) if test $pkg_failed = yes; then AC_MSG_RESULT([no]) _PKG_SHORT_ERRORS_SUPPORTED if test $_pkg_short_errors_supported = yes; then $1[]_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$2" 2>&1` else $1[]_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$2" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$$1[]_PKG_ERRORS" >&AS_MESSAGE_LOG_FD m4_default([$4], [AC_MSG_ERROR( [Package requirements ($2) were not met: $$1_PKG_ERRORS Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. _PKG_TEXT])[]dnl ]) elif test $pkg_failed = untried; then AC_MSG_RESULT([no]) m4_default([$4], [AC_MSG_FAILURE( [The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. _PKG_TEXT To get pkg-config, see .])[]dnl ]) else $1[]_CFLAGS=$pkg_cv_[]$1[]_CFLAGS $1[]_LIBS=$pkg_cv_[]$1[]_LIBS AC_MSG_RESULT([yes]) $3 fi[]dnl ])# PKG_CHECK_MODULES # PKG_INSTALLDIR(DIRECTORY) # ------------------------- # Substitutes the variable pkgconfigdir as the location where a module # should install pkg-config .pc files. By default the directory is # $libdir/pkgconfig, but the default can be changed by passing # DIRECTORY. The user can override through the --with-pkgconfigdir # parameter. AC_DEFUN([PKG_INSTALLDIR], [m4_pushdef([pkg_default], [m4_default([$1], ['${libdir}/pkgconfig'])]) m4_pushdef([pkg_description], [pkg-config installation directory @<:@]pkg_default[@:>@]) AC_ARG_WITH([pkgconfigdir], [AS_HELP_STRING([--with-pkgconfigdir], pkg_description)],, [with_pkgconfigdir=]pkg_default) AC_SUBST([pkgconfigdir], [$with_pkgconfigdir]) m4_popdef([pkg_default]) m4_popdef([pkg_description]) ]) dnl PKG_INSTALLDIR # PKG_NOARCH_INSTALLDIR(DIRECTORY) # ------------------------- # Substitutes the variable noarch_pkgconfigdir as the location where a # module should install arch-independent pkg-config .pc files. By # default the directory is $datadir/pkgconfig, but the default can be # changed by passing DIRECTORY. The user can override through the # --with-noarch-pkgconfigdir parameter. AC_DEFUN([PKG_NOARCH_INSTALLDIR], [m4_pushdef([pkg_default], [m4_default([$1], ['${datadir}/pkgconfig'])]) m4_pushdef([pkg_description], [pkg-config arch-independent installation directory @<:@]pkg_default[@:>@]) AC_ARG_WITH([noarch-pkgconfigdir], [AS_HELP_STRING([--with-noarch-pkgconfigdir], pkg_description)],, [with_noarch_pkgconfigdir=]pkg_default) AC_SUBST([noarch_pkgconfigdir], [$with_noarch_pkgconfigdir]) m4_popdef([pkg_default]) m4_popdef([pkg_description]) ]) dnl PKG_NOARCH_INSTALLDIR # PKG_CHECK_VAR(VARIABLE, MODULE, CONFIG-VARIABLE, # [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) # ------------------------------------------- # Retrieves the value of the pkg-config variable for the given module. AC_DEFUN([PKG_CHECK_VAR], [AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl AC_ARG_VAR([$1], [value of $3 for $2, overriding pkg-config])dnl _PKG_CONFIG([$1], [variable="][$3]["], [$2]) AS_VAR_COPY([$1], [pkg_cv_][$1]) AS_VAR_IF([$1], [""], [$5], [$4])dnl ])# PKG_CHECK_VAR clsync-0.4.1/main.c000066400000000000000000002371771252417542300141250ustar00rootroot00000000000000/* clsync - file tree sync utility based on inotify/kqueue/bsm Copyright (C) 2014 Dmitry Yu Okunev 0x8E30679C This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "common.h" #ifdef CAPABILITIES_SUPPORT # include // for capset()/capget() for --preserve-file-access #endif #if defined(__linux__) | defined(CAPABILITIES_SUPPORT) # include // for prctl() for --preserve-fil-access #endif #include // getpwnam() #include // getgrnam() #include // gkf #ifdef UNSHARE_SUPPORT # include // unshare() #endif #ifdef GETMNTENT_SUPPORT # include // getmntent() # include // umount2() #endif #include "error.h" #include "stringex.h" #include "sync.h" #include "malloc.h" #include "cluster.h" #include "fileutils.h" #include "socket.h" #include "syscalls.h" #include "rules.h" #if CGROUP_SUPPORT # include "cgroup.h" #endif #include "posix-hacks.h" //#include "revision.h" static const struct option long_options[] = { {"watch-dir", required_argument, NULL, WATCHDIR}, {"sync-handler", required_argument, NULL, SYNCHANDLER}, {"--", required_argument, NULL, SYNCHANDLERARGS0}, {"---", required_argument, NULL, SYNCHANDLERARGS1}, {"rules-file", required_argument, NULL, RULESFILE}, {"destination-dir", required_argument, NULL, DESTDIR}, {"mode", required_argument, NULL, MODE}, {"socket", required_argument, NULL, SOCKETPATH}, {"socket-auth", required_argument, NULL, SOCKETAUTH}, {"socket-mod", required_argument, NULL, SOCKETMOD}, {"socket-own", required_argument, NULL, SOCKETOWN}, {"status-file", required_argument, NULL, STATUSFILE}, {"background", optional_argument, NULL, BACKGROUND}, {"config-file", required_argument, NULL, CONFIGFILE}, {"config-block", required_argument, NULL, CONFIGBLOCK}, {"config-block-inherits",required_argument, NULL, CONFIGBLOCKINHERITS}, {"custom-signals", required_argument, NULL, CUSTOMSIGNALS}, {"pid-file", required_argument, NULL, PIDFILE}, {"uid", required_argument, NULL, UID}, {"gid", required_argument, NULL, GID}, {"privileged-uid", required_argument, NULL, PRIVILEGEDUID}, {"privileged-gid", required_argument, NULL, PRIVILEGEDGID}, {"sync-handler-uid", required_argument, NULL, SYNCHANDLERUID}, {"sync-handler-gid", required_argument, NULL, SYNCHANDLERGID}, {"chroot", required_argument, NULL, CHROOT}, #ifdef PIVOTROOT_OPT_SUPPORT {"pivot-root", required_argument, NULL, PIVOT_ROOT}, #endif #ifdef UNSHARE_SUPPORT {"detach-network", required_argument, NULL, DETACH_NETWORK}, {"detach-ipc", required_argument, NULL, DETACH_IPC}, {"detach-miscellanea", optional_argument, NULL, DETACH_MISCELLANEA}, #endif #ifdef CAPABILITIES_SUPPORT # ifdef SECCOMP_SUPPORT {"secure-splitting", no_argument, NULL, SECURESPLITTING}, # endif {"splitting", required_argument, NULL, SPLITTING}, {"check-execvp-args", optional_argument, NULL, CHECK_EXECVP_ARGS}, {"add-permitted-hook-files",required_argument, NULL, ADDPERMITTEDHOOKFILES}, # ifdef SECCOMP_SUPPORT {"seccomp-filter", optional_argument, NULL, SECCOMP_FILTER}, # endif {"forget-privthread-info",optional_argument, NULL, FORGET_PRIVTHREAD_INFO}, {"permit-mprotect", optional_argument, NULL, PERMIT_MPROTECT}, {"shm-mprotect", optional_argument, NULL, SHM_MPROTECT}, #endif #ifdef GETMNTENT_SUPPORT {"mountpoints", required_argument, NULL, MOUNTPOINTS}, #endif #ifdef CAPABILITIES_SUPPORT {"preserve-capabilities",required_argument, NULL, CAP_PRESERVE}, {"inherit-capabilities",optional_argument, NULL, CAPS_INHERIT}, #endif #ifdef CGROUP_SUPPORT {"forbid-devices", optional_argument, NULL, FORBIDDEVICES}, {"cgroup-group-name", required_argument, NULL, CG_GROUPNAME}, #endif {"threading", required_argument, NULL, THREADING}, {"retries", required_argument, NULL, RETRIES}, {"ignore-failures", optional_argument, NULL, IGNOREFAILURES}, {"exit-on-sync-skipping",optional_argument, NULL, EXITONSYNCSKIP}, {"output", required_argument, NULL, OUTPUT_METHOD}, {"one-file-system", optional_argument, NULL, ONEFILESYSTEM}, {"exclude-mount-points",optional_argument, NULL, EXCLUDEMOUNTPOINTS}, #ifdef CLUSTER_SUPPORT {"cluster-iface", required_argument, NULL, CLUSTERIFACE}, // Not implemented, yet {"cluster-ip", required_argument, NULL, CLUSTERMCASTIPADDR}, // Not implemented, yet {"cluster-port", required_argument, NULL, CLUSTERMCASTIPPORT}, // Not implemented, yet {"cluster-timeout", required_argument, NULL, CLUSTERTIMEOUT}, // Not implemented, yet {"cluster-node-name", required_argument, NULL, CLUSTERNODENAME}, // Not implemented, yet {"cluster-hash-dl-min", required_argument, NULL, CLUSTERHDLMIN}, {"cluster-hash-dl-max", required_argument, NULL, CLUSTERHDLMAX}, {"cluster-scan-dl-max", required_argument, NULL, CLUSTERSDLMAX}, #endif {"max-iterations", required_argument, NULL, MAXITERATIONS}, {"standby-file", required_argument, NULL, STANDBYFILE}, {"modification-signature",required_argument, NULL, MODSIGN}, {"timeout-sync", required_argument, NULL, SYNCTIMEOUT}, {"delay-sync", required_argument, NULL, SYNCDELAY}, {"delay-collect", required_argument, NULL, DELAY}, {"delay-collect-bigfile",required_argument, NULL, BFILEDELAY}, {"threshold-bigfile", required_argument, NULL, BFILETHRESHOLD}, {"cancel-syscalls", required_argument, NULL, CANCEL_SYSCALLS}, {"lists-dir", required_argument, NULL, OUTLISTSDIR}, {"have-recursive-sync", optional_argument, NULL, HAVERECURSIVESYNC}, {"synclist-simplify", optional_argument, NULL, SYNCLISTSIMPLIFY}, #ifdef AUTORULESW {"auto-add-rules-w", optional_argument, NULL, AUTORULESW}, #endif {"rsync-inclimit", required_argument, NULL, RSYNCINCLIMIT}, {"rsync-prefer-include",optional_argument, NULL, RSYNCPREFERINCLUDE}, {"ignore-exitcode", required_argument, NULL, IGNOREEXITCODE}, {"dont-unlink-lists", optional_argument, NULL, DONTUNLINK}, {"fts-experimental-optimization", optional_argument, NULL, FTS_EXPERIMENTAL_OPTIMIZATION}, {"full-initialsync", optional_argument, NULL, INITFULL}, {"only-initialsync", optional_argument, NULL, ONLYINITSYNC}, {"skip-initialsync", optional_argument, NULL, SKIPINITSYNC}, {"exit-on-no-events", optional_argument, NULL, EXITONNOEVENTS}, {"exit-hook", required_argument, NULL, EXITHOOK}, {"pre-exit-hook", required_argument, NULL, PREEXITHOOK}, {"verbose", optional_argument, NULL, VERBOSE}, {"debug", optional_argument, NULL, DEBUG}, {"dump-dir", required_argument, NULL, DUMPDIR}, {"quiet", optional_argument, NULL, QUIET}, {"monitor", required_argument, NULL, MONITOR}, {"label", required_argument, NULL, LABEL}, {"help", optional_argument, NULL, HELP}, {"version", optional_argument, NULL, SHOW_VERSION}, {NULL, 0, NULL, 0} }; #ifdef UNSHARE_SUPPORT static char *const detachnetworkways[] = { [DN_OFF] = "off", [DN_NONPRIVILEGED] = "non-privileged", [DN_EVERYWHERE] = "everywhere", NULL, }; #endif #ifdef PIVOTROOT_OPT_SUPPORT static char *const pivotrootways[] = { [PW_OFF] = "off", [PW_DIRECT] = "direct", [PW_AUTO] = "auto", [PW_AUTORO] = "auto-ro", NULL, }; #endif enum xstatfield { X_STAT_FIELD_RESET = 0, X_STAT_FIELD_DEV, X_STAT_FIELD_INO, X_STAT_FIELD_MODE, X_STAT_FIELD_NLINK, X_STAT_FIELD_UID, X_STAT_FIELD_GID, X_STAT_FIELD_RDEV, X_STAT_FIELD_SIZE, X_STAT_FIELD_BLKSIZE, X_STAT_FIELD_BLOCKS, X_STAT_FIELD_ATIME, X_STAT_FIELD_MTIME, X_STAT_FIELD_CTIME, X_STAT_FIELD_ALL, }; uint32_t xstatfield_to_statfield[] = { [X_STAT_FIELD_RESET] = STAT_FIELD_RESET, [X_STAT_FIELD_DEV] = STAT_FIELD_DEV, [X_STAT_FIELD_INO] = STAT_FIELD_INO, [X_STAT_FIELD_MODE] = STAT_FIELD_MODE, [X_STAT_FIELD_NLINK] = STAT_FIELD_NLINK, [X_STAT_FIELD_UID] = STAT_FIELD_UID, [X_STAT_FIELD_GID] = STAT_FIELD_GID, [X_STAT_FIELD_RDEV] = STAT_FIELD_RDEV, [X_STAT_FIELD_SIZE] = STAT_FIELD_SIZE, [X_STAT_FIELD_BLKSIZE] = STAT_FIELD_BLKSIZE, [X_STAT_FIELD_BLOCKS] = STAT_FIELD_BLOCKS, [X_STAT_FIELD_ATIME] = STAT_FIELD_ATIME, [X_STAT_FIELD_MTIME] = STAT_FIELD_MTIME, [X_STAT_FIELD_CTIME] = STAT_FIELD_CTIME, [X_STAT_FIELD_ALL] = STAT_FIELD_ALL, }; static char *const stat_fields[] = { [X_STAT_FIELD_RESET] = "", [X_STAT_FIELD_DEV] = "dev", [X_STAT_FIELD_INO] = "ino", [X_STAT_FIELD_MODE] = "mode", [X_STAT_FIELD_NLINK] = "nlink", [X_STAT_FIELD_UID] = "uid", [X_STAT_FIELD_GID] = "gid", [X_STAT_FIELD_RDEV] = "rdev", [X_STAT_FIELD_SIZE] = "size", [X_STAT_FIELD_BLKSIZE] = "blksize", [X_STAT_FIELD_BLOCKS] = "blocks", [X_STAT_FIELD_ATIME] = "atime", [X_STAT_FIELD_MTIME] = "mtime", [X_STAT_FIELD_CTIME] = "ctime", [X_STAT_FIELD_ALL] = "*", NULL }; enum x_csc_bm { X_CSC_RESET = 0, X_CSC_MON_STAT, }; uint32_t xcsc_to_csc[] = { [X_CSC_RESET] = CSC_RESET, [X_CSC_MON_STAT] = CSC_MON_STAT, }; static char *const syscalls_bitmask[] = { [X_CSC_RESET] = "", [X_CSC_MON_STAT] = "mon_stat", // disable {l,}stat{,64}()-s in mon_*.c NULL }; #ifdef CAPABILITIES_SUPPORT enum x_capabilities { X_CAP_RESET = 0, X_CAP_DAC_READ_SEARCH, X_CAP_SETUID, X_CAP_SETGID, X_CAP_KILL, X_CAP_MAX }; __u32 xcap_to_cap[] = { [X_CAP_DAC_READ_SEARCH] = CAP_DAC_READ_SEARCH, [X_CAP_SETUID] = CAP_SETUID, [X_CAP_SETGID] = CAP_SETGID, [X_CAP_KILL] = CAP_KILL, }; static char *const capabilities[] = { [X_CAP_RESET] = "", [X_CAP_DAC_READ_SEARCH] = "CAP_DAC_READ_SEARCH", [X_CAP_SETUID] = "CAP_SETUID", [X_CAP_SETGID] = "CAP_SETGID", [X_CAP_KILL] = "CAP_KILL", NULL }; #define XCAP_TO_CAP(x) (xcap_to_cap[x]) static char *const capsinherits[] = { [CI_PERMITTED] = "permittied", [CI_DONTTOUCH] = "dont-touch", [CI_CLSYNC] = "clsync", [CI_EMPTY] = "empty", }; #endif static char *const socketauth[] = { [SOCKAUTH_UNSET] = "", [SOCKAUTH_NULL] = "null", // [SOCKAUTH_PAM] = "pam", NULL }; static char *const threading_modes[] = { [PM_OFF] = "off", [PM_SAFE] = "safe", [PM_FULL] = "full", NULL }; #ifdef CAPABILITIES_SUPPORT static char *const splitting_modes[] = { [SM_OFF] = "off", [SM_THREAD] = "thread", [SM_PROCESS] = "process", NULL }; #endif static char *const notify_engines[] = { [NE_UNDEFINED] = "", [NE_INOTIFY] = "inotify", [NE_KQUEUE] = "kqueue", [NE_FANOTIFY] = "fanotify", [NE_BSM] = "bsm", [NE_BSM_PREFETCH] = "bsm_prefetch", [NE_DTRACEPIPE] = "dtracepipe", [NE_GIO] = "gio", NULL }; static char *const output_methods[] = { [OM_STDERR] = "stderr", [OM_STDOUT] = "stdout", [OM_SYSLOG] = "syslog", NULL }; static char *const modes[] = { [MODE_UNSET] = "", [MODE_SIMPLE] = "simple", [MODE_DIRECT] = "direct", [MODE_SHELL] = "shell", [MODE_RSYNCSHELL] = "rsyncshell", [MODE_RSYNCDIRECT] = "rsyncdirect", [MODE_RSYNCSO] = "rsyncso", [MODE_SO] = "so", NULL }; static char *const status_descr[] = { [STATE_EXIT] = "exiting", [STATE_STARTING] = "starting", [STATE_RUNNING] = "running", [STATE_SYNCHANDLER_ERR] = "synchandler error", [STATE_REHASH] = "rehashing", [STATE_TERM] = "terminating", [STATE_THREAD_GC] = "thread gc", [STATE_INITSYNC] = "initsync", [STATE_HOLDON] = "hold on", NULL }; int syntax() { info("possible options:"); int i=-1; while (long_options[++i].name != NULL) { switch (long_options[i].val) { case SYNCHANDLERARGS0: case SYNCHANDLERARGS1: continue; } if (long_options[i].val & OPTION_CONFIGONLY) continue; info("\t--%-24s%c%c%s", long_options[i].name, long_options[i].val & OPTION_LONGOPTONLY ? ' ' : '-', long_options[i].val & OPTION_LONGOPTONLY ? ' ' : long_options[i].val, (long_options[i].has_arg == required_argument ? " argument" : "")); } exit(EINVAL); } int ncpus; pid_t parent_pid; pid_t waitpid_timed(pid_t child_pid, int *status_p, long sec, long nsec) { struct timespec ts; int status; ts.tv_sec = sec; ts.tv_nsec = nsec; while (ts.tv_sec >= 0) { if (waitpid(child_pid, &status, WNOHANG)<0) { if (errno==ECHILD) return child_pid; return -1; } else if (status_p != NULL) *status_p = status; ts.tv_nsec -= WAITPID_TIMED_GRANULARITY; if (ts.tv_nsec < 0) { ts.tv_nsec += 1000*1000*1000; ts.tv_sec--; } } return 0; } int parent_isalive() { int rc; debug(12, "parent_pid == %u", parent_pid); if ((rc=kill(parent_pid, 0))) { if (errno == ESRCH) { debug(1, "kill(%u, 0) => %i; errno => %s", parent_pid, rc, strerror(errno)); return 0; } } return 1; } void child_sigchld() { if (getppid() != 1) return; debug(1, "Got SIGCHLD (parent ended). Exit."); exit(-1); return; } int sethandler_sigchld(void (*handler)()) { struct sigaction sa; sa.sa_handler = handler; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART | SA_NOCLDSTOP; critical_on (sigaction(SIGCHLD, &sa, 0) == -1); return 0; } # ifndef __linux__ void *watchforparent(void *parent_pid_p) { while (1) { child_sigchld(); sleep(SLEEP_SECONDS); } return NULL; } # endif pthread_t pthread_watchforparent; pid_t fork_helper() { pid_t pid = fork(); if (!pid) { // is child? parent_pid = getppid(); // Anti-zombie: # ifdef __linux__ // Linux have support of "prctl(PR_SET_PDEATHSIG, signal);" sethandler_sigchld(child_sigchld); prctl(PR_SET_PDEATHSIG, SIGCHLD); # else pthread_create(&pthread_watchforparent, NULL, watchforparent, &parent_pid); # endif debug(20, "parent_pid == %u", parent_pid); return 0; } return pid; } int version() { info(PROGRAM" v%i.%i.%i"REVISION"\n\t"AUTHOR"\n\nCompiled with options" #ifdef _DEBUG_SUPPORT " -D_DEBUG_SUPPORT" #endif #ifdef _DEBUG_FORCE " -D_DEBUG_FORCE" #endif #ifdef KQUEUE_SUPPORT " -DKQUEUE_SUPPORT" #endif #ifdef INOTIFY_SUPPORT " -DINOTIFY_SUPPORT" #endif #ifdef INOTIFY_OLD " -DINOTIFY_OLD" #endif #ifdef FANOTIFY_SUPPORT " -DFANOTIFY_SUPPORT" #endif #ifdef BSM_SUPPORT " -DBSM_SUPPORT" #endif #ifdef GIO_SUPPORT " -DGIO_SUPPORT" #endif #ifdef DTRACEPIPE_SUPPORT " -DDTRACEPIPE_SUPPORT" #endif #ifdef BACKTRACE_SUPPORT " -DBACKTRACE_SUPPORT" #endif #ifdef CAPABILITIES_SUPPORT " -DCAPABILITIES_SUPPORT" #endif #ifdef SECCOMP_SUPPORT " -DSECCOMP_SUPPORT" #endif #ifdef GETMNTENT_SUPPORT " -DGETMNTENT_SUPPORT" #endif #ifdef UNSHARE_SUPPORT " -DUNSHARE_SUPPORT" #endif #ifdef PIVOTROOT_OPT_SUPPORT " -DPIVOTROOT_OPT_SUPPORT" #endif #ifdef CGROUP_SUPPORT " -DCGROUP_SUPPORT" #endif #ifdef TRE_SUPPORT " -DTRE_SUPPORT" #endif #ifdef HL_LOCKS " -DHL_LOCKS" #endif , VERSION_MAJ, VERSION_MID, VERSION_MIN); exit(0); } int clsyncapi_getapiversion() { return CLSYNC_API_VERSION; } /** * @brief Gets raw (string) an option value by an option name * * @param[in] _ctx_p Context @ @param[in] variable_name The name of the option * * @retval char * Pointer to newly allocated string, if successful * @retval NULL On error * */ const char *parameter_get(const char *variable_name, void *_ctx_p) { const ctx_t *ctx_p = _ctx_p; const struct option *long_option_p = long_options; int param_id = -1; debug(8, "(\"%s\", %p)", variable_name, ctx_p); while (long_option_p->name != NULL) { if (!strcmp(long_option_p->name, variable_name)) { param_id = long_option_p->val; break; } long_option_p++; } if (param_id == -1) { errno = ENOENT; return NULL; } debug(9, "ctx_p->flags_values_raw[%i] == \"%s\"", param_id, ctx_p->flags_values_raw[param_id]); return ctx_p->flags_values_raw[param_id]; } /** * @brief Gets raw (string) an option value by an option name and * updates ctx_p->synchandler_argf * * @param[in] _ctx_p Context @ @param[in] variable_name The name of the option * * @retval char * Pointer to newly allocated string, if successful * @retval NULL On error * */ const char *parameter_get_wmacro(const char *variable_name, void *_ctx_p) { ctx_t *ctx_p = _ctx_p; static struct dosync_arg dosync_arg = {0}; debug(9, "(\"%s\", %p)", variable_name, _ctx_p); if (*variable_name < 'A' || *variable_name > 'Z') return parameter_get(variable_name, _ctx_p); if (!strcmp(variable_name, "RSYNC-ARGS")) { ctx_p->synchandler_argf |= SHFL_RSYNC_ARGS; return NULL; } if (!strcmp(variable_name, "INCLUDE-LIST")) { ctx_p->synchandler_argf |= SHFL_INCLUDE_LIST; return NULL; } const char *r = sync_parameter_get(variable_name, &dosync_arg); if (r == dosync_arg.outf_path) { ctx_p->synchandler_argf |= SHFL_INCLUDE_LIST_PATH; return NULL; } if (r == dosync_arg.excf_path) { ctx_p->synchandler_argf |= SHFL_EXCLUDE_LIST_PATH; return NULL; } errno = ENOENT; return NULL; } /** * @brief Expands option values, e. g. "/var/log/clsync-%label%.pid" -> "/var/log/clsync-clone.pid" * * @param[in] ctx_p Context * @param[in] arg An allocated string with unexpanded value. Will be free'd * @param[out] macro_count_p A pointer to count of found macro-s * @param[out] expand_count_p A pointer to count of expanded macro-s * @param[in] parameter_get A function to resolve macro-s * @param[in] parameter_get_arg An argument to the function * * @retval char * Pointer to newly allocated string, if successful * @retval NULL On error * */ char *parameter_expand( ctx_t *ctx_p, char *arg, int exceptionflags, int *macro_count_p, int *expand_count_p, const char *(*parameter_get)(const char *variable_name, void *arg), void *parameter_get_arg ) { debug(9, "(ctx_p, \"%s\" [%p], ...)", arg, arg); char *ret = NULL; size_t ret_size = 0, ret_len = 0; #ifdef PARANOID if (arg == NULL) { errno = EINVAL; return NULL; } #endif if (macro_count_p != NULL) *macro_count_p = 0; if (expand_count_p != NULL) *expand_count_p = 0; char *ptr = &arg[-1]; while (1) { ptr++; switch (*ptr) { case 0: if (ret == NULL) { debug(3, "Expanding value \"%s\" to \"%s\" (case #1)", arg, arg); return arg; } ret[ret_len] = 0; debug(3, "Expanding value \"%s\" to \"%s\" (case #0)", arg, ret); free(arg); return ret; case '%': { if (ptr[1] == '%') { ret[ret_len++] = *(ptr++); break; } debug(25, "A macro"); char nest_searching = 1; char *ptr_nest = ptr; while (nest_searching) { ptr_nest++; switch (*ptr_nest) { case 0: ret[ret_len] = 0; if (!(exceptionflags&1)) warning("Unexpected end of macro-substitution \"%s\" in value \"%s\"; result value is \"%s\"", ptr, arg, ret); free(arg); return ret; case '%': { char *variable_name; const char *variable_value; size_t variable_value_len; if (macro_count_p != NULL) (*macro_count_p)++; nest_searching = 0; *ptr_nest = 0; variable_name = &ptr[1]; debug(15, "The macro is \"%s\"", variable_name); if (!strcmp(variable_name, "PID")) { debug(35, "\"PID\"", variable_name); if (!*ctx_p->pid_str) { snprintf(ctx_p->pid_str, 64, "%u", ctx_p->pid); ctx_p->pid_str_len = strlen(ctx_p->pid_str); } variable_value = ctx_p->pid_str; variable_value_len = ctx_p->pid_str_len; } else if (*variable_name >= 'A' && *variable_name <= 'Z' && (exceptionflags&4)) { // Lazy substitution, preserving the value debug(35, "Lazy substitution", variable_name); variable_value = ptr; variable_value_len = (ptr_nest - ptr + 1); parameter_get(variable_name, parameter_get_arg); } else { // Substituting debug(35, "Substitution", variable_name); errno = 0; variable_value = parameter_get(variable_name, parameter_get_arg); if (variable_value == NULL) { if (!(exceptionflags&2) && (errno != ENOENT)) warning("Variable \"%s\" is not set (%s)", variable_name, strerror(errno)); *ptr_nest = '%'; errno = 0; break; } variable_value_len = strlen(variable_value); if (expand_count_p != NULL) (*expand_count_p)++; } *ptr_nest = '%'; if (ret_len+variable_value_len+1 >= ret_size) { ret_size = ret_len+variable_value_len+1 + ALLOC_PORTION; ret = xrealloc(ret, ret_size); } memcpy(&ret[ret_len], variable_value, variable_value_len); ret_len += variable_value_len; break; } } } ptr = ptr_nest; break; } default: { if (ret_len+2 >= ret_size) { ret_size += ALLOC_PORTION+2; ret = xrealloc(ret, ret_size); } ret[ret_len++] = *ptr; break; } } } error("Unknown internal error"); return arg; } static inline int synchandler_arg(char *arg, size_t arg_len, void *_ctx_p, enum shargsid shargsid) { ctx_t *ctx_p = _ctx_p; debug(9, "(\"%s\" [%p], %u, %p, %u)", arg, arg, arg_len, _ctx_p, shargsid); if (!strcmp(arg, "%RSYNC-ARGS%")) { char *args_e[] = RSYNC_ARGS_E, *args_i[] = RSYNC_ARGS_I, **args_p; free(arg); args_p = ctx_p->flags[RSYNCPREFERINCLUDE] ? args_i : args_e; while (*args_p != NULL) { #ifdef VERYPARANOID if (!strcmp(*args_p, "%RSYNC-ARGS%")) { errno = EINVAL; critical("Infinite recursion detected"); } #endif if (synchandler_arg(strdup(*args_p), strlen(*args_p), ctx_p, shargsid)) return errno; args_p++; } return 0; } if (ctx_p->synchandler_args[shargsid].c >= MAXARGUMENTS-2) { errno = E2BIG; error("There're too many sync-handler arguments " "(%u > "XTOSTR(MAXARGUMENTS-2)"; arg == \"%s\").", arg); return errno; } #ifdef _DEBUG_FORCE debug(14, "ctx_p->synchandler_args[%u].v[%u] = %p", shargsid, ctx_p->synchandler_args[shargsid].c, arg); #endif ctx_p->synchandler_args[shargsid].v[ctx_p->synchandler_args[shargsid].c++] = arg; return 0; } static int synchandler_arg0(char *arg, size_t arg_len, void *_ctx_p) { return synchandler_arg(arg, arg_len, _ctx_p, SHARGS_PRIMARY); } static int synchandler_arg1(char *arg, size_t arg_len, void *_ctx_p) { return synchandler_arg(arg, arg_len, _ctx_p, SHARGS_INITIAL); } /* strtol wrapper with error checks */ static inline long xstrtol(const char *str, int *err) { long res; char *endptr; errno = 0; res = strtol(str, &endptr, 0); if (errno || *endptr) { error("argument \"%s\" can't be parsed as a number", str); *err = EINVAL; } return res; } static inline int parse_customsignals(ctx_t *ctx_p, char *arg) { char *ptr = arg, *start = arg; int ret = 0; unsigned int signal; do { switch (*ptr) { case 0: case ',': case ':': // TODO: use xstrtol() instead of atoi() //signal = (unsigned int)xstrtol(start, &ret); signal = (unsigned int)atoi(start); if (ret) { errno = ret; return errno; } if (signal == 0) { // flushing the setting int i = 0; while (i < 256) { if (ctx_p->customsignal[i]) { free(ctx_p->customsignal[i]); ctx_p->customsignal[i] = NULL; } i++; } #ifdef _DEBUG_FORCE fprintf(stderr, "Force-Debug: parse_parameter(): Reset custom signals.\n"); #endif } else { if (*ptr != ':') { char ch = *ptr; *ptr = 0; errno = EINVAL; error("Expected \":\" in \"%s\"", start); *ptr = ch; return errno; } { char ch, *end; ptr++; end = ptr; while (*end && *end != ',') end++; if (end == ptr) { errno = EINVAL; error("Empty config block name on signal \"%u\"", signal); return errno; } if (signal > MAXSIGNALNUM) { errno = EINVAL; error("Too high value of the signal: \"%u\" > "XTOSTR(MAXSIGNALNUM)"", signal); return errno; } ch = *end; *end = 0; ctx_p->customsignal[signal] = strdup(ptr); *end = ch; #ifdef _DEBUG_FORCE fprintf(stderr, "Force-Debug: parse_parameter(): Adding custom signal %u.\n", signal); #endif ptr = end; } } start = ptr+1; break; case '0' ... '9': break; default: errno = EINVAL; error("Expected a digit, comma (or colon) but got \"%c\"", *ptr); return errno; } } while (*(ptr++)); return 0; } static int parse_parameter(ctx_t *ctx_p, uint16_t param_id, char *arg, paramsource_t paramsource) { int ret = 0; #ifdef _DEBUG_FORCE fprintf(stderr, "Force-Debug: parse_parameter(): %i: %i = \"%s\"\n", paramsource, param_id, arg); #endif switch (paramsource) { case PS_CONTROL: case PS_ARGUMENT: if (param_id & OPTION_CONFIGONLY) { syntax(); return 0; } ctx_p->flags_set[param_id] = 1; break; case PS_CONFIG: if (ctx_p->flags_set[param_id]) return 0; ctx_p->flags_set[param_id] = 1; break; case PS_DEFAULTS: #ifdef VERYPARANOID if (ctx_p->flags_set[param_id]) { error("Parameter #%i is already set. No need in setting the default value.", param_id); return 0; } #endif break; /* case PS_REHASH: arg = ctx_p->flags_values_raw[param_id]; #ifdef VERYPARANOID critical_on (arg == NULL); #endif debug(9, "Rehash setting %i -> \"%s\"", param_id, arg); break;*/ case PS_CORRECTION: critical_on (arg == NULL); debug(9, "Correcting setting %i -> \"%s\"", param_id, arg); break; default: error("Unknown parameter #%i source (value \"%s\").", param_id, arg!=NULL ? arg : ""); break; } if ((arg != NULL) /*&& (paramsource != PS_REHASH)*/) { if (param_id != SYNCHANDLERARGS0 && param_id != SYNCHANDLERARGS1) arg = parameter_expand(ctx_p, arg, 0, NULL, NULL, parameter_get, ctx_p); if (ctx_p->flags_values_raw[param_id] != NULL) free(ctx_p->flags_values_raw[param_id]); ctx_p->flags_values_raw[param_id] = arg; } switch (param_id) { case '?': case HELP: syntax(); break; case CONFIGFILE: ctx_p->config_path = *arg ? arg : NULL; break; case CONFIGBLOCK: ctx_p->config_block = *arg ? arg : NULL; break; case CONFIGBLOCKINHERITS: break; case CUSTOMSIGNALS: if (paramsource == PS_CONTROL) { warning("Cannot change \"custom-signal\" in run-time. Ignoring."); return 0; } if (parse_customsignals(ctx_p, arg)) return errno; break; case UID: { struct passwd *pwd = getpwnam(arg); ctx_p->flags[param_id]++; if (pwd == NULL) { ctx_p->uid = (unsigned int)xstrtol(arg, &ret); break; } ctx_p->uid = pwd->pw_uid; break; } case GID: { struct group *grp = getgrnam(arg); ctx_p->flags[param_id]++; if (grp == NULL) { ctx_p->gid = (unsigned int)xstrtol(arg, &ret); break; } ctx_p->gid = grp->gr_gid; break; } #ifdef CAPABILITIES_SUPPORT # ifdef SECCOMP_SUPPORT case SECURESPLITTING: { if (ctx_p->flags_values_raw[CHECK_EXECVP_ARGS] == NULL) ctx_p->flags[CHECK_EXECVP_ARGS]++; if (ctx_p->flags_values_raw[SECCOMP_FILTER] == NULL) ctx_p->flags[SECCOMP_FILTER]++; if (ctx_p->flags_values_raw[FORBIDDEVICES] == NULL) ctx_p->flags[FORBIDDEVICES]++; if (ctx_p->flags_values_raw[SPLITTING] != NULL) break; arg = "process"; } case SPLITTING: { char *value, *arg_orig = arg; if (!*arg) { ctx_p->flags[param_id] = 0; return 0; } splittingmode_t splittingmode = getsubopt(&arg, splitting_modes, &value); if((int)splittingmode == -1) { errno = EINVAL; error("Invalid splitting mode entered: \"%s\"", arg_orig); return EINVAL; } ctx_p->flags[SPLITTING] = splittingmode; if (param_id != SECURESPLITTING) break; switch (splittingmode) { case SM_THREAD: ctx_p->flags[FORGET_PRIVTHREAD_INFO]++; break; case SM_PROCESS: break; case SM_OFF: errno = EINVAL; error("Cannot understand \"--secure-splitting=off\". This configuration line have no sence."); break; } if (ctx_p->flags_values_raw[PERMIT_MPROTECT] == NULL) ctx_p->flags[PERMIT_MPROTECT] = (splittingmode != SM_THREAD); break; } # endif case PRIVILEGEDUID: { struct passwd *pwd = getpwnam(arg); ctx_p->flags[param_id]++; if (pwd == NULL) { ctx_p->privileged_uid = (unsigned int)xstrtol(arg, &ret); break; } ctx_p->privileged_uid = pwd->pw_uid; break; } case PRIVILEGEDGID: { struct group *grp = getgrnam(arg); ctx_p->flags[param_id]++; if (grp == NULL) { ctx_p->privileged_gid = (unsigned int)xstrtol(arg, &ret); break; } ctx_p->privileged_gid = grp->gr_gid; break; } case SYNCHANDLERUID: { struct passwd *pwd = getpwnam(arg); ctx_p->flags[param_id]++; if (pwd == NULL) { ctx_p->synchandler_uid = (unsigned int)xstrtol(arg, &ret); break; } ctx_p->synchandler_uid = pwd->pw_uid; break; } case SYNCHANDLERGID: { struct group *grp = getgrnam(arg); ctx_p->flags[param_id]++; if (grp == NULL) { ctx_p->synchandler_gid = (unsigned int)xstrtol(arg, &ret); break; } ctx_p->synchandler_gid = grp->gr_gid; break; } case CAP_PRESERVE: { char *subopts = arg; ctx_p->caps = 0; while (*subopts != 0) { char *value; __u32 cap = getsubopt(&subopts, capabilities, &value); debug(4, "cap == 0x%x", cap); if (cap != X_CAP_RESET) ctx_p->caps |= CAP_TO_MASK(XCAP_TO_CAP(cap)); } break; } case CAPS_INHERIT: { char *value, *arg_orig = arg; if (!*arg) { ctx_p->flags[param_id] = 0; return 0; } capsinherit_t capsinherit = getsubopt(&arg, capsinherits, &value); if((int)capsinherit == -1) { errno = EINVAL; error("Invalid capabilities inheriting mode entered: \"%s\"", arg_orig); return EINVAL; } ctx_p->flags[CAPS_INHERIT] = capsinherit; break; } #endif case CHROOT: if (paramsource == PS_CONTROL) { warning("Cannot change \"chroot\" in run-time. Ignoring."); return 0; } if (!*arg) { free(ctx_p->chroot_dir); ctx_p->chroot_dir = NULL; return 0; } ctx_p->chroot_dir = arg; break; #ifdef PIVOTROOT_OPT_SUPPORT case PIVOT_ROOT: { char *value, *arg_orig = arg; if (!*arg) { ctx_p->flags[PIVOT_ROOT] = DEFAULT_PIVOT_MODE; return 0; } pivotroot_way_t pivotway = getsubopt(&arg, pivotrootways, &value); if((int)pivotway == -1) { errno = EINVAL; error("Invalid pivot_root use way entered: \"%s\"", arg_orig); return EINVAL; } ctx_p->flags[PIVOT_ROOT] = pivotway; break; } #endif #ifdef UNSHARE_SUPPORT case DETACH_NETWORK: { char *value, *arg_orig = arg; if (!*arg) { ctx_p->flags[param_id] = 0; return 0; } detachnetwork_way_t detachnetwork_way = getsubopt(&arg, detachnetworkways, &value); if((int)detachnetwork_way == -1) { errno = EINVAL; error("Invalid network detach way entered: \"%s\"", arg_orig); return EINVAL; } ctx_p->flags[DETACH_NETWORK] = detachnetwork_way; break; } #endif #ifdef CAPABILITIES_SUPPORT case ADDPERMITTEDHOOKFILES: { char *ptr; if (paramsource == PS_CONTROL) { warning("Cannot change \"add-permitted-hook-files\" in run-time. Ignoring."); return 0; } while (ctx_p->permitted_hookfiles) free(ctx_p->permitted_hookfile[--ctx_p->permitted_hookfiles]); ptr = arg; while (1) { char *end = strchr(ptr, ','); if (end != NULL) *end = 0; if (!*ptr) { while (ctx_p->permitted_hookfiles) free(ctx_p->permitted_hookfile[--ctx_p->permitted_hookfiles]); if (end != NULL) ptr = &end[1]; continue; } if (ctx_p->permitted_hookfiles >= MAXPERMITTEDHOOKFILES) { errno = EINVAL; error("Too many permitted hook files"); return errno; } ctx_p->permitted_hookfile[ctx_p->permitted_hookfiles++] = strdup(ptr); if (end == NULL) break; *end = ','; ptr = &end[1]; } break; } #endif #ifdef GETMNTENT_SUPPORT case MOUNTPOINTS: { char *ptr; if (paramsource == PS_CONTROL) { warning("Cannot change \"mountpoints\" in run-time. Ignoring."); return 0; } while (ctx_p->mountpoints) free(ctx_p->mountpoint[--ctx_p->mountpoints]); if (!*arg) break; ptr = arg; while (1) { char *end = strchr(ptr, ','); if (end != NULL) *end = 0; if (!*ptr) { while (ctx_p->mountpoints) free(ctx_p->mountpoint[--ctx_p->mountpoints]); if (end != NULL) ptr = &end[1]; continue; } if (ctx_p->mountpoints >= MAXMOUNTPOINTS) { errno = EINVAL; error("Too many mountpoints"); return errno; } ctx_p->mountpoint[ctx_p->mountpoints++] = strdup(ptr); if (end == NULL) break; *end = ','; ptr = &end[1]; } break; } #endif case PIDFILE: if (paramsource == PS_CONTROL) { warning("Cannot change \"pid-file\" in run-time. Ignoring."); return 0; } ctx_p->pidfile = arg; break; case RETRIES: ctx_p->retries = (unsigned int)xstrtol(arg, &ret); break; case THREADING: { char *value, *arg_orig = arg; if (!*arg) { ctx_p->flags[param_id] = 0; return 0; } threadingmode_t threadingmode = getsubopt(&arg, threading_modes, &value); if((int)threadingmode == -1) { errno = EINVAL; error("Invalid threading mode entered: \"%s\"", arg_orig); return EINVAL; } ctx_p->flags[THREADING] = threadingmode; break; } case OUTPUT_METHOD: { char *value, *arg_orig = arg; if (!*arg) { ctx_p->flags[param_id] = 0; return 0; } outputmethod_t outputmethod = getsubopt(&arg, output_methods, &value); if((int)outputmethod == -1) { errno = EINVAL; error("Invalid log writing destination entered: \"%s\"", arg_orig); return EINVAL; } ctx_p->flags[OUTPUT_METHOD] = outputmethod; break; } #ifdef CLUSTER_SUPPORT case CLUSTERIFACE: ctx_p->cluster_iface = arg; break; case CLUSTERMCASTIPADDR: ctx_p->cluster_mcastipaddr = arg; break; case CLUSTERMCASTIPPORT: ctx_p->cluster_mcastipport = (uint16_t)xstrtol(arg, &ret); break; case CLUSTERTIMEOUT: ctx_p->cluster_timeout = (unsigned int)xstrtol(arg, &ret); break; case CLUSTERNODENAME: ctx_p->cluster_nodename = arg; break; case CLUSTERHDLMIN: ctx_p->cluster_hash_dl_min = (uint16_t)xstrtol(arg, &ret); break; case CLUSTERHDLMAX: ctx_p->cluster_hash_dl_max = (uint16_t)xstrtol(arg, &ret); break; case CLUSTERSDLMAX: ctx_p->cluster_scan_dl_max = (uint16_t)xstrtol(arg, &ret); break; #endif case OUTLISTSDIR: ctx_p->listoutdir = arg; break; case LABEL: ctx_p->label = arg; break; #ifdef CGROUP_SUPPORT case CG_GROUPNAME: ctx_p->cg_groupname = arg; break; #endif case STANDBYFILE: if(strlen(arg)) { ctx_p->standbyfile = arg; ctx_p->flags[STANDBYFILE] = 1; } else { ctx_p->standbyfile = NULL; ctx_p->flags[STANDBYFILE] = 0; } break; case MODSIGN: { char *subopts = arg; ctx_p->flags[MODSIGN] = 0; while (*subopts != 0) { char *value; typeof(ctx_p->flags[MODSIGN]) field = getsubopt(&subopts, stat_fields, &value); debug(4, "field == %i -> %x (%s)", field, xstatfield_to_statfield[field], value); if (field != X_STAT_FIELD_RESET) ctx_p->flags[MODSIGN] |= xstatfield_to_statfield[field]; } debug(5, "ctx_p->flags[MODSIGN] == 0x%x", ctx_p->flags[MODSIGN]); break; } case SYNCDELAY: ctx_p->syncdelay = (unsigned int)xstrtol(arg, &ret); break; case DELAY: ctx_p->_queues[QUEUE_NORMAL].collectdelay = (unsigned int)xstrtol(arg, &ret); break; case BFILEDELAY: ctx_p->_queues[QUEUE_BIGFILE].collectdelay = (unsigned int)xstrtol(arg, &ret); break; case BFILETHRESHOLD: ctx_p->bfilethreshold = (unsigned long)xstrtol(arg, &ret); break; case CANCEL_SYSCALLS: { char *subopts = arg; while (*subopts != 0) { char *value; typeof(ctx_p->flags[CANCEL_SYSCALLS]) syscall_bitmask = getsubopt(&subopts, syscalls_bitmask, &value); debug(4, "cancel syscall == %i -> 0x%x", syscall_bitmask, xcsc_to_csc[syscall_bitmask]); if (syscall_bitmask == X_CSC_RESET) { ctx_p->flags[CANCEL_SYSCALLS] = 0; continue; } ctx_p->flags[CANCEL_SYSCALLS] |= xcsc_to_csc[syscall_bitmask]; } break; } case MONITOR: { char *value, *arg_orig = arg; if (paramsource == PS_CONTROL) { warning("Cannot change \"monitor\" in run-time. Ignoring."); return 0; } if (!*arg) { ctx_p->flags_set[param_id] = 0; return 0; } notifyengine_t notifyengine = getsubopt(&arg, notify_engines, &value); if((int)notifyengine == -1) { errno = EINVAL; error("Invalid FS monitor subsystem entered: \"%s\"", arg_orig); return EINVAL; } switch (notifyengine) { #ifdef FANOTIFY_SUPPORT case NE_FANOTIFY: #endif #ifdef INOTIFY_SUPPORT case NE_INOTIFY: #endif #ifdef KQUEUE_SUPPORT case NE_KQUEUE: #endif #ifdef BSM_SUPPORT case NE_BSM: case NE_BSM_PREFETCH: #endif #ifdef GIO_SUPPORT case NE_GIO: #endif #ifdef DTRACEPIPE_SUPPORT case NE_DTRACEPIPE: #endif break; default: error(PROGRAM" is compiled without %s subsystem support. Recompile with option \"--with-%s\" if you're planning to use it.", arg_orig, arg_orig); return EINVAL; } ctx_p->flags[MONITOR] = notifyengine; break; } case RSYNCINCLIMIT: ctx_p->rsyncinclimit = (unsigned int)xstrtol(arg, &ret); break; case SYNCTIMEOUT: ctx_p->synctimeout = (unsigned int)xstrtol(arg, &ret); break; case PREEXITHOOK: if (strlen(arg)) { ctx_p->preexithookfile = arg; ctx_p->flags[PREEXITHOOK] = 1; } else { ctx_p->preexithookfile = NULL; ctx_p->flags[PREEXITHOOK] = 0; } break; case EXITHOOK: if (strlen(arg)) { ctx_p->exithookfile = arg; ctx_p->flags[EXITHOOK] = 1; } else { ctx_p->exithookfile = NULL; ctx_p->flags[EXITHOOK] = 0; } break; case IGNOREEXITCODE: { char *ptr = arg, *start = arg; unsigned char exitcode; do { switch(*ptr) { case 0: case ',': // *ptr=0; exitcode = (unsigned char)atoi(start); if (exitcode == 0) { // flushing the setting int i = 0; while (i < 256) ctx_p->isignoredexitcode[i++] = 0; #ifdef _DEBUG_FORCE fprintf(stderr, "Force-Debug: parse_parameter(): Reset ignored exitcodes.\n"); #endif } else { ctx_p->isignoredexitcode[exitcode] = 1; #ifdef _DEBUG_FORCE fprintf(stderr, "Force-Debug: parse_parameter(): Adding ignored exitcode %u.\n", exitcode); #endif } start = ptr+1; break; case '0' ... '9': break; default: errno = EINVAL; error("Expected a digit or comma but got \"%c\"", *ptr); return errno; } } while(*(ptr++)); break; } case SHOW_VERSION: version(); break; case WATCHDIR: if (paramsource == PS_CONTROL) { warning("Cannot change \"watch-dir\" in run-time. Ignoring."); return 0; } ctx_p->watchdir = arg; break; case SYNCHANDLER: ctx_p->handlerfpath = arg; break; case RULESFILE: ctx_p->rulfpath = arg; break; case DESTDIR: { char *sep = strstr(arg, "://"); if (ctx_p->destproto != NULL) { free(ctx_p->destproto); ctx_p->destproto = NULL; } ctx_p->destdir = arg; if (sep == NULL) { char *at_ptr = strchr(arg, '@'); char *cl_ptr = strchr(arg, ':'); if (at_ptr != NULL && cl_ptr != NULL && at_ptr < cl_ptr) { ctx_p->destproto = strdup("rsync+ssh"); debug(5, "Destination proto is: %s (case #0)", ctx_p->destproto); } break; } { char *ptr = arg; while (ptr < sep) { if (*ptr<'a' || *ptr>'z') break; ptr++; } if (ptr == sep) { size_t len = (ptr-arg)+1; ctx_p->destproto = xmalloc(len+1); memcpy(ctx_p->destproto, arg, len); ctx_p->destproto[len] = 0; } debug(5, "Destination proto is: %s (case #1)", ctx_p->destproto); } break; } case SOCKETPATH: ctx_p->socketpath = arg; break; case SOCKETAUTH: { char *value; ctx_p->flags[SOCKETAUTH] = getsubopt(&arg, socketauth, &value); if (ctx_p->flags[SOCKETAUTH] == -1) { error("Wrong socket auth mech entered: \"%s\"", arg); return EINVAL; } } case SOCKETMOD: if (!sscanf(arg, "%o", (unsigned int *)&ctx_p->socketmod)) { error("Non octal value passed to --socket-mod: \"%s\"", arg); return EINVAL; } ctx_p->flags[param_id]++; break; case SOCKETOWN: { char *colon = strchr(arg, ':'); uid_t uid; gid_t gid; if (colon == NULL) { struct passwd *pwent = getpwnam(arg); if(pwent == NULL) { error("Cannot find username \"%s\" (case #0)", arg); return EINVAL; } uid = pwent->pw_uid; gid = pwent->pw_gid; } else { char user[USER_LEN+2], group[GROUP_LEN+2]; memcpy(user, arg, MIN(USER_LEN, colon-arg)); user[colon-arg] = 0; strncpy(group, &colon[1], GROUP_LEN); errno=0; struct passwd *pwent = getpwnam(user); if(pwent == NULL) { error("Cannot find username \"%s\" (case #1)", user); return EINVAL; } errno=0; struct group *grent = getgrnam(group); if(grent == NULL) { error("Cannot find group \"%s\"", group); return EINVAL; } uid = pwent->pw_uid; gid = grent->gr_gid; } ctx_p->socketuid = uid; ctx_p->socketgid = gid; ctx_p->flags[param_id]++; debug(2, "socket: uid == %u; gid == %u", uid, gid); break; } case STATUSFILE: ctx_p->statusfile = arg; break; case DUMPDIR: ctx_p->dump_path = arg; break; case MODE: { char *value; ctx_p->flags[MODE] = getsubopt(&arg, modes, &value); if (ctx_p->flags[MODE] == -1) { error("Wrong mode name entered: \"%s\"", arg); return EINVAL; } break; } case SYNCHANDLERARGS0: str_splitargs(arg, synchandler_arg0, ctx_p); break; case SYNCHANDLERARGS1: str_splitargs(arg, synchandler_arg1, ctx_p); break; default: if (arg == NULL) ctx_p->flags[param_id]++; else ctx_p->flags[param_id] = xstrtol(arg, &ret); #ifdef _DEBUG_FORCE fprintf(stderr, "Force-Debug: flag %i is set to %i\n", param_id&0xff, ctx_p->flags[param_id]); #endif break; } return ret; } int arguments_parse(int argc, char *argv[], struct ctx *ctx_p) { int c; int option_index = 0; // Generating "optstring" (man 3 getopt_long) with using information from struct array "long_options" char *optstring = alloca((('z'-'a'+1)*3 + '9'-'0'+1)*3 + 1); char *optstring_ptr = optstring; const struct option *lo_ptr = long_options; while (lo_ptr->name != NULL) { if (!(lo_ptr->val & (OPTION_CONFIGONLY|OPTION_LONGOPTONLY))) { *(optstring_ptr++) = lo_ptr->val & 0xff; if (lo_ptr->has_arg == required_argument) *(optstring_ptr++) = ':'; if (lo_ptr->has_arg == optional_argument) { *(optstring_ptr++) = ':'; *(optstring_ptr++) = ':'; } } lo_ptr++; } *optstring_ptr = 0; #ifdef _DEBUG_FORCE fprintf(stderr, "Force-Debug: %s\n", optstring); #endif // Parsing arguments while (1) { c = getopt_long(argc, argv, optstring, long_options, &option_index); if (c == -1) break; int ret = parse_parameter(ctx_p, c, optarg == NULL ? NULL : strdup(optarg), PS_ARGUMENT); if (ret) return ret; } if (optind < argc) { synchandler_args_t *args_p = &ctx_p->synchandler_args[SHARGS_PRIMARY]; while (args_p->c) free(args_p->v[--args_p->c]); if ((optind+1 != argc) || (*argv[optind])) { // If there's only "" after the "--", just reset "synchandler_argc" to "0", otherwise: do { if (synchandler_arg0(strdup(argv[optind++]), 0, ctx_p)) return errno; } while (optind < argc); } } return 0; } void gkf_parse(ctx_t *ctx_p, GKeyFile *gkf, paramsource_t paramsource) { debug(9, ""); char *config_block = (char *)ctx_p->config_block; while (config_block != NULL) { const struct option *lo_ptr = long_options; if (config_block != ctx_p->config_block) { ctx_p->flags_values_raw[CONFIGBLOCKINHERITS] = NULL; ctx_p->flags_set[CONFIGBLOCKINHERITS] = 0; } while (lo_ptr->name != NULL) { gchar *value = g_key_file_get_value(gkf, config_block, lo_ptr->name, NULL); if(value != NULL) { int ret = parse_parameter(ctx_p, lo_ptr->val, value, paramsource); if(ret) exit(ret); } lo_ptr++; } if (config_block != ctx_p->config_block) free(config_block); config_block = ctx_p->flags_values_raw[CONFIGBLOCKINHERITS]; if (config_block != NULL) debug(2, "Next block is: %s", config_block); }; return; } int configs_parse(ctx_t *ctx_p, paramsource_t paramsource) { GKeyFile *gkf; gkf = g_key_file_new(); if (ctx_p->config_path) { GError *g_error = NULL; if (!strcmp(ctx_p->config_path, "/NULL/")) { debug(2, "Empty path to config file. Don't read any of config files."); return 0; } debug(1, "Trying config-file \"%s\" (case #0)", ctx_p->config_path); if (!g_key_file_load_from_file(gkf, ctx_p->config_path, G_KEY_FILE_NONE, &g_error)) { error("Cannot open/parse file \"%s\" (g_error #%u.%u: %s)", ctx_p->config_path, g_error->domain, g_error->code, g_error->message); g_key_file_free(gkf); return -1; } else gkf_parse(ctx_p, gkf, paramsource); } else { char *config_paths[] = CONFIG_PATHS; char **config_path_p = config_paths, *config_path_real = xmalloc(PATH_MAX); size_t config_path_real_size=PATH_MAX; char *homedir = getenv("HOME"); size_t homedir_len = (homedir == NULL ? 0 :strlen(homedir)); while (*config_path_p != NULL) { size_t config_path_len = strlen(*config_path_p); if (config_path_len+homedir_len+3 > config_path_real_size) { config_path_real_size = config_path_len+homedir_len+3; config_path_real = xmalloc(config_path_real_size); } if ((*config_path_p[0] != '/') && (homedir_len >= 0)) { memcpy(config_path_real, homedir, homedir_len); config_path_real[homedir_len] = '/'; memcpy(&config_path_real[homedir_len+1], *config_path_p, config_path_len+1); } else memcpy(config_path_real, *config_path_p, config_path_len+1); debug(1, "Trying config-file \"%s\" (case #1)", config_path_real); if (!g_key_file_load_from_file(gkf, config_path_real, G_KEY_FILE_NONE, NULL)) { debug(1, "Cannot open/parse file \"%s\"", config_path_real); config_path_p++; continue; } gkf_parse(ctx_p, gkf, paramsource); break; } free(config_path_real); } g_key_file_free(gkf); return 0; } int ctx_check(ctx_t *ctx_p) { int ret = 0; #ifdef CLUSTER_SUPPORT struct utsname utsname; #endif if (ctx_p->socketpath != NULL) { #ifndef ENABLE_SOCKET ret = EINVAL; error("clsync is compiled without control socket support, option \"--socket\" cannot be used."); #endif if (ctx_p->flags[SOCKETAUTH] == SOCKAUTH_UNSET) ctx_p->flags[SOCKETAUTH] = SOCKAUTH_NULL; } if ((ctx_p->flags[SOCKETOWN]) && (ctx_p->socketpath == NULL)) { ret = errno = EINVAL; error("\"--socket-own\" is useless without \"--socket\""); } if ((ctx_p->flags[SOCKETMOD]) && (ctx_p->socketpath == NULL)) { ret = errno = EINVAL; error("\"--socket-mod\" is useless without \"--socket\""); } if ((ctx_p->flags[SOCKETAUTH]) && (ctx_p->socketpath == NULL)) { ret = errno = EINVAL; error("\"--socket-auth\" is useless without \"--socket\""); } #ifdef PIVOTROOT_OPT_SUPPORT if ((ctx_p->flags[PIVOT_ROOT] != PW_OFF) && (ctx_p->chroot_dir == NULL)) { ret = errno = EINVAL; error("\"--pivot-root\" cannot be used without \"--chroot\""); } if ((ctx_p->flags[PIVOT_ROOT] != PW_OFF) && (ctx_p->mountpoints)) warning("\"--mountpoints\" is set while \"--pivot-root\" is set, too"); #endif #ifdef VERYPARANOID if ((ctx_p->retries != 1) && ctx_p->flags[THREADING]) { ret = errno = EINVAL; error("\"--retries\" values should be equal to \"1\" for this \"--threading\" value."); } #endif if (ctx_p->flags[STANDBYFILE] && (ctx_p->flags[MODE] == MODE_SIMPLE)) { ret = errno = EINVAL; error("Sorry but option \"--standby-file\" cannot be used in mode \"simple\", yet."); } if (ctx_p->flags[THREADING] && ctx_p->flags[ONLYINITSYNC]) { ret = errno = EINVAL; error("Conflicting options: This value of \"--threading\" cannot be used in conjunction with \"--only-initialsync\"."); } if (ctx_p->flags[THREADING] && ctx_p->flags[EXITONNOEVENTS]) { ret = errno = EINVAL; error("Conflicting options: This value of \"--threading\" cannot be used in conjunction with \"--exit-on-no-events\"."); } if (ctx_p->flags[THREADING] && ctx_p->flags[MAXITERATIONS]) { ret = errno = EINVAL; error("Conflicting options: This value of \"--threading\" cannot be used in conjunction with \"--max-iterations\"."); } if (ctx_p->flags[THREADING] && ctx_p->flags[PREEXITHOOK]) { ret = errno = EINVAL; error("Conflicting options: This value of \"--threading\" cannot be used in conjunction with \"--pre-exit-hook\"."); } if (ctx_p->flags[THREADING] && ctx_p->flags[SPLITTING] == SM_THREAD) { ret = errno = EINVAL; error("Conflicting options: This value of \"--threading\" cannot be used in conjunction with \"--splitting=thread\"."); } if (ctx_p->flags[SKIPINITSYNC] && ctx_p->flags[EXITONNOEVENTS]) { ret = errno = EINVAL; error("Conflicting options: \"--skip-initialsync\" and \"--exit-on-no-events\" cannot be used together."); } if (ctx_p->flags[ONLYINITSYNC] && ctx_p->flags[EXITONNOEVENTS]) { ret = errno = EINVAL; error("Conflicting options: \"--only-initialsync\" and \"--exit-on-no-events\" cannot be used together."); } if (ctx_p->flags[SKIPINITSYNC] && ctx_p->flags[ONLYINITSYNC]) { ret = errno = EINVAL; error("Conflicting options: \"--skip-initialsync\" and \"--only-initialsync\" cannot be used together."); } if (ctx_p->flags[INITFULL] && ctx_p->flags[SKIPINITSYNC]) { ret = errno = EINVAL; error("Conflicting options: \"--full-initialsync\" and \"--skip-initialsync\" cannot be used together."); } if (ctx_p->flags[MODSIGN] && (ctx_p->flags[CANCEL_SYSCALLS]&CSC_MON_STAT)) { ret = errno = EINVAL; error("Conflicting options: \"--modification-signature\" and \"--cancel-syscalls=mon_stat\" cannot be used together."); } if (ctx_p->flags[EXCLUDEMOUNTPOINTS]) ctx_p->flags[ONEFILESYSTEM]=1; if (ctx_p->flags[MODE] == MODE_UNSET) { ret = errno = EINVAL; error("\"--mode\" is not set."); } if (ctx_p->watchdir == NULL) { ret = errno = EINVAL; error("\"--watch-dir\" is not set."); } if (ctx_p->handlerfpath == NULL) { switch (ctx_p->flags[MODE]) { case MODE_DIRECT: ctx_p->handlerfpath = DEFAULT_CP_PATH; break; case MODE_RSYNCDIRECT: ctx_p->handlerfpath = DEFAULT_RSYNC_PATH; break; default: ret = errno = EINVAL; error("\"--sync-handler\" path is not set."); } } /* if (ctx_p->flags[SYNCHANDLERSO] && ctx_p->flags[RSYNC]) { ret = EINVAL; ret = errno = EINVAL; error("Option \"--rsync\" cannot be used in conjunction with \"--synchandler-so-module\"."); } */ // if (ctx_p->flags[SYNCHANDLERSO] && (ctx_p->listoutdir != NULL)) // error("Warning: Option \"--dir-lists\" has no effect conjunction with \"--synchandler-so-module\"."); // if (ctx_p->flags[SYNCHANDLERSO] && (ctx_p->destdir != NULL)) // error("Warning: Destination directory argument has no effect conjunction with \"--synchandler-so-module\"."); if ((ctx_p->flags[MODE] == MODE_RSYNCDIRECT) && (ctx_p->destdir == NULL)) { ret = errno = EINVAL; error("Mode \"rsyncdirect\" cannot be used without specifying \"--dest-dir\"."); } #ifdef CLUSTER_SUPPORT if ((ctx_p->flags[MODE] == MODE_RSYNCDIRECT ) && (ctx_p->cluster_iface != NULL)) { ret = errno = EINVAL; error("Mode \"rsyncdirect\" cannot be used in conjunction with \"--cluster-iface\"."); } if ((ctx_p->cluster_iface == NULL) && ((ctx_p->cluster_mcastipaddr != NULL) || (ctx_p->cluster_nodename != NULL) || (ctx_p->cluster_timeout) || (ctx_p->cluster_mcastipport))) { ret = errno = EINVAL; error("ctx \"--cluster-ip\", \"--cluster-node-name\", \"--cluster_timeout\" and/or \"cluster_ipport\" cannot be used without \"--cluster-iface\"."); } if (ctx_p->cluster_hash_dl_min > ctx_p->cluster_hash_dl_max) { ret = errno = EINVAL; error("\"--cluster-hash-dl-min\" cannot be greater than \"--cluster-hash-dl-max\"."); } if (ctx_p->cluster_hash_dl_max > ctx_p->cluster_scan_dl_max) { ret = errno = EINVAL; error("\"--cluster-hash-dl-max\" cannot be greater than \"--cluster-scan-dl-max\"."); } if (!ctx_p->cluster_timeout) ctx_p->cluster_timeout = DEFAULT_CLUSTERTIMEOUT; if (!ctx_p->cluster_mcastipport) ctx_p->cluster_mcastipport = DEFAULT_CLUSTERIPPORT; if (!ctx_p->cluster_mcastipaddr) ctx_p->cluster_mcastipaddr = DEFAULT_CLUSTERIPADDR; if (ctx_p->cluster_iface != NULL) { #ifndef _DEBUG_FORCE ret = errno = EINVAL; error("Cluster subsystem is not implemented, yet. Sorry."); #endif if (ctx_p->cluster_nodename == NULL) { if(!uname(&utsname)) ctx_p->cluster_nodename = strdup(utsname.nodename); debug(1, "cluster node name is: %s", ctx_p->cluster_nodename); } if (ctx_p->cluster_nodename == NULL) { ret = errno = EINVAL; error("Option \"--cluster-iface\" is set, but \"--cluster-node-name\" is not set and cannot get the nodename with uname()."); } else { ctx_p->cluster_nodename_len = strlen(ctx_p->cluster_nodename); } } #endif // CLUSTER_SUPPORT switch (ctx_p->flags[MODE]) { case MODE_RSYNCSO: ctx_p->synchandler_argf |= SHFL_EXCLUDE_LIST_PATH; ctx_p->synchandler_argf |= SHFL_INCLUDE_LIST_PATH; break; } if ( ctx_p->flags[RSYNCPREFERINCLUDE] && !( ctx_p->flags[MODE] == MODE_RSYNCDIRECT || ctx_p->flags[MODE] == MODE_RSYNCSHELL || ctx_p->flags[MODE] == MODE_RSYNCSO ) ) warning("Option \"--rsyncpreferinclude\" is useless if mode is not \"rsyncdirect\", \"rsyncshell\" or \"rsyncso\"."); #ifdef AUTORULESW if ( ( ctx_p->flags[MODE] == MODE_RSYNCDIRECT || ctx_p->flags[MODE] == MODE_RSYNCSHELL || ctx_p->flags[MODE] == MODE_RSYNCSO ) && ctx_p->flags[AUTORULESW] ) warning("Option \"--auto-add-rules-w\" in modes \"rsyncdirect\", \"rsyncshell\" and \"rsyncso\" may cause unexpected problems."); #endif /* if(ctx_p->flags[HAVERECURSIVESYNC] && (ctx_p->listoutdir == NULL)) { error("Option \"--dir-lists\" should be set to use option \"--have-recursive-sync\"."); ret = EINVAL; } */ if ( ctx_p->flags[HAVERECURSIVESYNC] && ( ctx_p->flags[MODE] == MODE_RSYNCDIRECT || ctx_p->flags[MODE] == MODE_RSYNCSHELL || ctx_p->flags[MODE] == MODE_RSYNCSO ) ) { ret = errno = EINVAL; error("Option \"--have-recursive-sync\" with nodes \"rsyncdirect\", \"rsyncshell\" and \"rsyncso\" are incompatible."); } if (ctx_p->flags[SYNCLISTSIMPLIFY] && (ctx_p->listoutdir == NULL)) { ret = errno = EINVAL; error("Option \"--dir-lists\" should be set to use option \"--synclist-simplify\"."); } if ( ctx_p->flags[SYNCLISTSIMPLIFY] && ( ctx_p->flags[MODE] == MODE_RSYNCDIRECT || ctx_p->flags[MODE] == MODE_RSYNCSHELL || ctx_p->flags[MODE] == MODE_RSYNCSO ) ) { ret = errno = EINVAL; error("Option \"--synclist-simplify\" with nodes \"rsyncdirect\" and \"rsyncshell\" are incompatible."); } #ifdef GIO_SUPPORT # ifdef SECCOMP_SUPPORT if ((ctx_p->flags[MONITOR] == NE_GIO) && (ctx_p->flags[SECCOMP_FILTER])) { ret = errno = EINVAL; error("GIO is not compatible with seccomp filter (\"--monitor=gio\" and \"--seccomp-filter\" are incompatible)"); } # endif #endif #ifdef FANOTIFY_SUPPORT if (ctx_p->flags[MONITOR] == NE_FANOTIFY) critical("fanotify is not supported, now!"); else #endif switch (ctx_p->flags[MONITOR]) { #ifdef INOTIFY_SUPPORT case NE_INOTIFY: #endif #ifdef FANOTIFY_SUPPORT case NE_FANOTIFY: #endif #ifdef KQUEUE_SUPPORT case NE_KQUEUE: #endif #ifdef BSM_SUPPORT case NE_BSM: case NE_BSM_PREFETCH: #endif #ifdef GIO_SUPPORT case NE_GIO: #endif #ifdef DTRACEPIPE_SUPPORT case NE_DTRACEPIPE: #endif break; default: ret = errno = EINVAL; error("Required one of the next options:" #ifdef INOTIFY_SUPPORT " \"--monitor=inotify\"" #endif #ifdef FANOTIFY_SUPPORT " \"--monitor=fanotify\"" #endif #ifdef KQUEUE_SUPPORT " \"--monitor=kqueue\"" #endif #ifdef BSM_SUPPORT " \"--monitor=bsm\"" #endif #ifdef GIO_SUPPORT " \"--monitor=gio\"" #endif #ifdef DTRACEPIPE_SUPPORT " \"--monitor=dtracepipe\"" #endif ); } if (ctx_p->flags[EXITHOOK]) { #ifdef VERYPARANOID if (ctx_p->exithookfile == NULL) { ret = errno = EINVAL; error("ctx_p->exithookfile == NULL"); } else #endif { if (access(ctx_p->exithookfile, X_OK) == -1) { error("\"%s\" is not executable.", ctx_p->exithookfile); if (!ret) ret = errno; } } } if (ctx_p->flags[CHECK_EXECVP_ARGS] && (ctx_p->flags[MODE] == MODE_DIRECT)) { ret = errno = EINVAL; error("Options --check-execvp-arguments/--secure-splitting cannot be used in conjuction with --mode=direct (see \"man 1 clsync\": --check-execvp-arguments)."); } #if 0 if (ctx_p->handlerfpath != NULL) if (access(ctx_p->handlerfpath, X_OK) == -1) { error("\"%s\" is not executable.", ctx_p->handlerfpath); if (!ret) ret = errno; } #endif return ret; } int config_block_parse(ctx_t *ctx_p, const char *const config_block_name) { int rc; debug(1, "(ctx_p, \"%s\")", config_block_name); ctx_p->config_block = config_block_name; rc = configs_parse(ctx_p, PS_CONTROL); if (!rc) rc = ctx_check(ctx_p); return errno = rc; } int ctx_set(ctx_t *ctx_p, const char *const parameter_name, const char *const parameter_value) { int ret = ENOENT; const struct option *lo_ptr = long_options; while (lo_ptr->name != NULL) { if (!strcmp(lo_ptr->name, parameter_name)) { ret = parse_parameter(ctx_p, lo_ptr->val, strdup(parameter_value), PS_CONTROL); break; } lo_ptr++; } ret = ctx_check(ctx_p); if (ret) critical("Cannot continue with this setup"); return ret; } void ctx_cleanup(ctx_t *ctx_p) { int i=0; debug(9, ""); while (i < OPTION_FLAGS) { if (ctx_p->flags_values_raw[i] != NULL) { free(ctx_p->flags_values_raw[i]); ctx_p->flags_values_raw[i] = NULL; } i++; } { int n = 0; while (n < SHARGS_MAX) { int i = 0, e = ctx_p->synchandler_args[n].c; while (i < e) { #ifdef _DEBUG_FORCE debug(14, "synchandler args: %u, %u: free(%p)", n, i, ctx_p->synchandler_args[n].v[i]); #endif free(ctx_p->synchandler_args[n].v[i]); ctx_p->synchandler_args[n].v[i] = NULL; i++; } ctx_p->synchandler_args[n].c = 0; n++; } } return; } int becomedaemon() { int pid; signal(SIGPIPE, SIG_IGN); switch((pid = fork())) { case -1: error("Cannot fork()."); return(errno); case 0: setsid(); break; default: debug(1, "fork()-ed, pid is %i.", pid); errno=0; exit(0); } return 0; } int main_cleanup(ctx_t *ctx_p) { int i=0; while((i < MAXRULES) && (ctx_p->rules[i].mask != RA_NONE)) regfree(&ctx_p->rules[i++].expr); debug(3, "%i %i %i %i", ctx_p->watchdirsize, ctx_p->watchdirwslashsize, ctx_p->destdirsize, ctx_p->destdirwslashsize); return 0; } int main_rehash(ctx_t *ctx_p) { debug(3, ""); int ret=0; main_cleanup(ctx_p); if(ctx_p->rulfpath != NULL) { ret = parse_rules_fromfile(ctx_p); if(ret) error("Got error from parse_rules_fromfile()."); } else { ctx_p->rules[0].perm = DEFAULT_RULES_PERM; ctx_p->rules[0].mask = RA_NONE; // Terminator. End of rules. } return ret; } FILE *main_statusfile_f; int main_status_update(ctx_t *ctx_p) { static state_t state_old = STATE_UNKNOWN; state_t state = ctx_p->state; debug(4, "%u", state); if (state == state_old) { debug(3, "State unchanged: %u == %u", state, state_old); return 0; } #ifdef VERYPARANOID if (status_descr[state] == NULL) { error("status_descr[%u] == NULL.", state); return EINVAL; } #endif setenv("CLSYNC_STATUS", status_descr[state], 1); if (ctx_p->statusfile == NULL) return 0; debug(3, "Setting status to %i: %s.", state, status_descr[state]); state_old=state; int ret = 0; if (ftruncate(fileno(main_statusfile_f), 0)) { error("Cannot ftruncate() the file \"%s\".", ctx_p->statusfile); return errno; } rewind(main_statusfile_f); if (fprintf(main_statusfile_f, "%s", status_descr[state]) <= 0) { // TODO: check output length error("Cannot write to file \"%s\".", ctx_p->statusfile); return errno; } if (fflush(main_statusfile_f)) { error("Cannot fflush() on file \"%s\".", ctx_p->statusfile); return errno; } return ret; } int argc; char **argv; #define UGID_PRESERVE (1<<16) int main(int _argc, char *_argv[]) { struct ctx *ctx_p = xcalloc(1, sizeof(*ctx_p)); argv = _argv; argc = _argc; int ret = 0, nret, rm_listoutdir = 0; SAFE (posixhacks_init(), errno = ret = _SAFE_rc); ctx_p->flags[MONITOR] = DEFAULT_NOTIFYENGINE; ctx_p->syncdelay = DEFAULT_SYNCDELAY; ctx_p->_queues[QUEUE_NORMAL].collectdelay = DEFAULT_COLLECTDELAY; ctx_p->_queues[QUEUE_BIGFILE].collectdelay = DEFAULT_BFILECOLLECTDELAY; ctx_p->_queues[QUEUE_INSTANT].collectdelay = COLLECTDELAY_INSTANT; ctx_p->_queues[QUEUE_LOCKWAIT].collectdelay = COLLECTDELAY_INSTANT; ctx_p->bfilethreshold = DEFAULT_BFILETHRESHOLD; ctx_p->rsyncinclimit = DEFAULT_RSYNCINCLUDELINESLIMIT; ctx_p->synctimeout = DEFAULT_SYNCTIMEOUT; #ifdef CLUSTER_SUPPORT ctx_p->cluster_hash_dl_min = DEFAULT_CLUSTERHDLMIN; ctx_p->cluster_hash_dl_max = DEFAULT_CLUSTERHDLMAX; ctx_p->cluster_scan_dl_max = DEFAULT_CLUSTERSDLMAX; #endif ctx_p->config_block = DEFAULT_CONFIG_BLOCK; ctx_p->retries = DEFAULT_RETRIES; ctx_p->flags[VERBOSE] = DEFAULT_VERBOSE; #ifdef PIVOTROOT_OPT_SUPPORT ctx_p->flags[PIVOT_ROOT] = DEFAULT_PIVOT_MODE; #endif #ifdef CAPABILITIES_SUPPORT ctx_p->flags[CAP_PRESERVE] = CAP_PRESERVE_TRY; ctx_p->caps = DEFAULT_PRESERVE_CAPABILITIES; ctx_p->flags[CAPS_INHERIT] = DEFAULT_CAPS_INHERIT; ctx_p->flags[DETACH_IPC] = DEFAULT_DETACH_IPC; parse_parameter(ctx_p, LABEL, strdup(DEFAULT_LABEL), PS_DEFAULTS); ncpus = sysconf(_SC_NPROCESSORS_ONLN); // Get number of available logical CPUs memory_init(); { struct passwd *pwd = getpwnam(DEFAULT_USER); ctx_p->uid = (pwd != NULL) ? pwd->pw_uid : DEFAULT_UID; ctx_p->flags[UID] = UGID_PRESERVE; } { struct group *grp = getgrnam(DEFAULT_GROUP); ctx_p->gid = (grp != NULL) ? grp->gr_gid : DEFAULT_GID; ctx_p->flags[GID] = UGID_PRESERVE; } #endif ctx_p->pid = getpid(); error_init(&ctx_p->flags[OUTPUT_METHOD], &ctx_p->flags[QUIET], &ctx_p->flags[VERBOSE], &ctx_p->flags[DEBUG]); nret = arguments_parse(argc, argv, ctx_p); if (nret) ret = nret; if (!ret) { nret = configs_parse(ctx_p, PS_CONFIG); if(nret) ret = nret; } if (!ctx_p->flags[PRIVILEGEDUID]) ctx_p->privileged_uid = getuid(); if (!ctx_p->flags[PRIVILEGEDGID]) ctx_p->privileged_gid = getgid(); if (!ctx_p->flags[SYNCHANDLERUID]) ctx_p->synchandler_uid = ctx_p->privileged_uid; if (!ctx_p->flags[SYNCHANDLERGID]) ctx_p->synchandler_gid = ctx_p->privileged_gid; #ifdef CGROUP_SUPPORT if (ctx_p->cg_groupname == NULL) { ctx_p->cg_groupname = parameter_expand(ctx_p, strdup(DEFAULT_CG_GROUPNAME), 2, NULL, NULL, parameter_get, ctx_p); ctx_p->flags_values_raw[CG_GROUPNAME] = ctx_p->cg_groupname; } #endif if (ctx_p->dump_path == NULL) { ctx_p->dump_path = parameter_expand(ctx_p, strdup(DEFAULT_DUMPDIR), 2, NULL, NULL, parameter_get, ctx_p); ctx_p->flags_values_raw[DUMPDIR] = ctx_p->dump_path; } if (!ctx_p->synchandler_args[SHARGS_PRIMARY].c) { char *args_line0 = NULL, *args_line1 = NULL; switch (ctx_p->flags[MODE]) { case MODE_SIMPLE: args_line0 = DEFAULT_SYNCHANDLER_ARGS_SIMPLE; break; case MODE_DIRECT: args_line0 = DEFAULT_SYNCHANDLER_ARGS_DIRECT; break; case MODE_SHELL: args_line0 = DEFAULT_SYNCHANDLER_ARGS_SHELL_NR; args_line1 = DEFAULT_SYNCHANDLER_ARGS_SHELL_R; break; case MODE_RSYNCDIRECT: args_line0 = (ctx_p->flags[RSYNCPREFERINCLUDE]) ? DEFAULT_SYNCHANDLER_ARGS_RDIRECT_I : DEFAULT_SYNCHANDLER_ARGS_RDIRECT_E; break; case MODE_RSYNCSHELL: args_line0 = (ctx_p->flags[RSYNCPREFERINCLUDE]) ? DEFAULT_SYNCHANDLER_ARGS_RSHELL_I : DEFAULT_SYNCHANDLER_ARGS_RSHELL_E; break; default: break; } if (args_line0 != NULL) { char *args_line = strdup(args_line0); parse_parameter(ctx_p, SYNCHANDLERARGS0, args_line, PS_DEFAULTS); } if (args_line1 != NULL) { char *args_line = strdup(args_line1); parse_parameter(ctx_p, SYNCHANDLERARGS1, args_line, PS_DEFAULTS); } } debug(4, "ncpus == %u", ncpus); debug(4, "debugging flags: %u %u %u %u", ctx_p->flags[OUTPUT_METHOD], ctx_p->flags[QUIET], ctx_p->flags[VERBOSE], ctx_p->flags[DEBUG]); if (ctx_p->watchdir != NULL) { char *rwatchdir = realpath(ctx_p->watchdir, NULL); if (rwatchdir == NULL) { error("Got error while realpath() on \"%s\" [#0].", ctx_p->watchdir); ret = errno; } debug(5, "rwatchdir == \"%s\"", rwatchdir); stat64_t stat64={0}; if (lstat64(ctx_p->watchdir, &stat64)) { error("Cannot lstat64() on \"%s\"", ctx_p->watchdir); if (!ret) ret = errno; } else { ctx_p->st_dev = stat64.st_dev; /* if ((stat64.st_mode & S_IFMT) == S_IFLNK) { // The proplems may be due to FTS_PHYSICAL option of fts_open() in sync_initialsync_rsync_walk(), // so if the "watch dir" is just a symlink it doesn't walk recursivly. For example, in "-R" case // it disables filters, because exclude-list will be empty. #ifdef VERYPARANOID error("Watch dir cannot be symlink, but \"%s\" is a symlink.", ctx_p->watchdir); ret = EINVAL; #else char *watchdir_resolved_part = xcalloc(1, PATH_MAX+2); ssize_t r = readlink(ctx_p->watchdir, watchdir_resolved_part, PATH_MAX+1); if (r>=PATH_MAX) { // TODO: check if it's possible ret = errno = EINVAL; error("Too long file path resolved from symbolic link \"%s\"", ctx_p->watchdir); } else if (r<0) { error("Cannot resolve symbolic link \"%s\": readlink() error", ctx_p->watchdir); ret = EINVAL; } else { char *watchdir_resolved; # ifdef PARANOID if (ctx_p->watchdirsize) if (ctx_p->watchdir != NULL) free(ctx_p->watchdir); # endif size_t watchdir_resolved_part_len = strlen(watchdir_resolved_part); ctx_p->watchdirsize = watchdir_resolved_part_len+1; // Not true for case of relative symlink if (*watchdir_resolved_part == '/') { // Absolute symlink watchdir_resolved = malloc(ctx_p->watchdirsize); memcpy(watchdir_resolved, watchdir_resolved_part, ctx_p->watchdirsize); } else { // Relative symlink :( char *rslash = strrchr(ctx_p->watchdir, '/'); char *watchdir_resolved_rel = xmalloc(PATH_MAX+2); size_t watchdir_resolved_rel_len = rslash-ctx_p->watchdir + 1; memcpy(watchdir_resolved_rel, ctx_p->watchdir, watchdir_resolved_rel_len); memcpy(&watchdir_resolved_rel[watchdir_resolved_rel_len], watchdir_resolved_part, watchdir_resolved_part_len+1); watchdir_resolved = realpath(watchdir_resolved_rel, NULL); free(watchdir_resolved_rel); } debug(1, "Symlink resolved: watchdir \"%s\" -> \"%s\"", ctx_p->watchdir, watchdir_resolved); ctx_p->watchdir = watchdir_resolved; } free(watchdir_resolved_part); #endif // VERYPARANOID else } */ } if (!ret) { parse_parameter(ctx_p, WATCHDIR, rwatchdir, PS_CORRECTION); ctx_p->watchdirlen = strlen(ctx_p->watchdir); ctx_p->watchdirsize = ctx_p->watchdirlen; #ifdef VERYPARANOID if (ctx_p->watchdirlen == 1) { ret = errno = EINVAL; error("Very-Paranoid: --watch-dir is supposed to be not \"/\"."); } #endif } if (!ret) { if (ctx_p->watchdirlen == 1) { ctx_p->watchdirwslash = ctx_p->watchdir; ctx_p->watchdirwslashsize = 0; ctx_p->watchdir_dirlevel = 0; } else { size_t size = ctx_p->watchdirlen + 2; char *newwatchdir = xmalloc(size); memcpy( newwatchdir, ctx_p->watchdir, ctx_p->watchdirlen); ctx_p->watchdirwslash = newwatchdir; ctx_p->watchdirwslashsize = size; memcpy(&ctx_p->watchdirwslash[ctx_p->watchdirlen], "/", 2); ctx_p->watchdir_dirlevel = fileutils_calcdirlevel(ctx_p->watchdirwslash); } } } if ((ctx_p->destdir != NULL) && (ctx_p->destproto == NULL)) { // "ctx_p->destproto == NULL" means "no protocol"/"local directory" char *rdestdir = realpath(ctx_p->destdir, NULL); if (rdestdir == NULL) { error("Got error while realpath() on \"%s\" [#1].", ctx_p->destdir); ret = errno; } debug(5, "rdestdir == \"%s\"", rdestdir); if (!ret) { parse_parameter(ctx_p, DESTDIR, rdestdir, PS_CORRECTION); ctx_p->destdirlen = strlen(ctx_p->destdir); ctx_p->destdirsize = ctx_p->destdirlen; if (ctx_p->destdirlen == 1) { ret = errno = EINVAL; error("destdir is supposed to be not \"/\"."); } } if (!ret) { size_t size = ctx_p->destdirlen + 2; char *newdestdir = xmalloc(size); memcpy( newdestdir, ctx_p->destdir, ctx_p->destdirlen); ctx_p->destdirwslash = newdestdir; ctx_p->destdirwslashsize = size; memcpy(&ctx_p->destdirwslash[ctx_p->destdirlen], "/", 2); } } else if (ctx_p->destproto != NULL) ctx_p->destdirwslash = ctx_p->destdir; if (ctx_p->rulfpath) { if (*ctx_p->rulfpath != '/') { ctx_p->rulfpath = realpath(ctx_p->rulfpath, NULL); if (ctx_p->rulfpath == NULL) error("Cannot find rules-file. Got error while realpath(\"%s\")", ctx_p->rulfpath); else ctx_p->rulfpathsize = 1; } } if (ctx_p->handlerfpath != NULL) { char *rhandlerfpath = realpath(ctx_p->handlerfpath, NULL); /* if (rhandlerfpath == NULL) { error("Got error while realpath() on \"%s\" [#0].", ctx_p->handlerfpath); ret = errno; } debug(5, "rhandlerfpath == \"%s\"", rhandlerfpath); ctx_p->handlerfpath = rhandlerfpath;*/ if (rhandlerfpath != NULL) ctx_p->handlerfpath = rhandlerfpath; } debug(9, "chdir(\"%s\");", ctx_p->watchdir); if (chdir(ctx_p->watchdir)) { error("Got error while chdir(\"%s\")", ctx_p->watchdir); ret = errno; } /* if (ctx_p->flags_values_raw[SYNCHANDLERARGS0] != NULL) parse_parameter(ctx_p, SYNCHANDLERARGS0, NULL, PS_REHASH); if (ctx_p->flags_values_raw[SYNCHANDLERARGS1] != NULL) parse_parameter(ctx_p, SYNCHANDLERARGS1, NULL, PS_REHASH); */ { int n = 0; while (n < SHARGS_MAX) { synchandler_args_t *args_p = &ctx_p->synchandler_args[n++]; debug(9, "Custom arguments %u count: %u", n-1, args_p->c); int i = 0; while (i < args_p->c) { int macros_count = -1, expanded = -1; args_p->v[i] = parameter_expand(ctx_p, args_p->v[i], 4, ¯os_count, &expanded, parameter_get_wmacro, ctx_p); debug(12, "args_p->v[%u] == \"%s\" (t: %u; e: %u)", i, args_p->v[i], macros_count, expanded); if (macros_count == expanded) args_p->isexpanded[i]++; i++; } } } ctx_p->state = STATE_STARTING; { #ifdef GETMNTENT_SUPPORT struct mntent *ent; FILE *ent_f; ent_f = NULL; if (ctx_p->mountpoints) { // Openning the file with mount list ent_f = setmntent("/proc/mounts", "r"); if (ent_f == NULL) { error("Got error while setmntent(\"/proc/mounts\", \"r\")"); ret = errno; } } #endif #ifdef UNSHARE_SUPPORT #define unshare_wrapper(a) \ if (unshare(a)) {\ error("Got error from unshare("TOSTR(a)")");\ ret = errno;\ } if (ctx_p->flags[DETACH_IPC]) { unshare(CLONE_NEWUTS); error_init_ipc(ctx_p->flags[SPLITTING] == SM_PROCESS ? IPCT_SHARED : IPCT_PRIVATE); } if (ctx_p->flags[DETACH_MISCELLANEA]) { unshare(CLONE_NEWIPC); unshare(CLONE_NEWUTS); unshare(CLONE_SYSVSEM); } if ((ctx_p->flags[PIVOT_ROOT] != PW_OFF) || ctx_p->mountpoints) { unshare_wrapper(CLONE_FILES); unshare_wrapper(CLONE_FS); unshare_wrapper(CLONE_NEWNS); } if (ctx_p->flags[DETACH_NETWORK] == DN_EVERYWHERE) unshare_wrapper(CLONE_NEWNET); #undef unshare_wrapper #endif if (ctx_p->chroot_dir != NULL) { #ifdef PIVOTROOT_OPT_SUPPORT switch (ctx_p->flags[PIVOT_ROOT]) { case PW_OFF: case PW_DIRECT: break; case PW_AUTO: case PW_AUTORO: { if (chdir(ctx_p->chroot_dir)) { error("Got error while chdir(\"%s\")", ctx_p->chroot_dir); ret = errno; } if (mkdir("old_root", 0700)) { if (errno != EEXIST) { error("Got error from mkdir(\"old_root\", 0700)"); ret = errno; break; } } if (mkdir(PIVOT_AUTO_DIR, 0700)) { if (errno != EEXIST) { error("Got error from mkdir(\""PIVOT_AUTO_DIR"\", 0700)"); ret = errno; break; } } unsigned long mount_flags = MS_BIND | MS_REC | ((ctx_p->flags[PIVOT_ROOT] == PW_AUTORO) ? MS_RDONLY : 0); if (mount(ctx_p->chroot_dir, PIVOT_AUTO_DIR, NULL, mount_flags, NULL)) { error("Got error while mount(\"%s\", \"%s\", NULL, %o, NULL)", ctx_p->chroot_dir, PIVOT_AUTO_DIR, mount_flags); ret = errno; break; } ctx_p->chroot_dir = PIVOT_AUTO_DIR; break; } } #endif debug(7, "chdir(\"%s\")", ctx_p->chroot_dir); if (chdir(ctx_p->chroot_dir)) { error("Got error while chdir(\"%s\")", ctx_p->chroot_dir); ret = errno; } } #ifdef GETMNTENT_SUPPORT if (ctx_p->mountpoints && (ent_f != NULL)) { // Getting mount-points to be umounted while (NULL != (ent = getmntent(ent_f))) { int i; debug(8, "Checking should \"%s\" be umount or not", ent->mnt_dir); i=0; while (i < ctx_p->mountpoints) { debug(9, "\"%s\" \"%s\"", ent->mnt_dir, ctx_p->mountpoint[i]); if (!strcmp(ent->mnt_dir, ctx_p->mountpoint[i])) { debug(9, "found"); break; } i++; } if (i >= ctx_p->mountpoints) { debug(1, "umount2(\"%s\", MNT_DETACH)", ent->mnt_dir); if (umount2(ent->mnt_dir, MNT_DETACH) && errno != ENOENT && errno != EINVAL) { error("Got error while umount2(\"%s\", MNT_DETACH)", ent->mnt_dir); ret = errno; } } } endmntent(ent_f); } #endif if (ctx_p->chroot_dir != NULL) { #ifdef PIVOTROOT_OPT_SUPPORT if (!ret) { switch (ctx_p->flags[PIVOT_ROOT]) { case PW_OFF: break; case PW_DIRECT: case PW_AUTO: case PW_AUTORO: if (pivot_root(".", "old_root")) { error("Got error while pivot_root(\".\", \"old_root\")"); ret = errno; } break; } } #endif debug(7, "chroot(\".\")"); if (chroot(".")) { error("Got error while chroot(\".\")"); ret = errno; } #ifdef PIVOTROOT_OPT_SUPPORT if (!ret) { switch (ctx_p->flags[PIVOT_ROOT]) { case PW_OFF: break; case PW_DIRECT: case PW_AUTO: case PW_AUTORO: if (umount2("old_root", MNT_DETACH)) { error("Got error while umount2(\"old_root\", MNT_DETACH)"); ret = errno; } break; } } #endif } } if (ctx_p->statusfile != NULL) { debug(1, "Trying to open the status file for writing."); main_statusfile_f = fopen(ctx_p->statusfile, "w"); if (main_statusfile_f != NULL) { uid_t uid = ctx_p->flags[UID] ? ctx_p->uid : getuid(); gid_t gid = ctx_p->flags[GID] ? ctx_p->gid : getgid(); debug(1, "Changing owner of the status file to %u:%u", uid, gid); if (fchown(fileno(main_statusfile_f), uid, gid)) warning("Cannot fchown(%u -> \"%s\", %u, %u)", fileno(main_statusfile_f), ctx_p->statusfile, uid, gid); main_status_update(ctx_p); } } #ifdef CAPABILITIES_SUPPORT debug(1, "Preserving Linux capabilities"); // Tell kernel not clear capabilities when dropping root if (prctl(PR_SET_KEEPCAPS, 1) < 0) { error("Cannot prctl(PR_SET_KEEPCAPS, 1) to preserve capabilities"); ret = errno; } #endif #ifdef CGROUP_SUPPORT if (ctx_p->flags[FORBIDDEVICES]) { error_on(clsync_cgroup_init(ctx_p)); error_on(clsync_cgroup_forbid_extra_devices()); error_on(clsync_cgroup_attach(ctx_p)); } #endif nret=main_rehash(ctx_p); if (nret) ret = nret; if (ctx_p->flags[GID]) { int rc; debug(3, "Trying to drop effective gid to %i", ctx_p->gid); rc = setegid(ctx_p->gid); if (rc && (ctx_p->flags[GID] != UGID_PRESERVE)) { error("Cannot setegid(%u)", ctx_p->gid); ret = errno; } } if (ctx_p->flags[UID]) { int rc; debug(3, "Trying to drop effective uid to %i", ctx_p->uid); rc = seteuid(ctx_p->uid); if (rc && (ctx_p->flags[UID] != UGID_PRESERVE)) { error("Cannot seteuid(%u)", ctx_p->uid); ret = errno; } } if (main_statusfile_f == NULL && ctx_p->statusfile != NULL) { debug(1, "Trying to open the status file for writing (after setuid()/setgid())."); main_statusfile_f = fopen(ctx_p->statusfile, "w"); if (main_statusfile_f == NULL) { error("Cannot open file \"%s\" for writing.", ctx_p->statusfile); ret = errno; } } debug(1, "%s [%s] (%p) -> %s [%s] (%p)", ctx_p->watchdir, ctx_p->watchdirwslash, ctx_p->watchdirwslash, ctx_p->destdir?ctx_p->destdir:"", ctx_p->destdirwslash?ctx_p->destdirwslash:"", ctx_p->destdirwslash); { int rc = ctx_check(ctx_p); if (!ret) ret = rc; } if ( (ctx_p->listoutdir == NULL) && ( ctx_p->synchandler_argf & ( SHFL_INCLUDE_LIST_PATH | SHFL_EXCLUDE_LIST_PATH ) ) ) { char *template = strdup(TMPDIR_TEMPLATE); ctx_p->listoutdir = mkdtemp(template); if (ctx_p->listoutdir == NULL) { ret = errno; error("Cannot create temporary dir for list files"); } else rm_listoutdir = 2; } if (ctx_p->listoutdir != NULL) { struct stat st={0}; errno = 0; if (stat(ctx_p->listoutdir, &st)) { if (errno == ENOENT) { warning("Directory \"%s\" doesn't exist. Creating it.", ctx_p->listoutdir); errno = 0; if (mkdir(ctx_p->listoutdir, S_IRWXU)) { error("Cannot create directory \"%s\".", ctx_p->listoutdir); ret = errno; } else rm_listoutdir = 1; } else { error("Got error while stat() on \"%s\".", ctx_p->listoutdir); ret = errno; } } if (!errno) if (st.st_mode & (S_IRWXG|S_IRWXO)) { #ifdef PARANOID ret = errno = EACCES; error("Insecure: Others have access to directory \"%s\". Exit.", ctx_p->listoutdir); #else warning("Insecure: Others have access to directory \"%s\".", ctx_p->listoutdir); #endif } } if (ctx_p->flags[BACKGROUND]) { nret = becomedaemon(); if (nret) ret = nret; } if (ctx_p->pidfile != NULL) { debug(2, "Trying to open the pidfile \"%s\"", ctx_p->pidfile); pid_t pid = getpid(); FILE *pidfile = fopen(ctx_p->pidfile, "w"); if (pidfile == NULL) { // If error if (errno == EACCES) { int fd; uid_t euid = geteuid(); gid_t egid = getegid(); debug(1, "Don't have permissions to open file \"%s\". Trying seteuid(0)+open()+fchown()+close()+seteuid(%i)", ctx_p->pidfile, euid); errno = 0; if (!errno) SAFE ( seteuid(0), ret = errno ); if (!errno) SAFE ( (fd = open(ctx_p->pidfile, O_CREAT|O_WRONLY, 0644)) == -1, ret = errno ); if (!errno) SAFE ( fchown(fd, euid, egid), ret = errno ); if (!errno) SAFE ( close(fd), ret = errno ); if (!errno) SAFE ( seteuid(euid), ret = errno ); pidfile = fopen(ctx_p->pidfile, "w"); } if (pidfile == NULL) { error("Cannot open file \"%s\" to write a pid there", ctx_p->pidfile); ret = errno; } } if (pidfile != NULL) { if (fprintf(pidfile, "%u", pid) < 0) { error("Cannot write pid into file \"%s\"", ctx_p->pidfile); ret = errno; } fclose(pidfile); } } debug(3, "Current errno is %i.", ret); // == RUNNING == if (ret == 0) ret = sync_run(ctx_p); // == /RUNNING == if (ctx_p->pidfile != NULL) { if (unlink(ctx_p->pidfile)) { FILE *pidfile; debug(1, "Cannot unlink pidfile \"%s\": %s. Just truncating the file.", ctx_p->pidfile, strerror(errno)); SAFE ( (pidfile = fopen(ctx_p->pidfile, "w")) == NULL, ret = errno ); if (pidfile != NULL) fclose(pidfile); } } if (ctx_p->statusfile != NULL) { if (main_statusfile_f != NULL) if (fclose(main_statusfile_f)) { error("Cannot close file \"%s\".", ctx_p->statusfile); ret = errno; } if (unlink(ctx_p->statusfile)) { error("Cannot unlink status file \"%s\"", ctx_p->statusfile); ret = errno; } } if ((!ctx_p->flags[DONTUNLINK]) && (ctx_p->listoutdir != NULL) && rm_listoutdir) { debug(2, "rmdir(\"%s\")", ctx_p->listoutdir); if (rmdir(ctx_p->listoutdir)) error("Cannot rmdir(\"%s\")", ctx_p->listoutdir); if (rm_listoutdir == 2) free(ctx_p->listoutdir); } /* if (ctx_p->flags[PIVOT_ROOT] == PW_AUTO || ctx_p->flags[PIVOT_ROOT] == PW_AUTORO) { umount2("/", MNT_DETACH); // DELETE THE DIRECTORY } */ main_cleanup(ctx_p); if (ctx_p->watchdirwslashsize) free(ctx_p->watchdirwslash); if (ctx_p->destdirwslashsize) free(ctx_p->destdirwslash); if (ctx_p->rulfpathsize) free(ctx_p->rulfpath); error_deinit(); ctx_cleanup(ctx_p); debug(1, "finished, exitcode: %i: %s.", ret, strerror(ret)); free(ctx_p); #ifndef __FreeBSD__ // Hanging up with 100%CPU eating, https://github.com/xaionaro/clsync/issues/97 SAFE (posixhacks_deinit(), errno = ret = _SAFE_rc); #endif return ret; } clsync-0.4.1/main.h000066400000000000000000000033361252417542300141160ustar00rootroot00000000000000/* clsync - file tree sync utility based on fanotify and inotify Copyright (C) 2013 Dmitry Yu Okunev 0x8E30679C This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ extern int ncpus; extern pid_t parent_pid; extern int argc; extern char *argv[]; extern int main_rehash(ctx_t *ctx_p); extern int main_status_update(ctx_t *ctx_p); extern int ctx_set(ctx_t *ctx_p, const char *const parameter_name, const char *const parameter_value); extern int config_block_parse(ctx_t *ctx_p, const char *const config_block_name); extern int rules_count(ctx_t *ctx_p); extern char *parameter_expand( ctx_t *ctx_p, char *arg, int exceptionflags, int *macros_count_p, int *expand_count_p, const char *(*parameter_get)(const char *variable_name, void *arg), void *parameter_get_arg ); extern pid_t fork_helper(); extern int parent_isalive(); extern int sethandler_sigchld(void (*handler)()); extern pid_t waitpid_timed(pid_t child_pid, int *status_p, long sec, long nsec); #define exit_on(cond) { debug(30, "exit_on: checking: %s", TOSTR(cond)); if (unlikely(cond)) { debug(1, "Exiting due to: "TOSTR(cond)); exit(0); } } clsync-0.4.1/malloc.c000066400000000000000000000117411252417542300144330ustar00rootroot00000000000000/* clsync - file tree sync utility based on inotify Copyright (C) 2013 Dmitry Yu Okunev 0x8E30679C This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "common.h" #include #include #ifdef CAPABILITIES_SUPPORT # include # include # ifdef SECCOMP_SUPPORT # include # include # endif #endif #include // shmget() #include // shmget() #include "malloc.h" #include "error.h" #include "configuration.h" #ifdef CAPABILITIES_SUPPORT long pagesize; # ifdef SECCOMP_SUPPORT int devzero_fd; # endif #endif void *xmalloc(size_t size) { debug(20, "(%li)", size); #ifdef PARANOID size++; // Just in case #endif void *ret = malloc(size); if (ret == NULL) critical("(%li): Cannot allocate memory.", size); #ifdef PARANOID memset(ret, 0, size); #endif return ret; } void *xcalloc(size_t nmemb, size_t size) { debug(20, "(%li, %li)", nmemb, size); #ifdef PARANOID nmemb++; // Just in case size++; // Just in case #endif void *ret = calloc(nmemb, size); if (ret == NULL) critical("(%li): Cannot allocate memory.", size); // memset(ret, 0, nmemb*size); // Just in case return ret; } void *xrealloc(void *oldptr, size_t size) { debug(20, "(%p, %li)", oldptr, size); #ifdef PARANOID size++; // Just in case #endif void *ret = realloc(oldptr, size); if (ret == NULL) critical("(%p, %li): Cannot reallocate memory.", oldptr, size); return ret; } #ifdef CAPABILITIES_SUPPORT void *malloc_align(size_t size) { size_t total_size; void *ret = NULL; debug(20, "(%li)", size); # ifdef PARANOID size++; // Just in case # endif total_size = size; # ifdef PARANOID total_size += pagesize-1; total_size /= pagesize; total_size *= pagesize; # endif if (posix_memalign(&ret, pagesize, total_size)) critical("(%li): Cannot allocate memory.", size); # ifdef PARANOID if (ret == NULL) critical("(%li): ptr == NULL.", size); # endif // memset(ret, 0, nmemb*size); // Just in case return ret; } void *calloc_align(size_t nmemb, size_t size) { size_t total_size; void *ret; debug(20, "(%li, %li)", nmemb, size); # ifdef PARANOID nmemb++; // Just in case size++; // Just in case # endif total_size = nmemb*size; ret = malloc_align(total_size); memset(ret, 0, total_size); return ret; } char *strdup_protect(const char *src, int prot) { size_t len = strlen(src); char *dst = malloc_align(len); strcpy(dst, src); if (mprotect(dst, len, prot)) critical("(%p, 0x%o): Got error from mprotect(%p, %lu, 0x%o)", src, prot, dst, len, prot); return dst; } # ifdef SECCOMP_SUPPORT int is_protected(void *addr) { char *_addr = addr, t; int is_protected; t = *_addr; is_protected = (read(devzero_fd, addr, 1) == -1); if (!is_protected) *_addr = t; return is_protected; } # endif #endif int memory_init() { #ifdef CAPABILITIES_SUPPORT pagesize = sysconf(_SC_PAGE_SIZE); if (pagesize == -1) critical("Got error from sysconf(_SC_PAGE_SIZE)"); # ifdef SECCOMP_SUPPORT devzero_fd = open(DEVZERO, O_RDONLY); if (devzero_fd == -1) critical("Got error while open(\""DEVZERO"\", O_RDONLY)"); # endif #endif return 0; } void *shm_malloc_try(size_t size) { void *ret; #ifdef PARANOID size++; #endif int privileged_shmid = shmget(0, size, IPC_PRIVATE|IPC_CREAT|0600); struct shmid_ds shmid_ds; if (privileged_shmid == -1) return NULL; ret = shmat(privileged_shmid, NULL, 0); if ((long)ret == -1) return NULL; debug(15, "ret == %p", ret); // Forbidding access for others to the pointer shmctl(privileged_shmid, IPC_STAT, &shmid_ds); shmid_ds.shm_perm.mode = 0; shmctl(privileged_shmid, IPC_SET, &shmid_ds); // Checking that nobody else attached to the shared memory before access forbidding shmctl(privileged_shmid, IPC_STAT, &shmid_ds); if (shmid_ds.shm_lpid != shmid_ds.shm_cpid) { error("A process (pid %u) attached to my shared memory. It's a security problem. Emergency exit."); shmdt (ret); return NULL; } return ret; } void *shm_malloc(size_t size) { void *ret; ret = shm_malloc_try(size); critical_on (ret == NULL); return ret; } void *shm_calloc(size_t nmemb, size_t size) { void *ret; size_t total_size; #ifdef PARANOID nmemb++; size++; #endif total_size = nmemb * size; ret = shm_malloc(total_size); critical_on (ret == NULL); memset(ret, 0, total_size); return ret; } void shm_free(void *ptr) { debug(25, "(%p)", ptr); shmdt(ptr); } clsync-0.4.1/malloc.h000066400000000000000000000026001252417542300144320ustar00rootroot00000000000000/* clsync - file tree sync utility based on fanotify and inotify Copyright (C) 2013 Dmitry Yu Okunev 0x8E30679C This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include extern void *xmalloc(size_t size); extern void *xcalloc(size_t nmemb, size_t size); extern void *xrealloc(void *oldptr, size_t size); #ifdef CAPABILITIES_SUPPORT extern void *malloc_align(size_t size); extern void *calloc_align(size_t nmemb, size_t size); extern char *strdup_protect(const char *src, int prot); # ifdef SECCOMP_SUPPORT extern int is_protected(void *addr); # endif #endif extern void *shm_malloc(size_t size); extern void *shm_malloc_try(size_t size); extern void *shm_calloc(size_t nmemb, size_t size); extern void shm_free(void *ptr); extern int memory_init(); clsync-0.4.1/man/000077500000000000000000000000001252417542300135675ustar00rootroot00000000000000clsync-0.4.1/man/man1/000077500000000000000000000000001252417542300144235ustar00rootroot00000000000000clsync-0.4.1/man/man1/clsync.1000066400000000000000000001467471252417542300160230ustar00rootroot00000000000000.\" Sorry for my English .\" --Dmitry Yu Okunev 0x8E30679C .\" .\" Thanks to oldlaptop [https://github.com/oldlaptop] for help with spelling .\" .TH CLSYNC 1 "JULY 2013" Linux "User Manuals" .SH NAME clsync \- live sync tool, written in GNU C .SH SYNOPSIS .B clsync [ ... ] \-\- [ sync\-handler\-arguments ] .SH DESCRIPTION .B clsync executes .I sync\-handler with appropriate arguments on FS events in directory .I watch\-dir using the .BR inotify (7) or other FS monitoring subsystems. .SH OPTIONS This options can be passed as arguments or to be used in the configuration file. To disable numeric option set to zero: .RS =0 .RE To disable string option (for example path to file) set to empty string: .RS = .RE Also you can use previously set values while setting new options. Substring .IR %option_name% will be substituted with previously set value of option .IR option_name . (see .BR "CONFIGURATION FILE" ) .I sync\-handler\-arguments applies only to modes: .RS simple, direct, shell, rsyncdirect, rsyncshell .RE To set .I sync\-handler\-arguments in config file use '\-\-'. An example: .RS \-\- = \-aH \-\-exclude\-from %EXCLUDE\-LIST% \-\-include\-from=%INCLUDE\-LIST% \-\-exclude '*' %watch\-dir%/ %destination\-dir%/ .RE .B \-W, \-\-watch\-dir .I watch\-dir .RS Root directory to be monitored by .BR clsync . Required. .PP .RE .B \-S, \-\-sync\-handler .I sync\-handler .RS Path to .I sync\-handler to be used for syncing by .BR clsync . (see .IR \-\-mode ) Is required for all modes except "direct" and "rsyncdirect" [see .BR "SYNC HANDLER MODES" ] .PP .RE .B \-R, \-\-rules\-file .I rules\-file .RS Path to file with filter rules of objects to be monitored. (see .BR RULES ) Is not set by default. .PP .RE .B \-D, \-\-destination\-dir .I destination\-directory .RS Defines directory to sync to for modes "rsyncdirect", "rsyncso" and "so". (see .IR \-\-mode ) Is not set by default. .PP .RE .B \-M, \-\-mode .I mode .RS Sets syncing mode. Possible values: .RS .IR simple .RS calls .IR sync\-handler " for every event" .RE .IR direct .RS calls .IR sync\-handler " for every sync" with passing files lists as arguments .RE .IR shell .RS calls .IR sync\-handler " for every sync" with passing files lists in a file .RE .IR rsyncdirect .RS calls rsync by path .IR sync\-handler " directly" .RE .IR rsyncshell .RS calls .IR sync\-handler " that supposed to run rsync for every sync (recommended mode)" .RE .IR rsyncso .RS loads shared object by path .IR sync\-handler " with " .BR dlopen "(3) and calls function " clsyncapi_rsync " function for every sync" .RE .IR so .RS loads shared object by path .IR sync\-handler " with " .BR dlopen "(3) and calls function " clsyncapi_sync " function for every sync" .RE .RE See .B SYNC HANDLER MODES .PP Required. .RE .B \-b, \-\-background .RS Daemonize, forcing clsync to fork() on start. Is not set by default. .PP .RE .B \-H, \-\-config\-file .I config\-file\-path .RS Use configuration from file .IR config\-file\-path (see .BR "CONFIGURATION FILE" ). Set to "/NULL/" if no config files should be read. Is not set by default. .PP .RE .B \-K, \-\-config\-block .I config\-block\-name .RS Use configuration block with name .IR config\-block\-name (see .BR "CONFIGURATION FILE" ). The default value is "default". .PP .RE .B \-\-config\-block\-inherits .I config\-parent\-block\-name .RS Use configuration block with name .IR config\-parent\-block\-name as parent for .IR config\-block\-name (see .BR "CONFIGURATION FILE" ). Options from .IR config\-parent\-block\-name will be inherited to .IR config\-block\-name . The default value is "default". .PP .RE .B \-\-custom\-signals .I custom\-signals .RS Set a list of signals and corresponding config block names. The config block will be use on catching the corresponding signal. Format is .RS .I signal:config\-block\-name[,signal:config\-block\-name[,...]] .RE For example: .RS \-\-custom\-signals=29:debug,28:normal .RE In this line signals "28" and "29" will be added to the sighandler. And clsync will use options from config block "debug" on signal 29 and "normal" on signal 28. To reset all custom signals use the 0-th signal (e.g. "\-\-custom\-signals=0"). The default value is "". .PP .RE .B \-z, \-\-pid\-file .I path\-to\-pidfile .RS Writes pid to file by path .IR path\-to\-pidfile . Is not set by default. .PP .RE .B \-\-status\-file .I status\-file\-path .RS Write status description into file with path .IR status\-file\-path . Possible statuses: .RS .IR starting .RS initializing subsystems and marking file tree with FS monitor subsystem .RE .IR initsync .RS processing initial syncing .RE .IR running .RS waiting for events or syncing .RE .IR "synchandler error" .RS waiting between synchandler execution tries (after a failure) [is used only while .BR \-\-threading =off] .RE .IR rehashing .RS reloading configuration files .RE .IR "thread gc" .RS running threads' garbage collector .RE .IR preexit .RS executing the .I \-\-pre\-exit\-hook .RE .IR terminating .RS running the last iteration (if required) and preparing to die .RE .IR exiting .RS executing the .I \-\-exit\-hook and cleaning up [for .BR valgrind (1)] .RE .RE Is not set by default. .PP .RE .B \-r, \-\-retries .I "number-of-tries" .RS Tries limit to sync with .IR sync-handler . .B clsync will die after .I number-of-tries tries. To try infinite set "0". Delay between tries is equal to .I \-\-delay\-sync value. The default value is "1". .RE .B \-\-ignore\-failures .RS Don't die on sync failures. Is not set by default. .RE .B \-\-exit\-on\-sync\-skip .RS Exit if some event could be skipped due to any reason. For example FreeBSD has a very short BSM event queue (1024). So it may be overflowed and some events can not climb to the queue. This option forces .B clsync to exit if the queue had been overflowed. Is not set by default. .RE .B \-p, \-\-threading .I threading-mode .RS Use .BR pthreads (7) to parallelize syncing processes. For example if .B clsync (with .BR \-\-threading=off ) is already syncing a huge file then all other syncs will be suspended until the huge file syncing finish. To prevent this suspends you can use "safe" or "full" threading mode. Possbile values: .RS .IR off .RS disable threading for syncing processes. .RE .IR safe .RS parallelize syncs but suspend syncings of object that are already syncing in another process (until the process finish). .RE .IR full .RS parallelize syncs without suspendings. .RE .RE Characteristics: .RS .IR off .RS New modifications won't be synced until old ones finish. .RE .IR safe .RS Theoretically is the best way. But may utilize of lot of CPU if there's a lot of simultaneous parallel syncs. (also this way is not well tested) .RE .IR full .RS May cause multiple simultaneous syncing of the same file, which in turn can cause bug inside .IR sync\-handler " (see below)." .RE .RE If you're running .B clsync with option .B \-\-threading=full in conjunction with .B rsync with option .BR \-\-backup , you may catch a bug due to nonatomicity of rsync's file replace operation. (see .BR DIAGNOSTICS ) The default value is "off". .RE .B \-Y, \-\-output .I log\-destination .RS Sets destination for log writing (errors, warnings, infos and debugging). Possible values: .RS .I stderr .br .I stdout .br .I syslog .RE The default value is "stderr". .RE .B \-\-one\-file\-system .RS Don't follow to different devices' mount points. This option just adds option "FTS_XDEV" for .BR fts_open (3) function. .B Warning! If you're using this option (but no \-\-exclude\-mount\-points) .B clsync will write neither includes nor excludes of content of mount points. .br This may cause problems e.g. you're using rsync for sync-handler without similar option "\-\-one\-file\-system". Is not set by default. .RE .B \-X, \-\-exclude\-mount\-points .RS Forces .I \-\-one\-file\-system but also add excludes to do not sync mount points. This requires to do .BR stat (2) syscalls on every dir and can reduce performance. Is not set by default. .RE .B \-\-socket .I socket\-path .RS Create a control socket by path .IR socket\-path . This's very experimental feature. Is not set by default. .RE .B \-\-socket\-own .I socket\-owner\-user[:socket\-owner\-group] .RS Sets the control socket owner user (and group). Is not set by default .RE .B \-\-socket\-mod .I socket\-mode .RS Sets the control socket mode [see .BR chmod (2)]. Is not set by default. .RE .\" .B \-c, \-\-cluster\-iface .\" .I interface\-ip .\" .RS .\" .B Not implemented, yet. .\" .\" .B DANGEROUS OPTION. This functionality wasn't tested well. You can lost your data. .\" .\" Enables inter-node notifing subsystem to prevent sync looping between nodes. .\" This's very useful features that provides ability of birectional sync of the .\" same directory between two or more nodes. .\" .I interface-ip .\" is an IP-address already assigned to the interface that will be used for .\" multicast notifing. .\" .\" Not enabled by default. .\" .\" To find out the IP-address on interface "eth0", you can use for example next .\" command: .\" .\" ip a s eth0 | awk '{if($1=="inet") {gsub("/.*", "", $2); print $2}}' .\" .\" Is not set by default. .\" .RE .\" .\" .PP .\" .B \-m, \-\-cluster\-ip .\" .I multicast\-ip .\" .RS .\" .B Not implemented yet. .\" .\" Sets IP-address for multicast group. .\" .\" This option can be used only in conjunction with .\" .BR \-\-cluster\-interface . .\" .\" Use IP-addresses from 224.0.0.0/4 for this option. .\" .\" The default value is "227.108.115.121". [(128+"c")."l"."s"."y"] .\" .RE .\" .\" .PP .\" .B \-P, \-\-cluster\-port .\" .I multicast\-port .\" .RS .\" .B Not implemented yet. .\" .\" Sets UDP-port number for multicast messages. .\" .\" This option can be used only in conjunction with .\" .BR \-\-cluster\-interface . .\" .\" .I multicast\-port .\" should be greater than 0 and less than 65535. .\" .\" The default value is "40079". [("n" << 8) + "c"] .\" .RE .\" .\" .PP .\" .B \-W, \-\-cluster\-timeout .\" .I cluster\-timeout .\" .RS .\" .B Not implemented yet. .\" .\" Sets timeout (in milliseconds) of waiting answer from another nodes of the .\" cluster. If there's no answer from some node, it will be excluded. .\" .\" The default value is "1000". [1 second] .\" .RE .\" .\" .PP .\" .B \-n, \-\-cluster\-node\-name .\" .I cluster\-node\-name .\" .RS .\" .B Not implemented yet. .\" .\" Sets the name of this node in the cluster. It will be used in action .\" scripts of another nodes (see .\" .BR "SYNC HANDLER MODES" ). .\" .\" The default value is $(uname \-n). .\" .RE .\" .\" .PP .\" .B \-n, \-\-cluster\-node\-id .\" .I cluster\-node\-id .\" .RS .\" .B Not implemented yet. .\" .\" Sets an ID for this node in the cluster. It's used for messaging between .\" cluster nodes. .\" .\" Use value "-1" to choose it automatically. .\" .\" The default value is "-1". .\" .RE .\" .\" .PP .\" .B \-o, \-\-cluster\-hash\-dl\-min .\" .I hash\-dirlevel\-min .\" .RS .\" Sets minimal directory level for ctime hashing (see .\" .BR CLUSTERING ). .\" .\" The default value is "1". .\" .RE .\" .\" .PP .\" .B \-O, \-\-cluster\-hash\-dl\-max .\" .I hash\-dirlevel\-max .\" .RS .\" .B Not implemented yet. .\" .\" Sets maximal directory level for ctime hashing (see .\" .BR CLUSTERING ). .\" .\" The default value is "16". .\" .RE .\" .\" .PP .\" .B \-\-cluster\-scan\-dl\-max .\" .I scan\-dirlevel\-max .\" .RS .\" .B Not implemented yet. .\" .\" Sets maximal directory level for ctime scanning (see .\" .BR CLUSTERING ). .\" .\" The default value is "32". .\" .RE .PP .B \-\-standby\-file .I standby\-file\-path .RS Sets file to path that should be checked before every sync. If file exists the sync will be suspended until the file is deleted. It may be useful if you need freeze destination directory while running some scripts. Is not set by default. .RE .PP .B \-\-max\-iterations .I iterations\-count .RS Sets synchronization iterations limit. One iteration means one sync-handler execution. .I iterations\-count .RS set to 0 means no limit (infinite loop). set to 1 means that only initial sync will be done set to n means that only initial sync and (n-1) sync-ups after that will be done .RE Hint: This option may be useful in conjunction with \-\-exit\-on\-no\-events to prevent infinite sync-up processes. The default value is "0". .RE .B \-\-modification\-signature .I signature\-mask .RS Sets file/dir modification recheck signature. If file is not modified (according to the signature) then don't sync it. See .I "struct stat" in .BR lstat (2) for possible fields. For example reasonable .IR signature\-mask \-s can be "dev,ino,mode,uid,gid,rdev,size,atime,mtime,ctime" (there's an alias for that — "*") or "uid,gid". Examples of use cases: .RS .B chown/chmod .RS If you're using clsync for fixing file/dir privileges [using .BR chown (1) and/or .BR chmod (1)] than reasonable signature will be "uid,gid". Full example: clsync \-w5 \-t5 \-T5 \-x1 \-W /var/www/site.example.org/root \-Mdirect \-Schown \-\-uid 0 \-\-gid 0 \-Ysyslog \-b1 \-\-modification\-signature uid,gid \-\- \-\-from=root www\-data:www\-data %INCLUDE\-LIST% .RE .B "bi\-directional syncing" .RS If you're going to setup bi\-directional syncing then you may use \-\-modification\-signature "*" to prevent sync loop between servers. .RE .B Not enough CPU .RS If rsync eats too many CPU with rechecking hashsums of files on their dry open()/close() due to some hacky script (for example "chown \-R www-data:www-data" in cron) then you can use \-\-modification\-signature "dev,ino,mode,uid,gid,rdev,size,atime,mtime" (without "blksize", "blocks", "nlink" and "ctime"). .RE .RE .B Warning! This option may eat a lot of memory on huge file trees. This option cannot be used together with "\-\-cancel\-syscalls=mon_stat" To disable file/dir modification rechecking use empty value — "". The default value is "". .RE .PP .B \-k, \-\-timeout\-sync .I sync\-timeout .RS Sets timeout for syncing processes. .B clsync will die if syncing process alive more than .I sync\-timeout seconds. Set "0" to disable the timeout. The default value is "86400" ["24 hours"]. .RE .PP .B \-w, \-\-delay\-sync .I additional\-delay .RS Sets the minimal delay (in seconds) between syncs. The default value is "30". .RE .PP .B \-t, \-\-delay\-collect .I ordinary\-delay .RS Sets the delay (in seconds) to collect events about ordinary files and directories. The default value is "30". .RE .PP .B \-T, \-\-delay\-collect\-bigfile .I bigfiles\-delay .RS Sets the delay (in seconds) to collect events about "big files" (see .IR \-\-threshold\-bigfile ). The default value is "1800". .RE .PP .B \-B, \-\-threshold\-bigfile .I filesize\-threshold .RS Sets file size threshold (in bytes) that separates ordinary files from "big files". Events about "big files" are processed in another queue with a separate collecting delay. This is supposed to be used as a means of unloading IO resources. To disable detection of "big files" set "0" (zero). This can improve perfomance by removing necessity in extra lstat() syscall. The default value is "134217728" ["128 MiB"]. .RE .B \-\-cancel\-syscalls .I syscalls\-mask .RS Sets syscalls to be bypassed. This may be used for to squeeze more performance. Possible values: .RS .B mon_stat .RS Skip lstat() calls while handling files/dirs events. This makes unpossible to determine files sizes (that is used by .B \-\-threshold\-bigfile option) and to use option .BR \-\-modification\-signature . .RE .RE You can combine this values using commas. To disable this option just use empty value — "". The default value is "". .RE .PP .B \-L, \-\-lists\-dir .I tmpdir\-path .RS Sets directory path to output temporary events\-lists files. See .BR "SYNC HANDLER MODES" . Is not set by default. .RE .PP .B \-\-have\-recursive\-sync .RS Use action "recursivesync" instead of "synclist" for directories that were just marked (see .B "SYNC HANDLER MODES" case .BR shell ). Is not set by default. .RE .PP .B \-\-synclist\-simplify .RS Removes the first 3 parameters in list files of action "synclist" (see .B "SYNC HANDLER MODES" case .BR shell ). Is not set by default. .RE .\" .PP .\" .B \-A, \-\-auto\-add\-rules\-w .\" .RS .\" Forces clsync to create a "w-rule" for every non-"w-rule" (see .\" .BR RULES ). .\" .\" Not recommended to use in modes "rsyncdirect", "rsyncshell" and "rsyncso" .\" .\" Is not set by default. .\" .RE .PP .B \-\-rsync\-inclimit .I rsync\-includes\-line\-limit .RS Sets soft limit for lines count in files by path .IR rsync\-listpath . Unfortunately, rsync works very slowly with huge "\-\-include\-from" files. So, .B clsync splits that list with approximately .I rsync\-includes\-line\-limit lines per list if it's too big, and executes by one rsync instance per list part. Use value "0" to disable the limit. The default value is "20000". .RE .PP .B \-\-rsync\-prefer\-include .RS Forces .B clsync to prefer a "lot of includes" method instead of a "excludes+includes" for rsync on recursive syncing. See cases .BR rsyncshell , .B rsyncdirect and .B rsyncso of .BR "SYNC HANDLER MODES" . This option is not recommended. Is not set by default. .RE .PP .B \-x, \-\-ignore\-exitcode .I exitcode .RS Forces .B clsync to do not process exitcode .I exitcode of .I sync\-handler as an error. You can set multiple ignores by passing this option multiple times. Recommended values for rsync case is "24". You can set multiple values with listing a lot of "\-x" options (e.g. "\-x 23 \-x 24") or via commas (e.g. "\-x 23,24"). To drop the list use zero exitcode (e.g. "\-x 0"). For example you can use "\-x 0,23" to drop the list and set "23"-th exitcode to be ignored. Is not set by default (or equally is set to "0"). .RE .PP .B \-U, \-\-dont\-unlink\-lists .RS Do not delete list\-files after .I sync\-handler has finished. This may be used for debugging purposes. Is not set by default. .RE .PP .B \-\-fts\-experimental\-optimization .RS Enable experimental features to optimize file tree scanning while using .BR fts "(3)." The features will be enabled by default after appropriate testing. At the moment the option doesn't do anything but can be used in future. Is not set by default. .RE .PP .B \-F, \-\-full\-initialsync .RS Ignore filter rules from .I rules-file on initial sync. This may be useful for quick start or e.g. if it's required to sync "/var/log/" tree but not sync every change from there. Is not set by default. .RE .PP .B \-\-only\-initialsync .RS Exit after initial syncing on clsync start. Is not set by default. .RE .PP .B \-\-exit\-on\-no\-events .RS Exit if there's no events. Works like .IR \-\-only\-initialsync , but also syncs events collected while the initial syncing. Unlike .I \-\-only\-initialsync this option uses FS monitor subsystem to monitor for new events while the initial syncing. This may reduce performance. On the other hand this way may be used to be sure, that everything is synced at the moment before clsync will exit. Is not set by default. .RE .PP .B \-\-skip\-initialsync .RS Skip initial syncing on clsync start. Is not set by default. .RE .PP .B \-\-exit\-hook .I path\-of\-exit\-hook\-program .RS Sets path of program to be executed on clsync exit. If this parameter is set then clsync will exec on exit: .RS .I path\-of\-exit\-hook\-program label .RE The execution will be skipped if initial sync wasn't complete. Is not set by default. .RE .PP .B \-\-pre\-exit\-hook .I path\-of\-pre\-exit\-hook\-program .RS Sets path of program to be executed before the last sync iteration (see .IR "\-\-max\-iterations" ", " "\-\-exit\-on\-no\-events" " and " .BR SIGNALS ")." If this parameter is set then clsync will exec on exit: .RS .I path\-of\-pre\-exit\-hook\-program label .RE The execution will be skipped if initial sync wasn't complete. If .B clsync finishes due to .I \-\-exit\-on\-no\-events and .I \-\-pre\-exit\-hook is set then the pre\-exit hook will be executed and additional sync iteration will be triggered. Is not set by default. .RE .PP .B \-v, \-\-verbose .RS This option is supposed to increase verbosity. But at the moment there's no "verbose output" in the code, so the option does nothing. :) Is not set by default. .RE .PP .B \-d, \-\-debug .RS Increases debugging output. This may be supplied multiple times for more debugging information, up to a maximum of five "d" flags (more will do nothing), for example "\-d \-d \-d \-d \-d" or "\-d5" (equivalent cases) Is not set by default. .RE .PP .B \-\-dump\-dir .RS Directory to write clsync's instance information by signal 29 (see .BR SIGNALS ")." The directory shouldn't exists before dumping. Is set to "/tmp/clsync\-dump\-%label%" by default. .RE .PP .B \-q, \-\-quiet .RS Suppresses error messages. Is not set by default. .RE .PP .B \-\-monitor .I monitor\-subsystem .RS Switches FS monitor subsystem. Possible values: .RS .IR inotify .RS .BR inotify "(7) [Linux, (FreeBSD via libinotify)]" Native, fast, reliable and well tested Linux FS monitor subsystem. There's no essential performance profit to use "inotify" instead of "kevent" on FreeBSD using "libinotify". It backends to "kevent" anyway. FreeBSD users: The libinotify on FreeBSD is still not ready and unusable for .B clsync to sync a lot of files and directories. .RE .IR gio .RS Use .B gio library. Crossplatform and tested library that backends to kqueue on FreeBSD and inotify on Linux. See .B inotify and .B kqueue sections here for details. .B Not well tested. Use with caution! .RE .IR kqueue .RS .BR kqueue "(2) [FreeBSD, (Linux via libkqueue)]" A *BSD kernel event notification mechanism (inc. timer, sockets, files etc). This monitor subsystem cannot determine file creation event, but it can determine a directory where something happened. So .B clsync is have to rescan whole dir every time on any content change. Moreover, kqueue requires an open() on every watched file/dir. But FreeBSD doesn't allow to open() symlink itself (without following) and it's highly invasively to open() pipes and devices. So .B clsync just won't call open() on everything except regular files and directories. Consequently, .B clsync cannot determine if something changed in symlink/pipe/socket and so on. However it still can determine if it will be created or deleted by watching the parent directory and rescaning it on every appropriate event. Also this API requires to open every monitored file and directory. So it may produce a huge amount of file descriptors. Be sure that .I kern.maxfiles is big enough (in FreeBSD). CPU/HDD expensive way. .B Not well tested. Use with caution! Linux users: The libkqueue on Linux is not working. He-he :) .RE .IR bsm .RS .BR bsm "(3) [FreeBSD]" Basic Security Module (BSM) Audit API. This is not a FS monitor subsystem, actually. It's just an API to access to audit information (inc. logs). .B clsync can setup audit to watch FS events and report it into log. After that .B clsync will just parse the log via .BR auditpipe "(4) [FreeBSD]." Reliable, but hacky way. It requires global audit reconfiguration that may hopple audit analysis. .B Warning! FreeBSD has a limit for queued events. In default FreeBSD kernel it's only 1024 events. So choose .B one of: .RS \- To patch the kernel to increase the limit. .br \- Don't use .B clsync on systems with too many file events. .br \- Use .I bsm_prefetch mode (but there's no guarantee in this case anyway). .RE See also option .IR \-\-exit\-on\-sync\-skip . .B Not well tested. Use with caution! Also file /etc/security/audit_control will be overwritten with: .RS #clsync .br .br dir:/var/audit .br flags:fc,fd,fw,fm,cl .br minfree:0 .br naflags:fc,fd,fw,fm,cl .br policy:cnt .br filesz:1M .RE unless it's already starts with "#clsync\\n" ("\\n" is a new line character). .RE .I bsm_prefetch .RS The same as .I bsm but all BSM events will be prefetched by an additional thread to prevent BSM queue overflow. This may utilize a lot of memory on systems with a high FS events frequency. However the thread may be not fast enough to unload the kernel BSM queue. So it may overflow anyway. .RE .RE The default value on Linux is "inotify". The default value on FreeBSD is "kqueue". .RE .PP .B \-l, \-\-label .I label .RS Sets a label for this instance of clsync. The .I label will be passed to .I sync\-handler every execution. The default value is "nolabel". .RE .PP .B \-h, \-\-help .RS Outputs options list and exits with exitcode "0". Is not set by default. .RE .PP .B \-V, \-\-version .RS Outputs clsync version and exits with exitcode "0". Is not set by default. .RE .PP .B \-\-cgroup\-group\-name .I cg\-group\-name .RS Set cgroup group name [see .BR cgroup_new_cgroup ()]. Is set to "clsync/%PID%" by default. .RE .SH SECURITY OPTIONS .B \-\-secure\-splitting .RS Implies "\-\-splitting=process \-\-check\-execvp\-arguments \-\-seccomp\-filter \-\-forbid\-devices" .RE .B \-u, \-\-uid .I uid .RS Drop user privileges to uid .I uid with .BR setuid (2) If there's a .BR capabilities (7) support then the default value is "nobody" (or "65534" if "nobody" not found), otherwise the option is not set by default; .PP .RE .B \-g, \-\-gid .I gid .RS Drop group privileges to gid .I gid with .BR setgid (2) If there's a .BR capabilities (7) support then the default value is "nogroup" (or "65534" if "nogroup" not found), otherwise the option is not set by default; .PP .RE .B \-\-privileged\-uid .I sync\-handler\-uid .RS An user ID to be used for the privileged process .BR "" "(see " "--splitting=process" ")". The default value is "$UID". .PP .RE .B \-\-privileged\-gid .I sync\-handler\-gid .RS A group ID to be used for the privileged process .BR "" "(see " "--splitting=process" ")". The default value is "$GID". .PP .RE .B \-\-sync\-handler\-uid .I sync\-handler\-uid .RS An user ID to be used for .IR sync\-handler . See .BR \-\-preserve\-capabilities . The default value is same as for .BR \-\-privileged-uid . .PP .RE .B \-\-sync\-handler\-gid .I sync\-handler\-gid .RS A group ID to be used for .IR sync\-handler . See .BR \-\-preserve\-capabilities . The default value is same as for .BR \-\-privileged-gid . .PP .RE .B \-C, \-\-preserve\-capabilities .I capabilities\-list .RS .B [Linux only, requires capabilities] Use .BR capset (2) and .BR prctl (2) to preserve "CAP_DAC_READ_SEARCH", "CAP_SETUID" or/and "CAP_SETGID" [see .BR capabilities (7)] Linux capability for process using .BR fts "(3), " inotify "(7) and " execve "(2)." This allows the preservation of enough FS privileges to watch a file tree and execute the .I sync\-handler with required uid and gid [see .B \-\-sync\-handler\-uid and .BR \-\-sync\-handler\-gid ] after dropping privileges via .BR setuid "(2) and " setgid "(2)" [see .B \-\-uid and .BR \-\-gid ] Possible values: .RS .B CAP_DAC_READ_SEARCH .RS To bypass FS read checks (for .BR fts " and " inotify ). .RE .B CAP_SETUID .RS To be able to use .BR setuid (2) before .BR execve (2) on the .BR sync\-handler . .RE .B CAP_SETGID .RS To be able to use .BR setgid (2) before .BR execve (2) on the .BR sync\-handler . .RE .B CAP_KILL .RS To be able to kill setuid()-ed processes .RE .br .br Any combinations of this values are also supported. The list may be presented as a comma separated values, like: .RS CAP_DAC_READ_SEARCH,CAP_SETUID,CAP_SETGID .RE .RE The default value is "CAP_DAC_READ_SEARCH,CAP_SETUID,CAP_SETGID,CAP_KILL" if the .B clsync runner have such privileges. .PP .RE .B \-\-inherit\-capabilities .RS .B [Linux only, requires capabilities] Sets a mode for capabilities inheriting. Possible values: .RS .B permitted .RS Inherits all permitted capabilities .RE .B dont-touch .RS Don't change inheritable capabilities set .RE .B clsync .RS Use .BR clsync 's effective capabilities set .RE .B empty .RS Reset all capabilities .RE .RE The default value is "empty". .RE .B \-\-splitting .I splitting\-type .RS Split the process/thread to privileged and non-privileged. This's an additional way to secure your system from any bug in .B clsync while running it with capabilities or root privileges. But .B clsync may utilize in few times more CPU resources. So it's a performance vs security trade off. You can essentialy reduce the overhead with using "high load locks" ("\-\-enable\-highload\-locks" of "./configure" file). If you're using this option and running the .I sync\-handler with the root user then it's highly recommended to enable .BR \-\-check\-execvp\-arguments , too. Otherwise in case of .B clsync security bug a hacker will be able to use execvp() with any arguments with root privileges. Possible values: .RS .B off .RS Disable this feature .RE .B thread .RS .B [Linux only, requires capabilities] Creates a separate thread for privileged operations. It's highly recommended to enable .B \-\-seccomp\-filter in this case. But that will forbid .BR \-\-threading . .RE .B process .RS More secure and portable way, but uses separate process and: .RS - forbids fanotify (that is not implemented yet anyway); .br - more complex code (and higher probability of error). .br - slower due to copying data between private and shared memory pages. .RE .B Recommended. .RE .RE Is set to "off" by default. .RE .B \-\-check\-execvp\-arguments .RS .B [Requires \-\-splitting=[thread|process]] .br .B [Blocks \-\-mode=direct] Enables execvp() arguments recheck in the privileged process (in case of their substitution to any exploit-given arguments). This option doesn't utilize a lot of CPU resources but forbids run-time changing of .I sync\-handler\-arguments and hook file paths. This option cannot be used in conjuction with .BR \-\-mode "=direct" due to an arbitrary number of arguments in this mode. Is not set by default. .RE .B \-\-add\-permitted\-hook\-files .I [hook\-path0,[hook\-path1[,...]]] .RS .B [Requires \-\-check\-execvp\-arguments] Adds paths to the list of permitted hook paths to bypass .B \-\-check\-execvp\-arguments checks. It may be required if you're going to change the hooks in run-time using .B \-\-custom\-signals or .BR \-\-socket . Is not set by default. .RE .B \-\-seccomp\-filter .RS .B [Linux only] Use .B seccomp filter to forbid syscalls that shouldn't be used by clsync. Forbid all syscalls for non-privileged process/thread, but .RS futex inotify_init1 alert stat fstat lstat open write close wait4 unlink tgkill clock_gettime rt_sigreturn brk mmap munmap wait4 rmdir exit_group select read rt_sigprocmask rt_sigaction nanosleep .RE Is not set by default. .RE .B \-\-permit\-mprotect .RS .B "[Requires \-\-seccomp\-filter]" Permits .BR mprotect (2) syscall. This syscall is required by .BR pthread_create (3), so it's required for .BR \-\-threading . Makes \-\-shm\-mprotect to be useless. Also it enables ability to change memory of privileged thread from non-privileged, so using of .B \-\-splitting=thread with this option is useless, too. Is set to "0" by default if \-\-splitting is set. Otherwise "1". .RE .B \-\-shm\-mprotect .RS .B "[Requires \-\-splitting=process]" Forbid writing or reading to/from shared memory when it shouldn't be. .BR mprotect (2) is used for the protection. This option is useless while .B \-\-permit\-mprotect is enabled. .RE .B \-\-chroot .I chroot\-directory .RS clsync chroot()\-s [see .BR chroot (2)] to directory .I chroot\-directory before any syncing processes. This option may be used in conjunction with .BR \-\-uid ", " \-\-gid or/and .B \-\-pivot\-root for security reasons. Remember! If you're chroot()\-ing somewhere, the .I sync\-handler will be limited by the chroot\-environment, too. If you're using rsync then you may want to "mount \-\-bind" some directories to the .IR chroot\-directory . Is not set by default. .PP .RE .B \-\-pivot\-root .I pivot\-root\-way .RS .B [Linux only, requires \-\-chroot] Sets a way of using .BR pivot_root (2) syscall to the .I chroot\-directory (to .BR umount (2) old rootfs). Possible values: .RS .B auto .RS Creates a directory "/dev/shm/clsync\-rootfs", .BR unshare "(2)-ing the mount namespace, " mount (2)-s the .I chroot\-directory to the directory and then .BR pivot_root "(2)-ing, " chroot "(2)-ing and " umount (2)-ing old rootfs. Directory "/dev/shm/clsync\-rootfs" won't be deleted after .B clsync finish. .RE .B auto-ro .RS The same as .B auto but mounts the directory with read-only option (MS_RDONLY). .RE .B direct .RS .BR unshare "(2)-ing the mount namespace, " pivot_root "(2)-ing, " chroot "(2)-ing and " umount (2)-ing old rootfs. Directory "old_root" should be created in .I chroot\-directory before running .B clsync in this mode. .RE .B off .RS Don't .BR pivot_root (2). .RE .RE The default value is "off". If .B \-\-chroot is used then recommended value is "auto\-ro". .RE .B \-\-mountpoints .I [mountpoint[,mountpoint[,mountpoint]]] .RS .B [Linux only] Umount (with MNT_DETACH) everything except listed mountpoints. Supposed to be used for security reasons as an alternative to .BR \-\-pivot\-root option. Is not set by default. .RE .B \-\-detach\-network .I detach\-network\-mode .RS .B [Linux only] Removes network in .B clsync instance. Possible values: .RS .B everywhere .RS Removes network for all processes. .RE .B non\-privileged .RS Removes network from non\-privileged process if option .B \-\-process\-splitting is enabled, otherwise doesn't do anything. .RE .B off .RS Don't do anything. .RE .RE The default value is "non\-privileged". .RE .B \-\-detach\-ipc .RS .B [Linux only] Make an own IPC namespace. Is set by default. .RE .B \-\-detach\-miscellanea .RS .B [Linux only] .BR unshare (2) on everything not listed above. Is not set by default. .RE .B \-\-forbid\-devices .RS .B [Linux only] Forbid any access to all devices except listed ones: .RS read access to: .RS /dev/console .br /dev/zero .br /dev/urandom .br /dev/random .RE write access to: .RS /dev/console .br /dev/null .RE .RE Is not set by default. .RE .SH PERFORMANCE Recommendations to improve the perfomance: .RS - Disable thread/process splitting. .br - Don't use clsync rules (use rules on sync-handler side) or/and use option "\-\-full\-initialsync" .br - Use option "\-B0". .br - Use option "\-\-cancel\-syscalls=mon_stat". .br - Use option "\-p safe" or "\-p full". .br - Disable debugging with "\-d0" or better disable debugging support at all with "./configure" option "\-\-enable\-debug=no" .br - Don't use option "\-\-exclude\-mount\-points" .br - Free memory for disk cache .br .RE You shouldn't follow all this recommendation blindfold. You should use only the ideas that fixes performance problems in your specific use case. And only if it's necessary. .SH SYNC HANDLER MODES .B clsync executes .I sync\-handler that supposed to take care of the actual syncing process. Therefore .B clsync is only a convenient way to run a syncing script. .B clsync can run .I sync\-handler in seven ways. Which way will be used depends on specified mode (see .IR \-\-mode ) .I sync\-handler\-arguments are used only in modes: .RS simple .br direct .br shell .br rsyncdirect .br rsyncshell .RE If .I sync\-handler\-arguments are not set then the default setting is used (see below). case .B simple .RS Executes for every syncing file/dir: .RS .I sync\-handler sync\-handler\-arguments .RE Default .I sync\-handler\-arguments are: .RS sync .I %label% %EVENT\-MASK% %INCLUDE\-LIST% .RE In this case, .I sync\-handler is supposed to non\-recursively sync file or directory by path .IR %INCLUDE\-LIST% . With .I %EVENT\-MASK% it's passed bitmask of events with the file or directory (see "/usr/include/linux/inotify.h"). Additional substitutions: .RS .B %EVENT\-MASK% .RS Is replaced by integer of events IDs. .RE .B %INCLUDE\-LIST% .RS Is replaced by absolute path of a file/dir to be synced. .RE .RE .RE case .B direct .RS Executes for every sync: .RS .I sync\-handler sync\-handler\-arguments .RE Default .I sync\-handler\-arguments are: .RS %INCLUDE\-LIST% %destination\-dir%/ .RE Additional substitutions: .RS .B %INCLUDE\-LIST% .RS Is replaced by a list of relative paths of files/dirs to be synced. .RE .RE .RE case .B shell .RS Executes for every sync (if .B recursivesync is not used instead): .RS .I sync\-handler sync\-handler\-arguments .RE Default .I sync\-handler\-arguments are: .RS synclist %label% %INCLUDE\-LIST\-PATH% .RE Default .I sync\-handler\-arguments for initial sync if .I \-\-have\-recursive\-sync is set are: .RS initialsync %label% %INCLUDE\-LIST% .RE In this case, .I sync\-handler is supposed to non\-recursively sync files and directories from list in a file by path %INCLUDE\-LIST\-PATH% on "synclist". Also .I sync\-handler is supposed to recursively sync data from directory by path %INCLUDE\-LIST\-PATH% with manual excluding extra files on "initialsync". Additional substitutions: .RS .B %TYPE% .RS Is replaced by "sync"/"initialsync". .RE .B %INCLUDE\-LIST\-PATH% .RS Is replaced by the path of the include list file. .RE .B %INCLUDE\-LIST% .RS Is replaced by a list of relative paths of files/dirs to be synced. .RE .RE Not recommended. Not well tested. .RE case .B rsyncdirect .RS Executes for every sync: .RS .I sync\-handler sync\-handler\-arguments .RE .I sync\-handler is supposed to be a path to .B rsync binary. Default .I sync\-handler\-arguments are: .RS \-aH \-\-delete \-\-exclude\-from %EXCLUDE\-LIST\-PATH% \-\-include\-from %INCLUDE\-LIST\-PATH% \-\-exclude='*' %watch\-dir%/ %destination\-dir%/ .RE if option .I \-\-rsync\-\-prefer\-include is not set and .RS \-aH \-\-delete \-\-include\-from %INCLUDE\-LIST\-PATH% \-\-exclude='*' %watch\-dir%/ %destination\-dir%/ .RE if the option is set Error code "24" from .I sync\-handler will be ignored in this case. We also recommend to ignore exitcode "23". Additional substitutions: .RS .B %INCLUDE\-LIST\-PATH% .RS Is replaced by the path of the include list file .RE .B %EXCLUDE\-LIST\-PATH% .RS Is replaced by the path of the exclude list file .RE .B %RSYNC\-ARGS% .RS Is replaced by default .IR sync\-handler\-arguments ", but" without "%watch\-dir%/ %destination\-dir%/" .RE .RE Recommended case. .RE case .B rsyncshell .RS Executes for every sync: .RS .I sync\-handler sync\-handler\-arguments .RE Default .I sync\-handler\-arguments are: .RS rsynclist %label% %INCLUDE\-LIST\-PATH% [%EXCLUDE\-LIST\-PATH%] .RE In this case, .I sync\-handler is supposed to run "rsync" application with parameters: \-aH \-\-delete\-before \-\-include\-from .I %INCLUDE\-LIST\-PATH% \-\-exclude '*' if option .I \-\-rsync\-prefer\-include is enabled. And with parameters: \-aH \-\-delete\-before \-\-exclude\-from .I %EXCLUDE\-LIST\-PATH% \-\-include\-from .I %INCLUDE\-LIST\-PATH% \-\-exclude '*' if option .I \-\-rsync\-prefer\-include is disabled. Additional substitutions: .RS .B %INCLUDE\-LIST\-PATH% .RS Is replaced by the path of the rsync include list file .RE .B %EXCLUDE\-LIST\-PATH% .RS Is replaced by the path of the rsync exclude list file .RE .RE Recommended case. .RE case .B rsyncso .RS In this case there's no direct exec*() calling. In this case .B clsync loads .I sync-handler as a shared library with .BR dlopen (3) and calls function "int clsyncapi_rsync(const char *inclist, const char *exclist)" from it for every sync. .br .B inclist is a path to file with rules for "\-\-include\-from" option of rsync. This argument is always not NULL. .br .B exclist is a path to file with rules for "\-\-exclude\-from" option of rsync. This argument is NULL if .B \-\-rsync\-prefer\-include is set. .br .I "Excludes takes precedence over includes." Also may be defined functions "int clsyncapi_init(ctx_t *, indexes_t *)" and "int clsyncapi_deinit()" to initialize and deinitialize the syncing process by this shared object. To fork the process should be used function "pid_t clsyncapi_fork(ctx_t *)" instead of "pid_t fork()" to make clsync be able to kill the child. See example file "clsync\-synchandler\-rsyncso.c". Recommended case. .RE case .B so .RS In this case there's no direct exec*() calling. In this case .B clsync loads .I sync-handler as a shared library with .BR dlopen (3) and calls function "int clsyncapi_sync(int n, api_eventinfo_t *ei)" from it for every sync. .B n is number of elements of .BR ei . .B ei is an array of structures with information about what and how to sync (see below). api_eventinfo_t is a structure: .RS struct api_eventinfo { .br uint32_t evmask; // event bitmask for file/dir by path .BR path . .br uint32_t flags; // flags of "how to sync" the file/dir .br size_t path_len; // strlen(path) .br const char *path; // the .B path to file/dir need to be synced .br eventobjtype_t objtype_old; // type of object by path .B path before the event. .br eventobjtype_t objtype_new; // type of object by path .B path after the event. .br }; .br typedef struct api_eventinfo api_eventinfo_t; .RE The event bitmask (evmask) values can be learned from "/usr/include/linux/inotify.h". There may be next flags' values (flags): .RS enum eventinfo_flags { .br EVIF_NONE = 0x00000000, // No modifier .br EVIF_RECURSIVELY = 0x00000001 // sync the file/dir recursively .br }; .RE .br Flag "EVIF_RECURSIVELY" may be used if option .I \-\-have\-recursive\-sync is set. Is that a file or directory by path .B path can be determined with .B objtype_old and .BR objtype_new . .br .B objtype_old reports about which type was the object by the path before the event. .br .B objtype_new reports about which type became the object by the path after the event. .B objtype_old and .BR objtype_new have type .BR eventobjtype_t . .RS enum eventobjtype { .br EOT_UNKNOWN = 0, // Unknown .br EOT_DOESNTEXIST = 1, // Doesn't exist (not created yet or already deleted) .br EOT_FILE = 2, // File .br EOT_DIR = 3, // Directory .br } typedef enum eventobjtype eventobjtype_t; .RE Also may be defined functions "int clsyncapi_init(options_t *, indexes_t *)" and "int clsyncapi_deinit()" to initialize and deinitialize the syncing process by this shared object. To fork the process should be used function "pid_t clsyncapi_fork(options_t *)" instead of "pid_t fork()" to make clsync be able to kill the child. See example file "clsync\-synchandler\-so.c". Recommended case. .RE .RE .SH ENVIRONMENT VARIABLES Output variables - variables that are set by clsync before calling .IR sync-handler . .B "Output variables" .RS CLSYNC_STATUS - .BR clsync 's status (see possible statuses in description of .IR \-\-status\-file ) .RE .RS CLSYNC_ITERATION - count of done synchronizaton iterations after initial sync see \-\-max\-iterations option .RE .SH RULES Filter rules can be used to set which events clsync should monitor and which events it should ignore. .B Caution! This rules doesn't guarantee that filtered file/dir won't be synced. This can occur because file or directory can appear in the moment of .B sync\-handler running (or after it but before the .B sync\-handler will reach the directory), so it'll be too late to add an exclusion. If you need a guarantee of file syncing preventing you can use internal filter rules of the .B sync\-handler program (for example, rsync has options "\-\-exclude", "\-\-exclude\-from" and "\-\-filter") or use disable any "recursive" syncs in .B clsync (and remove "-av" option of rsync if it's used). To disable recursive syncs you can use: .RS .B simple .RS Already non-recursive .RE .B direct .RS Already non-recursive .RE .B shell .RS Don't enable option \-\-have\-recursive\-sync. .RE .B rsyncdirect .RS Use option \-\-rsync\-prefer\-include and set .I sync\-handler\-arguments to \-lptgoD \-\-delete \-\-include\-from %INCLUDE\-LIST\-PATH% \-\-exclude='*' %watch\-dir%/ %destination\-dir%/ .RE .B rsyncshell .RS Use option \-\-rsync\-prefer\-include. .RE .B rsyncso .RS Use option \-\-rsync\-prefer\-include. .RE .B so .RS Don't enable option \-\-have\-recursive\-sync. .RE .RE Filter rules can be placed into .I rules\-file with one rule per line. Rule format: .I [+\-][fdw*]regexp .I + \- means include; .I \- \- means exclude; .I f \- means file; .I d \- means directory; .I w \- means walking to directory; .I * \- means all. For example: \-*^/[Tt]est It's not recommended to use .I w rules in modes "rsyncdirect", "rsyncshell" and "rsyncso". .BR rsync (1) allows one to set syncing and walking only together in "\-\-include" rules ("\-\-files\-from" is not appropriate due to problem with syncing files deletions). So there may be problems with clsync's .I w rules in this cases. More examples: Syncing pwdb files and sshd_config (non-rsync case): .RS +f^/passwd$ .br +f^/group$ .br +f^/shadow$ .br +f^/ssh/sshd_config$ .br +w^$ .br +w^/ssh$ .br \-* .RE .\" Syncing pwdb files and sshd_config (non-rsync case with option .\" .IR \-\-auto\-add\-rules\-w ): .\" .RS .\" +f^/passwd$ .\" .br .\" +f^/group$ .\" .br .\" +f^/shadow$ .\" .br .\" +f^/ssh/sshd_config$ .\" .br .\" \-* .\" .RE Syncing pwdb files and sshd_config (rsync case): .RS +f^/passwd$ .br +f^/group$ .br +f^/shadow$ .br +f^/ssh/sshd_config$ .br +d^$ .br +d^/ssh$ .br \-* .RE Syncing /srv/lxc tree (rsync case): .RS \-d/sess(ion)?s?$ .br \-f/tmp/ .br +* .RE .SH SIGNALS 1 \- (HUP) rereads filter rules 2 \- (INT) exits without waiting of syncing processes ("hard kill", kills children) 3 \- (QUIT) waits for current syncing processes and exit ("soft kill", waits for children) 10 \- runs threads' GC function 12 \- runs full resync 15 \- (TERM) exits without waiting of syncing processes ("hard kill", kills children) 16 \- interrupts sleep()/select() and wait() [for debugging and internal uses] 29 \- dump information to .IR dump\-dir [for debugging] If you need to kill clsync but leave children then you can use 9-th (KILL) signal. .SH DIAGNOSTICS Initial rsync process works very slow on clsync start .RS Probably there's too huge exclude list is passed to rsync. This can happened if you're excluding with regex in clsync's rules a lot of thousands files. They will be passed to rsync's exclude list one by one. To diagnose it, you can use "\-U" option and look into .I rsync\-exclude\-listpath file (see .B "SYNC HANDLER" case .BR d ) To prevent this, it's recommended to write such rules for rsync directly (not via clsync). For example, often problem is with PHP's session files. You shouldn't exclude them in clsync's rules with "\-f/sess_.*", but you should exclude it in rsync directly (e.g with «\-\-exclude "sess_*"»). .RE The following diagnostics may be issued on stderr: Error: Cannot inotify_add_watch() on [...]: No space left on device (errno: 28) .RS Not enough inotify watching descriptors is allowed. It can be fixed by increasing value of "sysctl fs.inotify.max_user_watches" .RE Error: Got non-zero exitcode .I exitcode [...] .RS .I sync\-handler returned non-zero exitcode. Probably, you should process exitcodes in it or your syncer process didn't worked well. I case of using rsync, you can find the exitcodes meanings in .BR "man 1 rsync" . If .I exitcode equals to 23 and you're using .B clsync in conjunction with .BR rsync , this may happend, for example in next cases: .RS \- Not enough space on destination. \- You're running clsync with .B \-\-threading=full and rsync with .BR \-\-backup . See a bugreport by URL: .IR https://bugzilla.samba.org/show_bug.cgi?id=10081 . .RE To confirm the problem, you can try to add "return 0" or "exit 0" into your .IR sync\-handler . .RE .B "Bad system call" .RS If \-\-use\-seccomp option is enabled then the error is probably caused by using of forbidden syscall. It's a .B clsync bug or hack attack attempt. .RE To get support see .BR SUPPORT . .SH CONFIGURATION FILE .B clsync supports configuration file. By default .B clsync tries to read next files (in specified order): .RS ~/.clsync.conf .br /etc/clsync/clsync.conf .RE This may be overrided with option .IR \-\-config\-file . .B clsync reads only one configuration file. In other words, if option .I \-\-config\-file is not set and file .B ~/.clsync.conf is accessible and parsable, .B clsync will not try to open .BR /etc/clsync/clsync.conf . Command line options have precedence over config file options. Configuration file is parsed with glib's g_key_file_* API. That means, that config should consits from groups (blocks) of key-value lines as in the example: .RS [default] .br background = 1 .br mode = rsyncshell .br debug = 0 .br output = syslog .br label = default .br pid\-file = /var/run/clsync\-%label%.pid [debug] .br config\-block\-inherits = default .br debug = 5 .br background = 0 .br output = stderr [test] .br mode=rsyncdirect .br debug=3 .RE Also glib's .B gkf API doesn't support multiple assignments. If you need to list some values (e.g. exitcodes) just list them with commas in single assignment (e.g. "ignore\-exitcode=23,24"). In this example there're 3 blocks are set - "default", "debug" and "test". And block "debug" inherited setup of block "default" except options "debug", "background" and "output". By default .B clsync uses block with name "default". Block name can be set by option .IR \-\-config\-block . .SH CLUSTERING Not implemented yet. .B Don't try to use cluster functionality. Not described yet. .SH EXAMPLES .B Mirroring a directory: .RS clsync \-Mrsyncdirect \-W/path/to/source_dir \-D/path/to/destination_dir .RE .B Syncing 'authorized_keys' files: .RS mkdir -p /etc/clsync/rules .br printf "+w^$\n+w^[^/]+$\n+w^[^/]+/.ssh$\n+f^[^/]+/.ssh/authorized_keys$\n-*" > /etc/clsync/rules/authorized_files_only .br clsync -Mdirect -Scp -W/mnt/master/home/ -D/home -R/etc/clsync/rules/authorized_files_only -- -Pfp --parents %INCLUDE-LIST% %destination-dir% .RE .B Mirroring a directory, but faster: .RS clsync \-w5 \-t5 \-T5 \-Mrsyncdirect \-W/path/to/source_dir \-D/path/to/destination_dir .RE .B Instant mirroring of a directory: .RS clsync \-w0 \-t0 \-T0 \-Mrsyncdirect \-W/path/to/source_dir \-D/path/to/destination_dir .RE .B Making two directories synchronous: .RS clsync \-Mrsyncdirect \-\-background \-z /var/run/clsync0.pid \-\-output syslog \-Mrsyncdirect \-W/path/to/dir1 \-D/path/to/dir2 \-\-modification\-signature '*' .br clsync \-Mrsyncdirect \-\-background \-z /var/run/clsync1.pid \-\-output syslog \-Mrsyncdirect \-W/path/to/dir2 \-D/path/to/dir1 \-\-modification\-signature '*' .RE .B Fixing privileges of a web-site: .RS clsync \-w3 \-t3 \-T3 \-x1 \-W/var/www/site.example.org/root \-Mdirect \-Schown \-\-uid 0 \-\-gid 0 \-Ysyslog \-b1 \-\-modification\-signature uid,gid \-\- \-\-from=root www\-data:www\-data %INCLUDE\-LIST% .RE .B "'Atomic' sync:" .RS clsync \-\-exit\-on\-no\-events \-\-max\-iterations=20 \-\-mode=rsyncdirect \-W/var/www_new \-Srsync \-\- %RSYNC\-ARGS% /var/www_new/ /var/www/ .RE .B Moving a web-server: .RS clsync \-\-exit\-on\-no\-events \-\-max\-iterations=20 \-\-pre\-exit\-hook=/root/stop\-here.sh \-\-exit\-hook=/root/start\-there.sh \-\-mode=rsyncdirect \-\-ignore\-exitcode=23,24 \-\-retries=3 \-W /var/www \-S rsync \-\- %RSYNC\-ARGS% /var/www/ rsync://clsync@another-host/var/www/ .RE .B Copying files to slave-nodes using .BR pdcp (1): .RS clsync \-Msimple \-S pdcp \-W /opt/global \-b \-Y syslog \-\- \-a %INCLUDE\-LIST% %INCLUDE\-LIST% .RE .B Copying files to slave-nodes using .BR uftp (1): .RS clsync \-Mdirect \-S uftp \-W/opt/global \-\-background=1 \-\-output=syslog \-\- \-M 248.225.233.1 %INCLUDE\-LIST% .RE .B A dry running to see .BR rsync (1) .B arguments that clsync will use: .RS clsync \-Mrsyncdirect \-S echo \-W/path/to/source_dir \-D/path/to/destination_dir .RE .B An another dry running to look how clsync will call .BR pdcp (1): .RS clsync \-Msimple \-S echo \-W /opt/global \-b0 \-\- pdcp \-a %INCLUDE\-LIST% %INCLUDE\-LIST% .RE More working examples you can try out in "/usr/share/doc/clsync/examples/" directory. Copy this directory somewhere (e.g. into "/tmp"). And try to run "clsync\-start\-rsync.sh" in there. Any files/directories modifications in "testdir/from" will be synced to "testdir/to" in a few seconds. .RE .SH AUTHOR Dmitry Yu Okunev 0x8E30679C .SH SUPPORT You can get support on official IRC-channel in Freenode "#clsync" or on github's issue tracking system of repository "https://github.com/xaionaro/clsync". Don't be afraid to ask about clsync configuration, ;). .SH "SEE ALSO" .BR rsync (1), .BR pthreads (7), .BR inotify (7) .BR kqueue (2) clsync-0.4.1/mon_bsm.c000066400000000000000000000556021252417542300146220ustar00rootroot00000000000000/* clsync - file tree sync utility based on inotify/kqueue Copyright (C) 2013-2014 Dmitry Yu Okunev 0x8E30679C This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "common.h" #include "malloc.h" #include "error.h" #include "indexes.h" #include "sync.h" #include "mon_bsm.h" #include #include #include #include #include struct bsm_event { u_int16_t type; char path [PATH_MAX]; char path_to[PATH_MAX]; int w_id; }; struct mondata { FILE *pipe; int config_fd; size_t event_count; size_t event_count_wasinqueue; size_t event_alloc; struct bsm_event *event; }; typedef struct mondata mondata_t; enum event_bits { UEM_DIR = 0x01, UEM_CREATED = 0x02, UEM_DELETED = 0x04, }; enum bsm_handle_type { BSM_HANDLER_CALLWAIT, BSM_HANDLER_ITERATE, }; struct recognize_event_return { struct { eventobjtype_t objtype_old; eventobjtype_t objtype_new; } f; struct { eventobjtype_t objtype_old; eventobjtype_t objtype_new; } t; }; pthread_t prefetcher_thread; pthread_mutex_t bsm_mutex_prefetcher = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t bsm_cond_gotevent = PTHREAD_COND_INITIALIZER; pthread_cond_t bsm_cond_queueend = PTHREAD_COND_INITIALIZER; int bsm_queue_len; int (*bsm_wait)(struct ctx *ctx_p, struct indexes *indexes_p, struct timeval *timeout_p); int (*bsm_handle)(struct ctx *ctx_p, struct indexes *indexes_p); extern int bsm_prefetcher(struct ctx *ctx_p); extern int bsm_wait_prefetched (struct ctx *ctx_p, struct indexes *indexes_p, struct timeval *timeout_p); extern int bsm_wait_noprefetch (struct ctx *ctx_p, struct indexes *indexes_p, struct timeval *timeout_p); extern int bsm_handle_prefetched(struct ctx *ctx_p, struct indexes *indexes_p); extern int bsm_handle_noprefetch(struct ctx *ctx_p, struct indexes *indexes_p); static inline void recognize_event(struct recognize_event_return *r, uint32_t event) { int is_created, is_deleted, is_moved; eventobjtype_t type; type = EOT_FILE; is_moved = 0; is_created = 0; is_deleted = 0; switch (event) { case AUE_MKDIR: case AUE_MKDIRAT: type = EOT_DIR; case AUE_OPEN_RC: case AUE_OPEN_RTC: case AUE_OPEN_WC: case AUE_OPEN_WTC: case AUE_OPEN_RWC: case AUE_OPEN_RWTC: case AUE_LINK: case AUE_LINKAT: case AUE_MKFIFO: case AUE_MKFIFOAT: case AUE_MKNOD: case AUE_MKNODAT: case AUE_SYMLINK: case AUE_SYMLINKAT: is_created = 1; break; case AUE_RMDIR: #if AUE_RMDIRAT case AUE_RMDIRAT: #endif type = EOT_DIR; case AUE_UNLINK: case AUE_UNLINKAT: is_deleted = 1; break; case AUE_RENAME: case AUE_RENAMEAT: type = EOT_DIR; is_moved = 1; break; case AUE_CLOSE: case AUE_CLOSEFROM: break; default: warning("Unknown event: 0x%x", event); break; } r->f.objtype_old = type; if (is_moved) { r->f.objtype_new = EOT_DOESNTEXIST; r->t.objtype_old = EOT_DOESNTEXIST; r->t.objtype_new = type; return; } r->f.objtype_new = type; if (is_created) r->f.objtype_old = EOT_DOESNTEXIST; if (is_deleted) r->f.objtype_new = EOT_DOESNTEXIST; return; } int auditd_restart() { debug(1, "Running \""AUDIT_CONTROL_INITSCRIPT" onerestart\""); pid_t pid = fork(); switch (pid) { case -1: error("Cannot fork()."); return -1; case 0: debug(5, "fork: execl(\""AUDIT_CONTROL_INITSCRIPT"\", \""AUDIT_CONTROL_INITSCRIPT"\", \"onerestart\", NULL);", pid); execl(AUDIT_CONTROL_INITSCRIPT, AUDIT_CONTROL_INITSCRIPT, "onerestart", NULL); error("fork: Cannot execute \""AUDIT_CONTROL_INITSCRIPT" onerestart\""); return -1; } debug(6, "Waiting for %u", pid); int status; if (waitpid(pid, &status, 0) != pid) { error("Cannot waitid()."); return -1; } int exitcode = WEXITSTATUS(status); if (exitcode) error("Got error while running \""AUDIT_CONTROL_INITSCRIPT" onerestart\""); debug(4, "exitcode == %u", exitcode); return exitcode; } int bsm_config_backup(mondata_t *mondata) { char buf[sizeof(AUDIT_CONTROL_HEADER)]; int fd = open(AUDIT_CONTROL_PATH, O_RDONLY); if (fd == -1) { debug(4, "Cannot open "AUDIT_CONTROL_PATH". No need for backup."); return 1; } int r = read(fd, buf, sizeof(AUDIT_CONTROL_HEADER)-1); close(fd); if (r == sizeof(AUDIT_CONTROL_HEADER)-1) if (!memcmp(buf, AUDIT_CONTROL_HEADER, sizeof(AUDIT_CONTROL_HEADER)-1)) { debug(4, "File "AUDIT_CONTROL_PATH" is already clsync-compatible."); return 0; } if (!access(AUDIT_CONTROL_PATH"-clsync_backup", R_OK)) { error("File \""AUDIT_CONTROL_PATH"-clsync_backup\" already exists. Cannot backup \""AUDIT_CONTROL_PATH"\"."); return -1; } debug(3, "mv: "AUDIT_CONTROL_PATH" -> "AUDIT_CONTROL_PATH"-clsync_backup"); rename(AUDIT_CONTROL_PATH, AUDIT_CONTROL_PATH"-clsync_backup"); close(fd); return 1; } int bsm_config_setup(mondata_t *mondata) { debug(3, ""); switch (bsm_config_backup(mondata)) { case 0: debug(4, "bsm_config_backup(): no reconfig required"); return 0; case -1: debug(4, "bsm_config_backup(): error"); return -1; } debug(3, "Writting a new audit_control file to \""AUDIT_CONTROL_PATH"\""); mondata->config_fd = open(AUDIT_CONTROL_PATH, O_RDONLY); flock(mondata->config_fd, LOCK_SH); int fd_w = open(AUDIT_CONTROL_PATH, O_WRONLY|O_CREAT); if (fd_w == -1) { error("Cannot open file \""AUDIT_CONTROL_PATH"\" for writing"); return -1; } int w; if ((w=write(fd_w, AUDIT_CONTROL_HEADER AUDIT_CONTROL_CONTENT, sizeof(AUDIT_CONTROL_HEADER AUDIT_CONTROL_CONTENT)-1)) != sizeof(AUDIT_CONTROL_HEADER AUDIT_CONTROL_CONTENT)-1) { error("Cannot write to \""AUDIT_CONTROL_HEADER AUDIT_CONTROL_CONTENT"\" (%u != %u)", w, sizeof(AUDIT_CONTROL_HEADER AUDIT_CONTROL_CONTENT)-1); return -1; } close(fd_w); if (auditd_restart()) { error("Cannot restart auditd to apply a new "AUDIT_CONTROL_PATH); return -1; } return 0; } int bsm_config_revert(mondata_t *mondata) { int rc = 0; int fd = mondata->config_fd; flock(fd, LOCK_UN); if (flock(fd, LOCK_NB|LOCK_EX)) return 0; debug(1, "I'm the last BSM clsync instance."); if (!access(AUDIT_CONTROL_PATH"-clsync_backup", R_OK)) { debug(1,"Reverting the audit config file (\""AUDIT_CONTROL_PATH"-clsync_backup\" -> \""AUDIT_CONTROL_PATH"\")."); rc = rename(AUDIT_CONTROL_PATH"-clsync_backup", AUDIT_CONTROL_PATH); } flock(fd, LOCK_UN); if (rc) { error("Got error while rename(\""AUDIT_CONTROL_PATH"-clsync_backup\", \""AUDIT_CONTROL_PATH"\")"); return -1; } return 0; } #define BSM_INIT_ERROR {\ free(ctx_p->fsmondata);\ ctx_p->fsmondata = NULL;\ return -1;\ } int bsm_init(ctx_t *ctx_p) { debug(9, ""); ctx_p->fsmondata = xcalloc(sizeof(mondata_t), 1); mondata_t *mondata = ctx_p->fsmondata; if (bsm_config_setup(mondata) == -1) BSM_INIT_ERROR; debug(5, "Openning \""AUDITPIPE_PATH"\""); FILE *pipe = fopen(AUDITPIPE_PATH, "r"); if (pipe == NULL) { error("Cannot open \""AUDITPIPE_PATH"\" for reading."); BSM_INIT_ERROR; } { // Setting auditpipe queue length to be maximal int fd; u_int len; fd = fileno(pipe); if (ioctl(fd, AUDITPIPE_GET_QLIMIT_MAX, &len) < 0) { error("Cannot read QLIMIT_MAX from auditpipe"); BSM_INIT_ERROR; } if (ioctl(fd, AUDITPIPE_SET_QLIMIT, &len) < 0) { error("Cannot set QLIMIT through auditpipe"); BSM_INIT_ERROR; } if (ioctl(fd, AUDITPIPE_GET_QLIMIT, &len) < 0) { error("Cannot read QLIMIT from auditpipe"); BSM_INIT_ERROR; } bsm_queue_len = len; debug(5, "auditpipe QLIMIT -> %i", (int)len); } if (setvbuf(pipe, NULL, _IONBF, 0)) { error("Cannot set unbuffered mode for auditpipe"); BSM_INIT_ERROR; } mondata->pipe = pipe; switch (ctx_p->flags[MONITOR]) { case NE_BSM: bsm_wait = bsm_wait_noprefetch; bsm_handle = bsm_handle_noprefetch; mondata->event = xcalloc(sizeof(*mondata->event), 1); break; case NE_BSM_PREFETCH: pthread_mutex_init(&bsm_mutex_prefetcher, NULL); pthread_cond_init (&bsm_cond_gotevent, NULL); pthread_cond_init (&bsm_cond_queueend, NULL); bsm_wait = bsm_wait_prefetched; bsm_handle = bsm_handle_prefetched; critical_on (pthread_create(&prefetcher_thread, NULL, (void *(*)(void *))bsm_prefetcher, ctx_p)); break; default: critical("Invalid ctx_p->flags[MONITOR]: %u", ctx_p->flags[MONITOR]); } return 0; } int select_rfd(int fd, struct timeval *timeout_p) { int rc; debug(9, "%i, {%li, %li}", fd, timeout_p == NULL ? -1 : timeout_p->tv_sec, timeout_p == NULL ? 0 : timeout_p->tv_usec); fd_set rfds; FD_ZERO(&rfds); FD_SET(fd, &rfds); rc = select(fd+1, &rfds, NULL, NULL, timeout_p); debug(9, "rc -> %i", rc); return rc; } int bsm_fetch(ctx_t *ctx_p, indexes_t *indexes_p, struct bsm_event *event_p, int pipe_fd, struct timeval *timeout_p, struct timeval *timeout_abs_p) { size_t au_len; size_t au_parsed; u_char *au_buf; tokenstr_t tok; int recalc_timeout; static int dont_wait = 0; struct timeval tv_abs; struct timeval timeout_zero = {0}; mondata_t *mondata = ctx_p->fsmondata; recalc_timeout = 0; if (timeout_p != NULL) if (timeout_p->tv_sec != 0 || timeout_p->tv_usec != 0) recalc_timeout = 1; while (42) { int path_count; // Checking if there already a recond in mondata if (*event_p->path) { debug(2, "we have an event. return 1."); return 1; } // Getting a record { int rc; rc = 0; if (dont_wait) { debug(4, "select() without waiting"); rc = select_rfd(pipe_fd, &timeout_zero); if (rc == 0) { dont_wait = 0; mondata->event_count_wasinqueue = 0; switch (ctx_p->flags[MONITOR]) { case NE_BSM_PREFETCH: pthread_cond_broadcast(&bsm_cond_queueend); break; default: break; } } else if (rc > 0) { mondata->event_count_wasinqueue++; if (mondata->event_count_wasinqueue+1 >= bsm_queue_len) critical_or_warning(ctx_p->flags[EXITONSYNCSKIP], "The was too many events in BSM queue (reached kernel BSM queue limit: %u).", bsm_queue_len); } } if (rc == 0) { if (recalc_timeout == 2) { debug(5, "old timeout_p->: tv_sec == %lu; tv_usec == %lu", timeout_p->tv_sec, timeout_p->tv_usec); gettimeofday(&tv_abs, NULL); if (timercmp(timeout_abs_p, &tv_abs, <)) timersub(timeout_abs_p, &tv_abs, timeout_p); else memset(timeout_p, 0, sizeof(*timeout_p)); debug(5, "new timeout_p->: tv_sec == %lu; tv_usec == %lu", timeout_p->tv_sec, timeout_p->tv_usec); } debug(3, "select() with timeout %li.%06li secs (recalc_timeout == %u).", timeout_p == NULL ? -1 : timeout_p->tv_sec, timeout_p == NULL ? 0 : timeout_p->tv_usec, recalc_timeout); rc = select_rfd(pipe_fd, timeout_p); if (rc > 0) mondata->event_count_wasinqueue++; if (recalc_timeout == 1) recalc_timeout++; } critical_on ((rc == -1) && (errno != EINTR)); if (rc == 0 || rc == -1) { debug(3, "rc == %i; errno == %i; return 0", rc, errno); return 0; } dont_wait = 1; au_len = au_read_rec(mondata->pipe, &au_buf); critical_on (au_len == -1); } // Parsing the record au_parsed = 0; path_count = 0; debug(3, "parsing the event (au_parsed == %u; au_len == %u)", au_parsed, au_len); while (au_parsed < au_len) { critical_on (au_fetch_tok(&tok, &au_buf[au_parsed], au_len - au_parsed) == -1); au_parsed += tok.len; debug(4, "au_fetch_tok(): au_parsed -> %u", tok.len); switch (tok.id) { case AUT_HEADER32: case AUT_HEADER32_EX: case AUT_HEADER64: case AUT_HEADER64_EX: { event_p->type = tok.tt.hdr32.e_type; path_count = 0; break; } case AUT_PATH: { char *ptr; int dir_wd, dir_iswatched; ptr = memrchr(tok.tt.path.path, '/', tok.tt.path.len); #ifdef PARANOID if (ptr == NULL) critical("relative path received from au_fetch_tok(): \"%s\" (len: %u)", tok.tt.path.path, tok.tt.path.len); #endif debug(6, "Event on \"%s\".", tok.tt.path.path); *ptr = 0; dir_wd = indexes_fpath2wd(indexes_p, tok.tt.path.path); dir_iswatched = (dir_wd != -1); debug(7, "Directory is \"%s\". dir_wd == %i; dir_iswatched == %u", tok.tt.path.path, dir_wd, dir_iswatched); *ptr = '/'; if (dir_iswatched) { debug(5, "Event on \"%s\" is watched. Pushing. path_count == %u", tok.tt.path.path, path_count); switch (path_count) { case 0: memcpy(event_p->path, tok.tt.path.path, tok.tt.path.len+1); break; case 1: memcpy(event_p->path_to, tok.tt.path.path, tok.tt.path.len+1); break; #ifdef PARANOID default: warning("To many paths on BSM event: \"%s\" (already count: %u)", tok.tt.path.path, path_count); break; #endif } } path_count++; break; } default: continue; } } // Cleanup debug(4, "clean up"); free(au_buf); } critical ("This code shouldn't be reached"); return -1; } enum bsm_handletype { BSM_HANDLE_CALLWAIT, BSM_HANDLE_ITERATE, }; typedef enum bsm_handletype bsm_handletype_t; int bsm_handle_allevents(struct ctx *ctx_p, struct indexes *indexes_p, bsm_handletype_t how) { debug(4, ""); static struct timeval tv={0}; mondata_t *mondata = ctx_p->fsmondata; int count, event_num; char *path_rel = NULL; size_t path_rel_len = 0; int left_count; event_num = 0; count = 0; #ifdef PARANOID g_hash_table_remove_all(indexes_p->fpath2ei_ht); #endif do { struct recognize_event_return r = {{0}}; char *path_stat; struct stat st, *st_p; mode_t st_mode; size_t st_size; struct bsm_event *event_p = &mondata->event[event_num]; #ifdef PARANOID if (!*event_p->path && !*event_p->path_to) { warning("no events are parsed (event_p == %p; mondata->event_count == %i).", event_p, mondata->event_count); continue; } #endif recognize_event(&r, event_p->type); if (r.t.objtype_new != EOT_UNKNOWN) path_stat = event_p->path_to; else path_stat = event_p->path; if ((r.t.objtype_new == EOT_DOESNTEXIST) || (ctx_p->flags[CANCEL_SYSCALLS]&CSC_MON_STAT) || lstat(path_stat, &st)) { debug(2, "Cannot lstat64(\"%s\", st). Seems, that the object had been deleted (%i) or option \"--cancel-syscalls=mon_stat\" (%i) is set.", path_stat, r.t.objtype_new == EOT_DOESNTEXIST, ctx_p->flags[CANCEL_SYSCALLS]&CSC_MON_STAT); if (r.f.objtype_old == EOT_DIR || r.f.objtype_new == EOT_DIR) st_mode = S_IFDIR; else st_mode = S_IFREG; st_size = 0; st_p = NULL; } else { st_mode = st.st_mode; st_size = st.st_size; st_p = &st; } if (*event_p->path) { if (sync_prequeue_loadmark(1, ctx_p, indexes_p, event_p->path, NULL, st_p, r.f.objtype_old, r.f.objtype_new, event_p->type, event_p->w_id, st_mode, st_size, &path_rel, &path_rel_len, NULL)) { error("Got error while load_mark-ing into pre-queue \"%s\"", event_p->path); count = -1; *event_p->path = 0; break; } *event_p->path = 0; count++; } if ((r.t.objtype_new != EOT_UNKNOWN) && *event_p->path_to) { if (sync_prequeue_loadmark(1, ctx_p, indexes_p, event_p->path_to, NULL, st_p, r.t.objtype_old, r.t.objtype_new, event_p->type, event_p->w_id, st_mode, st_size, &path_rel, &path_rel_len, NULL)) { error("Got error while load_mark-ing into pre-queue \"%s\"", event_p->path_to); count = -1; *event_p->path_to = 0; break; } *event_p->path_to = 0; count++; } switch (how) { case BSM_HANDLE_CALLWAIT: debug(15, "BSM_HANDLE_CALLWAIT"); left_count = bsm_wait(ctx_p, indexes_p, &tv); break; case BSM_HANDLE_ITERATE: debug(15, "BSM_HANDLE_ITERATE"); event_num++; left_count = mondata->event_count - event_num; break; } debug(10, "left_count: %i; event_num: %i; mondata->event_count: %i", left_count, event_num, mondata->event_count); } while (left_count > 0); switch (how) { case BSM_HANDLE_ITERATE: if (event_num < mondata->event_count) { memmove( mondata->event, &mondata->event[event_num], sizeof(*mondata->event)*(mondata->event_count - event_num) ); } mondata->event_count -= event_num; break; default: break; } free(path_rel); #ifdef VERYPARANOID path_rel = NULL; path_rel_len = 0; #endif // Globally queueing captured events: // Moving events from local queue to global ones sync_prequeue_unload(ctx_p, indexes_p); debug(4, "Result processed count: %i (left, mondata->event_count == %i)", count, mondata->event_count); if (count == -1) return -1; return count; } void bsm_prefetcher_sig_int(int signal) { debug(2, "signal -> %i. Sending pthread_cond_broadcast() to bsm_cond_gotevent and bsm_cond_queueend.", signal); pthread_cond_broadcast(&bsm_cond_gotevent); pthread_cond_broadcast(&bsm_cond_queueend); return; } static int bsm_prefetcher_running=2; int bsm_prefetcher(struct ctx *ctx_p) { mondata_t *mondata = ctx_p->fsmondata; indexes_t *indexes_p = ctx_p->indexes_p; struct bsm_event event, *event_p; register_blockthread(); signal(SIGUSR_BLOPINT, bsm_prefetcher_sig_int); int pipe_fd = fileno(mondata->pipe); mondata->event = xcalloc(sizeof(*mondata->event), ALLOC_PORTION); bsm_prefetcher_running = 1; while (bsm_prefetcher_running) { if (bsm_fetch(ctx_p, indexes_p, &event, pipe_fd, NULL, NULL) > 0) { // Pushing the event debug(5, "We have an event. Pushing."); #ifdef PARANOID critical_on (mondata->event_count >= BSM_QUEUE_LENGTH_MAX); #endif if (mondata->event_count >= mondata->event_alloc) { debug(2, "Increasing queue length: %u -> %u (limit is "XTOSTR(BSM_QUEUE_LENGTH_MAX)")", mondata->event_alloc, mondata->event_alloc+ALLOC_PORTION); mondata->event_alloc += ALLOC_PORTION; mondata->event = xrealloc(mondata->event, mondata->event_alloc*sizeof(*mondata->event)); memset(&mondata->event[mondata->event_count], 0, sizeof(*mondata->event)*(mondata->event_alloc - mondata->event_count)); } pthread_mutex_lock(&bsm_mutex_prefetcher); event_p = &mondata->event[mondata->event_count++]; memcpy(event_p, &event, sizeof(*event_p)); debug(2, "event_count -> %u (event_p == %p; event_p->path == \"%s\")", mondata->event_count, event_p, event_p->path); pthread_cond_broadcast(&bsm_cond_gotevent); pthread_mutex_unlock(&bsm_mutex_prefetcher); memset(&event, 0, sizeof(event)); } } return 0; } int bsm_wait_prefetched(struct ctx *ctx_p, struct indexes *indexes_p, struct timeval *timeout_p) { debug(3, "(ctx_p, indexes_p, %p {%u, %u})", timeout_p, timeout_p == NULL?-1:timeout_p->tv_sec, timeout_p == NULL?0:timeout_p->tv_usec); #ifdef PARANOID critical_on (timeout_p == NULL); #endif mondata_t *mondata = ctx_p->fsmondata; struct timespec ts_abs; struct timeval tv_abs, timeout_abs; #define INFINITETIME (3600 * 24 * 365 * 10) /* ~10 years */ if (timeout_p->tv_sec > INFINITETIME) timeout_p->tv_sec = INFINITETIME; #undef INFINITETIME gettimeofday(&tv_abs, NULL); timeradd(&tv_abs, timeout_p, &timeout_abs); ts_abs.tv_sec = timeout_abs.tv_sec; ts_abs.tv_nsec = timeout_abs.tv_usec*1000; pthread_mutex_lock(&bsm_mutex_prefetcher); if (mondata->event_count) { debug(2, "Already have an event. mondata->event_count == %i", mondata->event_count); pthread_mutex_unlock(&bsm_mutex_prefetcher); return mondata->event_count; } if (timeout_p->tv_sec == 0 && timeout_p->tv_sec == 0) { debug(2, "Zero timeout. Waiting for the current queue to be processed.") pthread_cond_wait(&bsm_cond_queueend, &bsm_mutex_prefetcher); pthread_mutex_unlock(&bsm_mutex_prefetcher); debug(3, "return mondata->event_count == %i", mondata->event_count); return mondata->event_count; } //l_pthread_cond_timedwait_restart: debug(10, "pthread_cond_timedwait(&bsm_cond_gotevent, &bsm_mutex_prefetcher, {%i, %i})", ts_abs.tv_sec, ts_abs.tv_nsec); if ((errno = pthread_cond_timedwait(&bsm_cond_gotevent, &bsm_mutex_prefetcher, &ts_abs))) { pthread_mutex_unlock(&bsm_mutex_prefetcher); switch (errno) { case ETIMEDOUT: #ifdef PARANOID critical_on (mondata->event_count); #endif debug(2, "Timed out -> no events (mondata->event_count == %i)", mondata->event_count); return 0; /* case EINTR: debug(3, "pthread_cond_timedwait() -> EINTR. Restarting."); goto l_pthread_cond_timedwait_restart;*/ default: critical("Got unhandled error on pthread_cond_timedwait()"); } } pthread_mutex_unlock(&bsm_mutex_prefetcher); /*#ifdef PARANOID critical_on (!mondata->event_count); #endif*/ debug(2, "%s. mondata->event_count == %i", mondata->event_count?"Got an event":"Got signal SIGUSR_BLOPINT", mondata->event_count); return mondata->event_count; } int bsm_handle_prefetched(struct ctx *ctx_p, struct indexes *indexes_p) { int count; debug(8, ""); pthread_mutex_lock(&bsm_mutex_prefetcher); count = bsm_handle_allevents(ctx_p, indexes_p, BSM_HANDLE_ITERATE); pthread_mutex_unlock(&bsm_mutex_prefetcher); return count; } int bsm_wait_noprefetch(struct ctx *ctx_p, struct indexes *indexes_p, struct timeval *timeout_p) { debug(3, "(ctx_p, indexes_p, %p {%u, %u})", timeout_p, timeout_p == NULL?-1:timeout_p->tv_sec, timeout_p == NULL?0:timeout_p->tv_usec); mondata_t *mondata = ctx_p->fsmondata; struct timeval timeout_abs, tv_abs; struct bsm_event *event_p = mondata->event; if (timeout_p->tv_sec != 0 || timeout_p->tv_usec != 0) { gettimeofday(&tv_abs, NULL); timeradd(&tv_abs, timeout_p, &timeout_abs); } int pipe_fd = fileno(mondata->pipe); if (*event_p->path) { debug(2, "We already have an event. Return 1."); return 1; } if (bsm_fetch(ctx_p, indexes_p, mondata->event, pipe_fd, timeout_p, &timeout_abs) == 0) { debug(2, "No events. Return 0"); return 0; } if (*event_p->path) { debug(2, "We have an event. Return 1."); return 1; } critical ("This code shouldn't be reached"); return -1; } int bsm_handle_noprefetch(struct ctx *ctx_p, struct indexes *indexes_p) { debug(3, ""); return bsm_handle_allevents(ctx_p, indexes_p, BSM_HANDLE_CALLWAIT); } int bsm_add_watch_dir(struct ctx *ctx_p, struct indexes *indexes_p, const char *const accpath) { static int id = 1; if (id == -1) id = (int)((unsigned int)~0 >> 2); // TODO: optimize this line out: while (indexes_wd2fpath(indexes_p, id) != NULL) id++; return id++; } int bsm_deinit(ctx_t *ctx_p) { void *ret; int rc = 0; mondata_t *mondata = ctx_p->fsmondata; bsm_prefetcher_running = 0; pthread_kill(prefetcher_thread, SIGUSR_BLOPINT); pthread_cond_destroy (&bsm_cond_gotevent); pthread_mutex_destroy(&bsm_mutex_prefetcher); pthread_join(prefetcher_thread, &ret); rc |= fclose(mondata->pipe); rc |= bsm_config_revert(mondata); free(ctx_p->fsmondata); ctx_p->fsmondata = NULL; rc |= auditd_restart(); return rc; } clsync-0.4.1/mon_bsm.h000066400000000000000000000022121252417542300146140ustar00rootroot00000000000000/* clsync - file tree sync utility based on inotify/kqueue Copyright (C) 2013-2014 Dmitry Yu Okunev 0x8E30679C This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ extern int bsm_init(ctx_t *ctx_p); extern int (*bsm_wait)(struct ctx *ctx_p, struct indexes *indexes_p, struct timeval *timeout_p); extern int (*bsm_handle)(struct ctx *ctx_p, struct indexes *indexes_p); extern int bsm_add_watch_dir(struct ctx *ctx_p, struct indexes *indexes_p, const char *const accpath); extern int bsm_deinit(ctx_t *ctx_p); clsync-0.4.1/mon_dtracepipe.c000066400000000000000000000132501252417542300161520ustar00rootroot00000000000000/* clsync - file tree sync utility based on inotify/kqueue Copyright (C) 2013-2014 Dmitry Yu Okunev 0x8E30679C This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "common.h" #include "malloc.h" #include "error.h" #include "indexes.h" #include "sync.h" #include "mon_dtracepipe.h" #define DTRACE_SCRIPT "BEGIN\ {\ dir = $1;\ dirlen = strlen(dir);\ }\ \ syscall::open*:entry\ /\ arg1 & (O_WRONLY|O_RDWR) &&\ substr(copyinstr(arg0),0,dirlen)==dir\ /\ {\ printf("%s\n",copyinstr(arg0));\ }\ \ syscall::mkdir*:entry\ /\ substr(copyinstr(arg0),0,dirlen)==dir\ /\ {\ printf("%s\n",copyinstr(arg0));\ }" struct mondata { FILE *pipe; }; typedef struct mondata mondata_t; #define DTRACEPIPE_INIT_ERROR {\ free(ctx_p->fsmondata);\ ctx_p->fsmondata = NULL;\ return -1;\ } int dtracepipe_init(ctx_t *ctx_p) { char cmd[BUFSIZ]; ctx_p->fsmondata = xcalloc(sizeof(mondata_t), 1); mondata_t *mondata = ctx_p->fsmondata; if (snprintf(cmd, "%s -n '%s' '%s'", DTRACE_PATH, DTRACE_SCRIPT, ) >= BUFSIZ) { errno = EMSGSIZE; error("Too long cmd."); DTRACEPIPE_INIT_ERROR; } FILE *pipe = popen(cmd, "r"); if (pipe == NULL) { error("Cannot popen(\""DTRACE_PATH"\", \"r\")"); DTRACEPIPE_INIT_ERROR; } if (setvbuf(pipe, NULL, _IONBF, 0)) { error("Cannot set unbuffered mode for pipe of \""DTRACE_PATH"\" process"); DTRACEPIPE_INIT_ERROR; } mondata->pipe = pipe; return 0; } char *dtracepipe_wait_line = NULL; size_t dtracepipe_wait_line_siz; int dtracepipe_wait(struct ctx *ctx_p, struct indexes *indexes_p, struct timeval *timeout_p) { mondata_t *mondata = ctx_p->fsmondata; struct timeval timeout_abs, tv_abs; int dontwait = 0; struct dtracepipe_event *event_p = &mondata->event; if (timeout_p->tv_sec == 0 && timeout_p->tv_usec == 0) dontwait = 1; if (!dontwait) { gettimeofday(&tv_abs, NULL); timeradd(&tv_abs, timeout_p, &timeout_abs); } int pipe_fd = fileno(mondata->pipe); while (42) { int path_count; // Checking if there already a recond in mondata if (*event_p->path) { debug(2, "we have an event. return 1."); return 1; } // Getting a record { debug(3, "select() with timeout %li.%06li secs (dontwait == %u).", timeout_p->tv_sec, timeout_p->tv_usec, dontwait); fd_set rfds; FD_ZERO(&rfds); FD_SET(pipe_fd, &rfds); int rc = select(pipe_fd+1, &rfds, NULL, NULL, timeout_p); if (rc == 0 || rc == -1) return rc; line_len = getline(&dtracepipe_wait_line, &dtracepipe_wait_line_siz, mondata->pipe); if (line_len == -1) { error("Cannot read line from \""DTRACE_PATH"\" pipe [using getline()]"); return -1; } if (!dontwait) { debug(5, "old timeout_p->: tv_sec == %lu; tv_usec == %lu", timeout_p->tv_sec, timeout_p->tv_usec); gettimeofday(&tv_abs, NULL); if (timercmp(&timeout_abs, &tv_abs, <)) timersub(&timeout_abs, &tv_abs, timeout_p); else memset(timeout_p, 0, sizeof(*timeout_p)); debug(5, "new timeout_p->: tv_sec == %lu; tv_usec == %lu", timeout_p->tv_sec, timeout_p->tv_usec); } } // Parsing the record path_count = 0; debug(3, "parsing the event"); while (au_parsed < au_len) { if (au_fetch_tok(&tok, &au_buf[au_parsed], au_len - au_parsed) == -1) return -1; au_parsed += tok.len; switch (tok.id) { case AUT_HEADER32: case AUT_HEADER32_EX: case AUT_HEADER64: case AUT_HEADER64_EX: { event_p->type = tok.tt.hdr32.e_type; path_count = 0; break; } case AUT_PATH: { char *ptr; int dir_wd, dir_iswatched; ptr = memrchr(tok.tt.path.path, '/', tok.tt.path.len); #ifdef PARANOID if (ptr == NULL) critical("relative path received from au_fetch_tok(): \"%s\" (len: %u)", tok.tt.path.path, tok.tt.path.len); #endif debug(6, "Event on \"%s\".", tok.tt.path.path); *ptr = 0; dir_wd = indexes_fpath2wd(indexes_p, tok.tt.path.path); dir_iswatched = (dir_wd != -1); debug(7, "Directory is \"%s\". dir_wd == %i; dir_iswatched == %u", tok.tt.path.path, dir_wd, dir_iswatched); *ptr = '/'; if (dir_iswatched) { debug(5, "Event on \"%s\" is watched. Pushing. path_count == %u", tok.tt.path.path, path_count); switch (path_count) { case 0: memcpy(event_p->path, tok.tt.path.path, tok.tt.path.len+1); break; case 1: memcpy(event_p->path_to, tok.tt.path.path, tok.tt.path.len+1); break; #ifdef PARANOID default: warning("To many paths on BSM event: \"%s\" (already count: %u)", tok.tt.path.path, path_count); break; #endif } } path_count++; break; } default: continue; } } // Cleanup debug(4, "clean up"); free(au_buf); au_buf = NULL; au_len = 0; au_parsed = 0; } return -1; } int dtracepipe_handle(struct ctx *ctx_p, struct indexes *indexes_p) { return -1; } int dtracepipe_add_watch_dir(struct ctx *ctx_p, struct indexes *indexes_p, const char *const accpath) { return -1; } int dtracepipe_deinit(ctx_t *ctx_p) { mondata_t *mondata = ctx_p->fsmondata; free(dtracepipe_wait_line); free(mondata); return -1; } clsync-0.4.1/mon_dtracepipe.h000066400000000000000000000022421252417542300161560ustar00rootroot00000000000000/* clsync - file tree sync utility based on inotify/kqueue Copyright (C) 2013-2014 Dmitry Yu Okunev 0x8E30679C This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ extern int dtracepipe_init(ctx_t *ctx_p); extern int dtracepipe_wait(struct ctx *ctx_p, struct indexes *indexes_p, struct timeval *tv_p); extern int dtracepipe_handle(struct ctx *ctx_p, struct indexes *indexes_p); extern int dtracepipe_add_watch_dir(struct ctx *ctx_p, struct indexes *indexes_p, const char *const accpath); extern int dtracepipe_deinit(ctx_t *ctx_p); clsync-0.4.1/mon_fanotify.c000066400000000000000000000041121252417542300156460ustar00rootroot00000000000000/* clsync - file tree sync utility based on inotify/kqueue Copyright (C) 2013-2014 Dmitry Yu Okunev 0x8E30679C This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #if 0 int fanotify_add_watch_dir(int fanotify_d, struct ctx *ctx_p, const char *const accpath) { return fanotify_mark(fanotify_d, FAN_MARK_ADD | FAN_MARK_DONT_FOLLOW, FANOTIFY_MARKMASK, AT_FDCWD, accpath); } int fanotify_loop(int fanotify_d, ctx_t *ctx_p, indexes_t *indexes_p) { struct fanotify_event_metadata buf[BUFSIZ/sizeof(struct fanotify_event_metadata) + 1]; int state = STATE_RUNNING; state_p = &state; while(state != STATE_EXIT) { struct fanotify_event_metadata *metadata; size_t len = read(fanotify_d, (void *)buf, sizeof(buf)-sizeof(*buf)); metadata=buf; if(len == -1) { error("cannot read(%i, &metadata, sizeof(metadata)).", fanotify_d); return errno; } while(FAN_EVENT_OK(metadata, len)) { debug(2, "metadata->pid: %i; metadata->fd: %i", metadata->pid, metadata->fd); if (metadata->fd != FAN_NOFD) { if (metadata->fd >= 0) { char *fpath = fd2fpath_malloc(metadata->fd); sync_queuesync(fpath_rel, 0, ctx_p, indexes_p, QUEUE_AUTO); debug(2, "Event %i on \"%s\".", metadata->mask, fpath); free(fpath); } } close(metadata->fd); metadata = FAN_EVENT_NEXT(metadata, len); } int ret; if((ret=sync_idle(fanotify_d, ctx_p, indexes_p))) { error("got error while sync_idle()."); return ret; } } return 0; } #endif clsync-0.4.1/mon_fanotify.h000066400000000000000000000016031252417542300156550ustar00rootroot00000000000000/* clsync - file tree sync utility based on inotify/kqueue Copyright (C) 2013-2014 Dmitry Yu Okunev 0x8E30679C This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ extern int fanotify_add_watch_dir(struct ctx *ctx_p, const char *const accpath); clsync-0.4.1/mon_gio.c000066400000000000000000000253211252417542300146120ustar00rootroot00000000000000/* clsync - file tree sync utility based on inotify/kqueue/bsm/gio Copyright (C) 2013-2014 Dmitry Yu Okunev 0x8E30679C This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ // The "queue" is actually "stack" in this code. It's a lack of design of this code. #include "common.h" #include "error.h" #include "sync.h" #include "indexes.h" #include "privileged.h" #include #include #include "mon_gio.h" struct filemondata { ctx_t *ctx_p; GFile *file; GFileMonitor *filemon; gulong handle_id; }; typedef struct filemondata filemondata_t; struct event { char *path; gulong handle_id; GFileMonitorEvent event_id; eventobjtype_t objtype_event; eventobjtype_t objtype_old; eventobjtype_t objtype_new; }; typedef struct event event_t; GHashTable *mondirs_ht; pthread_spinlock_t queue_lock; pthread_mutex_t gio_mutex_prefetcher = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t gio_cond_gotevent = PTHREAD_COND_INITIALIZER; event_t *queue = NULL; int queue_length; int queue_alloc; static inline void event_free(event_t *ev) { free(ev->path); return; } static inline int event_push(char *path, gulong handle_id, GFileMonitorEvent event, eventobjtype_t objtype_event, eventobjtype_t objtype_old, eventobjtype_t objtype_new) { event_t *ev; debug(30, "pthread_spin_lock(&queue_lock);"); pthread_spin_lock(&queue_lock); if (queue_length >= queue_alloc) { queue_alloc += ALLOC_PORTION; critical_on (queue_alloc >= GIO_QUEUE_LENGTH_MAX); queue = xrealloc(queue, queue_alloc*sizeof(*queue)); } ev = &queue[queue_length++]; ev->path = path; ev->event_id = event; ev->handle_id = handle_id; ev->objtype_event = objtype_event; ev->objtype_old = objtype_old; ev->objtype_new = objtype_new; debug(30, "pthread_spin_unlock(&queue_lock);"); pthread_spin_unlock(&queue_lock); return 0; } static inline event_t *event_pop() { static event_t ev; debug(30, "pthread_spin_lock(&queue_lock);"); pthread_spin_lock(&queue_lock); critical_on (!queue_length); memcpy(&ev, &queue[--queue_length], sizeof(ev)); debug(30, "pthread_spin_unlock(&queue_lock);"); pthread_spin_unlock(&queue_lock); return &ev; } static void dir_gotevent( GFileMonitor *filemon, GFile *file, GFile *file_other, GFileMonitorEvent event, gpointer arg ) { eventobjtype_t objtype_old, objtype_new, objtype; filemondata_t *fmdat = arg; ctx_t *ctx_p = fmdat->ctx_p; GFileType filetype = g_file_query_file_type(file, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL); debug(10, "%p %p %p %i %p %i", filemon, file, file_other, event, arg, filetype); char *path_full, *path_rel; switch (event) { case G_FILE_MONITOR_EVENT_DELETED: case G_FILE_MONITOR_EVENT_CREATED: case G_FILE_MONITOR_EVENT_CHANGED: case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED: path_full = g_file_get_path(file); path_rel = strdup(&path_full[ctx_p->watchdirlen+1]); g_free(path_full); debug(9, "Got event %i for \"%s\" (%i)", event, path_rel, filetype); break; default: break; } switch (filetype) { case G_FILE_TYPE_DIRECTORY: objtype = EOT_DIR; break; default: objtype = EOT_FILE; break; } switch (event) { case G_FILE_MONITOR_EVENT_DELETED: objtype_old = objtype; objtype_new = EOT_DOESNTEXIST; break; case G_FILE_MONITOR_EVENT_CREATED: objtype_old = EOT_DOESNTEXIST; objtype_new = objtype; break; default: objtype_old = objtype; objtype_new = objtype; break; } switch (event) { case G_FILE_MONITOR_EVENT_DELETED: debug(20, "g_hash_table_remove(mondirs_ht, \"%s\")", path_rel); g_hash_table_remove(mondirs_ht, path_rel); case G_FILE_MONITOR_EVENT_CREATED: case G_FILE_MONITOR_EVENT_CHANGED: case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED: debug(20, "event_push(\"%s\", %i, %i, %i, %i, %i)", path_rel, fmdat->handle_id, event, objtype, objtype_old, objtype_new); critical_on (event_push(path_rel, fmdat->handle_id, event, objtype, objtype_old, objtype_new)); break; default: break; } return; } int gio_add_watch_dir(ctx_t *ctx_p, indexes_t *indexes_p, const char *const accpath) { filemondata_t *fmdat; GError *error = NULL; debug(3, "\"%s\"", accpath); #ifdef PARANOID fmdat = g_hash_table_lookup(mondirs_ht, accpath); if (fmdat != NULL) { errno = EADDRINUSE; warning("Directory \"%s\" is already monitored.", accpath); return -1; } #endif fmdat = xmalloc(sizeof(*fmdat)); fmdat->ctx_p = ctx_p; fmdat->file = g_file_new_for_path(accpath); fmdat->filemon = g_file_monitor_directory(fmdat->file, 0, NULL, &error); fmdat->handle_id = g_signal_connect (fmdat->filemon, "changed", G_CALLBACK(dir_gotevent), fmdat); g_hash_table_replace(mondirs_ht, strdup(accpath), fmdat); return fmdat->handle_id; } int cancel_g_iteration_stop; pthread_t thread_g_iteration_stop; void *g_iteration_stop(void *_timeout_p) { struct timeval *timeout_p = _timeout_p; struct timeval tv_abs, timeout_abs; struct timespec ts_abs; debug(10, "{%u, %u}", timeout_p->tv_sec, timeout_p->tv_usec); critical_on (pthread_mutex_lock(&gio_mutex_prefetcher)); if (cancel_g_iteration_stop) { critical_on (pthread_mutex_unlock(&gio_mutex_prefetcher)); return NULL; } #define INFINITETIME (3600 * 24 * 365 * 10) /* ~10 years */ if (timeout_p->tv_sec > INFINITETIME) timeout_p->tv_sec = INFINITETIME; #undef INFINITETIME gettimeofday(&tv_abs, NULL); timeradd(&tv_abs, timeout_p, &timeout_abs); ts_abs.tv_sec = timeout_abs.tv_sec; ts_abs.tv_nsec = timeout_abs.tv_usec*1000; debug(10, "{%u, %u}", ts_abs.tv_sec, ts_abs.tv_nsec); switch ((errno = pthread_cond_timedwait(&gio_cond_gotevent, &gio_mutex_prefetcher, &ts_abs))) { case 0: case ETIMEDOUT: break; default: critical ("Got error while pthread_cond_timedwait(&gio_cond_gotevent, &gio_mutex_prefetcher, &ts_abs)"); } g_main_context_wakeup(NULL); pthread_mutex_unlock(&gio_mutex_prefetcher); debug(10, "return"); return NULL; } static inline int gio_wait_now(ctx_t *ctx_p, struct indexes *indexes_p, struct timeval *tv_p) { void *ret; int result; debug(3, "(ctx_p, indexes_p, %p {%u, %u})", tv_p, tv_p == NULL?-1:tv_p->tv_sec, tv_p == NULL?0:tv_p->tv_usec); #ifdef PARANOID critical_on (tv_p == NULL); #endif if (queue_length) { debug(9, "already: queue_length == %i", queue_length); return queue_length; } if (tv_p->tv_sec == 0 && tv_p->tv_usec == 0) { g_main_context_iteration(NULL, FALSE); debug(9, "nowait: queue_length == %i", queue_length); return queue_length; } cancel_g_iteration_stop = 0; pthread_create(&thread_g_iteration_stop, NULL, g_iteration_stop, tv_p); /* debug(30, "pthread_spin_unlock(&queue_lock);"); pthread_spin_unlock(&queue_lock); debug(30 , "g_main_context_iteration(NULL, FALSE);"); result = g_main_context_iteration(NULL, FALSE); debug(30, "pthread_spin_lock(&queue_lock);"); pthread_spin_lock(&queue_lock); if (queue_length) { debug(9, "already2: queue_length == %i", queue_length); return queue_length; } */ debug_call (40, pthread_spin_unlock(&queue_lock)); debug(20 , "g_main_context_iteration(NULL, TRUE); queue_length == %i", queue_length); result = g_main_context_iteration(NULL, TRUE); debug(10, "g_main_context_iteration() -> %i", result); debug_call (40, pthread_spin_lock(&queue_lock)); critical_on (pthread_mutex_lock(&gio_mutex_prefetcher)); cancel_g_iteration_stop = 1; critical_on (pthread_mutex_unlock(&gio_mutex_prefetcher)); critical_on (pthread_cond_broadcast(&gio_cond_gotevent)); critical_on (pthread_join(thread_g_iteration_stop, &ret)); debug(9, "queue_length == %i", queue_length); return queue_length; } int gio_wait(ctx_t *ctx_p, struct indexes *indexes_p, struct timeval *tv_p) { int ret; debug(30, "pthread_spin_lock(&queue_lock);"); debug_call (40, pthread_spin_lock(&queue_lock)); ret = gio_wait_now(ctx_p, indexes_p, tv_p); debug(30, "pthread_spin_unlock(&queue_lock);"); debug_call (40, pthread_spin_unlock(&queue_lock)); return ret; } int gio_handle(ctx_t *ctx_p, indexes_t *indexes_p) { static struct timeval tv={0}; int count; char *path_full = NULL; size_t path_full_len = 0; count = 0; while (gio_wait(ctx_p, indexes_p, &tv)) { event_t *ev = event_pop(); stat64_t lstat, *lstat_p; mode_t st_mode; size_t st_size; if ((ev->objtype_new == EOT_DOESNTEXIST) || (ctx_p->flags[CANCEL_SYSCALLS]&CSC_MON_STAT) || lstat64(ev->path, &lstat)) { debug(2, "Cannot lstat64(\"%s\", lstat). Seems, that the object had been deleted (%i) or option \"--cancel-syscalls mon_stat\" (%i) is set.", ev->path, ev->objtype_new == EOT_DOESNTEXIST, ctx_p->flags[CANCEL_SYSCALLS]&CSC_MON_STAT); st_mode = (ev->objtype_event == EOT_DIR ? S_IFDIR : S_IFREG); st_size = 0; lstat_p = NULL; } else { st_mode = lstat.st_mode; st_size = lstat.st_size; lstat_p = &lstat; } if (sync_prequeue_loadmark(1, ctx_p, indexes_p, NULL, ev->path, lstat_p, ev->objtype_old, ev->objtype_new, ev->event_id, ev->handle_id, st_mode, st_size, &path_full, &path_full_len, NULL)) { event_free(ev); count = -1; break; } event_free(ev); count++; } // Globally queueing captured events: // Moving events from local queue to global ones sync_prequeue_unload(ctx_p, indexes_p); free(path_full); #ifdef VERYPARANOID path_full = NULL; path_full_len = 0; #endif return count; } void free_filemondat(void *_fmdat) { filemondata_t *fmdat = _fmdat; g_signal_handler_disconnect(fmdat->file, fmdat->handle_id); free(fmdat->file); free(fmdat->filemon); free(fmdat); return; } GMainLoop *gio_loop = NULL; int gio_init(ctx_t *ctx_p) { queue_length = 0; queue_alloc = 0; pthread_mutex_init (&gio_mutex_prefetcher, NULL); pthread_cond_init (&gio_cond_gotevent, NULL); pthread_spin_init (&queue_lock, PTHREAD_PROCESS_SHARED); mondirs_ht = g_hash_table_new_full(g_str_hash, g_str_equal, free, free_filemondat); gio_loop = g_main_loop_new(NULL, TRUE); g_main_context_iteration(NULL, FALSE); return 0; } int gio_deinit(ctx_t *ctx_p) { /* g_main_loop_quit(gio_loop); g_hash_table_destroy (mondirs_ht); pthread_spin_destroy (&queue_lock); pthread_cond_destroy (&gio_cond_gotevent); pthread_mutex_destroy (&gio_mutex_prefetcher); */ return 0; } clsync-0.4.1/mon_gio.h000066400000000000000000000022031252417542300146110ustar00rootroot00000000000000/* clsync - file tree sync utility based on gio/kqueue/bsm/gio Copyright (C) 2013-2014 Dmitry Yu Okunev 0x8E30679C This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ extern int gio_wait(struct ctx *ctx_p, struct indexes *indexes_p, struct timeval *tv_p); extern int gio_handle(struct ctx *ctx_p, struct indexes *indexes_p); extern int gio_add_watch_dir(struct ctx *ctx_p, struct indexes *indexes_p, const char *const accpath); extern int gio_init(ctx_t *ctx_p); extern int gio_deinit(ctx_t *ctx_p); clsync-0.4.1/mon_inotify.c000066400000000000000000000125221252417542300155140ustar00rootroot00000000000000/* clsync - file tree sync utility based on inotify/kqueue Copyright (C) 2013-2014 Dmitry Yu Okunev 0x8E30679C This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "common.h" #include "error.h" #include "sync.h" #include "indexes.h" #include "privileged.h" #include "mon_inotify.h" enum event_bits { UEM_DIR = 0x01, UEM_CREATED = 0x02, UEM_DELETED = 0x04, }; struct recognize_event_return { eventobjtype_t objtype_old; eventobjtype_t objtype_new; }; static inline void recognize_event(struct recognize_event_return *r, uint32_t event) { eventobjtype_t type; int is_created; int is_deleted; type = (event & IN_ISDIR ? EOT_DIR : EOT_FILE); is_created = event & (IN_CREATE|IN_MOVED_TO); is_deleted = event & (IN_DELETE_SELF|IN_DELETE|IN_MOVED_FROM); debug(4, "type == %x; is_created == %x; is_deleted == %x", type, is_created, is_deleted); r->objtype_old = (is_created ? EOT_DOESNTEXIST : type); r->objtype_new = (is_deleted ? EOT_DOESNTEXIST : type); return; } int inotify_add_watch_dir(ctx_t *ctx_p, indexes_t *indexes_p, const char *const accpath) { int inotify_d = (int)(long)ctx_p->fsmondata; return privileged_inotify_add_watch(inotify_d, accpath, INOTIFY_MARKMASK, PC_INOTIFY_ADD_WATCH_DIR); } int inotify_wait(ctx_t *ctx_p, struct indexes *indexes_p, struct timeval *tv_p) { int inotify_d = (int)(long)ctx_p->fsmondata; debug(3, "select with timeout %li secs (fd == %u).", tv_p->tv_sec, inotify_d); fd_set rfds; FD_ZERO(&rfds); FD_SET(inotify_d, &rfds); return select(inotify_d+1, &rfds, NULL, NULL, tv_p); } #define INOTIFY_HANDLE_CONTINUE {\ ptr += sizeof(struct inotify_event) + event->len;\ count++;\ continue;\ } int inotify_handle(ctx_t *ctx_p, indexes_t *indexes_p) { static struct timeval tv={0}; int inotify_d = (int)(long)ctx_p->fsmondata; int count = 0; fd_set rfds; FD_ZERO(&rfds); FD_SET(inotify_d, &rfds); char *path_rel = NULL; size_t path_rel_len = 0; char *path_full = NULL; size_t path_full_size = 0; while (select(FD_SETSIZE, &rfds, NULL, NULL, &tv)) { char buf[BUFSIZ + 1]; size_t r = read(inotify_d, buf, BUFSIZ); if (r <= 0) { error("Got error while reading events from inotify with read()."); count = -1; goto l_inotify_handle_end; } #ifdef PARANOID g_hash_table_remove_all(indexes_p->fpath2ei_ht); #endif char *ptr = buf; char *end = &buf[r]; while (ptr < end) { struct inotify_event *event = (struct inotify_event *)ptr; // Removing stale wd-s if (event->mask & IN_IGNORED) { debug(2, "Cleaning up info about watch descriptor %i.", event->wd); indexes_remove_bywd(indexes_p, event->wd); INOTIFY_HANDLE_CONTINUE; } // Getting path char *fpath = indexes_wd2fpath(indexes_p, event->wd); if (fpath == NULL) { debug(2, "Event %p on stale watch (wd: %i).", (void *)(long)event->mask, event->wd); INOTIFY_HANDLE_CONTINUE; } debug(2, "Event %p on \"%s\" (wd: %i; fpath: \"%s\").", (void *)(long)event->mask, event->len>0?event->name:"", event->wd, fpath); // Getting full path size_t path_full_memreq = strlen(fpath) + event->len + 2; if (path_full_size < path_full_memreq) { path_full = xrealloc(path_full, path_full_memreq); path_full_size = path_full_memreq; } if (event->len>0) sprintf(path_full, "%s/%s", fpath, event->name); else sprintf(path_full, "%s", fpath); // Getting infomation about file/dir/etc struct recognize_event_return r = {0}; recognize_event(&r, event->mask); stat64_t lstat, *lstat_p; mode_t st_mode; size_t st_size; if ((r.objtype_new == EOT_DOESNTEXIST) || (ctx_p->flags[CANCEL_SYSCALLS]&CSC_MON_STAT) || lstat64(path_full, &lstat)) { debug(2, "Cannot lstat64(\"%s\", lstat). Seems, that the object had been deleted (%i) or option \"--cancel-syscalls mon_stat\" (%i) is set.", path_full, r.objtype_new == EOT_DOESNTEXIST, ctx_p->flags[CANCEL_SYSCALLS]&CSC_MON_STAT); st_mode = (event->mask & IN_ISDIR ? S_IFDIR : S_IFREG); st_size = 0; lstat_p = NULL; } else { st_mode = lstat.st_mode; st_size = lstat.st_size; lstat_p = &lstat; } if (sync_prequeue_loadmark(1, ctx_p, indexes_p, path_full, NULL, lstat_p, r.objtype_old, r.objtype_new, event->mask, event->wd, st_mode, st_size, &path_rel, &path_rel_len, NULL)) { count = -1; goto l_inotify_handle_end; } INOTIFY_HANDLE_CONTINUE; } // Globally queueing captured events: // Moving events from local queue to global ones sync_prequeue_unload(ctx_p, indexes_p); } l_inotify_handle_end: if (path_full != NULL) free(path_full); if (path_rel != NULL) free(path_rel); return count; } int inotify_deinit(ctx_t *ctx_p) { int inotify_d = (int)(long)ctx_p->fsmondata; debug(3, "Closing inotify_d"); return close(inotify_d); } clsync-0.4.1/mon_inotify.h000066400000000000000000000021541252417542300155210ustar00rootroot00000000000000/* clsync - file tree sync utility based on inotify/kqueue Copyright (C) 2013-2014 Dmitry Yu Okunev 0x8E30679C This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ extern int inotify_wait(struct ctx *ctx_p, struct indexes *indexes_p, struct timeval *tv_p); extern int inotify_handle(struct ctx *ctx_p, struct indexes *indexes_p); extern int inotify_add_watch_dir(struct ctx *ctx_p, struct indexes *indexes_p, const char *const accpath); extern int inotify_deinit(ctx_t *ctx_p); clsync-0.4.1/mon_kqueue.c000066400000000000000000000554471252417542300153470ustar00rootroot00000000000000/* clsync - file tree sync utility based on inotify/kqueue Copyright (C) 2014 Dmitry Yu Okunev 0x8E30679C This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "common.h" #include #include "error.h" #include "sync.h" #include "indexes.h" #include "fileutils.h" #include "calc.h" #include "glibex.h" #include "mon_kqueue.h" enum kqueue_status { KQUEUE_STATUS_UNKNOWN, KQUEUE_STATUS_RUNNING, KQUEUE_STATUS_DEINIT, KQUEUE_STATUS_DEAD, }; enum kqueue_status kqueue_status = KQUEUE_STATUS_UNKNOWN; struct monobj { ino_t inode; dev_t device; int fd; int dir_fd; char *name; size_t name_len; uint32_t name_hash; unsigned char type; size_t changelist_id; struct monobj *parent; // Case specific stuff union { // For directories only, for original (not dupes) obj_p only GTree *children_tree; // For duplicates only, see _kqueue_handle_oneevent_dircontent() struct monobj *origin; }; }; typedef struct monobj monobj_t; struct kqueue_data { int kqueue_d; struct kevent *changelist; size_t changelist_alloced; size_t changelist_used; struct kevent eventlist[KQUEUE_EVENTLISTSIZE]; size_t eventlist_count; monobj_t **obj_p_by_clid; // An associative array to get the monobj pointer by an changelist_id GTree *file_btree; GTree *fd_btree; }; struct recognize_event_return { union { struct { eventobjtype_t objtype_old:16; eventobjtype_t objtype_new:16; } v; uint32_t i; } u; }; ctx_t *ctx_p; indexes_t *indexes_p; static inline uint32_t recognize_event(uint32_t event, int is_dir) { struct recognize_event_return r = {{{0}}}; eventobjtype_t type; int is_created; int is_deleted; type = (is_dir ? EOT_DIR : EOT_FILE); is_created = event & (NOTE_LINK); is_deleted = event & (NOTE_DELETE); debug(4, "type == %x; is_created == %x; is_deleted == %x", type, is_created, is_deleted); r.u.v.objtype_old = type; r.u.v.objtype_new = type; if (is_created) r.u.v.objtype_old = EOT_DOESNTEXIST; if (is_deleted) r.u.v.objtype_new = EOT_DOESNTEXIST; return r.u.i; } extern int kqueue_sync(ctx_t *ctx_p, indexes_t *indexes_p, struct kevent *ev_p, monobj_t *obj_p); extern int kqueue_unmark(ctx_t *ctx_p, monobj_t *obj_p); void unmarkchild(gpointer _obj_p) { monobj_t *obj_p = _obj_p; static struct kevent ev = {0}; debug(10, "Calling kqueue_sync() on \"%s\" (obj_p: %p; dir_fd: %i; fd: %i)", obj_p->name, obj_p, obj_p->dir_fd, obj_p->fd); ev.ident = obj_p->fd; ev.fflags = NOTE_DELETE; critical_on (kqueue_sync(ctx_p, indexes_p, &ev, obj_p)); debug(4, "Unmarking the child \"%s\" (dir_fd: %i; fd: %i)", obj_p->name, obj_p->dir_fd, obj_p->fd); kqueue_unmark(ctx_p, obj_p); return; } gboolean unmarkchild_for_foreach(gpointer _obj_p, gpointer _value, gpointer _ctx_p) { unmarkchild(_obj_p); return FALSE; } void monobj_free(void *monobj_p) { monobj_t *obj_p = monobj_p; debug(20, "obj_p == %p; obj_p->fd == %i; obj_p->name == \"%s\"", obj_p, obj_p->fd, obj_p->name); if (kqueue_status != KQUEUE_STATUS_DEINIT) { if (obj_p->children_tree != NULL) { debug(20, "Removing children"); if (g_tree_nnodes(obj_p->children_tree)) { g_tree_foreach(obj_p->children_tree, unmarkchild_for_foreach, ctx_p); g_tree_destroy(obj_p->children_tree); } } if (obj_p->parent != NULL) { monobj_t *parent = obj_p->parent; debug(20, "Removing the obj from parent->children_tree (obj_p == %p; parent == %p; parent->children_tree == %p)", obj_p, parent, parent->children_tree); g_tree_remove(parent->children_tree, obj_p); } } debug(20, "free()-s"); free(obj_p->name); free(obj_p); return; } static gint monobj_filecmp(gconstpointer _a, gconstpointer _b, gpointer _ctx_p) { #ifdef VERYPARANOID critical_on (_a == NULL); critical_on (_b == NULL); #endif const monobj_t *a=_a, *b=_b; debug(95, "a == %p; b == %p", a, b); int diff_inode = a->inode - b->inode; debug(90, "diff_inode = %i", diff_inode); if (diff_inode) return diff_inode; int diff_device = a->device - b->device; debug(50, "diff_device = %i", diff_device); if (diff_device) return diff_device; int diff_dir_fd = a->dir_fd - b->dir_fd; debug(50, "diff_dir_fd = %i (%i - %i)", diff_dir_fd, a->dir_fd, b->dir_fd); if (diff_dir_fd) return diff_dir_fd; int diff_name_hash = a->name_hash - b->name_hash; debug(50, "diff_name_hash = %i", diff_name_hash); if (diff_name_hash) return diff_name_hash; debug(10, "strcmp(\"%s\", \"%s\") = %i", a->name, b->name, strcmp(a->name, b->name)); return strcmp(a->name, b->name); } static int monobj_fdcmp(gconstpointer a, gconstpointer b, gpointer _ctx_p) { return ((monobj_t *)a)->fd - ((monobj_t *)b)->fd; } int kqueue_init(ctx_t *_ctx_p) { struct kqueue_data *mondata; debug(9, "_ctx_p == %p", _ctx_p); ctx_p = _ctx_p; indexes_p = ctx_p->indexes_p; ctx_p->fsmondata = xcalloc(1, sizeof(struct kqueue_data)); if (ctx_p->fsmondata == NULL) return -1; struct kqueue_data *dat = ctx_p->fsmondata; dat->kqueue_d = kqueue(); mondata = ctx_p->fsmondata; mondata->file_btree = g_tree_new_full(monobj_filecmp, _ctx_p, monobj_free, NULL); mondata->fd_btree = g_tree_new_full(monobj_fdcmp, _ctx_p, NULL, NULL); kqueue_status = KQUEUE_STATUS_RUNNING; return 0; } int kqueue_mark(ctx_t *ctx_p, monobj_t *obj_p) { struct kqueue_data *dat = ctx_p->fsmondata; #ifdef VERYPARANOID if (obj_p == NULL) { errno = EINVAL; return -1; } #endif debug(9, ""); if (obj_p->dir_fd == -1) obj_p->fd = open(ctx_p->watchdir, O_RDONLY|O_NOFOLLOW); else obj_p->fd = openat(obj_p->dir_fd, obj_p->name, O_RDONLY|O_NOFOLLOW); debug(4, "obj_p-> (%p): dir_fd == %i; name == \"%s\"; fd == %i; type == %i (isdir == %i)", obj_p, obj_p->dir_fd, obj_p->name, obj_p->fd, obj_p->type, obj_p->type == DT_DIR); if (obj_p->fd == -1) { debug(2, "File/dir \"%s\" disappeared. Skipping", obj_p->name); return 0; } if (dat->changelist_used >= dat->changelist_alloced) { dat->changelist_alloced += ALLOC_PORTION; dat->changelist = xrealloc(dat->changelist, dat->changelist_alloced*sizeof(*dat->changelist)); dat->obj_p_by_clid = xrealloc(dat->obj_p_by_clid, dat->changelist_alloced*sizeof(*dat->obj_p_by_clid)); } switch (obj_p->type) { case DT_DIR: EV_SET(&dat->changelist[dat->changelist_used], obj_p->fd, EVFILT_VNODE, EV_ADD | EV_CLEAR, NOTE_WRITE | NOTE_EXTEND | NOTE_ATTRIB | NOTE_DELETE, 0, 0); break; default: EV_SET(&dat->changelist[dat->changelist_used], obj_p->fd, EVFILT_VNODE, EV_ADD | EV_CLEAR, NOTE_WRITE | NOTE_ATTRIB | NOTE_DELETE, 0, 0); break; } obj_p->changelist_id = dat->changelist_used++; dat->obj_p_by_clid[obj_p->changelist_id] = obj_p; return 0; } void child_free(monobj_t *node) { critical_on (kqueue_unmark(ctx_p, node)); } int kqueue_unmark(ctx_t *ctx_p, monobj_t *obj_p) { int changelist_id_last; debug(20, "obj_p == %p", obj_p); struct kqueue_data *dat = ctx_p->fsmondata; #ifdef VERYPARANOID if (obj_p == NULL) { errno = EINVAL; return -1; } #endif debug(30, "dat->changelist_used == %i", dat->changelist_used); if (dat->changelist_used) { dat->changelist_used--; changelist_id_last = dat->changelist_used; debug(30, "Checking: (obj_p->changelist_id [%i] < changelist_id_last [%i]) == %i", obj_p->changelist_id, changelist_id_last, (obj_p->changelist_id < changelist_id_last)); #ifdef PARANOID critical_on (obj_p->changelist_id > changelist_id_last); #endif if (obj_p->changelist_id < changelist_id_last) { debug(20, "dat->changelist: moving %i -> %i", changelist_id_last, obj_p->changelist_id); dat->obj_p_by_clid[ obj_p->changelist_id ] = dat->obj_p_by_clid[ changelist_id_last ]; dat->obj_p_by_clid[ obj_p->changelist_id ]->changelist_id = obj_p->changelist_id; memcpy(&dat->changelist[obj_p->changelist_id], &dat->changelist[changelist_id_last], sizeof(*dat->changelist)); debug(30, "dat->obj_p_by_clid[ obj_p->changelist_id ] == %p; " "dat->obj_p_by_clid[ obj_p->changelist_id ]->fd == %i; " "dat->obj_p_by_clid[ obj_p->changelist_id ]->name == \"%s\"", dat->obj_p_by_clid[ obj_p->changelist_id ], dat->obj_p_by_clid[ obj_p->changelist_id ]->fd, dat->obj_p_by_clid[ obj_p->changelist_id ]->name ); } } close(obj_p->fd); debug(20, "Removing the obj itself"); g_tree_remove(dat->fd_btree, obj_p); g_tree_remove(dat->file_btree, obj_p); return 0; } monobj_t *kqueue_start_watch(ctx_t *ctx_p, ino_t inode, dev_t device, int dir_fd, const char *const fname, size_t name_len, unsigned char type) { monobj_t *obj_p, *parent, parent_pattern; struct kqueue_data *dat = ctx_p->fsmondata; debug(3, "(ctx_p, %i, \"%s\", %u, %u)", dir_fd, fname, name_len, type); #ifdef VERYPARANOID obj_p = xcalloc(sizeof(*obj_p), 1); #else obj_p = xmalloc(sizeof(*obj_p)); #endif obj_p->inode = inode; obj_p->device = device; obj_p->dir_fd = dir_fd; obj_p->name_len = name_len; obj_p->name = xmalloc(obj_p->name_len+1); obj_p->type = type; memcpy(obj_p->name, fname, obj_p->name_len+1); obj_p->name_hash = adler32_calc((const unsigned char *)fname, name_len); parent = NULL; parent_pattern.fd = dir_fd; parent = g_tree_lookup(dat->fd_btree, &parent_pattern); if (parent != NULL) { obj_p->parent = parent; debug(20, "Adding a child for dir_fd == %i", dir_fd); g_tree_replace(parent->children_tree, obj_p, obj_p); debug(25, "parent->children_tree == %p", parent->children_tree); } debug(20, "parent == %p; obj_p == %p", parent, obj_p); switch (type) { case DT_DIR: obj_p->children_tree = g_tree_new_full(monobj_filecmp, ctx_p, NULL, NULL); debug(25, "dir_p->children_tree == %p", obj_p->children_tree); case DT_UNKNOWN: case DT_REG: if (kqueue_mark(ctx_p, obj_p)) { error("Got error while kqueue_mark()"); free(obj_p->name); free(obj_p); return NULL; } break; default: debug(4, "I won't open() this object due to it's type == %u.", type); break; } debug(8, "storing: inode == %u; device == %u; dir_fd == %i; fd == %i; parent == %p", obj_p->inode, obj_p->device, obj_p->dir_fd, obj_p->fd, parent); g_tree_replace(dat->file_btree, obj_p, obj_p); g_tree_replace( dat->fd_btree, obj_p, obj_p); return obj_p; } monobj_t *kqueue_add_watch_direntry(ctx_t *ctx_p, indexes_t *indexes_p, struct dirent *entry, monobj_t *dir_obj_p) { struct kqueue_data *dat = ctx_p->fsmondata; monobj_t *obj_p; uint32_t name_hash; #ifdef VERYPARANOID critical_on (entry == NULL); #endif size_t name_len = strlen(entry->d_name); name_hash = adler32_calc((unsigned char *)entry->d_name, name_len); { monobj_t obj; obj.inode = entry->d_ino; obj.device = dir_obj_p->device; obj.dir_fd = dir_obj_p->fd; obj.name_hash = name_hash; if ((obj_p = g_tree_lookup(dat->file_btree, &obj)) != NULL) return obj_p; } if ((obj_p = kqueue_start_watch(ctx_p, entry->d_ino, dir_obj_p->device, dir_obj_p->fd, entry->d_name, name_len, entry->d_type)) == NULL) error("Got error while kqueue_start_watch()"); obj_p->inode = entry->d_ino; obj_p->device = dir_obj_p->device; obj_p->dir_fd = dir_obj_p->fd; obj_p->name_hash = name_hash; return obj_p; } monobj_t *kqueue_add_watch_path(ctx_t *ctx_p, indexes_t *indexes_p, const char *const path) { struct stat st; struct kqueue_data *dat = ctx_p->fsmondata; monobj_t *obj_p = NULL; uint32_t name_hash; const char *file_name; int dir_fd; size_t name_len; #ifdef VERYPARANOID if (path == NULL) { errno = EINVAL; return NULL; } #endif debug(6, "(ctx_p, indexes_p, \"%s\")", path); { char *dir_path, *ptr; ptr = strrchr(path, '/'); if (ptr == NULL) { file_name = path; dir_fd = indexes_fpath2wd(indexes_p, ""); } else { dir_path = strdup(path); dir_path[ptr - path] = 0; dir_fd = indexes_fpath2wd(indexes_p, dir_path); if (dir_fd == -1) { if (strcmp(ctx_p->watchdir, path)) { errno = ENOENT; error("Cannot find file descriptor of directory \"%s\"", dir_path); return NULL; } } free(dir_path); file_name = &ptr[1]; } name_len = strlen(file_name); name_hash = adler32_calc((unsigned char *)file_name, name_len); } lstat(path, &st); { monobj_t obj; obj.inode = st.st_ino; obj.device = st.st_dev; obj.dir_fd = dir_fd; obj.name_hash = name_hash; obj.name = (char *)file_name; if ((obj_p = g_tree_lookup(dat->file_btree, &obj)) != NULL) return obj_p; } debug(9, "not monitored file/dir \"%s\", yet.", file_name); { const char *name_start; name_start = strrchr(path, '/'); if (name_start == NULL) name_start = path; else name_start++; if ((obj_p = kqueue_start_watch(ctx_p, st.st_ino, st.st_dev, dir_fd, file_name, name_len, (st.st_mode&S_IFMT) == S_IFDIR ? DT_DIR : DT_REG)) == NULL) error("Got error while kqueue_start_watch()"); } obj_p->inode = st.st_ino; obj_p->device = st.st_dev; obj_p->dir_fd = dir_fd; obj_p->name_hash = name_hash; return obj_p; } int kqueue_add_watch_dir(ctx_t *ctx_p, indexes_t *indexes_p, const char *const accpath) { DIR *dir; monobj_t *dir_obj_p = NULL; struct dirent *entry; #ifdef VERYPARANOID if (accpath == NULL) { errno = EINVAL; return -1; } #endif debug(5, "(ctx_p, indexes_p, \"%s\")", accpath); if ((dir_obj_p = kqueue_add_watch_path(ctx_p, indexes_p, accpath)) == NULL) { error("Got error while kqueue_add_watch_path(ctx_p, \"%s\")", accpath); return -1; } dir = fdopendir(dir_obj_p->fd); if (dir == NULL) return -1; while ((entry = readdir(dir))) { if (!memcmp(entry->d_name, ".", 2)) continue; if (!memcmp(entry->d_name, "..", 3)) continue; if (kqueue_add_watch_direntry(ctx_p, indexes_p, entry, dir_obj_p) == NULL) { error("Got error while kqueue_add_watch_direntry(ctx_p, indexes_p, entry {->d_name == \"%s\"}, %u)", entry->d_name, dir_obj_p->fd); return -1; } } return dir_obj_p->fd; } int kqueue_wait(ctx_t *ctx_p, struct indexes *indexes_p, struct timeval *tv_p) { struct kqueue_data *dat = ctx_p->fsmondata; struct timespec ts; debug(3, "tv_p->: tv_sec == %li; tv_usec == %li", tv_p->tv_sec, tv_p->tv_usec); #ifdef PARANOID if (tv_p == NULL) return dat->eventlist_count = kevent(dat->kqueue_d, dat->changelist, dat->changelist_used, dat->eventlist, KQUEUE_EVENTLISTSIZE, NULL); #endif ts.tv_sec = tv_p->tv_sec; ts.tv_nsec = tv_p->tv_usec * 1000; return dat->eventlist_count = kevent(dat->kqueue_d, dat->changelist, dat->changelist_used, dat->eventlist, KQUEUE_EVENTLISTSIZE, &ts); } // Not a thread-safe function! char *kqueue_getpath(ctx_t *ctx_p, indexes_t *indexes_p, monobj_t *obj_p) { char *dirpath; size_t dirpath_len; static char filepath[PATH_MAX+2]; size_t filepath_len; dirpath = indexes_wd2fpath(indexes_p, obj_p->fd); if (dirpath != NULL) return strdup(dirpath); if (obj_p->dir_fd == -1) { errno = ENOENT; error("Cannot find fd of parent directory of \"%s\"", obj_p->name); return NULL; } dirpath = indexes_wd2fpath(indexes_p, obj_p->dir_fd); if (dirpath == NULL) { errno = ENOENT; error("Cannot find directory with fd == %u", obj_p->dir_fd); return NULL; } dirpath_len = strlen(dirpath); filepath_len = dirpath_len + obj_p->name_len + 1; #ifdef PARANOID if (filepath_len + 1 >= PATH_MAX) { errno = ENAMETOOLONG; error("Too long file path: \"%s/%s\"", dirpath, obj_p->name); return NULL; } #endif memcpy(filepath, dirpath, dirpath_len); filepath[dirpath_len] = '/'; memcpy(&filepath[dirpath_len+1], obj_p->name, obj_p->name_len+1); return filepath; } int kqueue_sync(ctx_t *ctx_p, indexes_t *indexes_p, struct kevent *ev_p, monobj_t *obj_p) { stat64_t lstat, *lstat_p; char *path_full = kqueue_getpath(ctx_p, indexes_p, obj_p); #ifdef PARANOID if (path_full == NULL) { error("Cannot get full path for \"%s\" (fd: %u)", obj_p->name, obj_p->fd); return -1; } #endif debug(8, "path_full = \"%s\"", path_full); mode_t st_mode; size_t st_size; if ((ev_p->fflags == NOTE_DELETE) || (ctx_p->flags[CANCEL_SYSCALLS]&CSC_MON_STAT) || lstat64(path_full, &lstat)) { debug(2, "Cannot or cancelled lstat64(\"%s\", lstat). The object had been deleted (%i) or option \"--cancel-syscalls=mon_stat\" (%i) is set.", path_full, ev_p->fflags == NOTE_DELETE, ctx_p->flags[CANCEL_SYSCALLS]&CSC_MON_STAT); st_mode = (obj_p->type == DT_DIR ? S_IFDIR : S_IFREG); st_size = 0; lstat_p = NULL; } else { st_mode = lstat.st_mode; st_size = lstat.st_size; lstat_p = &lstat; } { char *path_rel = NULL; size_t path_rel_len = 0; struct recognize_event_return r; r.u.i = recognize_event(ev_p->fflags, obj_p->type == DT_DIR); int ret = sync_prequeue_loadmark(1, ctx_p, indexes_p, path_full, NULL, lstat_p, r.u.v.objtype_old, r.u.v.objtype_new, ev_p->fflags, ev_p->ident, st_mode, st_size, &path_rel, &path_rel_len, NULL); if (path_rel != NULL) free(path_rel); return ret; } return 0; } static inline int _kqueue_handle_oneevent_dircontent_item(struct kqueue_data *dat, ctx_t *ctx_p, indexes_t *indexes_p, monobj_t *dir_obj_p, struct dirent *entry, void *children_notfound) { static monobj_t obj, *obj_p; struct kevent ev = {0}; debug(9, "\"%s\"", entry->d_name); obj.type = entry->d_type; obj.inode = entry->d_ino; obj.device = dir_obj_p->device; obj.dir_fd = dir_obj_p->fd; obj.name = entry->d_name; obj.name_len = strlen(entry->d_name); obj.name_hash = adler32_calc((unsigned char *)entry->d_name, obj.name_len); debug(20, "Checking if the object is already monitored (obj_p == %p)", obj_p); if ((obj_p = g_tree_lookup(dat->file_btree, &obj)) != NULL) { debug(20, "Marking the the object is found"); g_tree_remove(children_notfound, obj_p); return 0; } debug(20, "Calling kqueue_start_watch() on the object"); if ((obj_p = kqueue_start_watch(ctx_p, entry->d_ino, dir_obj_p->device, dir_obj_p->fd, obj.name, obj.name_len, obj.type)) == NULL) { error("Got error while kqueue_start_watch()"); return -1; } debug(20, "Calling kqueue_sync() for the object"); ev.ident = obj_p->fd; ev.fflags = NOTE_LINK; if (kqueue_sync(ctx_p, indexes_p, &ev, obj_p)) { error("Got error while kqueue_sync()"); return -1; } return 0; } void monobj_freedup(gpointer _obj_p) { monobj_t *obj_p = _obj_p; free(obj_p->name); free(obj_p); return; } gpointer monobj_dup(gpointer _obj_p) { monobj_t *src = _obj_p, *dst; dst = xmalloc(sizeof(*src)); memcpy(dst, src, sizeof(*src)); dst->name = xmalloc(src->name_len+2); memcpy(dst->name, src->name, src->name_len+1); dst->origin = src; return dst; } gboolean unmarkdupchild(gpointer _obj_p, gpointer value, gpointer _ctx_p) { monobj_t *dupobj_p = _obj_p; monobj_t *obj_p = dupobj_p->origin; // ctx_t *ctx_p = _ctx_p; unmarkchild(obj_p); return FALSE; } int _kqueue_handle_oneevent_dircontent(ctx_t *ctx_p, indexes_t *indexes_p, monobj_t *obj_p) { DIR *dir; GTree *children_tree_dup; struct dirent *entry; struct kqueue_data *dat = ctx_p->fsmondata; debug(8, "obj_p == %p; obj_p->dir_fd == %i", obj_p, obj_p->dir_fd); debug(20, "open*()-ing the directory"); int fd; if (obj_p->dir_fd == -1) fd = open(ctx_p->watchdir, O_RDONLY|O_PATH); else fd = openat(obj_p->dir_fd, obj_p->name, O_RDONLY|O_PATH); debug(20, "fdopendir()-ing the directory"); dir = fdopendir(fd); if (dir == NULL) { debug(20, "dir == NULL. return 0"); return 0; } debug(20, "tdup()-ing the children_tree == %p", obj_p->children_tree); children_tree_dup = g_tree_dup(obj_p->children_tree, monobj_filecmp, ctx_p, monobj_freedup, NULL, monobj_dup, NULL); debug(8, "children_count == %i", g_tree_nnodes(children_tree_dup)); debug(20, "reading the directory"); while ((entry = readdir(dir))) { debug(10, "file/dir: \"%s\"", entry->d_name); if (!memcmp(entry->d_name, ".", 2)) continue; if (!memcmp(entry->d_name, "..", 3)) continue; if (_kqueue_handle_oneevent_dircontent_item(dat, ctx_p, indexes_p, obj_p, entry, children_tree_dup)) { error("Got error while _kqueue_handle_oneevent_dircontent_item(ctx_p, obj_p, entry {->d_name == \"%s\"})", entry->d_name); return -1; } } debug(20, "searching for deleted objects from the directory"); g_tree_foreach(children_tree_dup, unmarkdupchild, ctx_p); g_tree_destroy(children_tree_dup); debug(20, "end"); closedir(dir); return 0; } int kqueue_handle_oneevent(ctx_t *ctx_p, indexes_t *indexes_p, struct kevent *ev_p, monobj_t *obj_p) { #ifdef VERYPARANOID if (ev_p == NULL) { errno = EINVAL; return -1; } if (obj_p == NULL) { errno = EINVAL; return -1; } #endif debug(9, "obj_p->: name == \"%s\"; dir_fd == %i; type == 0x%x (isdir == %i); fd = %i. ev_p->fflags == 0x%x", obj_p->name, obj_p->dir_fd, obj_p->type, obj_p->type == DT_DIR, obj_p->fd, ev_p->fflags); int ret = 0; if (obj_p->type == DT_DIR && (ev_p->fflags & (NOTE_EXTEND|NOTE_WRITE))) ret |= _kqueue_handle_oneevent_dircontent(ctx_p, indexes_p, obj_p); if (ev_p->fflags & (NOTE_EXTEND|NOTE_WRITE|NOTE_ATTRIB|NOTE_DELETE|NOTE_RENAME)) ret |= kqueue_sync(ctx_p, indexes_p, ev_p, obj_p); if (ev_p->fflags & NOTE_DELETE) ret |= kqueue_unmark(ctx_p, obj_p); return ret; } int kqueue_handle(ctx_t *ctx_p, indexes_t *indexes_p) { static struct timeval tv={0}; struct kqueue_data *dat = ctx_p->fsmondata; debug(3, "dat->eventlist_count == %i", dat->eventlist_count); if (dat->eventlist_count == 0) return 0; int count = 0; do { int i = 0; #ifdef PARANOID g_hash_table_remove_all(indexes_p->fpath2ei_ht); #endif while (i < dat->eventlist_count) { struct kevent *ev_p = &dat->eventlist[i++]; monobj_t obj; obj.fd = ev_p->ident; monobj_t *obj_p = g_tree_lookup(dat->fd_btree, &obj); if (obj_p == NULL) { debug(3, "Cannot find internal structure for fd == %u. Skipping the event.", ev_p->ident); continue; } if (kqueue_handle_oneevent(ctx_p, indexes_p, ev_p, obj_p)) { error("Got error from kqueue_handle_oneevent()"); return -1; } count++; } // Globally queueing captured events: // Moving events from local queue to global ones sync_prequeue_unload(ctx_p, indexes_p); dat->eventlist_count = 0; } while (kqueue_wait(ctx_p, indexes_p, &tv)); return count; } int kqueue_deinit(ctx_t *ctx_p) { kqueue_status = KQUEUE_STATUS_DEINIT; struct kqueue_data *dat = ctx_p->fsmondata; debug(3, "dat->eventlist_count == %i", dat->eventlist_count); g_tree_destroy(dat->file_btree); g_tree_destroy(dat->fd_btree); kqueue_status = KQUEUE_STATUS_DEAD; return 0; } clsync-0.4.1/mon_kqueue.h000066400000000000000000000043111252417542300153340ustar00rootroot00000000000000/* clsync - file tree sync utility based on inotify/kqueue Copyright (C) 2014 Dmitry Yu Okunev 0x8E30679C This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ /* We will use inotify event masks while kqueue in clsync, so it's required to define it: */ #ifndef IN_ISDIR #define IN_ACCESS 0x00000001 /* File was accessed. */ #define IN_MODIFY 0x00000002 /* File was modified. */ #define IN_ATTRIB 0x00000004 /* Metadata changed. */ #define IN_CLOSE_WRITE 0x00000008 /* Writtable file was closed. */ #define IN_CLOSE_NOWRITE 0x00000010 /* Unwrittable file closed. */ #define IN_CLOSE (IN_CLOSE_WRITE | IN_CLOSE_NOWRITE) /* Close. */ #define IN_OPEN 0x00000020 /* File was opened. */ #define IN_MOVED_FROM 0x00000040 /* File was moved from X. */ #define IN_MOVED_TO 0x00000080 /* File was moved to Y. */ #define IN_MOVE (IN_MOVED_FROM | IN_MOVED_TO) /* Moves. */ #define IN_CREATE 0x00000100 /* Subfile was created. */ #define IN_DELETE 0x00000200 /* Subfile was deleted. */ #define IN_DELETE_SELF 0x00000400 /* Self was deleted. */ #define IN_MOVE_SELF 0x00000800 /* Self was moved. */ #define IN_IGNORED 0x00008000 #define IN_ISDIR 0x40000000 #endif extern int kqueue_init(); extern int kqueue_add_watch_dir(struct ctx *ctx_p, struct indexes *indexes_p, const char *const accpath); extern int kqueue_wait(struct ctx *ctx_p, struct indexes *indexes_p, struct timeval *tv_p); extern int kqueue_handle(struct ctx *ctx_p, struct indexes *indexes_p); extern int kqueue_deinit(ctx_t *ctx_p); clsync-0.4.1/pkgconfig/000077500000000000000000000000001252417542300147635ustar00rootroot00000000000000clsync-0.4.1/pkgconfig/libclsync.pc.in000066400000000000000000000003311252417542300176730ustar00rootroot00000000000000prefix=@prefix@ exec_prefix=@exec_prefix@ libdir=@libdir@ includedir=@includedir@ Name: clsync Description: Live sync utility Version: @VERSION@ Cflags: -pthread -I${includedir}/libclsync/ Libs: -L${libdir} -lclsync clsync-0.4.1/port-hacks.h000066400000000000000000000032561252417542300152460ustar00rootroot00000000000000/* clsync - file tree sync utility based on fanotify and inotify Copyright (C) 2014 Dmitry Yu Okunev 0x8E30679C This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef __PORT_HACKS_H #define __PORT_HACKS_H #ifndef ETIME #define ETIME ETIMEDOUT #endif #include #include #ifndef __FreeBSD__ typedef struct stat64 stat64_t; #endif #ifdef __FreeBSD__ # define O_PATH 0 typedef struct stat stat64_t; # include static inline int pthread_tryjoin_np(pthread_t thread, void **retval) { struct timespec abstime; int rc; abstime.tv_sec = 0; abstime.tv_nsec = 0; extern int pthread_timedjoin_np(pthread_t thread, void **value_ptr, const struct timespec *abstime); rc = pthread_timedjoin_np(thread, retval, &abstime); if (rc == ETIMEDOUT) rc = EBUSY; return rc; } static inline int lstat64(const char *pathname, struct stat *buf) { return lstat(pathname, buf); } #endif #ifdef CLSYNC_ITSELF # ifndef O_PATH # warning O_PATH is not set # define O_PATH 0 # endif #endif #endif // __PORT_HACKS_H clsync-0.4.1/posix-hacks.c000066400000000000000000000040551252417542300154150ustar00rootroot00000000000000/* clsync - file tree sync utility based on inotify/kqueue/bsm/gio Copyright (C) 2013-2014 Dmitry Yu Okunev 0x8E30679C This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifdef __FreeBSD__ #define _GNU_SOURCE #include #include #include #include #define __POSIX_HACKS_C int reserved_fd[FOPEN_MAX+1] = {-1}; int reserved_fd_used; static inline int reserve_fdpair(int idx) { int pipe_fds[2]; if (pipe2(pipe_fds, O_CLOEXEC|O_NONBLOCK)) return errno; reserved_fd[ idx ] = pipe_fds[0]; reserved_fd[ idx + 1 ] = pipe_fds[1]; return 0; } int posixhacks_init() { int i; // Reserving file descriptors from start to bypass FOPEN_MAX limit on fopen()/fdopen() i = 0; while (i < (FOPEN_MAX+1)/2) { if (reserve_fdpair (i<<1)) return errno; i++; } reserved_fd_used = 0; return 0; } FILE *posixhacks_fopen(const char *path, const char *mode) { close(reserved_fd[reserved_fd_used++]); return fopen(path, mode); } int posixhacks_fclose(FILE *fp) { int rc; int pipe_fds[2]; rc = fclose(fp); // reserving the file descriptor if (!(reserved_fd_used&1)) close(reserved_fd[reserved_fd_used++]); reserved_fd_used -= 2; if (reserve_fdpair (reserved_fd_used)) return errno; return rc; } int posixhacks_deinit() { int i; i = 0; while (i < (FOPEN_MAX+1)/2) { close(reserved_fd[ (i<<1) ]); close(reserved_fd[ (i<<1) + 1 ]); i++; } return 0; } #endif clsync-0.4.1/posix-hacks.h000066400000000000000000000023151252417542300154170ustar00rootroot00000000000000/* clsync - file tree sync utility based on inotify/kqueue/bsm/gio Copyright (C) 2013-2014 Dmitry Yu Okunev 0x8E30679C This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifdef __FreeBSD__ # ifndef __POSIX_HACKS_C # define fopen posixhacks_fopen # define fdopen posixhacks_fdopen # define fclose posixhacks_fclose # endif extern int posixhacks_init(); extern FILE *posixhacks_fopen(const char *path, const char *mode); extern int posixhacks_fclose(FILE *fp); extern int posixhacks_deinit(); #else # define posixhacks_init() (0) # define posixhacks_deinit() (0) #endif clsync-0.4.1/privileged.c000066400000000000000000001427211252417542300153210ustar00rootroot00000000000000/* clsync - file tree sync utility based on inotify/kqueue Copyright (C) 2013-2014 Dmitry Yu Okunev 0x8E30679C This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "common.h" // ctx.h #include "ctx.h" // ctx_t #include "error.h" // debug() #include "syscalls.h" // read_inf()/write_inf() #include "main.h" // ncpus #include "pthreadex.h" // pthread_*_shared() #include "malloc.h" // xmalloc() #ifdef CAPABILITIES_SUPPORT # include // pthread_create() # include // inotify_init() # include // fts_open() # include // fts_open() # include // fts_open() # include // errno # include // capset() # ifdef CGROUP_SUPPORT # include "cgroup.h" // clsync_cgroup_deinit() # endif #endif #include // execvp() #include // g_atomic_int_set() #ifdef UNSHARE_SUPPORT # include // unshare() #endif #ifndef HL_LOCKS # ifdef HL_LOCK_TRIES_AUTO # undef HL_LOCK_TRIES_AUTO # endif #endif #ifdef HL_LOCK_TRIES_AUTO # include // time() # include // fabs() #endif #include "privileged.h" #ifdef SECCOMP_SUPPORT # include // __NR_* # include // prctl() # include // struct sock_filter # include // SECCOMP_RET_* #define syscall_nr (offsetof(struct seccomp_data, nr)) /* Read: http://www.rawether.net/support/bpfhelp.htm */ # define SECCOMP_COPY_SYSCALL_TO_ACCUM \ BPF_STMT(BPF_LD+BPF_W+BPF_ABS, syscall_nr) # define SECCOMP_ALLOW \ BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW) # define SECCOMP_DENY \ BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_TRAP) # define SECCOMP_ALLOW_ACCUM_SYSCALL(syscall) \ BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, __NR_##syscall, 0, 1), \ SECCOMP_ALLOW # define FILTER_TABLE_NONPRIV \ SECCOMP_ALLOW_ACCUM_SYSCALL(futex), \ SECCOMP_ALLOW_ACCUM_SYSCALL(inotify_init1), \ SECCOMP_ALLOW_ACCUM_SYSCALL(alarm), \ SECCOMP_ALLOW_ACCUM_SYSCALL(stat), /* unused */ \ SECCOMP_ALLOW_ACCUM_SYSCALL(fstat), /* unused */ \ SECCOMP_ALLOW_ACCUM_SYSCALL(lstat), \ SECCOMP_ALLOW_ACCUM_SYSCALL(open), \ SECCOMP_ALLOW_ACCUM_SYSCALL(write), \ SECCOMP_ALLOW_ACCUM_SYSCALL(close), \ SECCOMP_ALLOW_ACCUM_SYSCALL(wait4), \ SECCOMP_ALLOW_ACCUM_SYSCALL(unlink), \ SECCOMP_ALLOW_ACCUM_SYSCALL(tgkill), \ SECCOMP_ALLOW_ACCUM_SYSCALL(clock_gettime), \ SECCOMP_ALLOW_ACCUM_SYSCALL(rt_sigreturn), \ SECCOMP_ALLOW_ACCUM_SYSCALL(brk), \ SECCOMP_ALLOW_ACCUM_SYSCALL(mmap), \ SECCOMP_ALLOW_ACCUM_SYSCALL(munmap), \ SECCOMP_ALLOW_ACCUM_SYSCALL(wait4), \ SECCOMP_ALLOW_ACCUM_SYSCALL(rmdir), \ SECCOMP_ALLOW_ACCUM_SYSCALL(exit_group), \ SECCOMP_ALLOW_ACCUM_SYSCALL(select), \ SECCOMP_ALLOW_ACCUM_SYSCALL(read), \ SECCOMP_ALLOW_ACCUM_SYSCALL(rt_sigprocmask), \ SECCOMP_ALLOW_ACCUM_SYSCALL(rt_sigaction), \ SECCOMP_ALLOW_ACCUM_SYSCALL(nanosleep), \ SECCOMP_ALLOW_ACCUM_SYSCALL(shmdt), \ SECCOMP_ALLOW_ACCUM_SYSCALL(clone), /* for --threading */ \ SECCOMP_ALLOW_ACCUM_SYSCALL(set_robust_list), /* for --threading? */ \ SECCOMP_ALLOW_ACCUM_SYSCALL(madvise), \ SECCOMP_ALLOW_ACCUM_SYSCALL(exit), \ /* Syscalls allowed to non-privileged thread */ static struct sock_filter filter_table[] = { SECCOMP_COPY_SYSCALL_TO_ACCUM, FILTER_TABLE_NONPRIV SECCOMP_DENY, }; static struct sock_filter filter_w_mprotect_table[] = { SECCOMP_COPY_SYSCALL_TO_ACCUM, FILTER_TABLE_NONPRIV SECCOMP_ALLOW_ACCUM_SYSCALL(mprotect), SECCOMP_DENY, }; int nonprivileged_seccomp_init(ctx_t *ctx_p) { struct sock_fprog *filter_p; struct sock_fprog filter = { .len = (unsigned short)(sizeof(filter_table)/sizeof(filter_table[0])), .filter = filter_table, }; struct sock_fprog filter_w_mprotect = { .len = (unsigned short)(sizeof(filter_w_mprotect_table)/sizeof(filter_w_mprotect_table[0])), .filter = filter_w_mprotect_table, }; debug(5, "enabling the seccomp"); filter_p = (ctx_p->flags[PERMIT_MPROTECT] ? &filter_w_mprotect : &filter); SAFE (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0), return -1); SAFE (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, filter_p), return -1); return 0; } #endif int (*privileged_fork_execvp)(const char *file, char *const argv[]); int (*privileged_kill_child)(pid_t pid, int sig, char ignoreerrors); #ifdef CAPABILITIES_SUPPORT pid_t helper_pid = 0; pthread_t privileged_thread; pthread_mutex_t *pthread_mutex_privileged_p; pthread_mutex_t *pthread_mutex_action_signal_p; pthread_mutex_t *pthread_mutex_action_entrance_p; pthread_mutex_t *pthread_mutex_runner_p; pthread_cond_t *pthread_cond_privileged_p; pthread_cond_t *pthread_cond_action_p; pthread_cond_t *pthread_cond_runner_p; # ifdef READWRITE_SIGNALLING int priv_read_fd; int priv_write_fd; int nonp_read_fd; int nonp_write_fd; # endif enum privileged_action { PA_UNKNOWN = 0, PA_SETUP, PA_DIE, PA_FTS_OPEN, PA_FTS_READ, PA_FTS_CLOSE, PA_INOTIFY_INIT, PA_INOTIFY_INIT1, PA_INOTIFY_ADD_WATCH, PA_INOTIFY_RM_WATCH, PA_FORK_EXECVP, PA_KILL_CHILD, PA_CLSYNC_CGROUP_DEINIT, PA_WAITPID, }; struct pa_fts_open_arg { char _path_argv[MAXARGUMENTS+1][PATH_MAX]; char *path_argv[MAXARGUMENTS+1]; char *const *path_argv_p; int options; int (*compar)(const FTSENT **, const FTSENT **); }; struct pa_inotify_add_watch_arg { int fd; char pathname[PATH_MAX]; const char *pathname_p; uint32_t mask; }; struct pa_inotify_rm_watch_arg { int fd; int wd; }; struct pa_fork_execvp_arg { char file[PATH_MAX]; const char *file_p; char _argv[MAXARGUMENTS+1][BUFSIZ]; char *argv[MAXARGUMENTS+1]; char *const *argv_p; }; struct pa_kill_child_arg { pid_t pid; int signal; char ignoreerrors; }; struct pa_waitpid_arg { pid_t pid; int status; int options; }; struct pa_arg { struct pa_fts_open_arg fts_open; struct pa_inotify_add_watch_arg inotify_add_watch; struct pa_inotify_rm_watch_arg inotify_rm_watch; struct pa_fork_execvp_arg fork_execvp; struct pa_kill_child_arg kill_child; struct pa_waitpid_arg waitpid; void *void_v; ctx_t *ctx_p; uint32_t uint32_v; }; # ifdef HL_LOCKS enum highload_lock_id { HLLOCK_HANDLER = 0, HLLOCK_MAX }; typedef enum highlock_lock_id hllockid_t; enum highlock_lock_state { HLLS_UNREADY = 0x00, HLLS_READY = 0x01, HLLS_FALLBACK = 0x02, HLLS_SIGNAL = 0x04, HLLS_GOTSIGNAL = 0x08, HLLS_WORKING = 0x10, }; typedef enum highlock_lock_state hllock_state_t; struct hl_lock { volatile int locallock_hl_setstate_ifstate; volatile int enabled; volatile int count_wait[HLLOCK_MAX]; volatile int count_signal[HLLOCK_MAX]; volatile hllock_state_t state[HLLOCK_MAX]; # ifdef HL_LOCK_TRIES_AUTO volatile unsigned long tries[PC_MAX]; volatile unsigned long count[PC_MAX]; volatile unsigned long delay[PC_MAX]; volatile double tries_step[PC_MAX]; # define tries_cur tries[callid] # else volatile unsigned long tries; # define tries_cur tries # endif }; # endif struct pa_fts_read_ret { FTSENT ftsent; char fts_accpath[PATH_MAX]; char fts_path[PATH_MAX]; char fts_name[PATH_MAX]; }; struct pa_ret { struct stat stat; struct pa_fts_read_ret fts_read; }; struct cmd { volatile struct pa_arg arg; volatile enum privileged_action action; # ifdef HL_LOCKS volatile unsigned long hl_lock_tries; # endif }; struct cmd_ret { volatile struct pa_ret ret_buf; volatile void *ret; volatile int _errno; }; volatile struct cmd *cmd_p; volatile struct cmd_ret *cmd_ret_p; # ifdef HL_LOCKS volatile struct hl_lock *hl_lock_p; # endif # ifdef HL_LOCKS static inline void hl_lock_init(volatile struct hl_lock *hl_lock_p) { debug(10, ""); hl_lock_p->enabled = 1; # ifdef HL_LOCK_TRIES_AUTO int i; i = 0; while (i < PC_MAX) { hl_lock_p->tries[i] = HL_LOCK_TRIES_INITIAL; hl_lock_p->delay[i] = ((unsigned long)~0)>>2; hl_lock_p->tries_step[i] = HL_LOCK_AUTO_K; i++; } # else hl_lock_p->tries = HL_LOCK_TRIES_INITIAL; # endif return; } # endif struct pa_options { synchandler_args_t args[SHARGS_MAX]; char *label; char *exithookfile; char *preexithookfile; char *permitted_hookfile[MAXPERMITTEDHOOKFILES+1]; int permitted_hookfiles; int isprocsplitting; int shm_mprotect; }; FTS *(*_privileged_fts_open) ( char * const *path_argv, int options, int (*compar)(const FTSENT **, const FTSENT **) # ifdef HL_LOCK_TRIES_AUTO , int callid # endif ); FTSENT *(*_privileged_fts_read) ( FTS *ftsp # ifdef HL_LOCK_TRIES_AUTO , int callid # endif ); int (*_privileged_fts_close) ( FTS *ftsp # ifdef HL_LOCK_TRIES_AUTO , int callid # endif ); int (*_privileged_inotify_init) (); int (*_privileged_inotify_init1) (int flags); int (*_privileged_inotify_add_watch) ( int fd, const char *pathname, uint32_t mask # ifdef HL_LOCK_TRIES_AUTO , int callid # endif ); int (*_privileged_inotify_rm_watch) ( int fd, int wd ); int (*_privileged_clsync_cgroup_deinit) (ctx_t *ctx_p); pid_t (*_privileged_waitpid) (pid_t pid, int *status, int options); int cap_enable(__u32 caps) { debug(1, "Enabling Linux capabilities 0x%x", caps); struct __user_cap_header_struct cap_hdr = {0}; struct __user_cap_data_struct cap_dat = {0}; cap_hdr.version = _LINUX_CAPABILITY_VERSION; if (capget(&cap_hdr, &cap_dat) < 0) { error("Cannot get capabilities with capget()"); return errno; } debug(3, "old: cap.eff == 0x%04x; new: cap.eff == 0x%04x", cap_dat.effective, cap_dat.effective|caps); cap_dat.effective |= caps; if (capset(&cap_hdr, &cap_dat) < 0) { error("Cannot set capabilities with capset()."); return errno; } return 0; } int cap_drop(ctx_t *ctx_p, __u32 caps) { debug(1, "Dropping all Linux capabilities but 0x%x", caps); struct __user_cap_header_struct cap_hdr = {0}; struct __user_cap_data_struct cap_dat = {0}; cap_hdr.version = _LINUX_CAPABILITY_VERSION; if (capget(&cap_hdr, &cap_dat) < 0) { error_or_debug((ctx_p->flags[CAP_PRESERVE] != CAP_PRESERVE_TRY) ? -1 : 3, "Cannot get capabilities with capget()"); return errno; } debug(3, "old: cap.eff == 0x%04x; cap.prm == 0x%04x; cap.inh == 0x%04x.", cap_dat.effective, cap_dat.permitted, cap_dat.inheritable); switch (ctx_p->flags[CAPS_INHERIT]) { case CI_PERMITTED: cap_dat.inheritable = cap_dat.permitted; break; case CI_DONTTOUCH: break; case CI_CLSYNC: cap_dat.inheritable = caps; break; case CI_EMPTY: cap_dat.inheritable = 0; break; } cap_dat.effective = caps; cap_dat.permitted = caps; debug(3, "new: cap.eff == 0x%04x; cap.prm == 0x%04x; cap.inh == 0x%04x.", cap_dat.effective, cap_dat.permitted, cap_dat.inheritable); if (capset(&cap_hdr, &cap_dat) < 0) { error_or_debug((ctx_p->flags[CAP_PRESERVE] != CAP_PRESERVE_TRY) ? -1 : 3, "Cannot set capabilities with capset()."); return errno; } return 0; } #endif int __privileged_kill_child_itself(pid_t child_pid, int signal, char ignoreerrors) { // Checking if it's a child if (waitpid(child_pid, NULL, WNOHANG)>=0) { debug(3, "Sending signal %u to child process with pid %u.", signal, child_pid); if (kill(child_pid, signal)) { if (!ignoreerrors) error("Got error while kill(%u, %u)", child_pid, signal); return errno; } waitpid_timed(child_pid, NULL, SLEEP_SECONDS, 0); } else return ENOENT; return 0; } #ifdef CAPABILITIES_SUPPORT int pa_strcmp(const char *s1, const char *s2, int isexpanded) { if (isexpanded) return strcmp(s1, s2); { const char *s1_start = NULL; const char *s2_start = NULL; while (1) { while (1) { if (!*s1 || !*s2) { if (!*s1 && s1_start != NULL) return 0; return *s1 != *s2; } if (*s1 == '%') { s1++; while (*s1 && *s1 != '%') s1++; s1++; s1_start = s1; s2_start = s2; continue; } if (*s1 != *s2) break; s1++; s2++; } if (s2_start == NULL) break; s2_start++; s1 = s1_start; s2 = s2_start; } return *s1 != *s2; } } int privileged_execvp_check_arguments(struct pa_options *opts, const char *u_file, char *const *u_argv) { int a_i; size_t u_argc; synchandler_args_t *args = opts->args; debug(9, ""); // Counting the number of arguments u_argc = 0; while (u_argv[u_argc] != NULL) u_argc++; a_i = 0; do { int i; int argc; char **argv; char *isexpanded; argc = args[a_i].c; argv = args[a_i].v; isexpanded = args[a_i].isexpanded; debug(8, "Checking the number of arguments: %i <> %i", argc, u_argc); if (argc != u_argc) continue; critical_on (!argc); debug(8, "Checking the execution file: \"%s\" <> \"%s\"; isexpanded == %i", argv[0], u_file, isexpanded[0]); if (pa_strcmp(argv[0], u_file, isexpanded[0])) { debug(1, "The file to be executed didn't match (argv[0] != u_file): \"%s\" != \"%s\"", argv[0], u_file); break; } debug(8, "Checking arguments"); i = 1; while (i < argc) { if (pa_strcmp(argv[i], u_argv[i], isexpanded[i])) { debug(1, "An argument #%i didn't match (argv[%i] != u_argv[%i]): \"%s\" != \"%s\"", argv[i], argv[i]); break; } i++; } // All arguments right? if (i == argc) break; // No? Ok the next "shargs". } while (++a_i < SHARGS_MAX); if (a_i < SHARGS_MAX) return 0; if (u_argc == 2) { int i; if ((opts->exithookfile != NULL) || (opts->preexithookfile != NULL)) { if (!strcmp(opts->label, u_argv[1])) { if (opts->exithookfile != NULL) if (!strcmp(opts->exithookfile, u_file)) return 0; if (opts->preexithookfile != NULL) if (!strcmp(opts->preexithookfile, u_file)) return 0; } } i = 0; while (i < opts->permitted_hookfiles) { if (!strcmp(opts->permitted_hookfile[i], u_file)) return 0; i++; } } debug(1, "a_i == %i; SHARGS_MAX == %i; u_argc == %i", SHARGS_MAX, a_i, u_argc); critical("Arguments are wrong. This should happend only on hacking attack."); return EPERM; } int pa_setup(struct pa_options *opts, ctx_t *ctx_p, uid_t *exec_uid_p, gid_t *exec_gid_p) { synchandler_args_t *args = opts->args; int a_i; a_i = 0; do { int i, argc_s; char **argv_s, **argv_d, *isex_s, *isex_d; argc_s = ctx_p->synchandler_args[a_i].c; argv_s = ctx_p->synchandler_args[a_i].v; isex_s = ctx_p->synchandler_args[a_i].isexpanded; argv_d = args[a_i].v; isex_d = args[a_i].isexpanded; if (argc_s >= MAXARGUMENTS) critical("Too many arguments"); if (argc_s < 1) continue; argv_d[0] = strdup_protect(ctx_p->handlerfpath, PROT_READ); i = 0; while (i < argc_s) { argv_d[i+1] = strdup_protect(argv_s[i], PROT_READ); isex_d[i+1] = isex_s[i]; i++; } i++; argv_d[i] = NULL; args[a_i].c = i; a_i++; } while (++a_i < SHARGS_MAX); *exec_uid_p = ctx_p->synchandler_uid; *exec_gid_p = ctx_p->synchandler_gid; opts->label = strdup_protect(ctx_p->label, PROT_READ); if (ctx_p->exithookfile != NULL) opts->exithookfile = strdup_protect(ctx_p->exithookfile, PROT_READ); if (ctx_p->preexithookfile != NULL) opts->preexithookfile = strdup_protect(ctx_p->preexithookfile, PROT_READ); { int i = 0; while (i < ctx_p->permitted_hookfiles) { opts->permitted_hookfile[i] = strdup_protect(ctx_p->permitted_hookfile[i], PROT_READ); i++; } opts->permitted_hookfile[i] = NULL; opts->permitted_hookfiles = i; } return 0; } int pa_unsetup(struct pa_options *opts) { free(opts->exithookfile); free(opts->preexithookfile); free(opts->label); { int a_i = 0; do { int i; i = 0; while (i < opts->args[a_i].c) { free(opts->args[a_i].v[i]); i++; } } while(++a_i < SHARGS_MAX); } { int i = 0; while (i < opts->permitted_hookfiles) { free(opts->permitted_hookfile[i]); i++; } } return 0; } # ifdef HL_LOCKS static inline int hl_isanswered(int lockid) { return hl_lock_p->count_wait[lockid] == hl_lock_p->count_signal[lockid]+1; } static inline int hl_isready(int lockid) { return hl_lock_p->count_wait[lockid] == hl_lock_p->count_signal[lockid]; } static inline void hl_setstate(int lockid, hllock_state_t stateid) { g_atomic_int_set(&hl_lock_p->state[lockid], stateid); } int hl_setstate_ifstate(int lockid, hllock_state_t stateid_new, hllock_state_t stateid_old_mask) { volatile int *local_lock_p = &hl_lock_p->locallock_hl_setstate_ifstate; debug(90, "%i, 0x%o, 0x%o", lockid, stateid_new, stateid_old_mask); if (*local_lock_p) return 0; debug(92, "%i", *local_lock_p); g_atomic_int_inc(local_lock_p); debug(92, "%i", *local_lock_p); if (*local_lock_p != 1) { g_atomic_int_dec_and_test(local_lock_p); return 0; } if (!(hl_lock_p->state[lockid]&stateid_old_mask)) { g_atomic_int_dec_and_test(local_lock_p); return 0; } debug(50, "success"); g_atomic_int_set(&hl_lock_p->state[lockid], stateid_new); g_atomic_int_dec_and_test(local_lock_p); #undef local_lock return 1; } static inline int hl_wait( int lockid # ifdef HL_LOCK_TRIES_AUTO , unsigned long hl_lock_tries # endif ) { volatile long try = 0; debug(15, ""); while (hl_lock_p->state[lockid] == HLLS_GOTSIGNAL); while (!hl_isready(lockid)); hl_setstate(lockid, HLLS_READY); hl_lock_p->count_wait[lockid]++; while (try++ < hl_lock_tries) if (hl_lock_p->state[lockid] == HLLS_SIGNAL) { hl_setstate(lockid, HLLS_GOTSIGNAL); debug(15, "got signal"); return 1; } while (!hl_setstate_ifstate(lockid, HLLS_FALLBACK, HLLS_READY|HLLS_SIGNAL)); debug(14, "fallback: hl_lock_p->count_wait[%u] == %u; hl_lock_p->count_signal[%u] = %u", lockid, hl_lock_p->count_wait[lockid], lockid, hl_lock_p->count_signal[lockid]); return 0; } static inline int hl_signal(int lockid) { debug(15, "%u", lockid); hl_lock_p->count_signal[lockid]++; if (hl_setstate_ifstate(lockid, HLLS_SIGNAL, HLLS_READY)) { while (hl_lock_p->state[lockid] != HLLS_GOTSIGNAL) { if (hl_lock_p->state[lockid] == HLLS_FALLBACK) { debug(15, "fallback"); return 0; } debug(95, "state == %i != %i, %i", hl_lock_p->state[lockid], HLLS_GOTSIGNAL, HLLS_FALLBACK); } debug(15, "the signal is sent"); hl_setstate(lockid, HLLS_WORKING); return 1; } debug(15, "not ready"); return 0; } void hl_shutdown(int lockid) { debug(1, ""); # ifdef PARANOID critical_on (HLLOCK_MAX != 1); // TODO: do this on compile time (not on running time) # ifdef HL_LOCK_TRIES_AUTO memset((void *)hl_lock_p->tries, 0, sizeof(hl_lock_p->tries)); # else hl_lock_p->tries = 0; # endif # endif hl_lock_p->state[lockid] = HLLS_FALLBACK; hl_lock_p->enabled = 0; return; } # endif static int helper_isalive_cache; static inline int helper_isalive_proc() { int rc; debug(12, "helper_pid == %u", helper_pid); if ((rc=waitpid(helper_pid, NULL, WNOHANG))>=0) return helper_isalive_cache=1; debug(1, "waitpid(%u, NULL, WNOHANG) => %i", helper_pid, rc); return helper_isalive_cache=0; } static inline int helper_isalive_thread() { int rc; debug(12, ""); if ((rc=pthread_kill(privileged_thread, 0))) return helper_isalive_cache=0; debug(1, "pthread_kill(privileged_thread, 0) => %i", helper_pid, rc); return helper_isalive_cache=1; } static inline int helper_isalive() { return helper_pid ? helper_isalive_proc() : helper_isalive_thread(); } int privileged_check() { if (helper_pid) critical_on(!helper_isalive_proc()); return 0; } int privileged_handler(ctx_t *ctx_p) { # ifdef READWRITE_SIGNALLING char buf[1] = {0}; # else struct timespec wait_timeout = {0}; # endif int setup = 0; uid_t exec_uid = 65535; gid_t exec_gid = 65535; struct pa_options *opts; int use_args_check = 0; int helper_isrunning = 1; opts = calloc_align(1, sizeof(*opts)); opts->isprocsplitting = (ctx_p->flags[SPLITTING] == SM_PROCESS); opts->shm_mprotect = ctx_p->flags[SHM_MPROTECT]; if (opts->isprocsplitting) { sigset_t sigset; sigemptyset(&sigset); /* Do not uncomment this. This causes handler closing on any terminal signal to parent process. In turn it causes: https://github.com/xaionaro/clsync/issues/104 sigaddset(&sigset, SIGALRM); sigaddset(&sigset, SIGHUP); sigaddset(&sigset, SIGQUIT); sigaddset(&sigset, SIGTERM); sigaddset(&sigset, SIGINT); */ # ifndef __linux__ # error There's no automatical mechanism that guarantees handler closing on non-linux. Don't use process splitting! # endif # ifdef __linux__ sigaddset(&sigset, SIGCHLD); # endif critical_on(pthread_sigmask(SIG_UNBLOCK, &sigset, NULL)); # ifndef __linux__ critical_on(!parent_isalive()); # endif } else { register_blockthread(); pthread_setname_np(pthread_self(), "clsync-helper"); } cap_drop(ctx_p, ctx_p->caps); debug(2, "Syncing with the runner"); pthread_mutex_lock(pthread_mutex_privileged_p); // Waiting for runner to get ready for signal pthread_mutex_lock(pthread_mutex_runner_p); pthread_mutex_unlock(pthread_mutex_runner_p); // Sending the signal that we're ready pthread_cond_signal(pthread_cond_runner_p); // The loop debug(2, "Running the loop"); while (helper_isrunning) { errno = 0; // Waiting for command debug(10, "Waiting for command"); if (opts->shm_mprotect) { mprotect((void *)cmd_p, sizeof(*cmd_p), PROT_WRITE); mprotect((void *)cmd_ret_p, sizeof(*cmd_ret_p), PROT_READ); } # ifdef HL_LOCKS debug(25, "hl_lock_p->enabled == %i", hl_lock_p->enabled); if (!hl_lock_p->enabled || !hl_wait( HLLOCK_HANDLER # ifdef HL_LOCK_TRIES_AUTO , hl_lock_p->tries[HLLOCK_HANDLER] # endif )) { if (opts->isprocsplitting) critical_on(!parent_isalive()); # endif # ifdef READWRITE_SIGNALLING # warning READWRITE_SIGNALLING can cause process hanging on clsync shutdown read_inf(priv_read_fd, buf, 1); # else int rc; while (1) { rc = pthread_cond_timedwait(pthread_cond_privileged_p, pthread_mutex_privileged_p, &wait_timeout); if (!rc) break; if (rc != ETIMEDOUT) critical("Got error while pthread_cond_timedwait()"); debug(10, "pthread_cond_timedwait() timed out"); if (opts->isprocsplitting) exit_on(!parent_isalive()); { debug(20, "Resetting wait_timeout"); struct timeval now; gettimeofday(&now, NULL); wait_timeout.tv_sec = now.tv_sec + SLEEP_SECONDS; wait_timeout.tv_nsec = now.tv_usec * 1000; } } # endif # ifdef HL_LOCKS if (hl_lock_p->enabled) hl_setstate(HLLOCK_HANDLER, HLLS_WORKING); } # endif if (opts->shm_mprotect) { mprotect((void *)cmd_p, sizeof(*cmd_p), PROT_READ); mprotect((void *)cmd_ret_p, sizeof(*cmd_ret_p), PROT_WRITE); } debug(10, "Got command %u (euid:egid => %i:%i)", cmd_p->action, geteuid(), getegid()); if (!setup && cmd_p->action != PA_SETUP) critical("A try to use commands before PA_SETUP"); switch (cmd_p->action) { case PA_SETUP: { debug(20, "PA_SETUP"); if (setup) critical("Double privileged_handler setuping. It can be if somebody is trying to hack the clsync."); critical_on(pa_setup(opts, cmd_p->arg.ctx_p, &exec_uid, &exec_gid)); mprotect(opts, sizeof(*opts), PROT_READ); use_args_check = cmd_p->arg.ctx_p->flags[CHECK_EXECVP_ARGS]; cap_drop(ctx_p, ctx_p->caps); // TODO: Find out why "permission denined" without this line setup++; critical_on(errno); break; } case PA_DIE: debug(20, "PA_DIE"); helper_isrunning = 0; break; case PA_FTS_OPEN: { volatile struct pa_fts_open_arg *arg_p = &cmd_p->arg.fts_open; char *const *path_argv_p =(void *)( opts->isprocsplitting ? arg_p->path_argv : arg_p->path_argv_p ); debug(20, "PA_FTS_OPEN (%s)", *path_argv_p); if (arg_p->compar != NULL) critical("\"arg_p->compar != NULL\" (arg_p->compar == %p) is forbidden because may be used to run an arbitrary code in the privileged thread.", arg_p->compar); cmd_ret_p->ret = fts_open(path_argv_p, arg_p->options, NULL); debug(21, "/PA_FTS_OPEN => %p", cmd_ret_p->ret); break; } case PA_FTS_READ: { debug(20, "PA_FTS_READ(%p)", cmd_p->arg.void_v); FTSENT *ret = fts_read(cmd_p->arg.void_v); if (ret == NULL) { cmd_ret_p->ret = NULL; debug(10, "cmd_ret_p->ret == NULL"); break; } if (!opts->isprocsplitting) { // Is the thread-splitting? cmd_ret_p->ret = ret; break; } { // Is the process splitting? struct pa_fts_read_ret *ret_buf = (void *)&cmd_ret_p->ret_buf.fts_read; memcpy(&ret_buf->ftsent, ret, sizeof(ret_buf->ftsent)); cmd_ret_p->ret = &ret_buf->ftsent; debug(25, "fts_path == <%s>", ret_buf->fts_path); strncpy(ret_buf->fts_accpath, ret->fts_accpath, sizeof(ret_buf->fts_accpath)); strncpy(ret_buf->fts_path, ret->fts_path, sizeof(ret_buf->fts_path)); ret_buf->ftsent.fts_accpath = ret_buf->fts_accpath; ret_buf->ftsent.fts_path = ret_buf->fts_path; } break; } case PA_FTS_CLOSE: debug(20, "PA_FTS_CLOSE"); cmd_ret_p->ret = (void *)(long)fts_close(cmd_p->arg.void_v); break; case PA_INOTIFY_INIT: debug(20, "PA_INOTIFY_INIT"); cmd_ret_p->ret = (void *)(long)inotify_init(); break; case PA_INOTIFY_INIT1: debug(20, "PA_INOTIFY_INIT1"); cmd_ret_p->ret = (void *)(long)inotify_init1(cmd_p->arg.uint32_v); break; case PA_INOTIFY_ADD_WATCH: { struct pa_inotify_add_watch_arg *arg_p = (void *)&cmd_p->arg.inotify_add_watch; const char *pathname = (opts->isprocsplitting ? arg_p->pathname : arg_p->pathname_p); debug(20, "PA_INOTIFY_ADD_WATCH(%u, <%s>, 0x%o)", arg_p->fd, pathname, arg_p->mask); cmd_ret_p->ret = (void *)(long)inotify_add_watch( arg_p->fd, pathname, arg_p->mask); break; } case PA_INOTIFY_RM_WATCH: { debug(20, "PA_INOTIFY_RM_WATCH"); struct pa_inotify_rm_watch_arg *arg_p = (void *)&cmd_p->arg.inotify_rm_watch; cmd_ret_p->ret = (void *)(long)inotify_rm_watch(arg_p->fd, arg_p->wd); break; } case PA_FORK_EXECVP: { struct pa_fork_execvp_arg *arg_p = (void *)&cmd_p->arg.fork_execvp; const char *file; char *const *argv; if (opts->isprocsplitting) { file = arg_p->file; argv = arg_p->argv; } else { file = arg_p->file_p; argv = arg_p->argv_p; } debug(20, "PA_FORK_EXECVP (\"%s\", argv)", file); if (use_args_check) privileged_execvp_check_arguments(opts, file, argv); pid_t pid = fork(); switch (pid) { case -1: error("Cannot fork()."); break; case 0: #ifdef ANTIPARANOID if (ctx_p->privileged_gid != exec_gid) #endif debug(4, "setgid(%u) == %i", exec_gid, setgid(exec_gid)); #ifdef ANTIPARANOID if (ctx_p->privileged_uid != exec_uid) #endif debug(4, "setuid(%u) == %i", exec_uid, setuid(exec_uid)); debug(3, "execvp(\"%s\", argv)", file); exit(execvp(file, argv)); } cmd_ret_p->ret = (void *)(long)pid; debug(21, "/PA_FORK_EXECVP"); break; } case PA_KILL_CHILD: { debug(20, "PA_KILL_CHILD"); struct pa_kill_child_arg *arg_p = (void *)&cmd_p->arg.kill_child; cmd_ret_p->ret = (void *)(long)__privileged_kill_child_itself(arg_p->pid, arg_p->signal, arg_p->ignoreerrors); break; } # ifdef CGROUP_SUPPORT case PA_CLSYNC_CGROUP_DEINIT: { debug(20, "PA_CLSYNC_CGROUP_DEINIT"); /* * That is strange, but setuid() doesn't work * without fork() in case of enabled seccomp * filter. So sorry for this hacky thing. * * TODO: fix that. */ int status; pid_t pid = fork(); switch (pid) { case -1: error("Cannot fork()."); break; case 0: debug(4, "setgid(0) == %i", setgid(0)); debug(4, "setuid(0) == %i", setuid(0)); exit(clsync_cgroup_deinit(cmd_p->arg.void_v)); } if (waitpid(pid, &status, 0) != pid) { switch (errno) { case ECHILD: debug(2, "Child %u has already died.", pid); break; default: error("Cannot waitid()."); cmd_ret_p->_errno = errno; cmd_ret_p->ret = (void *)(long)errno; } } // Return int exitcode = WEXITSTATUS(status); debug(3, "execution completed with exitcode %i", exitcode); cmd_ret_p->_errno = exitcode; cmd_ret_p->ret = (void *)(long)exitcode; break; } # endif case PA_WAITPID: { struct pa_waitpid_arg *arg_p = (void *)&cmd_p->arg.waitpid; debug(20, "PA_WAITPID(%u, 0x%o)", arg_p->pid, arg_p->options); cmd_ret_p->ret = (void *)(long)waitpid(arg_p->pid, &arg_p->status, arg_p->options); break; } default: critical("Unknown command type \"%u\". It's a buffer overflow (which means a security problem) or just an internal error."); } cmd_ret_p->_errno = errno; debug(10, "Result: %p, errno: %u. Sending the signal to non-privileged thread/process.", cmd_ret_p->ret, cmd_ret_p->_errno); # ifdef HL_LOCKS if (!hl_lock_p->enabled) { # endif # ifndef __linux__ critical_on(!parent_isalive()); # endif # ifdef READWRITE_SIGNALLING write_inf(nonp_write_fd, buf, 1); # else critical_on (pthread_mutex_lock(pthread_mutex_action_signal_p)); critical_on (pthread_mutex_unlock(pthread_mutex_action_signal_p)); critical_on (pthread_cond_signal(pthread_cond_action_p)); # endif # ifdef HL_LOCKS } # endif } pa_unsetup(opts); # ifdef HL_LOCKS hl_shutdown(HLLOCK_HANDLER); # endif pthread_mutex_unlock(pthread_mutex_privileged_p); debug(2, "Finished"); return 0; } static inline int privileged_action( # ifdef HL_LOCK_TRIES_AUTO int callid, # endif enum privileged_action action, void **ret_p ) { int rc = 0; # ifdef READWRITE_SIGNALLING char buf[1] = {0}; # endif # ifdef HL_LOCK_TRIES_AUTO clock_t start_ticks; int isadjusting; # endif # ifdef HL_LOCKS debug(10, "(%u, %p): %i", action, ret_p, hl_lock_p->enabled); # else debug(10, "(%u, %p)", action, ret_p); # endif pthread_mutex_lock(pthread_mutex_action_entrance_p); # ifndef READWRITE_SIGNALLING debug(10, "Waiting the privileged thread/process to get prepared for signal"); # ifdef HL_LOCKS if (hl_lock_p->enabled) { while (!hl_isanswered(HLLOCK_HANDLER)) if (!helper_isalive_cache) { debug(1, "The privileged thread/process is dead (#0). Ignoring the command."); rc = ENOENT; goto privileged_action_end; } } else { # endif critical_on(!helper_isalive_cache); pthread_mutex_lock(pthread_mutex_privileged_p); pthread_mutex_unlock(pthread_mutex_privileged_p); # ifdef HL_LOCKS } # endif # endif if (!helper_isalive_cache) { debug(1, "The privileged thread/process is dead (#1). Ignoring the command."); rc = ENOENT; goto privileged_action_end; } cmd_p->action = action; debug(10, "Sending information (action == %i) to the privileged thread/process", action); # ifdef HL_LOCK_TRIES_AUTO cmd_p->hl_lock_tries = hl_lock_p->tries[callid]; if ((isadjusting = hl_lock_p->enabled)) { isadjusting = hl_lock_p->tries[callid]; if (isadjusting) { isadjusting = ((double)fabs(hl_lock_p->tries_step[callid]-1) > (double)HL_LOCK_AUTO_K_FINISH); if (isadjusting) { isadjusting = !((++hl_lock_p->count[callid]) << (sizeof(hl_lock_p->count[callid])*CHAR_BIT - HL_LOCK_AUTO_INTERVAL)); debug(11, "isadjusting == %u; hl_lock_p->tries_step[%i] == %lf; hl_lock_p->count[%i] == %lu", isadjusting, callid, hl_lock_p->tries_step[callid], callid, hl_lock_p->count[callid]); if (isadjusting) start_ticks = clock(); } } } # endif # ifdef HL_LOCKS if (action == PA_DIE) hl_lock_p->enabled = 0; if (!hl_lock_p->enabled || !hl_signal(HLLOCK_HANDLER)) { # endif critical_on(!helper_isalive_cache); # ifdef READWRITE_SIGNALLING write_inf(priv_write_fd, buf, 1); # else # ifdef HL_LOCKS if (hl_lock_p->enabled) { debug(10, "Waiting the privileged thread/process to get prepared for signal (by fallback)"); critical_on (pthread_mutex_lock(pthread_mutex_privileged_p)); critical_on (pthread_mutex_unlock(pthread_mutex_privileged_p)); } else # endif critical_on (pthread_mutex_lock(pthread_mutex_action_signal_p)); critical_on (pthread_cond_signal(pthread_cond_privileged_p)); # endif # ifdef HL_LOCKS } # endif if (action == PA_DIE) goto privileged_action_end; debug(10, "Waiting for the answer"); # ifdef HL_LOCKS if (hl_lock_p->enabled) { while (!hl_isanswered(HLLOCK_HANDLER)) if (!helper_isalive_cache) { debug(1, "The privileged thread/process is dead (#2). Ignoring the command."); rc = ENOENT; goto privileged_action_end; } # ifdef HL_LOCK_TRIES_AUTO if (isadjusting) { unsigned long delay = (long)clock() - (long)start_ticks; long diff = delay - hl_lock_p->delay[callid]; debug(13, "diff == %li; hl_lock_p->delay[%i] == %lu; delay == %lu; delay*HL_LOCK_AUTO_THREADHOLD == %lu", diff, callid, hl_lock_p->delay[callid], delay, delay*HL_LOCK_AUTO_THREADHOLD) if (diff && ((unsigned long)labs(diff) > (unsigned long)delay*HL_LOCK_AUTO_THREADHOLD)) { if (diff > 0) hl_lock_p->tries_step[callid] = 1/((hl_lock_p->tries_step[callid]-1)/HL_LOCK_AUTO_DECELERATION+1); hl_lock_p->delay[callid] = delay; debug(12, "diff == %li; hl_lock_p->tries_step[%i] == %lf; hl_lock_p->delay[%i] == %lu", diff, callid, hl_lock_p->tries_step[callid], callid, hl_lock_p->delay[callid]); } hl_lock_p->tries[callid] *= hl_lock_p->tries_step[callid]; if (hl_lock_p->tries[callid] > HL_LOCK_AUTO_LIMIT_HIGH) hl_lock_p->tries[callid] = HL_LOCK_AUTO_LIMIT_HIGH; debug(14, "hl_lock_p->tries[%i] == %lu", callid, hl_lock_p->tries[callid]); } # endif } else { # endif critical_on(!helper_isalive_cache); # ifdef READWRITE_SIGNALLING read_inf(nonp_read_fd, buf, 1); # else critical_on (pthread_cond_wait(pthread_cond_action_p, pthread_mutex_action_signal_p)); # endif # ifdef HL_LOCKS } # endif if (ret_p != NULL) *ret_p = (void *)cmd_ret_p->ret; errno = cmd_ret_p->_errno; privileged_action_end: debug(10, "Unlocking pthread_mutex_action_*"); # ifndef READWRITE_SIGNALLING # ifdef HL_LOCKS if (!hl_lock_p->enabled) # endif pthread_mutex_unlock(pthread_mutex_action_signal_p); # endif pthread_mutex_unlock(pthread_mutex_action_entrance_p); return rc; } FTS *__privileged_fts_open_procsplit( char *const *path_argv, int options, int (*compar)(const FTSENT **, const FTSENT **) # ifdef HL_LOCK_TRIES_AUTO , int callid # endif ) { void *ret = NULL; int i; i = 0; while (path_argv[i] != NULL) { cmd_p->arg.fts_open.path_argv[i] = (void *)cmd_p->arg.fts_open._path_argv[i]; debug(25, "path_argv[%i] == <%s> (%p) -> %p", i, path_argv[i], path_argv[i], cmd_p->arg.fts_open.path_argv[i]); strncpy(cmd_p->arg.fts_open.path_argv[i], path_argv[i], sizeof(cmd_p->arg.fts_open._path_argv[i])); i++; critical_on(i >= MAXARGUMENTS); } cmd_p->arg.fts_open.path_argv[i] = NULL; cmd_p->arg.fts_open.options = options; cmd_p->arg.fts_open.compar = compar; privileged_action( # ifdef HL_LOCK_TRIES_AUTO callid, # endif PA_FTS_OPEN, &ret ); return ret; } FTS *__privileged_fts_open_threadsplit( char *const *path_argv, int options, int (*compar)(const FTSENT **, const FTSENT **) # ifdef HL_LOCK_TRIES_AUTO , int callid # endif ) { void *ret = NULL; cmd_p->arg.fts_open.path_argv_p = path_argv; cmd_p->arg.fts_open.options = options; cmd_p->arg.fts_open.compar = compar; privileged_action( # ifdef HL_LOCK_TRIES_AUTO callid, # endif PA_FTS_OPEN, &ret ); return ret; } FTSENT *__privileged_fts_read( FTS *ftsp # ifdef HL_LOCK_TRIES_AUTO , int callid # endif ) { void *ret = NULL; cmd_p->arg.void_v = ftsp; privileged_action( # ifdef HL_LOCK_TRIES_AUTO callid, # endif PA_FTS_READ, &ret ); return ret; } int __privileged_fts_close( FTS *ftsp # ifdef HL_LOCK_TRIES_AUTO , int callid # endif ) { void *ret = (void *)(long)-1; cmd_p->arg.void_v = ftsp; privileged_action( # ifdef HL_LOCK_TRIES_AUTO callid, # endif PA_FTS_CLOSE, &ret ); return (long)ret; } int __privileged_inotify_init() { void *ret = (void *)(long)-1; privileged_action( # ifdef HL_LOCK_TRIES_AUTO PC_DEFAULT, # endif PA_INOTIFY_INIT, &ret ); return (long)ret; } int __privileged_inotify_init1(int flags) { void *ret = (void *)(long)-1; cmd_p->arg.uint32_v = flags; privileged_action( # ifdef HL_LOCK_TRIES_AUTO PC_DEFAULT, # endif PA_INOTIFY_INIT1, &ret ); return (long)ret; } int __privileged_inotify_add_watch_threadsplit( int fd, const char *pathname, uint32_t mask # ifdef HL_LOCK_TRIES_AUTO , int callid # endif ) { debug(25, "(%i, <%s>, o%o, ?)", fd, pathname, mask); void *ret = (void *)(long)-1; cmd_p->arg.inotify_add_watch.pathname_p = pathname; cmd_p->arg.inotify_add_watch.fd = fd; cmd_p->arg.inotify_add_watch.mask = mask; privileged_action( # ifdef HL_LOCK_TRIES_AUTO callid, # endif PA_INOTIFY_ADD_WATCH, &ret ); return (long)ret; } int __privileged_inotify_add_watch_procsplit( int fd, const char *pathname, uint32_t mask # ifdef HL_LOCK_TRIES_AUTO , int callid # endif ) { debug(25, "(%i, <%s>, o%o, ?)", fd, pathname, mask); void *ret = (void *)(long)-1; strncpy((void *)cmd_p->arg.inotify_add_watch.pathname, pathname, sizeof(cmd_p->arg.inotify_add_watch.pathname)); cmd_p->arg.inotify_add_watch.fd = fd; cmd_p->arg.inotify_add_watch.mask = mask; privileged_action( # ifdef HL_LOCK_TRIES_AUTO callid, # endif PA_INOTIFY_ADD_WATCH, &ret ); return (long)ret; } int __privileged_inotify_rm_watch( int fd, int wd ) { void *ret = (void *)(long)-1; cmd_p->arg.inotify_rm_watch.fd = fd; cmd_p->arg.inotify_rm_watch.wd = wd; privileged_action( # ifdef HL_LOCK_TRIES_AUTO PC_DEFAULT, # endif PA_INOTIFY_RM_WATCH, &ret ); return (long)ret; } # ifdef CGROUP_SUPPORT int __privileged_clsync_cgroup_deinit(ctx_t *ctx_p) { void *ret = (void *)(long)-1; cmd_p->arg.ctx_p = ctx_p; privileged_action( # ifdef HL_LOCK_TRIES_AUTO PC_DEFAULT, # endif PA_CLSYNC_CGROUP_DEINIT, &ret ); return (long)ret; } # endif int __privileged_fork_setuid_execvp_procsplit( const char *file, char *const argv[] ) { int i; void *ret = (void *)(long)-1; strncpy((void *)cmd_p->arg.fork_execvp.file, file, sizeof(cmd_p->arg.fork_execvp.file)); i=0; while (argv[i] != NULL) { cmd_p->arg.fork_execvp.argv[i] = (void *)cmd_p->arg.fork_execvp._argv[i]; strncpy(cmd_p->arg.fork_execvp.argv[i], argv[i], sizeof(cmd_p->arg.fork_execvp._argv[i])); i++; critical_on(i >= MAXARGUMENTS); } cmd_p->arg.fork_execvp.argv[i] = NULL; privileged_action( # ifdef HL_LOCK_TRIES_AUTO PC_DEFAULT, # endif PA_FORK_EXECVP, &ret ); return (long)ret; } int __privileged_fork_setuid_execvp_threadsplit( const char *file, char *const argv[] ) { void *ret = (void *)(long)-1; cmd_p->arg.fork_execvp.file_p = file; cmd_p->arg.fork_execvp.argv_p = argv; privileged_action( # ifdef HL_LOCK_TRIES_AUTO PC_DEFAULT, # endif PA_FORK_EXECVP, &ret ); return (long)ret; } int __privileged_kill_child_wrapper(pid_t pid, int signal, char ignoreerrors) { void *ret = (void *)(long)-1; cmd_p->arg.kill_child.pid = pid; cmd_p->arg.kill_child.signal = signal; cmd_p->arg.kill_child.ignoreerrors = ignoreerrors; privileged_action( # ifdef HL_LOCK_TRIES_AUTO PC_DEFAULT, # endif PA_KILL_CHILD, &ret); return (long)ret; } pid_t __privileged_waitpid(pid_t pid, int *status, int options) { void *ret = (void *)(long)-1; cmd_p->arg.waitpid.pid = pid; cmd_p->arg.waitpid.options = options; privileged_action( # ifdef HL_LOCK_TRIES_AUTO PC_DEFAULT, # endif PA_WAITPID, &ret); if (status != NULL) *status = cmd_p->arg.waitpid.status; return (long)ret; } #endif uid_t __privileged_fork_execvp_uid; gid_t __privileged_fork_execvp_gid; int __privileged_fork_execvp(const char *file, char *const argv[]) { debug(4, ""); pid_t pid = fork(); switch (pid) { case -1: error("Cannot fork()."); return -1; case 0: debug(4, "setgid(%u) == %i", __privileged_fork_execvp_gid, setgid(__privileged_fork_execvp_gid)); debug(4, "setuid(%u) == %i", __privileged_fork_execvp_uid, setuid(__privileged_fork_execvp_uid)); errno = 0; execvp(file, argv); exit(errno); } return pid; } #ifdef CAPABILITIES_SUPPORT #define pthread_mutex_init_smart(mutex_p) _pthread_mutex_init_smart(ctx_p->flags[SPLITTING]==SM_PROCESS, mutex_p) static inline int _pthread_mutex_init_smart(int isshared, pthread_mutex_t **mutex_p) { int rc; pthread_mutex_t *mutex, initial = PTHREAD_MUTEX_INITIALIZER; if (isshared) return pthread_mutex_init_shared(mutex_p); mutex = xmalloc(sizeof(*mutex)); memcpy(mutex, &initial, sizeof(*mutex)); rc = pthread_mutex_init(mutex, NULL); if (rc) return rc; *mutex_p = mutex; return rc; } #define pthread_mutex_destroy_smart(mutex_p) _pthread_mutex_destroy_smart(ctx_p->flags[SPLITTING]==SM_PROCESS, mutex_p) static inline int _pthread_mutex_destroy_smart(int isshared, pthread_mutex_t *mutex_p) { int rc; if (isshared) return pthread_mutex_destroy_shared(mutex_p); rc = pthread_mutex_destroy(mutex_p); if (rc) return rc; free(mutex_p); return 0; } #define pthread_cond_init_smart(cond_p) _pthread_cond_init_smart(ctx_p->flags[SPLITTING]==SM_PROCESS, cond_p) static inline int _pthread_cond_init_smart(int isshared, pthread_cond_t **cond_p) { int rc; pthread_cond_t *cond, initial = PTHREAD_COND_INITIALIZER; if (isshared) return pthread_cond_init_shared(cond_p); cond = xmalloc(sizeof(*cond)); memcpy(cond, &initial, sizeof(*cond)); rc = pthread_cond_init(cond, NULL); if (rc) return rc; *cond_p = cond; return rc; } #define pthread_cond_destroy_smart(cond_p) _pthread_cond_destroy_smart(ctx_p->flags[SPLITTING]==SM_PROCESS, cond_p) static inline int _pthread_cond_destroy_smart(int isshared, pthread_cond_t *cond_p) { int rc; if (isshared) return pthread_cond_destroy_shared(cond_p); rc = pthread_cond_destroy(cond_p); if (rc) return rc; free(cond_p); return 0; } #endif int privileged_init(ctx_t *ctx_p) { #ifdef READWRITE_SIGNALLING int pipefds[2]; #endif #ifdef CAPABILITIES_SUPPORT if (ctx_p->flags[SPLITTING] == SM_OFF) { #endif _privileged_fork_execvp = __privileged_fork_execvp; __privileged_fork_execvp_uid = ctx_p->synchandler_uid; __privileged_fork_execvp_gid = ctx_p->synchandler_gid; _privileged_kill_child = __privileged_kill_child_itself; #ifdef CAPABILITIES_SUPPORT _privileged_fts_open = (typeof(_privileged_fts_open)) fts_open; _privileged_fts_read = (typeof(_privileged_fts_read)) fts_read; _privileged_fts_close = (typeof(_privileged_fts_close)) fts_close; _privileged_inotify_init = (typeof(_privileged_inotify_init)) inotify_init; _privileged_inotify_init1 = (typeof(_privileged_inotify_init1)) inotify_init1; _privileged_inotify_add_watch = (typeof(_privileged_inotify_add_watch)) inotify_add_watch; _privileged_inotify_rm_watch = (typeof(_privileged_inotify_rm_watch)) inotify_rm_watch; # ifdef CGROUP_SUPPORT _privileged_clsync_cgroup_deinit= (typeof(_privileged_clsync_cgroup_deinit)) clsync_cgroup_deinit; # endif _privileged_waitpid = (typeof(_privileged_waitpid)) waitpid; cap_drop(ctx_p, ctx_p->caps); #endif return 0; #ifdef CAPABILITIES_SUPPORT } _privileged_fts_read = __privileged_fts_read; _privileged_fts_close = __privileged_fts_close; _privileged_inotify_init = __privileged_inotify_init; _privileged_inotify_init1 = __privileged_inotify_init1; _privileged_inotify_rm_watch = __privileged_inotify_rm_watch; _privileged_kill_child = __privileged_kill_child_wrapper; # ifdef CGROUP_SUPPORT _privileged_clsync_cgroup_deinit= __privileged_clsync_cgroup_deinit; # endif _privileged_waitpid = __privileged_waitpid; SAFE ( pthread_mutex_init_smart(&pthread_mutex_privileged_p), return errno;); SAFE ( pthread_mutex_init_smart(&pthread_mutex_action_entrance_p), return errno;); SAFE ( pthread_mutex_init_smart(&pthread_mutex_action_signal_p), return errno;); SAFE ( pthread_mutex_init_smart(&pthread_mutex_runner_p), return errno;); SAFE ( pthread_cond_init_smart (&pthread_cond_privileged_p), return errno;); SAFE ( pthread_cond_init_smart (&pthread_cond_action_p), return errno;); SAFE ( pthread_cond_init_smart (&pthread_cond_runner_p), return errno;); # ifdef READWRITE_SIGNALLING SAFE ( pipe2(pipefds, O_CLOEXEC), return errno;); priv_read_fd = pipefds[0]; priv_write_fd = pipefds[1]; SAFE ( pipe2(pipefds, O_CLOEXEC), return errno;); nonp_read_fd = pipefds[0]; nonp_write_fd = pipefds[1]; # endif SAFE ( pthread_mutex_lock(pthread_mutex_runner_p), return errno;); # ifdef UNSHARE_SUPPORT unshare(CLONE_NEWIPC); # endif switch (ctx_p->flags[SPLITTING]) { case SM_THREAD: { _privileged_fts_open = __privileged_fts_open_threadsplit; _privileged_inotify_add_watch = __privileged_inotify_add_watch_threadsplit; _privileged_fork_execvp = __privileged_fork_setuid_execvp_threadsplit; cmd_p = calloc_align(1, sizeof(*cmd_p)); cmd_ret_p = calloc_align(1, sizeof(*cmd_ret_p)); # ifdef HL_LOCKS hl_lock_p = calloc_align(1, sizeof(*hl_lock_p)); hl_lock_init(hl_lock_p); # endif // Running the privileged thread SAFE ( pthread_create(&privileged_thread, NULL, (void *(*)(void *))privileged_handler, ctx_p), return errno); if (ctx_p->flags[FORGET_PRIVTHREAD_INFO]) privileged_thread = 0; break; } case SM_PROCESS: { _privileged_fts_open = __privileged_fts_open_procsplit; _privileged_inotify_add_watch = __privileged_inotify_add_watch_procsplit; _privileged_fork_execvp = __privileged_fork_setuid_execvp_procsplit; cmd_p = shm_calloc(1, sizeof(*cmd_p)); cmd_ret_p = shm_calloc(1, sizeof(*cmd_ret_p)); # ifdef HL_LOCKS hl_lock_p = shm_calloc(1, sizeof(*hl_lock_p)); hl_lock_init(hl_lock_p); # endif // Running the privileged helper SAFE ( (helper_pid = fork_helper()) == -1, return errno); if (!helper_pid) { if (ctx_p->privileged_gid != ctx_p->gid) { // SAFE ( cap_enable(CAP_TO_MASK(CAP_SETGID)), return errno; ); debug(3, "[privileged] Trying to set real gid to %i (ctx_p->privileged_gid)", ctx_p->privileged_gid); SAFE( setgid(ctx_p->privileged_gid), return errno); } if (ctx_p->privileged_uid != ctx_p->uid) { // SAFE ( cap_enable(CAP_TO_MASK(CAP_SETUID)), return errno; ); debug(3, "[privileged] Trying to set real uid to %i (ctx_p->privileged_uid)", ctx_p->privileged_uid); SAFE( setuid(ctx_p->privileged_uid), return errno); } exit(privileged_handler(ctx_p)); } break; } default: critical("Invalid ctx_p->flags[SPLITTING]: %i", ctx_p->flags[SPLITTING]); } if (ctx_p->flags[GID] || ctx_p->flags[UID]) SAFE( seteuid(0), {error("Not enough permission to start a privileged thread/fork"); return errno;}); if (ctx_p->flags[GID]) { SAFE( setegid(0), return errno); debug(3, "[non-privileged] Trying to drop real gid %i (ctx_p->gid)", getgid(), ctx_p->gid); SAFE( setgid(ctx_p->gid), return errno ); } if (ctx_p->flags[UID]) { debug(3, "[non-privileged] Trying to drop real uid %i (ctx_p->uid)", getuid(), ctx_p->uid); SAFE( setuid(ctx_p->uid), return errno ); } # ifdef HL_LOCKS if (ncpus == 1) hl_shutdown(HLLOCK_HANDLER); # endif critical_on(!helper_isalive()); # ifdef UNSHARE_SUPPORT // The rest routines if (ctx_p->flags[DETACH_NETWORK] == DN_NONPRIVILEGED) { SAFE ( cap_enable(CAP_TO_MASK(CAP_SYS_ADMIN)), return errno; ); SAFE ( unshare(CLONE_NEWNET), return errno; ); } # endif SAFE ( cap_drop(ctx_p, 0), return errno; ); debug(4, "Waiting for the privileged thread to get prepared"); pthread_cond_wait(pthread_cond_runner_p, pthread_mutex_runner_p); pthread_mutex_unlock(pthread_mutex_runner_p); debug(4, "Sending the settings (exec_uid == %u; exec_gid == %u)", ctx_p->synchandler_uid, ctx_p->synchandler_gid); cmd_p->arg.ctx_p = ctx_p; privileged_action( # ifdef HL_LOCK_TRIES_AUTO PC_DEFAULT, # endif PA_SETUP, NULL ); SAFE (pthread_mutex_destroy_smart(pthread_mutex_runner_p), return errno;); SAFE (pthread_cond_destroy_smart(pthread_cond_runner_p), return errno;); # ifdef SECCOMP_SUPPORT if (ctx_p->flags[SECCOMP_FILTER]) nonprivileged_seccomp_init(ctx_p); # endif debug(5, "Finish"); return 0; #endif } int privileged_deinit(ctx_t *ctx_p) { int ret = 0; #ifdef CAPABILITIES_SUPPORT if (ctx_p->flags[SPLITTING] == SM_OFF) return 0; SAFE ( privileged_action( # ifdef HL_LOCK_TRIES_AUTO PC_DEFAULT, # endif PA_DIE, NULL ), ret = errno ); # ifdef HL_LOCK_TRIES_AUTO { int i=0; while (i < PC_MAX) { debug(1, "hl_lock_p->tries[%i] == %lu", i, hl_lock_p->tries[i]); i++; } } # endif # ifdef HL_LOCKS hl_shutdown(HLLOCK_HANDLER); # endif switch (ctx_p->flags[SPLITTING]) { case SM_THREAD: { if (ctx_p->flags[FORGET_PRIVTHREAD_INFO]) { pthread_mutex_lock(pthread_mutex_privileged_p); pthread_mutex_unlock(pthread_mutex_privileged_p); } else { SAFE ( pthread_join(privileged_thread, NULL), ret = errno ); } free((void *)cmd_p); free((void *)cmd_ret_p); # ifdef HL_LOCKS free((void *)hl_lock_p); # endif break; } case SM_PROCESS: { int status; if (!ctx_p->flags[SECCOMP_FILTER]) { if (!__privileged_kill_child_itself(helper_pid, SIGKILL, 1)) { debug(9, "waitpid(%u, ...)", helper_pid); waitpid(helper_pid, &status, 0); } } shm_free((void *)cmd_p); shm_free((void *)cmd_ret_p); # ifdef HL_LOCKS shm_free((void *)hl_lock_p); # endif break; } } /* SAFE ( pthread_mutex_destroy_smart(pthread_mutex_privileged_p), ret = errno ); SAFE ( pthread_mutex_destroy_smart(pthread_mutex_action_entrance_p), ret = errno ); SAFE ( pthread_mutex_destroy_smart(pthread_mutex_action_signal_p), ret = errno ); SAFE ( pthread_cond_destroy_smart(pthread_cond_privileged_p), ret = errno ); SAFE ( pthread_cond_destroy_smart(pthread_cond_action_p), ret = errno ); */ #endif debug(2, "endof privileged_deinit()"); return ret; } clsync-0.4.1/privileged.h000066400000000000000000000076471252417542300153350ustar00rootroot00000000000000/* clsync - file tree sync utility based on inotify/kqueue Copyright (C) 2013-2014 Dmitry Yu Okunev 0x8E30679C This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifdef HL_LOCK_TRIES_AUTO # define IF_HL_LOCK_TRIES_AUTO(a) a #else # define IF_HL_LOCK_TRIES_AUTO(a) {} #endif #ifdef CAPABILITIES_SUPPORT enum priv_callid { PC_DEFAULT = 0, PC_SYNC_INIIALSYNC_WALK_FTS_OPEN, PC_SYNC_INIIALSYNC_WALK_FTS_READ, PC_SYNC_INIIALSYNC_WALK_FTS_CLOSE, PC_SYNC_MARK_WALK_FTS_OPEN, PC_SYNC_MARK_WALK_FTS_READ, PC_SYNC_MARK_WALK_FTS_CLOSE, PC_INOTIFY_ADD_WATCH_DIR, PC_MAX }; extern FTS *(*_privileged_fts_open) ( char * const *path_argv, int options, int (*compar)(const FTSENT **, const FTSENT **) # ifdef HL_LOCK_TRIES_AUTO , int callid # endif ); extern FTSENT *(*_privileged_fts_read) ( FTS *ftsp # ifdef HL_LOCK_TRIES_AUTO , int callid # endif ); extern int (*_privileged_fts_close) ( FTS *ftsp # ifdef HL_LOCK_TRIES_AUTO , int callid # endif ); extern int (*_privileged_inotify_init) (); extern int (*_privileged_inotify_init1) (int flags); extern int (*_privileged_inotify_add_watch) ( int fd, const char *pathname, uint32_t mask # ifdef HL_LOCK_TRIES_AUTO , int callid # endif ); extern int (*_privileged_inotify_rm_watch) ( int fd, int wd ); #ifdef CGROUP_SUPPORT extern int (*_privileged_clsync_cgroup_deinit) (ctx_t *ctx_p); #endif extern pid_t (*_privileged_waitpid) (pid_t pid, int *status, int options); extern int privileged_check(); # ifdef HL_LOCK_TRIES_AUTO # define privileged_fts_open(a,b,c,d) _privileged_fts_open(a,b,c,d) # define privileged_fts_read(a,b) _privileged_fts_read(a,b) # define privileged_fts_close(a,b) _privileged_fts_close(a,b) # define privileged_inotify_add_watch(a,b,c,d) _privileged_inotify_add_watch(a,b,c,d) # else # define privileged_fts_open(a,b,c,d) _privileged_fts_open(a,b,c) # define privileged_fts_read(a,b) _privileged_fts_read(a) # define privileged_fts_close(a,b) _privileged_fts_close(a) # define privileged_inotify_add_watch(a,b,c,d) _privileged_inotify_add_watch(a,b,c) # endif # define privileged_inotify_init _privileged_inotify_init # define privileged_inotify_init1 _privileged_inotify_init1 # define privileged_inotify_rm_watch _privileged_inotify_rm_watch # define privileged_clsync_cgroup_deinit _privileged_clsync_cgroup_deinit # define privileged_waitpid _privileged_waitpid #else # define privileged_check(...) {} # define privileged_fts_open(a,b,c,d) fts_open(a,b,c) # define privileged_fts_read(a,b) fts_read(a) # define privileged_fts_close(a,b) fts_close(a) # define privileged_inotify_init inotify_init # define privileged_inotify_init1 inotify_init1 # define privileged_inotify_add_watch(a,b,c,d) inotify_add_watch(a,b,c) # define privileged_inotify_rm_watch inotify_rm_watch # ifdef CGROUP_SUPPORT # define privileged_clsync_cgroup_deinit clsync_cgroup_deinit # endif # define privileged_waitpid waitpid #endif extern int (*_privileged_kill_child)( pid_t pid, int sig, char ignoreerrors ); extern int (*_privileged_fork_execvp)( const char *file, char *const argv[] ); #define privileged_kill_child _privileged_kill_child #define privileged_fork_execvp _privileged_fork_execvp extern int privileged_init(struct ctx *ctx_p); extern int privileged_deinit(struct ctx *ctx_p); clsync-0.4.1/program.h000066400000000000000000000016651252417542300146440ustar00rootroot00000000000000/* clsync - file tree sync utility based on inotify Copyright (C) 2013 Dmitry Yu Okunev 0x8E30679C This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #define PROGRAM "clsync" #define VERSION_MAJ 0 #define VERSION_MID 4 #define VERSION_MIN 1 #define AUTHOR "Dmitry Yu Okunev 0x8E30679C" clsync-0.4.1/pthreadex.c000066400000000000000000000044701252417542300151510ustar00rootroot00000000000000/* clsync - file tree sync utility based on inotify/kqueue Copyright (C) 2013-2014 Dmitry Yu Okunev 0x8E30679C This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include "pthreadex.h" #include "malloc.h" int pthread_mutex_init_shared(pthread_mutex_t **mutex_p) { static pthread_mutex_t mutex_initial = PTHREAD_MUTEX_INITIALIZER; *mutex_p = shm_malloc_try(sizeof(**mutex_p)); memcpy(*mutex_p, &mutex_initial, sizeof(mutex_initial)); pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); return pthread_mutex_init(*mutex_p, &attr); } int pthread_mutex_destroy_shared(pthread_mutex_t *mutex_p) { int rc; rc = pthread_mutex_destroy(mutex_p); shm_free(mutex_p); return rc; } int pthread_cond_init_shared(pthread_cond_t **cond_p) { static pthread_cond_t cond_initial = PTHREAD_COND_INITIALIZER; *cond_p = shm_malloc(sizeof(**cond_p)); memcpy(*cond_p, &cond_initial, sizeof(cond_initial)); pthread_condattr_t attr; pthread_condattr_init(&attr); pthread_condattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); return pthread_cond_init(*cond_p, &attr); } int pthread_cond_destroy_shared(pthread_cond_t *cond_p) { int rc; rc = pthread_cond_destroy(cond_p); shm_free(cond_p); return rc; } int pthread_mutex_reltimedlock(pthread_mutex_t *mutex_p, long tv_sec, long tv_nsec) { struct timespec abs_time; if (clock_gettime(CLOCK_REALTIME, &abs_time)) return -1; abs_time.tv_sec += tv_sec; abs_time.tv_nsec += tv_nsec; if (abs_time.tv_nsec > 1000*1000*1000) { abs_time.tv_sec++; abs_time.tv_nsec -= 1000*1000*1000; } return pthread_mutex_timedlock(mutex_p, &abs_time); } clsync-0.4.1/pthreadex.h000066400000000000000000000022461252417542300151550ustar00rootroot00000000000000/* clsync - file tree sync utility based on inotify/kqueue Copyright (C) 2013-2014 Dmitry Yu Okunev 0x8E30679C This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include extern int pthread_mutex_init_shared(pthread_mutex_t **mutex_p); extern int pthread_mutex_destroy_shared(pthread_mutex_t *mutex_p); extern int pthread_cond_init_shared(pthread_cond_t **cond_p); extern int pthread_cond_destroy_shared(pthread_cond_t *cond_p); extern int pthread_mutex_reltimedlock(pthread_mutex_t *mutex_p, long tv_sec, long tv_nsec); clsync-0.4.1/rpm/000077500000000000000000000000001252417542300136125ustar00rootroot00000000000000clsync-0.4.1/rpm/build-rpm-from-git.sh000066400000000000000000000027321252417542300175670ustar00rootroot00000000000000#!/bin/bash # # author: Enrique Martinez # license: GPL-3+ # GITREV=$(git rev-parse --short HEAD) VERSION=${GITREV}git inc_version() { local v=$1 if [ -z $2 ]; then local tdk='^((?:[0-9]+\.)*)([0-9]+)($)' else local tdk='^((?:[0-9]+\.){'$(($2-1))'})([0-9]+)(\.|$)' for (( p=`grep -o "\."<<<".$v"|wc -l`; p<$2; p++)); do v+=.0; done; fi val=`echo -e "$v" | perl -pe 's/^.*'$tdk'.*$/$2/'` echo "$v" | perl -pe s/$tdk.*$'/${1}'`printf %0${#val}s $(($val+1))`/ } GITROOT=$(git rev-parse --show-toplevel) cd $GITROOT if [ -f $GITROOT/rpm/$VERSION.buildnum ]; then BUILDNUM=`cat ./rpm/$VERSION.buildnum` else BUILDNUM=0 fi BUILDNUM=$(inc_version $BUILDNUM) echo $BUILDNUM > $GITROOT/rpm/$VERSION.buildnum RPMTOPDIR=$GITROOT/rpm/build if [ ! -d $RPMTOPDIR ]; then mkdir -p $RPMTOPDIR fi echo "BUILDING RPM Version: $VERSION, BuildNumber: $BUILDNUM" mkdir -p $RPMTOPDIR/{SOURCES,SPECS} git archive --format=tar --prefix=clsync-${VERSION}/ HEAD | gzip -c > $RPMTOPDIR/SOURCES/clsync-${VERSION}.tar.gz sed -e "s/@VERSION@/$VERSION/" -e "s/@BUILDNUM@/$BUILDNUM/" $GITROOT/rpm/clsync.spec > $RPMTOPDIR/SPECS/clsync.spec cat $GITROOT/rpm/clsync.init > $RPMTOPDIR/SOURCES/clsync.init rpmbuild --quiet \ --define "_topdir $RPMTOPDIR" \ --define "_rpmdir $GITROOT/rpm" \ --define "_srcrpmdir $GITROOT/rpm" \ --define '_rpmfilename %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm' \ -ba $RPMTOPDIR/SPECS/clsync.spec && rm -rf $RPMTOPDIR && echo Doneclsync-0.4.1/rpm/clsync.init000066400000000000000000000020731252417542300157740ustar00rootroot00000000000000#! /bin/sh # # author: Enrique Martinez # license: GPL-3+ # # clsync # chkconfig: 2345 98 02 # pidfile: /var/run/clsync/clsync.pid # Source function library. . /etc/rc.d/init.d/functions # Source networking configuration. . /etc/sysconfig/network CLSYNCCMD=/usr/bin/clsync # Source monit configuration. if [ -f /etc/sysconfig/clsync ] ; then . /etc/sysconfig/clsync fi [ -f $CLSYNCCMD ] || exit 0 RETVAL=0 # See how we were called. case "$1" in start) echo -n "Starting clsync: " daemon $CLSYNCCMD --pid-file /var/run/clsync/clsync.pid RETVAL=$? echo [ $RETVAL = 0 ] && touch /var/lock/subsys/nosearch_cached ;; stop) echo -n "Stopping clsync: " killproc -p /var/run/clsync/clsync.pid RETVAL=$? echo [ $RETVAL = 0 ] && rm -f /var/lock/subsys/clsync ;; restart) $0 stop $0 start ;; reload) echo -n "Reloading clsync: " killproc -p /var/run/clsync/clsync.pid clsync -HUP RETVAL=$? echo ;; status) status -p /var/run/clsync/clsync.pid clsync RETVAL=$? ;; *) echo "Usage: $0 (start|stop|reload|status)" exit 1 esac exit $RETVAL clsync-0.4.1/rpm/clsync.spec000066400000000000000000000057441252417542300157730ustar00rootroot00000000000000# # author: Enrique Martinez # license: GPL-3+ # Summary: Live sync tool based on inotify Name: clsync Version: @VERSION@ Release: @BUILDNUM@ License: GPL-3+ Group: Applications/System URL: https://github.com/xaionaro/clsync Source0: clsync-%{version}.tar.gz Source1: clsync.init BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root BuildRequires: glib2-devel BuildRequires: autoconf %description live sync tool based on inotify, written in GNU C Clsync recursively watches for source directory and executes external program to sync the changes. Clsync is adapted to use together with rsync. This utility is much more lightweight than competitors and supports such features as separate queue for big files, regex file filter, multi-threading. %package devel Summary: Development Files for clsync Group: Applications/System Requires: clsync = %{version}-%{release} %description devel live sync tool based on inotify, written in GNU C Clsync recursively watches for source directory and executes external program to sync the changes. Clsync is adapted to use together with rsync. This utility is much more lightweight than competitors and supports such features as separate queue for big files, regex file filter, multi-threading. %prep %setup %build autoreconf -if %configure make %install make install DESTDIR=%{buildroot} install -D -p -m 0750 %{SOURCE1} %{buildroot}/etc/init.d/clsync mkdir -p %{buildroot}/etc/clsync/rules mkdir -p %{buildroot}/var/tmp/clsync/from mkdir -p %{buildroot}/var/tmp/clsync/to mkdir -p %{buildroot}/var/run/clsync cat > %{buildroot}/etc/clsync/clsync.conf < %{buildroot}/etc/clsync/rules/default < - 0.4-1 - A lot of fixes * Thu Jan 9 2014 Dmitry Yu Okunev - 0.3-1 - Added support of control socket * Thu Oct 24 2013 Barak A. Pearlmutter - 0.2.1-1 - New upstream version * Fri Oct 11 2013 Barak A. Pearlmutter - 0.1-2 - Tweak debian/watch to ignore debian releases * Sat Sep 07 2013 Barak A. Pearlmutter - 0.1-1 - Initial release (Closes: #718769 )clsync-0.4.1/rules.c000066400000000000000000000244531252417542300143220ustar00rootroot00000000000000/* clsync - file tree sync utility based on inotify/kqueue Copyright (C) 2013-2014 Dmitry Yu Okunev 0x8E30679C This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "common.h" #include // g_hash_table_* #include "rules.h" #include "error.h" int rule_complete(rule_t *rule_p, char *expr, size_t *rules_count_p) { debug(3, "<%s>.", expr); #ifdef VERYPARANOID if (rule_p->mask == RA_NONE) { error("Received a rule with rule_p->mask == 0x00. Exit."); return EINVAL; } #endif char buf[BUFSIZ]; int ret = 0; if (rule_p->num >= MAXRULES) { error("Too many rules (%i >= %i).", rule_p->num, MAXRULES); return ENOMEM; } if ((ret = regcomp(&rule_p->expr, expr, REG_EXTENDED | REG_NOSUB))) { regerror(ret, &rule_p->expr, buf, BUFSIZ); error("Invalid regexp pattern <%s>: %s (regex-errno: %i).", expr, buf, ret); return ret; } (*rules_count_p)++; return ret; } int parse_rules_fromfile(ctx_t *ctx_p) { int ret = 0; char *rulfpath = ctx_p->rulfpath; rule_t *rules = ctx_p->rules; size_t *rules_count_p = &ctx_p->rules_count; char *line_buf=NULL; FILE *f = fopen(rulfpath, "r"); if(f == NULL) { rules->mask = RA_NONE; // Terminator. End of rules' chain. rules->perm = DEFAULT_RULES_PERM; error("Cannot open \"%s\" for reading.", rulfpath); return errno; } GHashTable *autowrules_ht = g_hash_table_new_full(g_str_hash, g_str_equal, free, 0); int i=0; size_t linelen, size=0; while((linelen = getline(&line_buf, &size, f)) != -1) { if(linelen>1) { uint8_t sign = 0; char *line = line_buf; rule_t *rule; rule = &rules[i]; #ifdef VERYPARANOID memset(rule, 0, sizeof(*rule)); #endif rule->num = i++; line[--linelen] = 0; // Parsing the first character of the line switch(*line) { case '+': sign = RS_PERMIT; break; case '-': sign = RS_REJECT; break; case '#': // Comment? i--; // Canceling new rule continue; default: error("Wrong rule action <%c>.", *line); return EINVAL; } line++; linelen--; // Parsing the second character of the line *line |= 0x20; // lower-casing // Default rule->mask and rule->perm // rule->mask - sets bitmask of operations that are affected by the rule // rule->perm - sets bitmask of permit/reject for every operation. Effect have only bits specified by the rule->mask. rule->mask = RA_ALL; switch(sign) { case RS_REJECT: rule->perm = RA_NONE; break; case RS_PERMIT: rule->perm = RA_ALL; break; } switch(*line) { case '*': rule->objtype = 0; // "0" - means "of any type" break; #ifdef DETAILED_FTYPE case 's': rule->objtype = S_IFSOCK; break; case 'l': rule->objtype = S_IFLNK; break; case 'b': rule->objtype = S_IFBLK; break; case 'c': rule->objtype = S_IFCHR; break; case 'p': rule->objtype = S_IFIFO; break; #endif case 'f': rule->objtype = S_IFREG; break; case 'd': rule->objtype = S_IFDIR; break; case 'w': // accept or reject walking to directory if ( (ctx_p->flags[MODE] == MODE_RSYNCDIRECT) || (ctx_p->flags[MODE] == MODE_RSYNCSHELL) || (ctx_p->flags[MODE] == MODE_RSYNCSO) ) warning("Used \"w\" rule in \"--rsync\" case." " This may cause unexpected problems."); rule->objtype = S_IFDIR; rule->mask = RA_WALK; break; default: warning("Cannot parse the rule <%s>", &line[-1]); i--; // Canceling new rule continue; } line++; linelen--; // Parsing the rest part of the line debug(1, "Rule #%i <%c>[0x%02x 0x%02x] <%c>[0x%04x] pattern <%s> (length: %i).", rule->num, line[-2], rule->perm, rule->mask, line[-1], rule->objtype, line, linelen); if((ret=rule_complete(rule, line, rules_count_p))) goto l_parse_rules_fromfile_end; // Post-processing: line--; linelen++; #ifdef AUTORULESW if(*line != 'w') { // processing --auto-add-rules-w if(ctx_p->flags[AUTORULESW] && (sign == RS_PERMIT)) { // Preparing to add appropriate w-rules char skip = 0; char *expr = alloca(linelen+2); memcpy(expr, line, linelen+1); size_t exprlen = linelen; // Making expr to be starting with '^' if(line[1] == '^') { expr++; exprlen--; } else *expr = '^'; char *end; if(*line == 'd' || *line == '*') { // "d" rule already doing what we need, so we can skip the last level end = &expr[exprlen]; if(end[-1] != '$') *(end++) = '$'; *end = 0; // debug(3, "Don't adding w-rule for \"%s\" due to [*d]-rule for \"%s\"", // expr, &line[1]); g_hash_table_insert(autowrules_ht, strdup(expr), GINT_TO_POINTER(1)); } if(!skip) { do { // Decreasing directory level and make the '$' ending end = strrchr(expr, '/'); if(end != NULL) { if(end[-1] != '$') *(end++) = '$'; *end = 0; exprlen = (size_t)(end - expr); } else { expr[1] = '$'; expr[2] = 0; exprlen = 2; } // Checking if it not already set if(!g_hash_table_lookup(autowrules_ht, expr)) { // Switching to next rule: rule = &rules[i]; rule->num = i++; // Adding the rule rule->objtype = S_IFDIR; rule->mask = RA_WALK; rule->perm = RA_WALK; debug(1, "Rule #%i <+> pattern <%s> (length: %i) [auto].", rule->num, expr, exprlen); if((ret=rule_complete(rule, expr, rules_count_p))) goto l_parse_rules_fromfile_end; g_hash_table_insert(autowrules_ht, strdup(expr), GINT_TO_POINTER(1)); } } while (end != NULL); } } } #endif } } l_parse_rules_fromfile_end: if (size) free(line_buf); fclose(f); debug(3, "Adding tail-rule #%u (effective #%u).", -1, i); rules[i].mask = RA_NONE; // Terminator. End of rules' chain. rules[i].perm = DEFAULT_RULES_PERM; g_hash_table_destroy(autowrules_ht); #ifdef _DEBUG_FORCE debug(3, "Total (p == %p):", rules); i=0; do { debug(4, "\t%i\t%i\t%p/%p", i, rules[i].objtype, (void *)(long)rules[i].perm, (void *)(long)rules[i].mask); i++; } while(rules[i].mask != RA_NONE); #endif return ret; } /** * @brief Checks file path by rules' expressions (parsed from file) * * @param[in] fpath Path to file of directory * @param[in] st_mode st_mode received via *stat() functions * @param[in] rules_p Pointer to start of rules array * @param[in] ruleaction Operaton ID (see ruleaction_t) * @param[i/o] rule_pp Pointer to pointer to rule, where the last search ended. Next search will be started from the specified rule. Can be "NULL" to disable this feature. * * @retval perm Permission bitmask * */ // Checks file path by rules' expressions (parsed from file) // Return: RS_PERMIT or RS_REJECT for the "file path" and specified ruleaction ruleaction_t rules_search_getperm(const char *fpath, mode_t st_mode, rule_t *rules_p, const ruleaction_t ruleaction, rule_t **rule_pp) { debug(3, "rules_search_getperm(\"%s\", %p, %p, %p, %p)", fpath, (void *)(unsigned long)st_mode, rules_p, (void *)(long)ruleaction, (void *)(long)rule_pp ); int i; i = 0; rule_t *rule_p = rules_p; mode_t ftype = st_mode & S_IFMT; #ifdef _DEBUG_FORCE debug(3, "Rules (p == %p):", rules_p); i=0; do { debug(3, "\t%i\t%i\t%p/%p", i, rules_p[i].objtype, (void *)(long)rules_p[i].perm, (void *)(long)rules_p[i].mask); i++; } while (rules_p[i].mask != RA_NONE); #endif i=0; if (rule_pp != NULL) if (*rule_pp != NULL) { debug(3, "Previous position is set."); if (rule_p->mask == RA_NONE) return rule_p->perm; rule_p = ++(*rule_pp); i = rule_p->num; } debug(3, "Starting from position %i", i); while (rule_p->mask != RA_NONE) { debug(3, "%i -> %p/%p: type compare: %p, %p -> %i", i, (void *)(long)rule_p->perm, (void *)(long)rule_p->mask, (void *)(unsigned long)ftype, (void *)(unsigned long)rule_p->objtype, (unsigned char)!(rule_p->objtype && (rule_p->objtype != ftype)) ); if (!(rule_p->mask & ruleaction)) { // Checking wrong operation type debug(3, "action-mask mismatch. Skipping."); rule_p++;i++;// = &rules_p[++i]; continue; } if (rule_p->objtype && (rule_p->objtype != ftype)) { debug(3, "objtype mismatch. Skipping."); rule_p++;i++;// = &rules_p[++i]; continue; } if(!regexec(&rule_p->expr, fpath, 0, NULL, 0)) break; debug(3, "doesn't match regex. Skipping."); rule_p++;i++;// = &rules_p[++i]; } debug(2, "matched to rule #%u for \"%s\":\t%p/%p (queried: %p).", rule_p->mask==RA_NONE?-1:i, fpath, (void *)(long)rule_p->perm, (void *)(long)rule_p->mask, (void *)(long)ruleaction ); if (rule_pp != NULL) *rule_pp = rule_p; return rule_p->perm; } ruleaction_t rules_getperm(const char *fpath, mode_t st_mode, rule_t *rules_p, ruleaction_t ruleactions) { rule_t *rule_p = NULL; ruleaction_t gotpermto = 0; ruleaction_t resultperm = 0; debug(3, "rules_getperm(\"%s\", %p, %p (#%u), %p)", fpath, (void *)(long)st_mode, rules_p, rules_p->num, (void *)(long)ruleactions); while((gotpermto&ruleactions) != ruleactions) { rules_search_getperm(fpath, st_mode, rules_p, ruleactions, &rule_p); if(rule_p->mask == RA_NONE) { // End of rules' list resultperm |= rule_p->perm & (gotpermto^RA_ALL); break; } resultperm |= rule_p->perm & ((gotpermto^rule_p->mask)&rule_p->mask); // Adding perm bitmask of operations that was unknown before gotpermto |= rule_p->mask; // Adding the mask } debug(3, "rules_getperm(\"%s\", %p, rules_p, %p): result perm is %p", fpath, (void *)(long)st_mode, (void *)(long)ruleactions, (void *)(long)resultperm); return resultperm; } clsync-0.4.1/rules.h000066400000000000000000000021531252417542300143200ustar00rootroot00000000000000/* clsync - file tree sync utility based on inotify/kqueue Copyright (C) 2013-2014 Dmitry Yu Okunev 0x8E30679C This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ extern int parse_rules_fromfile(struct ctx *ctx_p); extern ruleaction_t rules_search_getperm(const char *fpath, mode_t st_mode, rule_t *rules_p, const ruleaction_t ruleaction, rule_t **rule_pp); extern ruleaction_t rules_getperm(const char *fpath, mode_t st_mode, struct rule *rules_p, ruleaction_t ruleactions); clsync-0.4.1/socket.c000066400000000000000000000520021252417542300144470ustar00rootroot00000000000000/* clsync - file tree sync utility based on inotify Copyright (C) 2013 Dmitry Yu Okunev 0x8E30679C This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "common.h" #include #include #include #include #include #include #include #include // for "struct sockaddr_un" #include #include "configuration.h" #include "error.h" #include "malloc.h" #include "program.h" #include "socket.h" pthread_mutex_t socket_thread_mutex = PTHREAD_MUTEX_INITIALIZER; int clsyncsockthreads_last = -1; int clsyncsockthreads_count = 0; int clsyncsockthreads_num = 0; char clsyncsockthread_busy[SOCKET_MAX+1] = {0}; socket_sockthreaddata_t sockthreaddata[SOCKET_MAX+1] = {{0}}; int socket_gc() { int i=clsyncsockthreads_last+1; while(i) { i--; switch(sockthreaddata[i].state) { case CLSTATE_DIED: debug(3, "Forgeting clsyncsock #%u", i); pthread_join(sockthreaddata[i].thread, NULL); sockthreaddata[i].state = CLSTATE_NONE; break; default: break; } } return 0; } static char *recv_stps[SOCKET_MAX]; static char *recv_ptrs[SOCKET_MAX]; const char *const textmessage_args[SOCKCMD_MAXID] = { [SOCKCMD_REQUEST_NEGOTIATION] = "%u", [SOCKCMD_REQUEST_DUMP] = "%s", [SOCKCMD_REQUEST_SET] = "%s\003/ %s\003/", [SOCKCMD_REPLY_NEGOTIATION] = "%u", [SOCKCMD_REPLY_ACK] = "%u %lu", [SOCKCMD_REPLY_EINVAL] = "%u %lu", [SOCKCMD_REPLY_VERSION] = "%u %u %s", [SOCKCMD_REPLY_INFO] = "%s\003/ %s\003/ %x %x", [SOCKCMD_REPLY_UNKNOWNCMD] = "%u %lu", [SOCKCMD_REPLY_INVALIDCMDID] = "%lu", [SOCKCMD_REPLY_EEXIST] = "%s\003/", [SOCKCMD_REPLY_EPERM] = "%s\003/", [SOCKCMD_REPLY_ECUSTOM] = "%s\003/ %s\003/ %u %s\003/", }; const char *const textmessage_descr[SOCKCMD_MAXID] = { [SOCKCMD_REQUEST_NEGOTIATION] = "Protocol version is %u.", [SOCKCMD_REPLY_NEGOTIATION] = "Protocol version is %u.", [SOCKCMD_REPLY_ACK] = "Acknowledged command: id == %u; num == %lu.", [SOCKCMD_REPLY_EINVAL] = "Rejected command: id == %u; num == %lu. Invalid arguments: %s.", [SOCKCMD_REPLY_LOGIN] = "Enter your login and password, please.", [SOCKCMD_REPLY_UNEXPECTEDEND] = "Need to go, sorry. :)", [SOCKCMD_REPLY_DIE] = "Okay :(", [SOCKCMD_REPLY_BYE] = "Bye.", [SOCKCMD_REPLY_VERSION] = "clsync v%u.%u%s", [SOCKCMD_REPLY_INFO] = "config_block == \"%s\"; label == \"%s\"; flags == %x; flags_set == %x.", [SOCKCMD_REPLY_SET] = "Set", [SOCKCMD_REPLY_DUMP] = "Ready", [SOCKCMD_REPLY_UNKNOWNCMD] = "Unknown command.", [SOCKCMD_REPLY_INVALIDCMDID] = "Invalid command id. Required: 0 <= cmd_id < 1000.", [SOCKCMD_REPLY_EEXIST] = "File exists: \"%s\".", [SOCKCMD_REPLY_EPERM] = "Permission denied: \"%s\".", [SOCKCMD_REPLY_ECUSTOM] = "%s(%s): Error #%u: \"%s\".", }; int socket_check_bysock(int sock) { int error_code, ret; socklen_t error_code_len = sizeof(error_code); if ((ret=getsockopt(sock, SOL_SOCKET, SO_ERROR, &error_code, &error_code_len))) { return errno; } if (error_code) { errno = error_code; return error_code; } return 0; } static inline int socket_check(clsyncsock_t *clsyncsock_p) { return socket_check_bysock(clsyncsock_p->sock); } clsyncsock_t *socket_new(int clsyncsock_sock) { clsyncsock_t *clsyncsock_p = xmalloc(sizeof(*clsyncsock_p)); debug(2, "sock == %i.", clsyncsock_sock); clsyncsock_p->sock = clsyncsock_sock; clsyncsock_p->prot = SOCKET_DEFAULT_PROT; clsyncsock_p->subprot = SOCKET_DEFAULT_SUBPROT; return clsyncsock_p; } int socket_cleanup(clsyncsock_t *clsyncsock_p) { int clsyncsock_sock = clsyncsock_p->sock; debug(2, "sock == %i.", clsyncsock_sock); recv_ptrs[clsyncsock_sock] = NULL; recv_stps[clsyncsock_sock] = NULL; free(clsyncsock_p); return 0; } int socket_close(clsyncsock_t *clsyncsock_p) { close(clsyncsock_p->sock); return socket_cleanup(clsyncsock_p); } int socket_thread_delete(socket_sockthreaddata_t *threaddata_p) { int thread_id; pthread_mutex_lock(&socket_thread_mutex); thread_id = threaddata_p->id; socket_close(threaddata_p->clsyncsock_p); clsyncsockthreads_count--; if (clsyncsockthreads_last == thread_id) clsyncsockthreads_last = thread_id-1; clsyncsockthread_busy[thread_id]=0; threaddata_p->state = CLSTATE_DIED; if (threaddata_p->freefunct_arg != NULL) threaddata_p->freefunct_arg(threaddata_p->arg); pthread_mutex_unlock(&socket_thread_mutex); return 0; } clsyncsock_t *socket_accept(int sock) { // Cleaning up after died connections (getting free space for new connection) socket_gc(); // Getting new connection int clsyncsock_sock = accept(sock, NULL, NULL); if(clsyncsock_sock == -1) { error("socket_accept(%i): Cannot accept()", sock); return NULL; } return socket_new(clsyncsock_sock); } clsyncsock_t *socket_listen_unix(const char *const socket_path) { // creating a simple unix socket int s; s = socket(AF_UNIX, SOCK_STREAM, 0); // checking the path // already exists? - unlink if (!access(socket_path, F_OK)) if (unlink(socket_path)) { error("Cannot unlink() \"%s\".", socket_path); close(s); return NULL; } // binding { struct sockaddr_un addr; memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path)-1); if (bind(s, (struct sockaddr *)&addr, sizeof(addr))) { error("Cannot bind() on address \"%s\".", socket_path); close(s); return NULL; } } // starting to listening if (listen(s, SOCKET_BACKLOG)) { error("Cannot listen() on address \"%s\".", socket_path); close(s); return NULL; } return socket_new(s); } #ifdef SOCKET_PROVIDER_LIBCLSYNC clsyncsock_t *socket_connect_unix(const char *const socket_path) { // creating a simple unix socket int s; s = socket(AF_UNIX, SOCK_STREAM, 0); if (s == -1) return NULL; // checking the path if (access(socket_path, F_OK)) { error("Cannot access() to \"%s\".", socket_path); close(s); return NULL; } // connecting { struct sockaddr_un addr; memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path)-1); if (connect(s, (struct sockaddr *)&addr, sizeof(addr))) { error("Cannot connect() to address \"%s\".", socket_path); close(s); return NULL; } } return socket_new(s); } #endif int _socket_send(clsyncsock_t *clsyncsock, uint64_t *cmd_num_p, sockcmd_id_t cmd_id, va_list ap) { int ret; char prebuf0[SOCKET_BUFSIZ], prebuf1[SOCKET_BUFSIZ], sendbuf[SOCKET_BUFSIZ]; ret = 0; switch (clsyncsock->prot) { case 0: switch (clsyncsock->subprot) { case SUBPROT0_TEXT: { va_list ap_copy; debug(3, "%p %p %p", prebuf0, textmessage_args[cmd_id], ap_copy); if (textmessage_args[cmd_id]) { va_copy(ap_copy, ap); vsprintf(prebuf0, textmessage_args[cmd_id], ap_copy); } else *prebuf0 = 0; va_copy(ap_copy, ap); vsprintf(prebuf1, textmessage_descr[cmd_id], ap); size_t sendlen = sprintf( sendbuf, "%lu %u %s :%s\n", (*cmd_num_p)++, cmd_id, prebuf0, prebuf1 ); debug(5, "send(): \"%s\"", sendbuf); send(clsyncsock->sock, sendbuf, sendlen, 0); break; } /* case SUBPROT0_BINARY: break;*/ default: error("Unknown subprotocol with id %u.", clsyncsock->subprot); ret = EINVAL; goto l_socket_send_end; } break; default: error("Unknown protocol with id %u.", clsyncsock->prot); ret = EINVAL; goto l_socket_send_end; } l_socket_send_end: return ret; } int socket_reply(clsyncsock_t *clsyncsock_p, sockcmd_t *sockcmd_p, sockcmd_id_t cmd_id, ...) { va_list ap; int ret; uint64_t cmd_num = sockcmd_p->cmd_num; va_start(ap, cmd_id); ret = _socket_send(clsyncsock_p, &cmd_num, cmd_id, ap); va_end(ap); return ret; } int socket_send(clsyncsock_t *clsyncsock_p, sockcmd_id_t cmd_id, ...) { va_list ap; int ret; va_start(ap, cmd_id); ret = _socket_send(clsyncsock_p, &clsyncsock_p->cmd_num, cmd_id, ap); va_end(ap); return ret; } int socket_send_cb(clsyncsock_t *clsyncsock_p, sockcmd_id_t cmd_id, clsyncsock_cb_funct_t cb, void *cb_arg, ...) { if (clsyncsock_p->cbqueue_len >= CLSYNCSOCK_WINDOW) { errno = EOVERFLOW; error("Callback queue overflowed. Closing the socket."); socket_close(clsyncsock_p); return errno; } { va_list ap; int ret; uint64_t cmd_num = clsyncsock_p->cmd_num; va_start(ap, cb_arg); ret = _socket_send(clsyncsock_p, &clsyncsock_p->cmd_num, cmd_id, ap); va_end(ap); if (!ret) { clsynccbqueue_t *cbq = &clsyncsock_p->cbqueue[clsyncsock_p->cbqueue_len]; int id; cbq->cmd_num = cmd_num; cbq->callback_funct = cb; cbq->callback_arg = cb_arg; id = cmd_num % (2*CLSYNCSOCK_WINDOW); while (clsyncsock_p->cbqueue_cache[id] != NULL) id++; clsyncsock_p->cbqueue_cache[id] = cbq; clsyncsock_p->cbqueue_len++; } return ret; } } static inline int socket_overflow_fix(char *buf, char **data_start_p, char **data_end_p) { debug(3, "buf==%p; data_start==%p; data_end==%p", buf, *data_start_p, *data_end_p); if(buf == *data_start_p) return 0; size_t ptr_diff = *data_start_p - buf; if(*data_start_p != *data_end_p) { *data_start_p = buf; *data_end_p = buf; return ptr_diff; } size_t data_length = *data_end_p - *data_start_p; memmove(buf, *data_start_p, data_length); *data_start_p = buf; *data_end_p = &buf[data_length]; return ptr_diff; } #define PARSE_TEXT_DATA_SSCANF(dat_t, ...) {\ sockcmd_p->data = xmalloc(sizeof(dat_t));\ dat_t *d = (dat_t *)sockcmd_p->data;\ if (sscanf(args, textmessage_args[sockcmd_p->cmd_id], __VA_ARGS__) < min_args)\ return EINVAL;\ } static inline int parse_text_data(sockcmd_t *sockcmd_p, char *args, size_t args_len) { debug(6, "(%p, %p, %u)", sockcmd_p, args, args_len); if (!args_len) return 0; int min_args = 0; const char *ptr = (const char *)textmessage_args[sockcmd_p->cmd_id]; if (ptr != NULL) { while(*ptr) { if(*ptr == '%') { if(ptr[1] == '%') ptr++; else min_args++; } ptr++; } } switch (sockcmd_p->cmd_id) { case SOCKCMD_REQUEST_NEGOTIATION: case SOCKCMD_REPLY_NEGOTIATION: PARSE_TEXT_DATA_SSCANF(sockcmd_dat_negotiation_t, &d->prot, &d->subprot); break; case SOCKCMD_REQUEST_DUMP: PARSE_TEXT_DATA_SSCANF(sockcmd_dat_dump_t, &d->dir_path); break; case SOCKCMD_REQUEST_SET: PARSE_TEXT_DATA_SSCANF(sockcmd_dat_set_t, &d->key, &d->value); break; case SOCKCMD_REPLY_ACK: PARSE_TEXT_DATA_SSCANF(sockcmd_dat_ack_t, &d->cmd_id, &d->cmd_num); break; case SOCKCMD_REPLY_EINVAL: PARSE_TEXT_DATA_SSCANF(sockcmd_dat_einval_t, &d->cmd_id, &d->cmd_num); break; case SOCKCMD_REPLY_VERSION: if (args_len > sizeof(1<<8)) args[args_len=1<<8] = 0; PARSE_TEXT_DATA_SSCANF(sockcmd_dat_version_t, &d->major, &d->minor, &d->revision); break; case SOCKCMD_REPLY_INFO: if (args_len > sizeof(1<<8)) args[args_len=1<<8] = 0; PARSE_TEXT_DATA_SSCANF(sockcmd_dat_info_t, &d->config_block, &d->label, &d->flags, &d->flags_set); break; case SOCKCMD_REPLY_UNKNOWNCMD: PARSE_TEXT_DATA_SSCANF(sockcmd_dat_unknowncmd_t, &d->cmd_id, &d->cmd_num); break; case SOCKCMD_REPLY_INVALIDCMDID: PARSE_TEXT_DATA_SSCANF(sockcmd_dat_invalidcmd_t, &d->cmd_num); break; case SOCKCMD_REPLY_EEXIST: PARSE_TEXT_DATA_SSCANF(sockcmd_dat_eexist_t, &d->file_path); break; case SOCKCMD_REPLY_EPERM: PARSE_TEXT_DATA_SSCANF(sockcmd_dat_eperm_t, &d->descr); break; default: sockcmd_p->data = xmalloc(args_len+1); memcpy(sockcmd_p->data, args, args_len); ((char *)sockcmd_p->data)[args_len] = 0; break; } return 0; } int socket_recv(clsyncsock_t *clsyncsock, sockcmd_t *sockcmd_p) { static char bufs[SOCKET_MAX][SOCKET_BUFSIZ]; char *buf, *ptr, *start, *end; int clsyncsock_sock; size_t filled_length, rest_length, recv_length, filled_length_new; errno = 0; clsyncsock_sock = clsyncsock->sock; buf = bufs[clsyncsock_sock]; start = recv_stps[clsyncsock_sock]; start = (start==NULL ? buf : start); ptr = recv_ptrs[clsyncsock_sock]; ptr = (ptr==NULL ? buf : ptr); debug(3, "buf==%p; start==%p; ptr==%p", buf, start, ptr); while (1) { filled_length = ptr-buf; rest_length = SOCKET_BUFSIZ-filled_length-16; if (rest_length <= 0) { if(!socket_overflow_fix(buf, &start, &ptr)) { debug(1, "Got too big message. Ignoring."); ptr = buf; } continue; } recv_length = recv(clsyncsock_sock, ptr, rest_length, 0); filled_length_new = filled_length + recv_length; debug(5, "recv_length == %u; filled_length_new == %u", recv_length, filled_length_new); if (recv_length == 0) return ECONNRESET; if (recv_length < 0) return errno; switch (clsyncsock->prot) { case 0: { // Checking if binary uint16_t cmd_id_binary = *(uint16_t *)buf; clsyncsock->subprot = ( cmd_id_binary == SOCKCMD_REQUEST_NEGOTIATION || cmd_id_binary == SOCKCMD_REPLY_NEGOTIATION ) ? SUBPROT0_BINARY : SUBPROT0_TEXT; // Processing switch (clsyncsock->subprot) { case SUBPROT0_TEXT: if ((end=strchr(ptr, '\n')) != NULL) { if (sscanf(start, "%lu %u", &sockcmd_p->cmd_num, (unsigned int *)&sockcmd_p->cmd_id) != 2) { *end = 0; error("It's expected to parse \"%%lu %%u\" from \"%s\"", start); *end = '\n'; return errno = ENOMSG; } char *str_args = start; // Skipping the first number while (*str_args >= '0' && *str_args <= '9') str_args++; // Skipping the space str_args++; // Skipping the second number while (*str_args >= '0' && *str_args <= '9') str_args++; // Skipping the space str_args++; // Parsing the arguments if (end > str_args) parse_text_data(sockcmd_p, str_args, end-str_args); // TODO Process message here goto l_socket_recv_end; } break; default: return errno = ENOPROTOOPT; } break; } default: return errno = ENOPROTOOPT; } } l_socket_recv_end: // ---------------------------------- // buf ptr end filled // cut: --------------- // start ptr // new new start = &end[1]; ptr = &buf[filled_length_new]; // No data buffered. Reset "start" and "ptr". if (start == ptr) { start = buf; ptr = buf; } // Remembering the values recv_stps[clsyncsock_sock] = start; recv_ptrs[clsyncsock_sock] = ptr; debug(3, "sockcmd_p->cmd_num == %lu; sockcmd_p->cmd_id == %i; buf==%p; ptr==%p; end==%p, filled=%p, buf_end==%p", sockcmd_p->cmd_num, sockcmd_p->cmd_id, buf, ptr, end, &buf[filled_length_new], &buf[SOCKET_BUFSIZ]); return 0; } int socket_sendinvalid(clsyncsock_t *clsyncsock_p, sockcmd_t *sockcmd_p) { if (sockcmd_p->cmd_id >= 1000) return socket_reply(clsyncsock_p, sockcmd_p, SOCKCMD_REPLY_INVALIDCMDID, sockcmd_p->cmd_num); else return socket_reply(clsyncsock_p, sockcmd_p, SOCKCMD_REPLY_UNKNOWNCMD, sockcmd_p->cmd_id, sockcmd_p->cmd_num); } int socket_procclsyncsock(socket_sockthreaddata_t *arg) { char _sockcmd_buf[SOCKET_BUFSIZ]={0}; sockcmd_t *sockcmd_p = (sockcmd_t *)_sockcmd_buf; clsyncsock_t *clsyncsock_p = arg->clsyncsock_p; clsyncsock_procfunct_t procfunct = arg->procfunct; //sockprocflags_t flags = arg->flags; enum auth_flags { AUTHFLAG_ENTERED_LOGIN = 0x01, }; typedef enum auth_flags auth_flags_t; auth_flags_t auth_flags = 0; debug(3, "Started new thread for new connection."); arg->state = (arg->authtype == SOCKAUTH_NULL) ? CLSTATE_MAIN : CLSTATE_AUTH; socket_send(clsyncsock_p, SOCKCMD_REQUEST_NEGOTIATION, clsyncsock_p->prot, clsyncsock_p->subprot); while ((arg->running && *arg->running) && (arg->state==CLSTATE_AUTH || arg->state==CLSTATE_MAIN)) { debug(3, "Iteration."); // Receiving message int ret; if ((ret = socket_recv(clsyncsock_p, sockcmd_p))) { debug(2, "Got error while receiving a message from clsyncsock with sock %u. Ending the thread.", arg->clsyncsock_p->sock); break; } // Checking for a callback for this answer { uint64_t cmd_num = sockcmd_p->cmd_num; int i; i = cmd_num % (2*CLSYNCSOCK_WINDOW); while (clsyncsock_p->cbqueue_cache[i] != NULL) { if (clsyncsock_p->cbqueue_cache[i]->cmd_num == cmd_num) { // Found! clsynccbqueue_t *cbq; cbq = clsyncsock_p->cbqueue_cache[i]; // Calling the callback function cbq->callback_funct(arg, sockcmd_p, cbq->callback_arg); // Removing from queue memcpy(cbq, &clsyncsock_p->cbqueue[--clsyncsock_p->cbqueue_len], sizeof(*cbq)); clsyncsock_p->cbqueue_cache[i] = NULL; } i++; } } // Processing the message if (procfunct(arg, sockcmd_p)) switch (sockcmd_p->cmd_id) { case SOCKCMD_REPLY_NEGOTIATION: case SOCKCMD_REQUEST_NEGOTIATION: { sockcmd_dat_negotiation_t *data = (sockcmd_dat_negotiation_t *)sockcmd_p->data; switch (data->prot) { case 0: switch (data->subprot) { case SUBPROT0_TEXT: case SUBPROT0_BINARY: clsyncsock_p->subprot = data->subprot; if (sockcmd_p->cmd_id == SOCKCMD_REQUEST_NEGOTIATION) socket_reply(clsyncsock_p, sockcmd_p, SOCKCMD_REPLY_NEGOTIATION, data->prot, data->subprot); else { socket_reply(clsyncsock_p, sockcmd_p, SOCKCMD_REPLY_ACK, sockcmd_p->cmd_id, sockcmd_p->cmd_num); debug(1, "Negotiated proto: %u %u", data->prot, data->subprot); } break; default: socket_reply(clsyncsock_p, sockcmd_p, SOCKCMD_REPLY_EINVAL, sockcmd_p->cmd_id, sockcmd_p->cmd_num, "Incorrect subprotocol id"); } break; default: socket_reply(clsyncsock_p, sockcmd_p, SOCKCMD_REPLY_EINVAL, sockcmd_p->cmd_id, sockcmd_p->cmd_num, "Incorrect protocol id"); } break; } case SOCKCMD_REQUEST_VERSION: { socket_reply(clsyncsock_p, sockcmd_p, SOCKCMD_REPLY_VERSION, VERSION_MAJ, VERSION_MIN, REVISION); break; } case SOCKCMD_REQUEST_QUIT: { socket_reply(clsyncsock_p, sockcmd_p, SOCKCMD_REPLY_BYE); arg->state = CLSTATE_DYING; break; } default: socket_sendinvalid(clsyncsock_p, sockcmd_p); break; } if (sockcmd_p->data != NULL) { free(sockcmd_p->data); sockcmd_p->data = NULL; } // Check if the socket is still alive if (socket_check(clsyncsock_p)) { debug(1, "clsyncsock socket error: %s", strerror(errno)); break; } // Sending prompt switch (arg->state) { case CLSTATE_AUTH: if (!(auth_flags&AUTHFLAG_ENTERED_LOGIN)) socket_send(clsyncsock_p, SOCKCMD_REQUEST_LOGIN); break; default: break; } } debug(3, "Ending a connection thread."); socket_thread_delete(arg); return 0; } socket_sockthreaddata_t *socket_thread_new() { pthread_mutex_lock(&socket_thread_mutex); socket_sockthreaddata_t *threaddata_p = &sockthreaddata[clsyncsockthreads_num]; if (clsyncsockthreads_num >= SOCKET_MAX) { error("Warning: socket_thread_new(): Too many connection threads."); errno = EUSERS; pthread_mutex_unlock(&socket_thread_mutex); return NULL; } threaddata_p->id = clsyncsockthreads_num; clsyncsockthread_busy[clsyncsockthreads_num]=1; // TODO: SECURITY: Possible DoS-attack on huge "SOCKET_MAX" value. Fix it. while (clsyncsockthread_busy[++clsyncsockthreads_num]); #ifdef PARANOID // Processing the events: checking if previous check were been made right if (threaddata_p->state != CLSTATE_NONE) { // This's not supposed to be error("Internal-Error: socket_newconnarg(): connproc_arg->state != CLSTATE_NONE"); pthread_mutex_unlock(&socket_thread_mutex); errno = EILSEQ; return NULL; } #endif // Processing the events: creating a thread for new connection debug(3, "clsyncsockthreads_count == %u;\tclsyncsockthreads_last == %u;\tclsyncsockthreads_num == %u", clsyncsockthreads_count, clsyncsockthreads_last, clsyncsockthreads_num); clsyncsockthreads_last = MAX(clsyncsockthreads_last, clsyncsockthreads_num); clsyncsockthreads_count++; pthread_mutex_unlock(&socket_thread_mutex); return threaddata_p; } socket_sockthreaddata_t *socket_thread_attach(clsyncsock_t *clsyncsock_p) { socket_sockthreaddata_t *threaddata_p = socket_thread_new(); if (threaddata_p == NULL) return NULL; threaddata_p->clsyncsock_p = clsyncsock_p; return threaddata_p; } int socket_thread_start(socket_sockthreaddata_t *threaddata_p) { if(pthread_create(&threaddata_p->thread, NULL, (void *(*)(void *))socket_procclsyncsock, threaddata_p)) { error("Cannot create a thread for connection"); return errno; } return 0; } int socket_init() { return 0; } int socket_deinit() { return 0; } clsync-0.4.1/socket.h000066400000000000000000000150321252417542300144560ustar00rootroot00000000000000/* clsync - file tree sync utility based on inotify Copyright (C) 2013 Dmitry Yu Okunev 0x8E30679C This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef __CLSYNC_SOCKET_H #define __CLSYNC_SOCKET_H #ifdef __linux__ #include #endif #include #include #include "clsync.h" #include "ctx.h" #define SOCKET_DEFAULT_PROT 0 #define SOCKET_DEFAULT_SUBPROT SUBPROT0_TEXT // buffer size #define SOCKET_BUFSIZ (1<<12) #if PIC # define SOCKET_PROVIDER_LIBCLSYNC #else # define SOCKET_PROVIDER_CLSYNC #endif #ifdef SOCKET_PROVIDER_LIBCLSYNC # define SOCKET_MAX SOCKET_MAX_LIBCLSYNC #endif #ifdef SOCKET_PROVIDER_CLSYNC # define SOCKET_MAX SOCKET_MAX_CLSYNC #endif struct socket_sockthreaddata; struct sockcmd; typedef int (*clsyncsock_cb_funct_t)(struct socket_sockthreaddata *thread, struct sockcmd *sockcmd_p, void *arg); struct clsynccbqueue { uint64_t cmd_num; clsyncsock_cb_funct_t callback_funct; void *callback_arg; }; typedef struct clsynccbqueue clsynccbqueue_t; struct clsyncsock { int sock; uint16_t prot; uint16_t subprot; uint64_t cmd_num; size_t cbqueue_len; clsynccbqueue_t cbqueue[CLSYNCSOCK_WINDOW+1]; clsynccbqueue_t *cbqueue_cache[4*CLSYNCSOCK_WINDOW+1]; // It's a hacky hash-table of size "CLSYNCSOCK_WINDOW*2" }; typedef struct clsyncsock clsyncsock_t; struct clsyncthread { clsyncsock_t *clsyncsock_p; void *arg; void *funct_arg_free; }; typedef struct clsyncthread clsyncthread_t; enum subprot0 { SUBPROT0_TEXT, SUBPROT0_BINARY, }; typedef enum subprot0 subprot0_t; enum clsyncsock_state { CLSTATE_NONE = 0, CLSTATE_AUTH, CLSTATE_MAIN, CLSTATE_DYING, CLSTATE_DIED, }; typedef enum clsyncsock_state clsyncsock_state_t; enum sockcmd_id { SOCKCMD_REQUEST_NEGOTIATION = 000, SOCKCMD_REPLY_NEGOTIATION = 001, SOCKCMD_REPLY_ACK = 150, SOCKCMD_REPLY_UNKNOWNCMD = 160, SOCKCMD_REPLY_INVALIDCMDID = 161, SOCKCMD_REPLY_EINVAL = 162, SOCKCMD_REPLY_EEXIST = 163, SOCKCMD_REPLY_EPERM = 164, SOCKCMD_REPLY_ECUSTOM = 199, SOCKCMD_REQUEST_VERSION = 200, SOCKCMD_REQUEST_INFO = 201, SOCKCMD_REQUEST_DUMP = 202, SOCKCMD_REQUEST_LOGIN = 210, SOCKCMD_REQUEST_SET = 211, SOCKCMD_REQUEST_DIE = 240, SOCKCMD_REQUEST_QUIT = 250, SOCKCMD_REPLY_VERSION = 300, SOCKCMD_REPLY_INFO = 301, SOCKCMD_REPLY_DUMP = 302, SOCKCMD_REPLY_LOGIN = 310, SOCKCMD_REPLY_SET = 311, SOCKCMD_REPLY_DIE = 340, SOCKCMD_REPLY_BYE = 350, SOCKCMD_REPLY_UNEXPECTEDEND = 351, SOCKCMD_MAXID }; typedef enum sockcmd_id sockcmd_id_t; struct sockcmd_dat_negotiation { uint16_t prot; uint16_t subprot; }; typedef struct sockcmd_dat_negotiation sockcmd_dat_negotiation_t; struct sockcmd_dat_ack { uint64_t cmd_num; uint16_t cmd_id; }; typedef struct sockcmd_dat_ack sockcmd_dat_ack_t; #define sockcmd_dat_einval sockcmd_dat_ack #define sockcmd_dat_einval_t sockcmd_dat_ack_t #define sockcmd_dat_unknowncmd sockcmd_dat_ack #define sockcmd_dat_unknowncmd_t sockcmd_dat_ack_t struct sockcmd_dat_invalidcmd { uint64_t cmd_num; }; typedef struct sockcmd_dat_invalidcmd sockcmd_dat_invalidcmd_t; struct sockcmd_dat_version { int major; int minor; char revision[1<<8]; }; typedef struct sockcmd_dat_version sockcmd_dat_version_t; struct sockcmd_dat_info { char config_block[1<<8]; char label[1<<8]; char flags[OPTION_FLAGS]; char flags_set[OPTION_FLAGS]; }; typedef struct sockcmd_dat_info sockcmd_dat_info_t; struct sockcmd_dat_dump { char dir_path[PATH_MAX]; }; typedef struct sockcmd_dat_dump sockcmd_dat_dump_t; struct sockcmd_dat_eexist { char file_path[PATH_MAX]; }; typedef struct sockcmd_dat_eexist sockcmd_dat_eexist_t; struct sockcmd_dat_eperm { char descr[BUFSIZ]; }; typedef struct sockcmd_dat_eperm sockcmd_dat_eperm_t; struct sockcmd_dat_set { char key[BUFSIZ]; char value[BUFSIZ]; }; typedef struct sockcmd_dat_set sockcmd_dat_set_t; struct sockcmd { uint64_t cmd_num; uint16_t cmd_id; size_t data_len; void *data; }; typedef struct sockcmd sockcmd_t; enum sockprocflags { SOCKPROCFLAG_NONE = 0x00, }; typedef enum sockprocflags sockprocflags_t; enum sockauth_id { SOCKAUTH_UNSET = 0, SOCKAUTH_NULL, SOCKAUTH_PAM, }; typedef enum sockauth_id sockauth_id_t; struct socket_sockthreaddata; typedef int (*clsyncsock_procfunct_t)(struct socket_sockthreaddata *, sockcmd_t *); typedef void (*freefunct_t)(void *); struct socket_sockthreaddata { int id; clsyncsock_procfunct_t procfunct; freefunct_t freefunct_arg; clsyncsock_t *clsyncsock_p; void *arg; clsyncsock_state_t state; sockauth_id_t authtype; int *running; // Pointer to interger with non-zero value to continue running sockprocflags_t flags; pthread_t thread; }; typedef struct socket_sockthreaddata socket_sockthreaddata_t; extern int socket_reply(clsyncsock_t *clsyncsock_p, sockcmd_t *sockcmd_p, sockcmd_id_t cmd_id, ...); extern int socket_send(clsyncsock_t *clsyncsock, sockcmd_id_t cmd_id, ...); extern int socket_send_cb(clsyncsock_t *clsyncsock_p, sockcmd_id_t cmd_id, clsyncsock_cb_funct_t cb, void *cb_arg, ...); extern int socket_sendinvalid(clsyncsock_t *clsyncsock_p, sockcmd_t *sockcmd_p); extern int socket_recv(clsyncsock_t *clsyncsock, sockcmd_t *sockcmd); extern int socket_check_bysock(int sock); extern clsyncsock_t *socket_accept(int sock); extern int socket_cleanup(clsyncsock_t *clsyncsock_p); extern int socket_close(clsyncsock_t *clsyncsock_p); extern int socket_init(); extern int socket_deinit(); extern int socket_procclsyncsock(socket_sockthreaddata_t *arg); extern clsyncsock_t *socket_connect_unix(const char *const socket_path); extern clsyncsock_t *socket_listen_unix (const char *const socket_path); extern socket_sockthreaddata_t *socket_thread_attach(clsyncsock_t *clsyncsock_p); extern int socket_thread_start(socket_sockthreaddata_t *threaddata_p); extern int clsyncsocks_num; extern int clsyncsocks_count; extern int clsyncsocks_last; extern const char *const textmessage_args[]; extern const char *const textmessage_descr[]; #endif clsync-0.4.1/stringex.c000066400000000000000000000061611252417542300150270ustar00rootroot00000000000000/* clsync - file tree sync utility based on inotify/kqueue/bsm Copyright (C) 2014 Dmitry Yu Okunev 0x8E30679C This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include // free() #include // strtok_r() #include // errno #include "malloc.h" #include "error.h" static int _str_splitargs( char *ptr, char **arg_start_p, int quotes, int (*handler)(char *, size_t, void *), char *additional_arg ) { char *arg_start, *arg; size_t arg_len; int rc; arg_start = *arg_start_p; *arg_start_p = &ptr[1]; arg_len = ptr - arg_start; if (arg_len == 0) // Skipping nearby spaces return 0; arg = xmalloc(arg_len+1); if (quotes) { int s, d; s = d = 0; while (s < arg_len) { if (arg_start[s]) arg[d++] = arg_start[s]; s++; } arg_len = d; } else memcpy(arg, arg_start, arg_len); #ifdef _DEBUG debug(15, "%p %p %i: <%s>", arg_start, ptr, arg_len, arg); #endif arg[arg_len] = 0; if ((rc = handler(arg, arg_len, additional_arg))) { free(arg); return rc; } return 0; } int str_splitargs( char *_instr, int (*handler)(char *, size_t, void *), void *arg ) { debug(9, ""); char *arg_start, *ptr, *instr; int quotes = 0; instr = strdup(_instr); ptr = instr; arg_start = instr; while (1) { ptr = strpbrk(ptr, " \t\"\'"); #ifdef _DEBUG debug(10, "ptr == %p", ptr); #endif if (ptr == NULL) break; #ifdef _DEBUG debug(10, "*ptr == \"%c\" (%i)", *ptr, *ptr); #endif switch (*(ptr++)) { case ' ': case '\t': { int rc; if ((rc = _str_splitargs(&ptr[-1], &arg_start, quotes, handler, arg))) return rc; quotes = 0; break; } case '"': ptr[-1] = 0; quotes++; while ((ptr = strchr(ptr, '"')) != NULL) { // Checking for escaping char *p; p = &ptr[-1]; while (*p == '\\') { p--; #ifdef PARANOID if (p < instr) critical("Dangerous internal error"); #endif } if ((ptr-p)%2) break; } if (ptr == NULL) { errno = EINVAL; error("Unterminated quote <\"> in string: <%s>", instr); return errno; } *ptr = 0; quotes++; ptr++; break; case '\'': ptr[-1] = 0; quotes++; ptr = strchr(ptr, '\''); if (ptr == NULL) { errno = EINVAL; error("Unterminated quote <'> in string: <%s>", instr); return errno; } *ptr = 0; quotes++; ptr++; break; } } int rc = _str_splitargs(strchr(arg_start, 0), &arg_start, quotes, handler, arg); free(instr); return rc; } clsync-0.4.1/stringex.h000066400000000000000000000016341252417542300150340ustar00rootroot00000000000000/* clsync - file tree sync utility based on inotify/kqueue/bsm Copyright (C) 2014 Dmitry Yu Okunev 0x8E30679C This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ extern int str_splitargs(const char *const instr, int (*handler)(char *outstr, size_t outstr_len, void *arg), void *arg); clsync-0.4.1/sync.c000066400000000000000000003467241252417542300141540ustar00rootroot00000000000000/* clsync - file tree sync utility based on inotify/kqueue Copyright (C) 2013-2014 Dmitry Yu Okunev 0x8E30679C This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "common.h" #if KQUEUE_SUPPORT # include "mon_kqueue.h" #endif #if INOTIFY_SUPPORT # include "mon_inotify.h" #endif #if FANOTIFY_SUPPORT # include "mon_fanotify.h" #endif #if BSM_SUPPORT # include "mon_bsm.h" # include #endif #if GIO_SUPPORT # include # include "mon_gio.h" #endif #include "main.h" #include "error.h" #include "fileutils.h" #include "malloc.h" #include "cluster.h" #include "sync.h" #include "glibex.h" #include "control.h" #include "indexes.h" #include "privileged.h" #include "rules.h" #if CGROUP_SUPPORT # include "cgroup.h" #endif #include #include pthread_t pthread_sighandler; // seqid - is a counter of main loop. But it may overflow and it's required to compare // seqid-values anyway. // So if (a-b) is too big, let's assume, that "b>1) #define SEQID_EQ(a, b) ((a)==(b)) #define SEQID_GE(a, b) ((a)-(b) < SEQID_WINDOW) #define SEQID_LE(a, b) ((b)-(a) < SEQID_WINDOW) #define SEQID_GT(a, b) ((!SEQID_EQ(a, b)) && (SEQID_GE(a, b))) #define SEQID_LT(a, b) ((!SEQID_EQ(a, b)) && (SEQID_LE(a, b))) static unsigned int _sync_seqid_value=0; static inline unsigned int sync_seqid() { return _sync_seqid_value++; } static inline void setenv_iteration(uint32_t iteration_num) { char iterations[sizeof("4294967296")]; // 4294967296 == 2**32 sprintf(iterations, "%i", iteration_num); setenv("CLSYNC_ITERATION", iterations, 1); return; } static inline void finish_iteration(ctx_t *ctx_p) { if (ctx_p->iteration_num < ~0) // ~0 is the max value for unsigned variables ctx_p->iteration_num++; if (!ctx_p->flags[THREADING]) setenv_iteration(ctx_p->iteration_num); debug(3, "next iteration: %u/%u", ctx_p->iteration_num, ctx_p->flags[MAXITERATIONS]); return; } gpointer eidup(gpointer ei_gp) { eventinfo_t *ei = (eventinfo_t *)ei_gp; eventinfo_t *ei_dup = (eventinfo_t *)xmalloc(sizeof(*ei)); memcpy(ei_dup, ei, sizeof(*ei)); return (gpointer)ei_dup; } static inline void evinfo_merge(ctx_t *ctx_p, eventinfo_t *evinfo_dst, eventinfo_t *evinfo_src) { debug(3, "evinfo_dst: seqid_min == %u; seqid_max == %u; objtype_old == %i; objtype_new == %i; \t" "evinfo_src: seqid_min == %u; seqid_max == %u; objtype_old == %i; objtype_new == %i", evinfo_dst->seqid_min, evinfo_dst->seqid_max, evinfo_dst->objtype_old, evinfo_dst->objtype_new, evinfo_src->seqid_min, evinfo_src->seqid_max, evinfo_src->objtype_old, evinfo_src->objtype_new ); #if KQUEUE_SUPPORT | INOTIFY_SUPPORT switch(ctx_p->flags[MONITOR]) { #ifdef KQUEUE_SUPPORT case NE_KQUEUE: #endif #ifdef INOTIFY_SUPPORT case NE_INOTIFY: #endif evinfo_dst->evmask |= evinfo_src->evmask; break; } #endif evinfo_dst->flags |= evinfo_src->flags; if(SEQID_LE(evinfo_src->seqid_min, evinfo_dst->seqid_min)) { evinfo_dst->objtype_old = evinfo_src->objtype_old; evinfo_dst->seqid_min = evinfo_src->seqid_min; } if(SEQID_GE(evinfo_src->seqid_max, evinfo_dst->seqid_max)) { evinfo_dst->objtype_new = evinfo_src->objtype_new; evinfo_dst->seqid_max = evinfo_src->seqid_max; switch(ctx_p->flags[MONITOR]) { #ifdef GIO_SUPPORT case NE_GIO: evinfo_dst->evmask = evinfo_src->evmask; break; #endif #ifdef BSM_SUPPORT case NE_BSM: case NE_BSM_PREFETCH: evinfo_dst->evmask = evinfo_src->evmask; break; #endif default: break; } } return; } static inline int _exitcode_process(ctx_t *ctx_p, int exitcode) { if (ctx_p->isignoredexitcode[(unsigned char)exitcode]) return 0; if (exitcode && !((ctx_p->flags[MODE]==MODE_RSYNCDIRECT) && (exitcode == 24))) { error("Got non-zero exitcode %i from __sync_exec().", exitcode); return exitcode; } return 0; } int exitcode_process(ctx_t *ctx_p, int exitcode) { int err = _exitcode_process(ctx_p, exitcode); if(err) error("Got error-report from exitcode_process().\nExitcode is %i, strerror(%i) returns \"%s\". However strerror() is not ensures compliance " "between exitcode and error description for every utility. So, e.g if you're using rsync, you should look for the error description " "into rsync's manpage (\"man 1 rsync\"). Also some advices about diagnostics can be found in clsync's manpage (\"man 1 clsync\", see DIAGNOSTICS)", exitcode, exitcode, strerror(exitcode)); return err; } threadsinfo_t *thread_info() { // TODO: optimize this static threadsinfo_t threadsinfo={{{{0}}},{{{0}}},0}; if (!threadsinfo.mutex_init) { int i=0; while (i < PTHREAD_MUTEX_MAX) { if (pthread_mutex_init(&threadsinfo.mutex[i], NULL)) { error("Cannot pthread_mutex_init()."); return NULL; } if (pthread_cond_init (&threadsinfo.cond [i], NULL)) { error("Cannot pthread_cond_init()."); return NULL; } i++; } threadsinfo.mutex_init++; } return &threadsinfo; } #define thread_info_lock() _thread_info_lock(__FUNCTION__) static inline threadsinfo_t *_thread_info_lock(const char *const function_name) { threadsinfo_t *threadsinfo_p = thread_info(); debug(4, "used by %s()", function_name); pthread_mutex_lock (&threadsinfo_p->mutex[PTHREAD_MUTEX_THREADSINFO]); return threadsinfo_p; } #define thread_info_unlock(...) _thread_info_unlock(__FUNCTION__, __VA_ARGS__) static inline int _thread_info_unlock(const char *const function_name, int rc) { threadsinfo_t *threadsinfo_p = thread_info(); debug(4, "used by %s()", function_name); pthread_mutex_unlock(&threadsinfo_p->mutex[PTHREAD_MUTEX_THREADSINFO]); return rc; } int threads_foreach(int (*funct)(threadinfo_t *, void *), state_t state, void *arg) { int i, rc; threadsinfo_t *threadsinfo_p = thread_info_lock(); #ifdef PARANOID if(threadsinfo_p == NULL) return thread_info_unlock(EINVAL); #endif rc = 0; i = 0; while (i < threadsinfo_p->used) { threadinfo_t *threadinfo_p = &threadsinfo_p->threads[i++]; if ((state == STATE_UNKNOWN) || (threadinfo_p->state == state)) { if((rc=funct(threadinfo_p, arg))) break; } } return thread_info_unlock(rc); } time_t thread_nextexpiretime() { time_t nextexpiretime = 0; threadsinfo_t *threadsinfo_p = thread_info_lock(); #ifdef PARANOID if(threadsinfo_p == NULL) return thread_info_unlock(0); #endif int thread_num = threadsinfo_p->used; while(thread_num--) { threadinfo_t *threadinfo_p = &threadsinfo_p->threads[thread_num]; debug(3, "threadsinfo_p->threads[%i].state == %i;\tthreadsinfo_p->threads[%i].pthread == %p;\tthreadsinfo_p->threads[%i].expiretime == %i", thread_num, threadinfo_p->state,thread_num, threadinfo_p->pthread, thread_num, threadinfo_p->expiretime); if(threadinfo_p->state == STATE_EXIT) continue; if(threadinfo_p->expiretime) { if(nextexpiretime) nextexpiretime = MIN(nextexpiretime, threadinfo_p->expiretime); else nextexpiretime = threadinfo_p->expiretime; } } thread_info_unlock(0); debug(3, "nextexpiretime == %i", nextexpiretime); return nextexpiretime; } threadinfo_t *thread_new() { threadsinfo_t *threadsinfo_p = thread_info_lock(); #ifdef PARANOID if(threadsinfo_p == NULL) { thread_info_unlock(0); return NULL; } #endif int thread_num; threadinfo_t *threadinfo_p; if(threadsinfo_p->stacklen) { threadinfo_p = threadsinfo_p->threadsstack[--threadsinfo_p->stacklen]; thread_num = threadinfo_p->thread_num; } else { if(threadsinfo_p->used >= threadsinfo_p->allocated) { threadsinfo_p->allocated += ALLOC_PORTION; debug(2, "Reallocated memory for threadsinfo -> %i.", threadsinfo_p->allocated); threadsinfo_p->threads = (threadinfo_t *) xrealloc((char *)threadsinfo_p->threads, sizeof(*threadsinfo_p->threads) *(threadsinfo_p->allocated+2)); threadsinfo_p->threadsstack = (threadinfo_t **)xrealloc((char *)threadsinfo_p->threadsstack, sizeof(*threadsinfo_p->threadsstack)*(threadsinfo_p->allocated+2)); } thread_num = threadsinfo_p->used++; threadinfo_p = &threadsinfo_p->threads[thread_num]; } #ifdef PARANOID memset(threadinfo_p, 0, sizeof(*threadinfo_p)); #else threadinfo_p->expiretime = 0; threadinfo_p->errcode = 0; threadinfo_p->exitcode = 0; #endif threadinfo_p->thread_num = thread_num; threadinfo_p->state = STATE_RUNNING; debug(2, "thread_new -> thread_num: %i; used: %i", thread_num, threadsinfo_p->used); thread_info_unlock(0); return threadinfo_p; } int thread_del_bynum(int thread_num) { debug(2, "thread_del_bynum(%i)", thread_num); threadsinfo_t *threadsinfo_p = thread_info_lock(); #ifdef PARANOID if(threadsinfo_p == NULL) return thread_info_unlock(errno); #endif if(thread_num >= threadsinfo_p->used) return thread_info_unlock(EINVAL); threadinfo_t *threadinfo_p = &threadsinfo_p->threads[thread_num]; threadinfo_p->state = STATE_EXIT; char **ptr = threadinfo_p->argv; if(ptr != NULL) { while(*ptr) free(*(ptr++)); free(threadinfo_p->argv); } if(thread_num == (threadsinfo_p->used-1)) { threadsinfo_p->used--; debug(3, "thread_del_bynum(%i): there're %i threads left (#0).", thread_num, threadsinfo_p->used - threadsinfo_p->stacklen); return thread_info_unlock(0); } threadinfo_t *t = &threadsinfo_p->threads[threadsinfo_p->used-1]; if(t->state == STATE_EXIT) { threadsinfo_p->used--; debug(3, "%i [%p] -> %i [%p]; left: %i", threadsinfo_p->used, t->pthread, thread_num, threadinfo_p->pthread, threadsinfo_p->used - threadsinfo_p->stacklen); memcpy(threadinfo_p, t, sizeof(*threadinfo_p)); } else { #ifdef PARANOID if(threadsinfo_p->stacklen >= threadsinfo_p->allocated) { error("Threads metadata structures pointers stack overflowed!"); return thread_info_unlock(EINVAL); } #endif threadsinfo_p->threadsstack[threadsinfo_p->stacklen++] = threadinfo_p; } debug(3, "thread_del_bynum(%i): there're %i threads left (#1).", thread_num, threadsinfo_p->used - threadsinfo_p->stacklen); return thread_info_unlock(0); } int thread_gc(ctx_t *ctx_p) { int thread_num; time_t tm = time(NULL); debug(3, "tm == %i; thread %p", tm, pthread_self()); if (!ctx_p->flags[THREADING]) return 0; threadsinfo_t *threadsinfo_p = thread_info_lock(); #ifdef PARANOID if (threadsinfo_p == NULL) return thread_info_unlock(errno); #endif debug(2, "There're %i threads.", threadsinfo_p->used); thread_num=-1; while (++thread_num < threadsinfo_p->used) { int err; threadinfo_t *threadinfo_p = &threadsinfo_p->threads[thread_num]; debug(3, "Trying thread #%i (==%i) (state: %i; expire at: %i, now: %i, exitcode: %i, errcode: %i; i_p: %p; p: %p).", thread_num, threadinfo_p->thread_num, threadinfo_p->state, threadinfo_p->expiretime, tm, threadinfo_p->exitcode, threadinfo_p->errcode, threadinfo_p, threadinfo_p->pthread); if (threadinfo_p->state == STATE_EXIT) continue; if (threadinfo_p->expiretime && (threadinfo_p->expiretime <= tm)) { if(pthread_tryjoin_np(threadinfo_p->pthread, NULL)) { // TODO: check this pthread_tryjoin_np() on error returnings error("Debug3: thread_gc(): Thread #%i is alive too long: %lu <= %lu (started at %lu)", thread_num, threadinfo_p->expiretime, tm, threadinfo_p->starttime); return thread_info_unlock(ETIME); } } #ifndef VERYPARANOID if (threadinfo_p->state != STATE_TERM) { debug(3, "Thread #%i is busy, skipping (#0).", thread_num); continue; } #endif debug(3, "Trying to join thread #%i: %p", thread_num, threadinfo_p->pthread); #ifndef VERYPARANOID switch ((err=pthread_join(threadinfo_p->pthread, NULL))) { #else switch ((err=pthread_tryjoin_np(threadinfo_p->pthread, NULL))) { case EBUSY: debug(3, "Thread #%i is busy, skipping (#1).", thread_num); continue; #endif case EDEADLK: case EINVAL: case 0: debug(3, "Thread #%i is finished with exitcode %i (errcode %i), deleting. threadinfo_p == %p", thread_num, threadinfo_p->exitcode, threadinfo_p->errcode, threadinfo_p); break; default: error("Got error while pthread_join() or pthread_tryjoin_np().", strerror(err), err); return thread_info_unlock(errno); } if (threadinfo_p->errcode) { error("Got error from thread #%i: errcode %i.", thread_num, threadinfo_p->errcode); thread_info_unlock(0); thread_del_bynum(thread_num); return threadinfo_p->errcode; } thread_info_unlock(0); if (thread_del_bynum(thread_num)) return errno; thread_info_lock(); } debug(3, "There're %i threads left.", threadsinfo_p->used - threadsinfo_p->stacklen); return thread_info_unlock(0); } int thread_cleanup(ctx_t *ctx_p) { debug(3, ""); threadsinfo_t *threadsinfo_p = thread_info_lock(); #ifdef PARANOID if (threadsinfo_p == NULL) return thread_info_unlock(errno); #endif // Waiting for threads: debug(1, "There're %i opened threads. Waiting.", threadsinfo_p->used); while (threadsinfo_p->used) { // int err; threadinfo_t *threadinfo_p = &threadsinfo_p->threads[--threadsinfo_p->used]; if (threadinfo_p->state == STATE_EXIT) continue; //pthread_kill(threadinfo_p->pthread, SIGTERM); debug(1, "killing pid %i with SIGTERM", threadinfo_p->child_pid); kill(threadinfo_p->child_pid, SIGTERM); pthread_join(threadinfo_p->pthread, NULL); debug(2, "thread #%i exitcode: %i", threadsinfo_p->used, threadinfo_p->exitcode); /* if(threadinfo_p->callback) if((err=threadinfo_p->callback(ctx_p, threadinfo_p->argv))) warning("Got error from callback function.", strerror(err), err); */ char **ptr = threadinfo_p->argv; while (*ptr) free(*(ptr++)); free(threadinfo_p->argv); } debug(3, "All threads are closed."); // Freeing if (threadsinfo_p->allocated) { free(threadsinfo_p->threads); free(threadsinfo_p->threadsstack); } if (threadsinfo_p->mutex_init) { int i=0; while(i < PTHREAD_MUTEX_MAX) { pthread_mutex_destroy(&threadsinfo_p->mutex[i]); pthread_cond_destroy (&threadsinfo_p->cond [i]); i++; } } #ifdef PARANOID // Reseting memset(threadsinfo_p, 0, sizeof(*threadsinfo_p)); // Just in case; #endif debug(3, "done."); return thread_info_unlock(0); } volatile state_t *state_p = NULL; volatile int exitcode = 0; #define SHOULD_THREAD(ctx_p) ((ctx_p->flags[THREADING] != PM_OFF) && (ctx_p->flags[THREADING] != PM_SAFE || ctx_p->iteration_num)) int exec_argv(char **argv, int *child_pid) { debug(3, "Thread %p.", pthread_self()); pid_t pid; int status; // Forking pid = privileged_fork_execvp(argv[0], (char *const *)argv); // debug(3, "After fork thread %p"")".", pthread_self() ); debug(3, "Child pid is %u", pid); // Setting *child_pid value if (child_pid) *child_pid = pid; // Waiting for process end #ifdef VERYPARANOID sigset_t sigset_exec, sigset_old; sigemptyset(&sigset_exec); sigaddset(&sigset_exec, SIGUSR_BLOPINT); pthread_sigmask(SIG_BLOCK, &sigset_exec, &sigset_old); #endif // debug(3, "Pre-wait thread %p"")".", pthread_self() ); if (privileged_waitpid(pid, &status, 0) != pid) { switch (errno) { case ECHILD: debug(2, "Child %u is already dead.", pid); break; default: error("Cannot waitid()."); return errno; } } // debug(3, "After-wait thread %p"")".", pthread_self() ); #ifdef VERYPARANOID pthread_sigmask(SIG_SETMASK, &sigset_old, NULL); #endif // Return int exitcode = WEXITSTATUS(status); debug(3, "execution completed with exitcode %i", exitcode); return exitcode; } static inline int thread_exit(threadinfo_t *threadinfo_p, int exitcode ) { int err=0; threadinfo_p->exitcode = exitcode; #if _DEBUG_FORCE | VERYPARANOID if (threadinfo_p->pthread != pthread_self()) { error("pthread id mismatch! (i_p->p) %p != (p) %p""", threadinfo_p->pthread, pthread_self() ); return EINVAL; } #endif if (threadinfo_p->callback) { if (threadinfo_p->ctx_p->flags[DEBUG]>2) { debug(3, "thread %p, argv: ", threadinfo_p->pthread); char **argv = threadinfo_p->argv; while(*argv) { debug(3, "\t%p == %s", *argv, *argv); argv++; } } if ((err=threadinfo_p->callback(threadinfo_p->ctx_p, threadinfo_p->callback_arg))) { error("Got error from callback function.", strerror(err), err); threadinfo_p->errcode = err; } } // Notifying the parent-thread, that it's time to collect garbage threads threadinfo_p->state = STATE_TERM; debug(3, "thread %p is sending signal to sighandler to call GC", threadinfo_p->pthread); return pthread_kill(pthread_sighandler, SIGUSR_THREAD_GC); } static inline void so_call_sync_finished(int n, api_eventinfo_t *ei) { int i = 0; api_eventinfo_t *ei_i = ei; while (i < n) { #ifdef PARANOID if (ei_i->path == NULL) { warning("ei_i->path == NULL"); i++; continue; } #endif free((char *)ei_i->path); ei_i++; i++; } if (ei != NULL) free(ei); return; } int so_call_sync_thread(threadinfo_t *threadinfo_p) { debug(3, "thread_num == %i; threadinfo_p == %p; i_p->pthread %p; thread %p", threadinfo_p->thread_num, threadinfo_p, threadinfo_p->pthread, pthread_self()); ctx_t *ctx_p = threadinfo_p->ctx_p; int n = threadinfo_p->n; api_eventinfo_t *ei = threadinfo_p->ei; int err=0, rc=0, try_again = 0; do { try_again = 0; threadinfo_p->try_n++; rc = ctx_p->handler_funct.sync(n, ei); if ((err=exitcode_process(threadinfo_p->ctx_p, rc))) { try_again = ((!ctx_p->retries) || (threadinfo_p->try_n < ctx_p->retries)) && (ctx_p->state != STATE_TERM) && (ctx_p->state != STATE_EXIT); warning("Bad exitcode %i (errcode %i). %s.", rc, err, try_again?"Retrying":"Give up"); if (try_again) { debug(2, "Sleeping for %u seconds before the retry.", ctx_p->syncdelay); sleep(ctx_p->syncdelay); } } } while (err && ((!ctx_p->retries) || (threadinfo_p->try_n < ctx_p->retries)) && (ctx_p->state != STATE_TERM) && (ctx_p->state != STATE_EXIT)); if (err && !ctx_p->flags[IGNOREFAILURES]) { error("Bad exitcode %i (errcode %i)", rc, err); threadinfo_p->errcode = err; } so_call_sync_finished(n, ei); if ((err=thread_exit(threadinfo_p, rc))) { exitcode = err; // This's global variable "exitcode" pthread_kill(pthread_sighandler, SIGTERM); } return rc; } static inline int so_call_sync(ctx_t *ctx_p, indexes_t *indexes_p, int n, api_eventinfo_t *ei) { debug(2, "n == %i", n); if (!SHOULD_THREAD(ctx_p)) { int rc=0, ret=0, err=0; int try_n=0, try_again; state_t status = STATE_UNKNOWN; // indexes_p->nonthreaded_syncing_fpath2ei_ht = g_hash_table_dup(indexes_p->fpath2ei_ht, g_str_hash, g_str_equal, free, free, (gpointer(*)(gpointer))strdup, eidup); indexes_p->nonthreaded_syncing_fpath2ei_ht = indexes_p->fpath2ei_ht; do { try_again = 0; try_n++; alarm(ctx_p->synctimeout); rc = ctx_p->handler_funct.sync(n, ei); alarm(0); if ((err=exitcode_process(ctx_p, rc))) { if ((try_n == 1) && (ctx_p->state != STATE_TERM) && (ctx_p->state != STATE_EXIT)) { status = ctx_p->state; ctx_p->state = STATE_SYNCHANDLER_ERR; main_status_update(ctx_p); } try_again = ((!ctx_p->retries) || (try_n < ctx_p->retries)) && (ctx_p->state != STATE_TERM) && (ctx_p->state != STATE_EXIT); warning("Bad exitcode %i (errcode %i). %s.", rc, err, try_again?"Retrying":"Give up"); if (try_again) { debug(2, "Sleeping for %u seconds before the retry.", ctx_p->syncdelay); sleep(ctx_p->syncdelay); } } } while (err && ((!ctx_p->retries) || (try_n < ctx_p->retries)) && (ctx_p->state != STATE_TERM) && (ctx_p->state != STATE_EXIT)); if (err && !ctx_p->flags[IGNOREFAILURES]) { error("Bad exitcode %i (errcode %i)", rc, err); ret = err; } else if (status != STATE_UNKNOWN) { ctx_p->state = status; main_status_update(ctx_p); } // g_hash_table_destroy(indexes_p->nonthreaded_syncing_fpath2ei_ht); indexes_p->nonthreaded_syncing_fpath2ei_ht = NULL; so_call_sync_finished(n, ei); return ret; } threadinfo_t *threadinfo_p = thread_new(); if (threadinfo_p == NULL) return errno; threadinfo_p->try_n = 0; threadinfo_p->callback = NULL; threadinfo_p->argv = NULL; threadinfo_p->ctx_p = ctx_p; threadinfo_p->starttime = time(NULL); threadinfo_p->fpath2ei_ht = g_hash_table_dup(indexes_p->fpath2ei_ht, g_str_hash, g_str_equal, free, free, (gpointer(*)(gpointer))strdup, eidup); threadinfo_p->n = n; threadinfo_p->ei = ei; threadinfo_p->iteration = ctx_p->iteration_num; if (ctx_p->synctimeout) threadinfo_p->expiretime = threadinfo_p->starttime + ctx_p->synctimeout; if (pthread_create(&threadinfo_p->pthread, NULL, (void *(*)(void *))so_call_sync_thread, threadinfo_p)) { error("Cannot pthread_create()."); return errno; } debug(3, "thread %p", threadinfo_p->pthread); return 0; } static inline int so_call_rsync_finished(ctx_t *ctx_p, const char *inclistfile, const char *exclistfile) { int ret0, ret1; debug(5, ""); if (ctx_p->flags[DONTUNLINK]) return 0; if (inclistfile == NULL) { error("inclistfile == NULL."); return EINVAL; } debug(3, "unlink()-ing \"%s\"", inclistfile); ret0 = unlink(inclistfile); if (ctx_p->flags[RSYNCPREFERINCLUDE]) return ret0; if (exclistfile == NULL) { error("exclistfile == NULL."); return EINVAL; } debug(3, "unlink()-ing \"%s\"", exclistfile); ret1 = unlink(exclistfile); return ret0 == 0 ? ret1 : ret0; } int so_call_rsync_thread(threadinfo_t *threadinfo_p) { debug(3, "thread_num == %i; threadinfo_p == %p; i_p->pthread %p; thread %p", threadinfo_p->thread_num, threadinfo_p, threadinfo_p->pthread, pthread_self()); ctx_t *ctx_p = threadinfo_p->ctx_p; char **argv = threadinfo_p->argv; int err=0, rc=0, try_again; do { try_again=0; threadinfo_p->try_n++; rc = ctx_p->handler_funct.rsync(argv[0], argv[1]); if ((err=exitcode_process(threadinfo_p->ctx_p, rc))) { try_again = ((!ctx_p->retries) || (threadinfo_p->try_n < ctx_p->retries)) && (ctx_p->state != STATE_TERM) && (ctx_p->state != STATE_EXIT); warning("Bad exitcode %i (errcode %i). %s.", rc, err, try_again?"Retrying":"Give up"); if (try_again) { debug(2, "Sleeping for %u seconds before the retry.", ctx_p->syncdelay); sleep(ctx_p->syncdelay); } } } while (try_again); if (err && !ctx_p->flags[IGNOREFAILURES]) { error("Bad exitcode %i (errcode %i)", rc, err); threadinfo_p->errcode = err; } if ((err=so_call_rsync_finished(ctx_p, argv[0], argv[1]))) { exitcode = err; // This's global variable "exitcode" pthread_kill(pthread_sighandler, SIGTERM); } free(argv[0]); free(argv[1]); free(argv); if ((err=thread_exit(threadinfo_p, rc))) { exitcode = err; // This's global variable "exitcode" pthread_kill(pthread_sighandler, SIGTERM); } return rc; } static inline int so_call_rsync(ctx_t *ctx_p, indexes_t *indexes_p, const char *inclistfile, const char *exclistfile) { debug(2, "inclistfile == \"%s\"; exclistfile == \"%s\"", inclistfile, exclistfile); if (!SHOULD_THREAD(ctx_p)) { debug(3, "ctx_p->handler_funct.rsync == %p", ctx_p->handler_funct.rsync); // indexes_p->nonthreaded_syncing_fpath2ei_ht = g_hash_table_dup(indexes_p->fpath2ei_ht, g_str_hash, g_str_equal, free, free, (gpointer(*)(gpointer))strdup, eidup); indexes_p->nonthreaded_syncing_fpath2ei_ht = indexes_p->fpath2ei_ht; int rc=0, err=0; int try_n=0, try_again; state_t status = STATE_UNKNOWN; do { try_again = 0; try_n++; alarm(ctx_p->synctimeout); rc = ctx_p->handler_funct.rsync(inclistfile, exclistfile); alarm(0); if ((err=exitcode_process(ctx_p, rc))) { if ((try_n == 1) && (ctx_p->state != STATE_TERM) && (ctx_p->state != STATE_EXIT)) { status = ctx_p->state; ctx_p->state = STATE_SYNCHANDLER_ERR; main_status_update(ctx_p); } try_again = ((!ctx_p->retries) || (try_n < ctx_p->retries)) && (ctx_p->state != STATE_TERM) && (ctx_p->state != STATE_EXIT); warning("Bad exitcode %i (errcode %i). %s.", rc, err, try_again?"Retrying":"Give up"); if (try_again) { debug(2, "Sleeping for %u seconds before the retry.", ctx_p->syncdelay); sleep(ctx_p->syncdelay); } } } while (try_again); if (err && !ctx_p->flags[IGNOREFAILURES]) { error("Bad exitcode %i (errcode %i)", rc, err); rc = err; } else if (status != STATE_UNKNOWN) { ctx_p->state = status; main_status_update(ctx_p); } // g_hash_table_destroy(indexes_p->nonthreaded_syncing_fpath2ei_ht); indexes_p->nonthreaded_syncing_fpath2ei_ht = NULL; int ret_cleanup; if ((ret_cleanup=so_call_rsync_finished(ctx_p, inclistfile, exclistfile))) return rc ? rc : ret_cleanup; return rc; } threadinfo_t *threadinfo_p = thread_new(); if(threadinfo_p == NULL) return errno; threadinfo_p->try_n = 0; threadinfo_p->callback = NULL; threadinfo_p->argv = xmalloc(sizeof(char *) * 3); threadinfo_p->ctx_p = ctx_p; threadinfo_p->starttime = time(NULL); threadinfo_p->fpath2ei_ht = g_hash_table_dup(indexes_p->fpath2ei_ht, g_str_hash, g_str_equal, free, free, (gpointer(*)(gpointer))strdup, eidup); threadinfo_p->iteration = ctx_p->iteration_num; threadinfo_p->argv[0] = strdup(inclistfile); threadinfo_p->argv[1] = strdup(exclistfile); if(ctx_p->synctimeout) threadinfo_p->expiretime = threadinfo_p->starttime + ctx_p->synctimeout; if(pthread_create(&threadinfo_p->pthread, NULL, (void *(*)(void *))so_call_rsync_thread, threadinfo_p)) { error("Cannot pthread_create()."); return errno; } debug(3, "thread %p", threadinfo_p->pthread); return 0; } // === SYNC_EXEC() === { //#define SYNC_EXEC(...) (SHOULD_THREAD(ctx_p) ? sync_exec_thread : sync_exec )(__VA_ARGS__) #define SYNC_EXEC_ARGV(...) (SHOULD_THREAD(ctx_p) ? sync_exec_argv_thread : sync_exec_argv)(__VA_ARGS__) #define debug_argv_dump(level, argv)\ if (unlikely(ctx_p->flags[DEBUG] >= level))\ argv_dump(level, argv) static inline void argv_dump(int debug_level, char **argv) { #ifdef _DEBUG_FORCE debug(19, "(%u, %p)", debug_level, argv); #endif char **argv_p = argv; while (*argv_p != NULL) { debug(debug_level, "%p: \"%s\"", *argv_p, *argv_p); argv_p++; } return; } #define _sync_exec_getargv(argv, firstarg, COPYARG) {\ va_list arglist;\ va_start(arglist, firstarg);\ \ int i = 0;\ do {\ char *arg;\ if(i >= MAXARGUMENTS) {\ error("Too many arguments (%i >= %i).", i, MAXARGUMENTS);\ return ENOMEM;\ }\ arg = (char *)va_arg(arglist, const char *const);\ argv[i] = arg!=NULL ? COPYARG : NULL;\ } while(argv[i++] != NULL);\ va_end(arglist);\ } char *sync_path_rel2abs(ctx_t *ctx_p, const char *path_rel, size_t path_rel_len, size_t *path_abs_len_p, char *path_abs_oldptr) { if (path_rel == NULL) return NULL; if (path_rel_len == -1) path_rel_len = strlen(path_rel); char *path_abs; size_t watchdirlen = (ctx_p->watchdir == ctx_p->watchdirwslash) ? 0 : ctx_p->watchdirlen; // if [watchdir == "/"] ? 0 : watchdir.length() size_t path_abs_len = path_rel_len + watchdirlen + 1; path_abs = (path_abs_len_p == NULL || path_abs_len >= *path_abs_len_p) ? xrealloc(path_abs_oldptr, path_abs_len+1) : path_abs_oldptr; if (path_abs_oldptr == NULL) { memcpy(path_abs, ctx_p->watchdir, watchdirlen); path_abs[watchdirlen] = '/'; } memcpy(&path_abs[watchdirlen+1], path_rel, path_rel_len+1); if (path_abs_len_p != NULL) *path_abs_len_p = path_abs_len; return path_abs; } char *sync_path_abs2rel(ctx_t *ctx_p, const char *path_abs, size_t path_abs_len, size_t *path_rel_len_p, char *path_rel_oldptr) { if (path_abs == NULL) return NULL; if (path_abs_len == -1) path_abs_len = strlen(path_abs); size_t path_rel_len; char *path_rel; size_t watchdirlen = (ctx_p->watchdir == ctx_p->watchdirwslash) ? 0 : ctx_p->watchdirlen; signed long path_rel_len_signed = path_abs_len - (watchdirlen+1); path_rel_len = (path_rel_len_signed > 0) ? path_rel_len_signed : 0; path_rel = (path_rel_len_p == NULL || path_rel_len >= *path_rel_len_p) ? xrealloc(path_rel_oldptr, path_rel_len+1) : path_rel_oldptr; if (!path_rel_len) { path_rel[0] = 0; return path_rel; } memcpy(path_rel, &path_abs[watchdirlen+1], path_rel_len+1); #ifdef VERYPARANOID // Removing "/" on the end debug(3, "\"%s\" (len: %i) --%i--> \"%s\" (len: %i) + ", path_abs, path_abs_len, path_rel[path_rel_len - 1] == '/', ctx_p->watchdirwslash, watchdirlen+1); if (path_rel[path_rel_len - 1] == '/') path_rel[--path_rel_len] = 0x00; debug(3, "\"%s\" (len: %i)", path_rel, path_rel_len); #endif if (path_rel_len_p != NULL) *path_rel_len_p = path_rel_len; return path_rel; } pid_t clsyncapi_fork(ctx_t *ctx_p) { // if(ctx_p->flags[THREADING]) // return fork(); // Cleaning stale pids. TODO: Optimize this. Remove this GC. int i=0; while (i < ctx_p->children) { if (privileged_waitpid(ctx_p->child_pid[i], NULL, WNOHANG)<0) if(errno==ECHILD) ctx_p->child_pid[i] = ctx_p->child_pid[--ctx_p->children]; i++; } // Too many children if (ctx_p->children >= MAXCHILDREN) { errno = ECANCELED; return -1; } // Forking pid_t pid = fork(); ctx_p->child_pid[ctx_p->children++] = pid; return pid; } int sync_exec_argv(ctx_t *ctx_p, indexes_t *indexes_p, thread_callbackfunct_t callback, thread_callbackfunct_arg_t *callback_arg_p, char **argv) { debug(2, ""); debug_argv_dump(2, argv); // indexes_p->nonthreaded_syncing_fpath2ei_ht = g_hash_table_dup(indexes_p->fpath2ei_ht, g_str_hash, g_str_equal, free, free, (gpointer(*)(gpointer))strdup, eidup); indexes_p->nonthreaded_syncing_fpath2ei_ht = indexes_p->fpath2ei_ht; int exitcode=0, ret=0, err=0; int try_n=0, try_again; state_t status = STATE_UNKNOWN; do { try_again = 0; try_n++; debug(2, "try_n == %u (retries == %u)", try_n, ctx_p->retries); alarm(ctx_p->synctimeout); ctx_p->children = 1; exitcode = exec_argv(argv, ctx_p->child_pid ); ctx_p->children = 0; alarm(0); if ((err=exitcode_process(ctx_p, exitcode))) { if ((try_n == 1) && (ctx_p->state != STATE_TERM) && (ctx_p->state != STATE_EXIT)) { status = ctx_p->state; ctx_p->state = STATE_SYNCHANDLER_ERR; main_status_update(ctx_p); } try_again = ((!ctx_p->retries) || (try_n < ctx_p->retries)) && (ctx_p->state != STATE_TERM) && (ctx_p->state != STATE_EXIT); warning("Bad exitcode %i (errcode %i). %s.", exitcode, err, try_again?"Retrying":"Give up"); if (try_again) { debug(2, "Sleeping for %u seconds before the retry.", ctx_p->syncdelay); sleep(ctx_p->syncdelay); } } } while(try_again); if (err && !ctx_p->flags[IGNOREFAILURES]) { error("Bad exitcode %i (errcode %i)", exitcode, err); ret = err; } else if (status != STATE_UNKNOWN) { ctx_p->state = status; main_status_update(ctx_p); } if (callback != NULL) { int nret = callback(ctx_p, callback_arg_p); if (nret) { error("Got error while callback()."); if (!ret) ret=nret; } } // g_hash_table_destroy(indexes_p->nonthreaded_syncing_fpath2ei_ht); indexes_p->nonthreaded_syncing_fpath2ei_ht = NULL; return ret; } /* static inline int sync_exec(ctx_t *ctx_p, indexes_t *indexes_p, thread_callbackfunct_t callback, thread_callbackfunct_arg_t *callback_arg_p, ...) { int rc; debug(2, ""); char **argv = (char **)xcalloc(sizeof(char *), MAXARGUMENTS); memset(argv, 0, sizeof(char *)*MAXARGUMENTS); _sync_exec_getargv(argv, callback_arg_p, arg); rc = sync_exec_argv(ctx_p, indexes_p, callback, callback_arg_p, argv); free(argv); return rc; } */ int __sync_exec_thread(threadinfo_t *threadinfo_p) { char **argv = threadinfo_p->argv; ctx_t *ctx_p = threadinfo_p->ctx_p; debug(3, "thread_num == %i; threadinfo_p == %p; i_p->pthread %p; thread %p""", threadinfo_p->thread_num, threadinfo_p, threadinfo_p->pthread, pthread_self() ); int err=0, exec_exitcode=0, try_again; do { try_again = 0; threadinfo_p->try_n++; exec_exitcode = exec_argv(argv, &threadinfo_p->child_pid ); if ((err=exitcode_process(threadinfo_p->ctx_p, exec_exitcode))) { try_again = ((!ctx_p->retries) || (threadinfo_p->try_n < ctx_p->retries)) && (ctx_p->state != STATE_TERM) && (ctx_p->state != STATE_EXIT); warning("__sync_exec_thread(): Bad exitcode %i (errcode %i). %s.", exec_exitcode, err, try_again?"Retrying":"Give up"); if (try_again) { debug(2, "Sleeping for %u seconds before the retry.", ctx_p->syncdelay); sleep(ctx_p->syncdelay); } } } while (try_again); if (err && !ctx_p->flags[IGNOREFAILURES]) { error("Bad exitcode %i (errcode %i)", exec_exitcode, err); threadinfo_p->errcode = err; } g_hash_table_destroy(threadinfo_p->fpath2ei_ht); if ((err=thread_exit(threadinfo_p, exec_exitcode))) { exitcode = err; // This's global variable "exitcode" pthread_kill(pthread_sighandler, SIGTERM); } debug(3, "thread_num == %i; threadinfo_p == %p; i_p->pthread %p; thread %p""; errcode %i", threadinfo_p->thread_num, threadinfo_p, threadinfo_p->pthread, pthread_self(), threadinfo_p->errcode); return exec_exitcode; } static inline int sync_exec_argv_thread(ctx_t *ctx_p, indexes_t *indexes_p, thread_callbackfunct_t callback, thread_callbackfunct_arg_t *callback_arg_p, char **argv) { debug(2, ""); debug_argv_dump(2, argv); threadinfo_t *threadinfo_p = thread_new(); if (threadinfo_p == NULL) return errno; threadinfo_p->try_n = 0; threadinfo_p->callback = callback; threadinfo_p->callback_arg = callback_arg_p; threadinfo_p->argv = argv; threadinfo_p->ctx_p = ctx_p; threadinfo_p->starttime = time(NULL); threadinfo_p->fpath2ei_ht = g_hash_table_dup(indexes_p->fpath2ei_ht, g_str_hash, g_str_equal, free, free, (gpointer(*)(gpointer))strdup, eidup); threadinfo_p->iteration = ctx_p->iteration_num; if (ctx_p->synctimeout) threadinfo_p->expiretime = threadinfo_p->starttime + ctx_p->synctimeout; if (pthread_create(&threadinfo_p->pthread, NULL, (void *(*)(void *))__sync_exec_thread, threadinfo_p)) { error("Cannot pthread_create()."); return errno; } debug(3, "thread %p", threadinfo_p->pthread); return 0; } /* static inline int sync_exec_thread(ctx_t *ctx_p, indexes_t *indexes_p, thread_callbackfunct_t callback, thread_callbackfunct_arg_t *callback_arg_p, ...) { debug(2, ""); char **argv = (char **)xcalloc(sizeof(char *), MAXARGUMENTS); memset(argv, 0, sizeof(char *)*MAXARGUMENTS); _sync_exec_getargv(argv, callback_arg_p, strdup(arg)); return sync_exec_argv_thread(ctx_p, indexes_p, callback, callback_arg_p, argv); } */ // } === SYNC_EXEC() === static int sync_queuesync(const char *fpath_rel, eventinfo_t *evinfo, ctx_t *ctx_p, indexes_t *indexes_p, queue_id_t queue_id) { debug(3, "sync_queuesync(\"%s\", ...): fsize == %lu; tres == %lu, queue_id == %u", fpath_rel, evinfo->fsize, ctx_p->bfilethreshold, queue_id); if(queue_id == QUEUE_AUTO) queue_id = (evinfo->fsize > ctx_p->bfilethreshold) ? QUEUE_BIGFILE : QUEUE_NORMAL; queueinfo_t *queueinfo = &ctx_p->_queues[queue_id]; if(!queueinfo->stime) queueinfo->stime = time(NULL); // char *fpath_rel = sync_path_abs2rel(ctx_p, fpath, -1, NULL, NULL); // Filename can contain "" character that conflicts with event-row separator of list-files. if(strchr(fpath_rel, '\n')) { // At the moment, we will just ignore events of such files :( debug(3, "There's \"\\n\" character in path \"%s\". Ignoring it :(. Feedback to: https://github.com/xaionaro/clsync/issues/12", fpath_rel); return 0; } #ifdef CLUSTER_SUPPORT if(ctx_p->cluster_iface) cluster_capture(fpath_rel); #endif eventinfo_t *evinfo_q = indexes_lookupinqueue(indexes_p, fpath_rel, queue_id); if(evinfo_q == NULL) { eventinfo_t *evinfo_dup = (eventinfo_t *)xmalloc(sizeof(*evinfo_dup)); memcpy(evinfo_dup, evinfo, sizeof(*evinfo_dup)); return indexes_queueevent(indexes_p, strdup(fpath_rel), evinfo_dup, queue_id); } else { evinfo_merge(ctx_p, evinfo_q, evinfo); } return 0; } static inline void evinfo_initialevmask(ctx_t *ctx_p, eventinfo_t *evinfo_p, int isdir) { switch(ctx_p->flags[MONITOR]) { #ifdef FANOTIFY_SUPPORT case NE_FANOTIFY: critical("fanotify is not supported"); break; #endif #if INOTIFY_SUPPORT | KQUEUE_SUPPORT #ifdef INOTIFY_SUPPORT case NE_INOTIFY: #endif #ifdef KQUEUE_SUPPORT case NE_KQUEUE: #endif evinfo_p->evmask = IN_CREATE_SELF; if (isdir) evinfo_p->evmask |= IN_ISDIR; break; #endif #ifdef BSM_SUPPORT case NE_BSM: case NE_BSM_PREFETCH: evinfo_p->evmask = (isdir ? AUE_MKDIR : AUE_OPEN_RWC); break; #endif #ifdef GIO_SUPPORT case NE_GIO: evinfo_p->evmask = G_FILE_MONITOR_EVENT_CREATED; break; #endif #ifdef VERYPARANOID default: critical("Unknown monitor subsystem: %u", ctx_p->flags[MONITOR]); #endif } return; } static inline void api_evinfo_initialevmask(ctx_t *ctx_p, api_eventinfo_t *evinfo_p, int isdir) { eventinfo_t evinfo = {0}; evinfo_initialevmask(ctx_p, &evinfo, isdir); evinfo_p->evmask = evinfo.evmask; return; } int sync_dosync(const char *fpath, uint32_t evmask, ctx_t *ctx_p, indexes_t *indexes_p); int sync_initialsync_walk(ctx_t *ctx_p, const char *dirpath, indexes_t *indexes_p, queue_id_t queue_id, initsync_t initsync) { int ret = 0; const char *rootpaths[] = {dirpath, NULL}; eventinfo_t evinfo; FTS *tree; rule_t *rules_p = ctx_p->rules; debug(2, "(ctx_p, \"%s\", indexes_p, %i, %i).", dirpath, queue_id, initsync); char skip_rules = (initsync==INITSYNC_FULL) && ctx_p->flags[INITFULL]; char rsync_and_prefer_excludes = ( (ctx_p->flags[MODE]==MODE_RSYNCDIRECT) || (ctx_p->flags[MODE]==MODE_RSYNCSHELL) || (ctx_p->flags[MODE]==MODE_RSYNCSO) ) && !ctx_p->flags[RSYNCPREFERINCLUDE]; if ((!ctx_p->flags[RSYNCPREFERINCLUDE]) && skip_rules) return 0; skip_rules |= (ctx_p->rules_count == 0); char fts_no_stat = ( ( initsync == INITSYNC_FULL ) || ( ctx_p->_queues[QUEUE_NORMAL ].collectdelay == ctx_p->_queues[QUEUE_BIGFILE].collectdelay ) || ( ctx_p->bfilethreshold == 0 ) ) && !(ctx_p->flags[EXCLUDEMOUNTPOINTS]); int fts_opts = FTS_NOCHDIR | FTS_PHYSICAL | (fts_no_stat ? FTS_NOSTAT : 0) | (ctx_p->flags[ONEFILESYSTEM] ? FTS_XDEV : 0); debug(3, "fts_opts == %p", (void *)(long)fts_opts); tree = privileged_fts_open((char *const *)&rootpaths, fts_opts, NULL, PC_SYNC_INIIALSYNC_WALK_FTS_OPEN); if (tree == NULL) { error("Cannot privileged_fts_open() on \"%s\".", dirpath); return errno; } memset(&evinfo, 0, sizeof(evinfo)); FTSENT *node; char *path_rel = NULL; size_t path_rel_len = 0; #ifdef VERYPARANOID errno = 0; #endif while ((node = privileged_fts_read(tree, PC_SYNC_INIIALSYNC_WALK_FTS_READ))) { switch (node->fts_info) { // Duplicates: case FTS_DP: continue; // To sync: case FTS_DEFAULT: case FTS_SL: case FTS_SLNONE: case FTS_F: case FTS_D: case FTS_DOT: case FTS_DC: // TODO: think about case of FTS_DC case FTS_NSOK: break; // Error cases: case FTS_ERR: case FTS_NS: case FTS_DNR: { int fts_errno = node->fts_errno; if (fts_errno == ENOENT) { debug(1, "Got error while privileged_fts_read(): %s (errno: %i; fts_info: %i).", strerror(fts_errno), fts_errno, node->fts_info); continue; } else { error("Got error while privileged_fts_read(): %s (errno: %i; fts_info: %i).", strerror(fts_errno), fts_errno, node->fts_info); ret = node->fts_errno; goto l_sync_initialsync_walk_end; } } default: error("Got unknown fts_info vlaue while privileged_fts_read(): %i.", node->fts_info); ret = EINVAL; goto l_sync_initialsync_walk_end; } path_rel = sync_path_abs2rel(ctx_p, node->fts_path, -1, &path_rel_len, path_rel); debug(3, "Pointing to \"%s\" (node->fts_info == %i)", path_rel, node->fts_info); if (ctx_p->flags[EXCLUDEMOUNTPOINTS] && node->fts_info==FTS_D) { if (rsync_and_prefer_excludes) { if (node->fts_statp->st_dev != ctx_p->st_dev) { debug(3, "Excluding \"%s\" due to location on other device: node->fts_statp->st_dev [0x%o] != ctx_p->st_dev [0x%o]", path_rel, node->fts_statp->st_dev, ctx_p->st_dev); if (queue_id == QUEUE_AUTO) { int i=0; while (iflags[RSYNCPREFERINCLUDE]) error("Excluding mount points is not implentemted for non \"rsync*\" modes."); } mode_t st_mode = fts_no_stat ? (node->fts_info==FTS_D ? S_IFDIR : S_IFREG) : node->fts_statp->st_mode; if (!skip_rules) { ruleaction_t perm = rules_getperm(path_rel, st_mode, rules_p, RA_WALK|RA_MONITOR); if (!(perm&RA_WALK)) { debug(3, "Rejecting to walk into \"%s\".", path_rel); fts_set(tree, node, FTS_SKIP); } if (!(perm&RA_MONITOR)) { debug(3, "Excluding \"%s\".", path_rel); if (rsync_and_prefer_excludes) { if (queue_id == QUEUE_AUTO) { int i=0; while (ifts_info==FTS_D); switch (ctx_p->flags[MODE]) { case MODE_SIMPLE: SAFE(sync_dosync(node->fts_path, evinfo.evmask, ctx_p, indexes_p), debug(1, "fpath == \"%s\"; evmask == 0x%o", node->fts_path, evinfo.evmask); return -1;); continue; default: break; } evinfo.seqid_min = sync_seqid(); evinfo.seqid_max = evinfo.seqid_min; evinfo.objtype_old = EOT_DOESNTEXIST; evinfo.objtype_new = node->fts_info==FTS_D ? EOT_DIR : EOT_FILE; evinfo.fsize = fts_no_stat ? 0 : node->fts_statp->st_size; debug(3, "queueing \"%s\" (depth: %i) with int-flags %p", node->fts_path, node->fts_level, (void *)(unsigned long)evinfo.flags); int _ret = sync_queuesync(path_rel, &evinfo, ctx_p, indexes_p, queue_id); if (_ret) { error("Got error while queueing \"%s\".", node->fts_path); ret = errno; goto l_sync_initialsync_walk_end; } continue; } /* "FTS optimization" */ if ( skip_rules && node->fts_info == FTS_D && !ctx_p->flags[EXCLUDEMOUNTPOINTS] ) { debug(4, "\"FTS optimizator\""); fts_set(tree, node, FTS_SKIP); } } if (errno) { error("Got error while privileged_fts_read() and related routines."); ret = errno; goto l_sync_initialsync_walk_end; } if (privileged_fts_close(tree, PC_SYNC_INIIALSYNC_WALK_FTS_CLOSE)) { error("Got error while privileged_fts_close()."); ret = errno; goto l_sync_initialsync_walk_end; } l_sync_initialsync_walk_end: if (path_rel != NULL) free(path_rel); return ret; } const char *sync_parameter_get(const char *variable_name, void *_dosync_arg_p) { struct dosync_arg *dosync_arg_p = _dosync_arg_p; ctx_t *ctx_p = dosync_arg_p->ctx_p; #ifdef _DEBUG_FORCE debug(15, "(\"%s\", %p): 0x%x, \"%s\"", variable_name, _dosync_arg_p, ctx_p == NULL ? 0 : ctx_p->synchandler_argf, dosync_arg_p->evmask_str); #endif if ((ctx_p == NULL || (ctx_p->synchandler_argf & SHFL_INCLUDE_LIST_PATH)) && !strcmp(variable_name, "INCLUDE-LIST-PATH")) return dosync_arg_p->outf_path; else if ((ctx_p == NULL || (ctx_p->synchandler_argf & SHFL_EXCLUDE_LIST_PATH)) && !strcmp(variable_name, "EXCLUDE-LIST-PATH")) return dosync_arg_p->excf_path; else if (!strcmp(variable_name, "TYPE")) return dosync_arg_p->list_type_str; else if (!strcmp(variable_name, "EVENT-MASK")) return dosync_arg_p->evmask_str; errno = ENOENT; return NULL; } static char **sync_customargv(ctx_t *ctx_p, struct dosync_arg *dosync_arg_p, synchandler_args_t *args_p) { int d, s; char **argv = (char **)xcalloc(sizeof(char *), MAXARGUMENTS+2); s = d = 0; argv[d++] = strdup(ctx_p->handlerfpath); while (s < args_p->c) { char *arg = args_p->v[s]; char isexpanded = args_p->isexpanded[s]; s++; #ifdef _DEBUG_FORCE debug(30, "\"%s\" [%p]", arg, arg); #endif if (isexpanded) { #ifdef _DEBUG_FORCE debug(19, "\"%s\" [%p] is already expanded, just strdup()-ing it", arg, arg); #endif argv[d++] = strdup(arg); continue; } if (!strcmp(arg, "%INCLUDE-LIST%")) { int i = 0, e = dosync_arg_p->include_list_count; const char **include_list = dosync_arg_p->include_list; #ifdef _DEBUG_FORCE debug(19, "INCLUDE-LIST: e == %u; d,s: %u,%u", e, d, s); #endif while (i < e) { #ifdef PARANOID if (d >= MAXARGUMENTS) { errno = E2BIG; critical("Too many arguments"); } #endif argv[d++] = parameter_expand(ctx_p, strdup(include_list[i++]), 0, NULL, NULL, sync_parameter_get, dosync_arg_p); #ifdef _DEBUG_FORCE debug(19, "include-list: argv[%u] == %p", d-1, argv[d-1]); #endif } continue; } #ifdef PARANOID if (d >= MAXARGUMENTS) { errno = E2BIG; critical("Too many arguments"); } #endif argv[d] = parameter_expand(ctx_p, strdup(arg), 0, NULL, NULL, sync_parameter_get, dosync_arg_p); #ifdef _DEBUG_FORCE debug(19, "argv[%u] == %p \"%s\"", d, argv[d], argv[d]); #endif d++; } argv[d] = NULL; #ifdef _DEBUG_FORCE debug(18, "return %p", argv); #endif return argv; } static void argv_free(char **argv) { char **argv_p; #ifdef _DEBUG_FORCE debug(18, "(%p)", argv); #endif #ifdef VERYPARANOID if (argv == NULL) critical(MSG_SECURITY_PROBLEM("argv_free(NULL)")); #endif argv_p = argv; while (*argv_p != NULL) { #ifdef _DEBUG_FORCE debug(25, "free(%p)", *argv_p); #endif free(*(argv_p++)); } free(argv); return; } static inline int sync_initialsync_finish(ctx_t *ctx_p, initsync_t initsync, int ret) { finish_iteration(ctx_p); return ret; } int sync_initialsync(const char *path, ctx_t *ctx_p, indexes_t *indexes_p, initsync_t initsync) { int ret; queue_id_t queue_id; debug(3, "(\"%s\", ctx_p, indexes_p, %i)", path, initsync); #ifdef CLUSTER_SUPPORT if(initsync == INITSYNC_FULL) { if(ctx_p->cluster_iface) return cluster_initialsync(); } #endif if (initsync == INITSYNC_FULL) queue_id = QUEUE_INSTANT; else queue_id = QUEUE_NORMAL; // non-RSYNC case: if( !( (ctx_p->flags[MODE]==MODE_RSYNCDIRECT) || (ctx_p->flags[MODE]==MODE_RSYNCSHELL) || (ctx_p->flags[MODE]==MODE_RSYNCSO) ) ) { debug(3, "syncing \"%s\"", path); if(ctx_p->flags[HAVERECURSIVESYNC]) { if(ctx_p->flags[MODE] == MODE_SO) { api_eventinfo_t *ei = (api_eventinfo_t *)xmalloc(sizeof(*ei)); #ifdef PARANIOD memset(ei, 0, sizeof(*ei)); #endif api_evinfo_initialevmask(ctx_p, ei, 1); ei->flags = EVIF_RECURSIVELY; ei->path_len = strlen(path); ei->path = strdup(path); ei->objtype_old = EOT_DOESNTEXIST; ei->objtype_new = EOT_DIR; ret = so_call_sync(ctx_p, indexes_p, 1, ei); return sync_initialsync_finish(ctx_p, initsync, ret); } else { struct dosync_arg dosync_arg; synchandler_args_t *args_p; args_p = ctx_p->synchandler_args[SHARGS_INITIAL].c ? &ctx_p->synchandler_args[SHARGS_INITIAL] : &ctx_p->synchandler_args[SHARGS_PRIMARY]; dosync_arg.ctx_p = ctx_p; *dosync_arg.include_list = path; dosync_arg.include_list_count = 1; dosync_arg.list_type_str = "initialsync"; char **argv = sync_customargv(ctx_p, &dosync_arg, args_p); ret = SYNC_EXEC_ARGV( ctx_p, indexes_p, NULL, NULL, argv); if (!SHOULD_THREAD(ctx_p)) // If it's a thread then it will free the argv in GC. If not a thread then we have to free right here. argv_free(argv); return sync_initialsync_finish(ctx_p, initsync, ret); } } #ifdef DOXYGEN sync_exec_argv(NULL, NULL); sync_exec_argv_thread(NULL, NULL); #endif ret = sync_initialsync_walk(ctx_p, path, indexes_p, queue_id, initsync); if(ret) error("Cannot get synclist"); return sync_initialsync_finish(ctx_p, initsync, ret); } // RSYNC case: if(!ctx_p->flags[RSYNCPREFERINCLUDE]) { queueinfo_t *queueinfo = &ctx_p->_queues[queue_id]; if(!queueinfo->stime) queueinfo->stime = time(NULL); // Useful for debugging eventinfo_t *evinfo = (eventinfo_t *)xmalloc(sizeof(*evinfo)); memset(evinfo, 0, sizeof(*evinfo)); evinfo->flags |= EVIF_RECURSIVELY; evinfo->seqid_min = sync_seqid(); evinfo->seqid_max = evinfo->seqid_min; evinfo->objtype_old = EOT_DOESNTEXIST; evinfo->objtype_new = EOT_DIR; // Searching for excludes ret = sync_initialsync_walk(ctx_p, path, indexes_p, queue_id, initsync); if(ret) { error("Cannot get exclude what to exclude"); return sync_initialsync_finish(ctx_p, initsync, ret); } debug(3, "queueing \"%s\" with int-flags %p", path, (void *)(unsigned long)evinfo->flags); char *path_rel = sync_path_abs2rel(ctx_p, path, -1, NULL, NULL); ret = indexes_queueevent(indexes_p, path_rel, evinfo, queue_id); return sync_initialsync_finish(ctx_p, initsync, ret); } // Searching for includes ret = sync_initialsync_walk(ctx_p, path, indexes_p, queue_id, initsync); return sync_initialsync_finish(ctx_p, initsync, ret); } int sync_notify_mark(ctx_t *ctx_p, const char *accpath, const char *path, size_t pathlen, indexes_t *indexes_p) { debug(3, "(..., \"%s\", %i,...)", path, pathlen); int wd = indexes_fpath2wd(indexes_p, path); if(wd != -1) { debug(1, "\"%s\" is already marked (wd: %i). Skipping.", path, wd); return wd; } debug(5, "ctx_p->notifyenginefunct.add_watch_dir(ctx_p, indexes_p, \"%s\")", accpath); if((wd = ctx_p->notifyenginefunct.add_watch_dir(ctx_p, indexes_p, accpath)) == -1) { if(errno == ENOENT) return -2; error("Cannot ctx_p->notifyenginefunct.add_watch_dir() on \"%s\".", path); return -1; } debug(6, "endof ctx_p->notifyenginefunct.add_watch_dir(ctx_p, indexes_p, \"%s\")", accpath); indexes_add_wd(indexes_p, wd, path, pathlen); return wd; } #ifdef CLUSTER_SUPPORT static inline int sync_mark_walk_cluster_modtime_update(ctx_t *ctx_p, const char *path, short int dirlevel, mode_t st_mode) { if(ctx_p->cluster_iface) { int ret=cluster_modtime_update(path, dirlevel, st_mode); if(ret) error("cannot cluster_modtime_update()"); return ret; } return 0; } #endif int sync_mark_walk(ctx_t *ctx_p, const char *dirpath, indexes_t *indexes_p) { int ret = 0; const char *rootpaths[] = {dirpath, NULL}; FTS *tree; rule_t *rules_p = ctx_p->rules; debug(2, "(ctx_p, \"%s\", indexes_p).", dirpath); int fts_opts = FTS_NOCHDIR|FTS_PHYSICAL|FTS_NOSTAT|(ctx_p->flags[ONEFILESYSTEM]?FTS_XDEV:0); debug(3, "fts_opts == %p", (void *)(long)fts_opts); tree = privileged_fts_open((char *const *)rootpaths, fts_opts, NULL, PC_SYNC_MARK_WALK_FTS_OPEN); if (tree == NULL) { error_or_debug((ctx_p->state == STATE_STARTING) ?-1:2, "Cannot privileged_fts_open() on \"%s\".", dirpath); return errno; } FTSENT *node; char *path_rel = NULL; size_t path_rel_len = 0; #ifdef VERYPARANOID errno = 0; #endif while ((node = privileged_fts_read(tree, PC_SYNC_MARK_WALK_FTS_READ))) { #ifdef CLUSTER_SUPPORT int ret; #endif debug(2, "walking: \"%s\" (depth %u): fts_info == %i", node->fts_path, node->fts_level, node->fts_info); switch(node->fts_info) { // Duplicates: case FTS_DP: continue; // Files: case FTS_DEFAULT: case FTS_SL: case FTS_SLNONE: case FTS_F: case FTS_NSOK: #ifdef CLUSTER_SUPPORT if ((ret=sync_mark_walk_cluster_modtime_update(ctx_p, node->fts_path, node->fts_level, S_IFREG))) goto l_sync_mark_walk_end; #endif continue; // Directories (to mark): case FTS_D: case FTS_DC: // TODO: think about case of FTS_DC case FTS_DOT: #ifdef CLUSTER_SUPPORT if ((ret=sync_mark_walk_cluster_modtime_update(ctx_p, node->fts_path, node->fts_level, S_IFDIR))) goto l_sync_mark_walk_end; #endif break; // Error cases: case FTS_ERR: case FTS_NS: case FTS_DNR: if (errno == ENOENT) { debug(1, "Got error while privileged_fts_read(); fts_info: %i.", node->fts_info); continue; } else { error_or_debug((ctx_p->state == STATE_STARTING) ?-1:2, "Got error while privileged_fts_read(); fts_info: %i.", node->fts_info); ret = errno; goto l_sync_mark_walk_end; } default: error_or_debug((ctx_p->state == STATE_STARTING) ?-1:2, "Got unknown fts_info vlaue while privileged_fts_read(): %i.", node->fts_info); ret = EINVAL; goto l_sync_mark_walk_end; } path_rel = sync_path_abs2rel(ctx_p, node->fts_path, -1, &path_rel_len, path_rel); ruleaction_t perm = rules_search_getperm(path_rel, S_IFDIR, rules_p, RA_WALK, NULL); debug(3, "perm == 0x%o", perm); if (!(perm&RA_WALK)) { debug(2, "setting an FTS_SKIP on the directory"); if (fts_set(tree, node, FTS_SKIP)) warning("Got error while fts_set(tree, node, FTS_SKIP): %s", path_rel); } if (!(perm&RA_MONITOR)) { debug(2, "don't mark the directory"); continue; } debug(2, "marking \"%s\" (depth %u)", node->fts_path, node->fts_level); int wd = sync_notify_mark(ctx_p, node->fts_accpath, node->fts_path, node->fts_pathlen, indexes_p); if (wd == -1) { error_or_debug((ctx_p->state == STATE_STARTING) ?-1:2, "Got error while notify-marking \"%s\".", node->fts_path); ret = errno; goto l_sync_mark_walk_end; } debug(2, "watching descriptor is %i.", wd); } if (errno) { error_or_debug((ctx_p->state == STATE_STARTING) ?-1:2, "Got error while privileged_fts_read() and related routines."); ret = errno; goto l_sync_mark_walk_end; } if (privileged_fts_close(tree, PC_SYNC_MARK_WALK_FTS_CLOSE)) { error_or_debug((ctx_p->state == STATE_STARTING) ?-1:2, "Got error while privileged_fts_close()."); ret = errno; goto l_sync_mark_walk_end; } l_sync_mark_walk_end: if (path_rel != NULL) free(path_rel); return ret; } int sync_notify_init(ctx_t *ctx_p) { switch (ctx_p->flags[MONITOR]) { #ifdef FANOTIFY_SUPPORT case NE_FANOTIFY: { ctx_p->fsmondata = (long)fanotify_init(FANOTIFY_FLAGS, FANOTIFY_EVFLAGS); if((long)ctx_p->fsmondata == -1) { error("cannot fanotify_init(%i, %i).", FANOTIFY_FLAGS, FANOTIFY_EVFLAGS); return -1; } return 0; } #endif #ifdef INOTIFY_SUPPORT case NE_INOTIFY: { # ifdef INOTIFY_OLD ctx_p->fsmondata = (void *)(long)inotify_init(); # if INOTIFY_FLAGS != 0 # warning Do not know how to set inotify flags (too old system) # endif # else ctx_p->fsmondata = (void *)(long)inotify_init1(INOTIFY_FLAGS); # endif if ((long)ctx_p->fsmondata == -1) { error("cannot inotify_init1(%i).", INOTIFY_FLAGS); return -1; } return 0; } #endif #ifdef KQUEUE_SUPPORT case NE_KQUEUE: { int kqueue_d = kqueue_init(ctx_p); if(kqueue_d == -1) { error("cannot kqueue_init(ctx_p)."); return -1; } return 0; } #endif #ifdef BSM_SUPPORT case NE_BSM: case NE_BSM_PREFETCH: { int bsm_d = bsm_init(ctx_p); if(bsm_d == -1) { error("cannot bsm_init(ctx_p)."); return -1; } return 0; } #endif #ifdef GIO_SUPPORT case NE_GIO: { critical_on (gio_init(ctx_p) == -1); return 0; } #endif } error("unknown notify-engine: %i", ctx_p->flags[MONITOR]); errno = EINVAL; return -1; } static inline int sync_dosync_exec(ctx_t *ctx_p, indexes_t *indexes_p, const char *evmask_str, const char *fpath) { int rc; struct dosync_arg dosync_arg; debug(20, "(ctx_p, indexes_p, \"%s\", \"%s\")", evmask_str, fpath); dosync_arg.ctx_p = ctx_p; *dosync_arg.include_list = fpath; dosync_arg.include_list_count = 1; dosync_arg.list_type_str = "sync"; dosync_arg.evmask_str = evmask_str; char **argv = sync_customargv(ctx_p, &dosync_arg, &ctx_p->synchandler_args[SHARGS_PRIMARY]); rc = SYNC_EXEC_ARGV( ctx_p, indexes_p, NULL, NULL, argv); if (!SHOULD_THREAD(ctx_p)) // If it's a thread then it will free the argv in GC. If not a thread then we have to free right here. argv_free(argv); return rc; #ifdef DOXYGEN sync_exec_argv(NULL, NULL); sync_exec_argv_thread(NULL, NULL); #endif } int sync_dosync(const char *fpath, uint32_t evmask, ctx_t *ctx_p, indexes_t *indexes_p) { int ret; #ifdef CLUSTER_SUPPORT ret = cluster_lock(fpath); if(ret) return ret; #endif char *evmask_str = xmalloc(1<<8); sprintf(evmask_str, "%u", evmask); ret = sync_dosync_exec(ctx_p, indexes_p, evmask_str, fpath); free(evmask_str); #ifdef CLUSTER_SUPPORT ret = cluster_unlock_all(); #endif return ret; } int fileischanged(ctx_t *ctx_p, indexes_t *indexes_p, const char *path_rel, stat64_t *lstat_p, int is_deleted) { if (lstat_p == NULL || !ctx_p->flags[MODSIGN]) return 1; debug(9, "Checking modification signature"); fileinfo_t *finfo = indexes_fileinfo(indexes_p, path_rel); if (finfo != NULL) { uint32_t diff; if (!(diff=stat_diff(&finfo->lstat, lstat_p) & ctx_p->flags[MODSIGN])) { debug(8, "Modification signature: File not changed: \"%s\"", path_rel); return 0; // Skip file syncing if it's metadata not changed enough (according to "--modification-signature" setting) } debug(8, "Modification signature: stat_diff == 0x%o; significant diff == 0x%o (ctx_p->flags[MODSIGN] == 0x%o)", diff, diff&ctx_p->flags[MODSIGN], ctx_p->flags[MODSIGN]); if (is_deleted) { debug(8, "Modification signature: Deleting information about \"%s\"", path_rel); indexes_fileinfo_add(indexes_p, path_rel, NULL); free(finfo); } else { debug(8, "Modification signature: Updating information about \"%s\"", path_rel); memcpy(&finfo->lstat, lstat_p, sizeof(finfo->lstat)); } } else { debug(8, "There's no information about this file/dir: \"%s\". Just remembering the current state.", path_rel); // Adding file/dir information finfo = xmalloc(sizeof(*finfo)); memcpy(&finfo->lstat, lstat_p, sizeof(finfo->lstat)); indexes_fileinfo_add(indexes_p, path_rel, finfo); } return 1; } static inline int sync_indexes_fpath2ei_addfixed(ctx_t *ctx_p, indexes_t *indexes_p, const char *fpath, eventinfo_t *evinfo) { static const char fpath_dot[] = "."; const char *fpath_fixed; fpath_fixed = fpath; switch (ctx_p->flags[MODE]) { case MODE_DIRECT: // If fpath is empty (that means CWD) then assign it to "." if (!*fpath) fpath_fixed = fpath_dot; break; default: break; } return indexes_fpath2ei_add(indexes_p, strdup(fpath_fixed), evinfo); } int sync_prequeue_loadmark ( int monitored, ctx_t *ctx_p, indexes_t *indexes_p, const char *path_full, const char *path_rel, stat64_t *lstat_p, eventobjtype_t objtype_old, eventobjtype_t objtype_new, uint32_t event_mask, int event_wd, mode_t st_mode, off_t st_size, char **path_buf_p, size_t *path_buf_len_p, eventinfo_t *evinfo ) { debug(10, "%i %p %p %p %p %p %i %i 0x%o %i %i %i %p %p %p", monitored, ctx_p, indexes_p, path_full, path_rel, lstat_p, objtype_old, objtype_new, event_mask, event_wd, st_mode, st_size, path_buf_p, path_buf_len_p, evinfo ); #ifdef PARANOID // &path_buf and &path_buf_len are passed to do not reallocate memory for path_rel/path_full each time if ((path_buf_p == NULL || path_buf_len_p == NULL) && (path_full == NULL || path_rel == NULL)) { error("path_rel_p == NULL || path_rel_len_p == NULL"); return EINVAL; } #endif #ifdef VERYPARANOID if (path_full == NULL && path_rel == NULL) { error("path_full == NULL && path_rel == NULL"); return EINVAL; } #endif if (path_rel == NULL) { *path_buf_p = sync_path_abs2rel(ctx_p, path_full, -1, path_buf_len_p, *path_buf_p); path_rel = *path_buf_p; } ruleaction_t perm = RA_ALL; if (st_mode) { // Checking by filter rules perm = rules_getperm(path_rel, st_mode, ctx_p->rules, RA_WALK|RA_MONITOR); if(!(perm&(RA_MONITOR|RA_WALK))) { return 0; } } // Handling different cases int is_dir = objtype_old == EOT_DIR || objtype_new == EOT_DIR; int is_created = objtype_old == EOT_DOESNTEXIST; int is_deleted = objtype_new == EOT_DOESNTEXIST; debug(4, "is_dir == %x; is_created == %x; is_deleted == %x", is_dir, is_created, is_deleted); if (is_dir) { if (is_created) { int ret; if (perm & RA_WALK) { if (path_full == NULL) { *path_buf_p = sync_path_rel2abs(ctx_p, path_rel, -1, path_buf_len_p, *path_buf_p); path_full = *path_buf_p; } if (monitored) { ret = sync_mark_walk(ctx_p, path_full, indexes_p); if(ret) { debug(1, "Seems, that directory \"%s\" disappeared, while trying to mark it.", path_full); return 0; } } ret = sync_initialsync(path_full, ctx_p, indexes_p, INITSYNC_SUBDIR); if (ret) { errno = ret; error("Got error from sync_initialsync()"); return errno; } } fileischanged(ctx_p, indexes_p, path_rel, lstat_p, is_deleted); // Just to remember it's state return 0; } else if (is_deleted) { debug(2, "Disappeared \".../%s\".", path_rel); } } if (!(perm&RA_WALK)) { return 0; } if (!fileischanged(ctx_p, indexes_p, path_rel, lstat_p, is_deleted)) { debug(4, "The file is not changed. Returning."); return 0; } switch (ctx_p->flags[MODE]) { case MODE_SIMPLE: return SAFE(sync_dosync(path_rel, event_mask, ctx_p, indexes_p), debug(1, "fpath == \"%s\"; evmask == 0x%o", path_rel, event_mask); return -1;); default: break; } // Locally queueing the event int isnew = 0; if (evinfo == NULL) evinfo = indexes_fpath2ei(indexes_p, path_rel); else isnew++; // It's new for prequeue (but old for lockwait queue) if (evinfo == NULL) { evinfo = (eventinfo_t *)xmalloc(sizeof(*evinfo)); memset(evinfo, 0, sizeof(*evinfo)); evinfo->fsize = st_size; evinfo->wd = event_wd; evinfo->seqid_min = sync_seqid(); evinfo->seqid_max = evinfo->seqid_min; evinfo->objtype_old = objtype_old; isnew++; debug(3, "new event: fsize == %i; wd == %i", evinfo->fsize, evinfo->wd); } else { evinfo->seqid_max = sync_seqid(); } switch(ctx_p->flags[MONITOR]) { #ifdef KQUEUE_SUPPORT case NE_KQUEUE: #endif #ifdef INOTIFY_SUPPORT case NE_INOTIFY: #endif #if KQUEUE_SUPPORT | INOTIFY_SUPPORT evinfo->evmask |= event_mask; break; #endif #ifdef BSM_SUPPORT case NE_BSM: case NE_BSM_PREFETCH: evinfo->evmask = event_mask; break; #endif #ifdef GIO_SUPPORT case NE_GIO: evinfo->evmask = event_mask; break; #endif } evinfo->objtype_new = objtype_new; debug(2, "path_rel == \"%s\"; evinfo->objtype_old == %i; evinfo->objtype_new == %i; " "evinfo->seqid_min == %u; evinfo->seqid_max == %u", path_rel, evinfo->objtype_old, evinfo->objtype_new, evinfo->seqid_min, evinfo->seqid_max ); if (isnew) // Fix the path (if required) and call indexes_fpath2ei_add() to remeber the new object to be synced sync_indexes_fpath2ei_addfixed(ctx_p, indexes_p, path_rel, evinfo); return 0; } void _sync_idle_dosync_collectedexcludes(gpointer fpath_gp, gpointer flags_gp, gpointer arg_gp) { char *fpath = (char *)fpath_gp; indexes_t *indexes_p = ((struct dosync_arg *)arg_gp)->indexes_p; debug(3, "\"%s\", %u (%p).", fpath, GPOINTER_TO_INT(flags_gp), flags_gp); indexes_addexclude_aggr(indexes_p, strdup(fpath), (eventinfo_flags_t)GPOINTER_TO_INT(flags_gp)); return; } // Is not a thread-save function! eventinfo_t *ht_fpath_isincluded(GHashTable *ht, const char *const fpath) { static char buf[PATH_MAX+2]={0}; char *ptr, *end; eventinfo_t *evinfo = g_hash_table_lookup(ht, fpath); debug(5, "looking up for \"%s\": %p", fpath, evinfo); if (evinfo != NULL) return evinfo; if (!*fpath) return NULL; evinfo = g_hash_table_lookup(ht, ""); if (evinfo != NULL) { debug(5, "recursive looking up for \"\": %p (%x: recusively: %x)", evinfo, evinfo->flags, evinfo->flags & EVIF_RECURSIVELY); if (evinfo->flags & EVIF_RECURSIVELY) return evinfo; } size_t fpath_len = strlen(fpath); memcpy(buf, fpath, fpath_len+1); ptr = buf; end = &buf[fpath_len]; while(ptr < end) { if (*ptr == '/') { *ptr = 0; evinfo = g_hash_table_lookup(ht, buf); if (evinfo != NULL) { debug(5, "recursive looking up for \"%s\": %p (%x: recusively: %x)", buf, evinfo, evinfo->flags, evinfo->flags & EVIF_RECURSIVELY); *ptr = '/'; if (evinfo->flags & EVIF_RECURSIVELY) return evinfo; } } ptr++; } return evinfo; } int _sync_islocked(threadinfo_t *threadinfo_p, void *_fpath) { char *fpath = _fpath; eventinfo_t *evinfo = ht_fpath_isincluded(threadinfo_p->fpath2ei_ht, fpath); debug(4, "scanning thread %p: fpath<%s> -> evinfo<%p>", threadinfo_p->pthread, fpath, evinfo); if (evinfo != NULL) return 1; return 0; } static inline int sync_islocked(const char *const fpath) { int rc = threads_foreach(_sync_islocked, STATE_RUNNING, (void *)fpath); debug(3, "<%s>: %u", fpath, rc); return rc; } void _sync_idle_dosync_collectedevents(gpointer fpath_gp, gpointer evinfo_gp, gpointer arg_gp) { char *fpath = (char *)fpath_gp; eventinfo_t *evinfo = (eventinfo_t *)evinfo_gp; int *evcount_p =&((struct dosync_arg *)arg_gp)->evcount; ctx_t *ctx_p = ((struct dosync_arg *)arg_gp)->ctx_p; indexes_t *indexes_p = ((struct dosync_arg *)arg_gp)->indexes_p; queue_id_t queue_id = (queue_id_t)((struct dosync_arg *)arg_gp)->data; debug(3, "queue_id == %i.", queue_id); if (ctx_p->flags[THREADING] == PM_SAFE) if (sync_islocked(fpath)) { debug(3, "\"%s\" is locked, dropping to waitlock queue", fpath); eventinfo_t *evinfo_dup = xmalloc(sizeof(*evinfo_dup)); memcpy(evinfo_dup, evinfo, sizeof(*evinfo)); sync_queuesync(fpath, evinfo_dup, ctx_p, indexes_p, QUEUE_LOCKWAIT); return; } if ((ctx_p->listoutdir == NULL) && (!(ctx_p->synchandler_argf & SHFL_INCLUDE_LIST)) && (!(ctx_p->flags[MODE]==MODE_SO))) { debug(3, "calling sync_dosync()"); SAFE(sync_dosync(fpath, evinfo->evmask, ctx_p, indexes_p), debug(1, "fpath == \"%s\"; evmask == 0x%o", fpath, evinfo->evmask); exit(errno ? errno : -1)); // TODO: remove exit() from here return; } int isnew = 0; eventinfo_t *evinfo_idx = indexes_fpath2ei(indexes_p, fpath); if (evinfo_idx == NULL) { evinfo_idx = (eventinfo_t *)xmalloc(sizeof(*evinfo_idx)); memset(evinfo_idx, 0, sizeof(*evinfo_idx)); isnew++; (*evcount_p)++; evinfo_idx->evmask = evinfo->evmask; evinfo_idx->flags = evinfo->flags; evinfo_idx->objtype_old = evinfo->objtype_old; evinfo_idx->objtype_new = evinfo->objtype_new; evinfo_idx->seqid_min = evinfo->seqid_min; evinfo_idx->seqid_max = evinfo->seqid_max; } else evinfo_merge(ctx_p, evinfo_idx, evinfo); int _queue_id = 0; while (_queue_id < QUEUE_MAX) { if(_queue_id == queue_id) { _queue_id++; continue; } eventinfo_t *evinfo_q = indexes_lookupinqueue(indexes_p, fpath, _queue_id); if(evinfo_q != NULL) { evinfo_merge(ctx_p, evinfo_idx, evinfo_q); indexes_removefromqueue(indexes_p, fpath, _queue_id); if(!indexes_queuelen(indexes_p, _queue_id)) ctx_p->_queues[_queue_id].stime = 0; } _queue_id++; } if (isnew) { debug(4, "Collecting \"%s\"", fpath); // Fix the path (if required) and call indexes_fpath2ei_add() to remeber the new object to be synced sync_indexes_fpath2ei_addfixed(ctx_p, indexes_p, fpath, evinfo_idx); } else free(fpath); return; } struct trylocked_arg { char *path_full; size_t path_full_len; }; gboolean sync_trylocked(gpointer fpath_gp, gpointer evinfo_gp, gpointer arg_gp) { char *fpath = (char *)fpath_gp; eventinfo_t *evinfo = (eventinfo_t *)evinfo_gp; struct dosync_arg *arg_p = (struct dosync_arg *)arg_gp; ctx_t *ctx_p = arg_p->ctx_p; indexes_t *indexes_p = arg_p->indexes_p; struct trylocked_arg *data = arg_p->data; if (!sync_islocked(fpath)) { if (sync_prequeue_loadmark(0, ctx_p, indexes_p, NULL, fpath, NULL, evinfo->evmask, evinfo->objtype_old, evinfo->objtype_new, 0, 0, 0, &data->path_full, &data->path_full_len, evinfo)) { critical("Cannot re-queue \"%s\" to be synced", fpath); return FALSE; } return TRUE; } return FALSE; } int sync_idle_dosync_collectedevents_cleanup(ctx_t *ctx_p, thread_callbackfunct_arg_t *arg_p) { int ret0 = 0, ret1 = 0; if(ctx_p->flags[DONTUNLINK]) return 0; debug(3, "(ctx_p, {inc: %p, exc: %p}) thread %p", arg_p->incfpath, arg_p->excfpath, pthread_self()); if (arg_p->excfpath != NULL) { debug(3, "unlink()-ing exclude-file: \"%s\"", arg_p->excfpath); ret0 = unlink(arg_p->excfpath); free(arg_p->excfpath); } if (arg_p->incfpath != NULL) { debug(3, "unlink()-ing include-file: \"%s\"", arg_p->incfpath); ret1 = unlink(arg_p->incfpath); free(arg_p->incfpath); } free(arg_p); return ret0 ? ret0 : ret1; } void sync_queuesync_wrapper(gpointer fpath_gp, gpointer evinfo_gp, gpointer arg_gp) { char *fpath_rel = (char *)fpath_gp; eventinfo_t *evinfo = (eventinfo_t *)evinfo_gp; ctx_t *ctx_p = ((struct dosync_arg *)arg_gp)->ctx_p; indexes_t *indexes_p = ((struct dosync_arg *)arg_gp)->indexes_p; sync_queuesync(fpath_rel, evinfo, ctx_p, indexes_p, QUEUE_AUTO); return; } int sync_prequeue_unload(ctx_t *ctx_p, indexes_t *indexes_p) { struct dosync_arg dosync_arg; dosync_arg.ctx_p = ctx_p; dosync_arg.indexes_p = indexes_p; debug(3, "collected %i events per this time.", g_hash_table_size(indexes_p->fpath2ei_ht)); g_hash_table_foreach(indexes_p->fpath2ei_ht, sync_queuesync_wrapper, &dosync_arg); g_hash_table_remove_all(indexes_p->fpath2ei_ht); return 0; } int sync_idle_dosync_collectedevents_aggrqueue(queue_id_t queue_id, ctx_t *ctx_p, indexes_t *indexes_p, struct dosync_arg *dosync_arg) { time_t tm = time(NULL); queueinfo_t *queueinfo = &ctx_p->_queues[queue_id]; if ((queueinfo->stime + queueinfo->collectdelay > tm) && (queueinfo->collectdelay != COLLECTDELAY_INSTANT) && (!ctx_p->flags[EXITONNOEVENTS])) { debug(3, "(%i, ...): too early (%i + %i > %i).", queue_id, queueinfo->stime, queueinfo->collectdelay, tm); return 0; } queueinfo->stime = 0; int evcount_real = g_hash_table_size(indexes_p->fpath2ei_coll_ht[queue_id]); debug(3, "(%i, ...): evcount_real == %i", queue_id, evcount_real); if (evcount_real<=0) { debug(3, "(%i, ...): no events, return 0.", queue_id); return 0; } switch (queue_id) { case QUEUE_LOCKWAIT: { struct trylocked_arg arg_data = {0}; dosync_arg->data = &arg_data; g_hash_table_foreach_remove(indexes_p->fpath2ei_coll_ht[queue_id], sync_trylocked, dosync_arg); // Placing to global queues recently unlocked objects sync_prequeue_unload(ctx_p, indexes_p); #ifdef PARANOID if (arg_data.path_full != NULL) #endif free(arg_data.path_full); break; } default: { g_hash_table_foreach(indexes_p->fpath2ei_coll_ht[queue_id], _sync_idle_dosync_collectedevents, dosync_arg); g_hash_table_remove_all(indexes_p->fpath2ei_coll_ht[queue_id]); if(!ctx_p->flags[RSYNCPREFERINCLUDE]) { g_hash_table_foreach(indexes_p->exc_fpath_coll_ht[queue_id], _sync_idle_dosync_collectedexcludes, dosync_arg); g_hash_table_remove_all(indexes_p->exc_fpath_coll_ht[queue_id]); } break; } } return 0; } int sync_idle_dosync_collectedevents_uniqfname(ctx_t *ctx_p, char *fpath, char *name) { pid_t pid = getpid(); time_t tm = time(NULL); stat64_t stat64; int counter = 0; do { snprintf(fpath, PATH_MAX, "%s/.clsync-%s.%u.%lu.%lu.%u", ctx_p->listoutdir, name, pid, (long)pthread_self(), (unsigned long)tm, rand()); // To be unique lstat64(fpath, &stat64); if(counter++ > COUNTER_LIMIT) { error("Cannot find unused filename for list-file. The last try was \"%s\".", fpath); return ENOENT; } } while(errno != ENOENT); // TODO: find another way to check if the object exists errno=0; return 0; } int sync_idle_dosync_collectedevents_listcreate(struct dosync_arg *dosync_arg_p, char *name) { debug(3, "Creating %s file", name); char *fpath = dosync_arg_p->outf_path; ctx_t *ctx_p = dosync_arg_p->ctx_p; int ret; if ((ret=sync_idle_dosync_collectedevents_uniqfname(ctx_p, fpath, name))) { error("sync_idle_dosync_collectedevents_listcreate: Cannot get unique file name."); return ret; } dosync_arg_p->outf = fopen(fpath, "w"); if (dosync_arg_p->outf == NULL) { error("Cannot open \"%s\" as file for writing.", fpath); return errno; } setbuffer(dosync_arg_p->outf, dosync_arg_p->buf, BUFSIZ); debug(3, "Created list-file \"%s\"", fpath); dosync_arg_p->linescount = 0; return 0; } size_t rsync_escape_result_size = 0; char *rsync_escape_result = NULL; void rsync_escape_cleanup() { if(rsync_escape_result_size) free(rsync_escape_result); } const char *rsync_escape(const char *path) { size_t sc_count = 0; size_t i = 0; while(1) { switch(path[i]) { case 0: goto l_rsync_escape_loop0_end; case '[': case ']': case '*': case '?': case '\\': sc_count++; } i++; }; l_rsync_escape_loop0_end: if(!sc_count) return path; size_t required_size = i+sc_count+1; if(required_size >= rsync_escape_result_size) { rsync_escape_result_size = required_size + ALLOC_PORTION; rsync_escape_result = xrealloc(rsync_escape_result, rsync_escape_result_size); } // TODO: Optimize this. Second "switch" is a bad way. i++; while(i--) { rsync_escape_result[i+sc_count] = path[i]; switch(path[i]) { case '[': case ']': case '*': case '?': case '\\': sc_count--; rsync_escape_result[i+sc_count] = '\\'; break; } } return rsync_escape_result; } static inline int rsync_outline(FILE *outf, char *outline, eventinfo_flags_t flags) { #ifdef VERYPARANOID critical_on(outf == NULL); #endif if (flags & EVIF_RECURSIVELY) { debug(3, "Recursively \"%s\": Writing to rsynclist: \"%s/***\".", outline, outline); critical_on ( fprintf(outf, "%s/***\n", outline) <= 0 ); } else if (flags & EVIF_CONTENTRECURSIVELY) { debug(3, "Content-recursively \"%s\": Writing to rsynclist: \"%s/**\".", outline, outline); critical_on ( fprintf(outf, "%s/**\n", outline) <= 0 ); } else { debug(3, "Non-recursively \"%s\": Writing to rsynclist: \"%s\".", outline, outline); critical_on ( fprintf(outf, "%s\n", outline) <= 0 ); } return 0; } gboolean rsync_aggrout(gpointer outline_gp, gpointer flags_gp, gpointer arg_gp) { struct dosync_arg *dosync_arg_p = (struct dosync_arg *)arg_gp; char *outline = (char *)outline_gp; FILE *outf = dosync_arg_p->outf; eventinfo_flags_t flags = (eventinfo_flags_t)GPOINTER_TO_INT(flags_gp); // debug(3, "\"%s\"", outline); int ret; if((ret=rsync_outline(outf, outline, flags))) { error("Got error from rsync_outline(). Exit."); exit(ret); // TODO: replace this with kill(0, ...) } return TRUE; } static inline int rsync_listpush(indexes_t *indexes_p, const char *fpath, size_t fpath_len, eventinfo_flags_t flags, int *linescount_p) { char *fpathwslash; if(fpath_len>0) { // Prepending with the slash fpathwslash = alloca(fpath_len+2); fpathwslash[0] = '/'; memcpy(&fpathwslash[1], fpath, fpath_len+1); } else { // In this case slash is not required fpathwslash = (char *)fpath; } fpathwslash = (char *)rsync_escape(fpathwslash); char *end=fpathwslash; debug(3, "\"%s\": Adding to rsynclist: \"%s\" with flags %p.", fpathwslash, fpathwslash, (void *)(long)flags); indexes_outaggr_add(indexes_p, strdup(fpathwslash), flags); if(linescount_p != NULL) (*linescount_p)++; while(end != NULL) { if(*fpathwslash == 0x00) break; debug(3, "Non-recursively \"%s\": Adding to rsynclist: \"%s\".", fpathwslash, fpathwslash); indexes_outaggr_add(indexes_p, strdup(fpathwslash), EVIF_NONE); if(linescount_p != NULL) (*linescount_p)++; end = strrchr(fpathwslash, '/'); if(end == NULL) break; if(end - fpathwslash <= 0) break; *end = 0x00; }; return 0; } gboolean sync_idle_dosync_collectedevents_rsync_exclistpush(gpointer fpath_gp, gpointer flags_gp, gpointer arg_gp) { struct dosync_arg *dosync_arg_p = (struct dosync_arg *)arg_gp; char *fpath = (char *)fpath_gp; FILE *excf = dosync_arg_p->outf; eventinfo_flags_t flags = GPOINTER_TO_INT(flags_gp); // ctx_t *ctx_p = dosync_arg_p->ctx_p; // indexes_t *indexes_p = dosync_arg_p->indexes_p; debug(3, "\"%s\"", fpath); size_t fpath_len = strlen(fpath); char *fpathwslash; if(fpath_len>0) { // Prepending with the slash fpathwslash = alloca(fpath_len+2); fpathwslash[0] = '/'; memcpy(&fpathwslash[1], fpath, fpath_len+1); } else { // In this case slash is not required fpathwslash = fpath; } fpathwslash = (char *)rsync_escape(fpathwslash); int ret; if((ret=rsync_outline(excf, fpathwslash, flags))) { error("Got error from rsync_outline(). Exit."); exit(ret); // TODO: replace this with kill(0, ...) } return TRUE; } int sync_idle_dosync_collectedevents_commitpart(struct dosync_arg *dosync_arg_p) { ctx_t *ctx_p = dosync_arg_p->ctx_p; indexes_t *indexes_p = dosync_arg_p->indexes_p; debug(3, "Committing the file (flags[MODE] == %i)", ctx_p->flags[MODE]); if ( (ctx_p->flags[MODE] == MODE_RSYNCDIRECT) || (ctx_p->flags[MODE] == MODE_RSYNCSHELL) || (ctx_p->flags[MODE] == MODE_RSYNCSO) ) g_hash_table_foreach_remove(indexes_p->out_lines_aggr_ht, rsync_aggrout, dosync_arg_p); if (dosync_arg_p->outf != NULL) { critical_on(fclose(dosync_arg_p->outf)); dosync_arg_p->outf = NULL; } if (dosync_arg_p->evcount > 0) { thread_callbackfunct_arg_t *callback_arg_p; debug(3, "%s [%s] (%p) -> %s [%s]", ctx_p->watchdir, ctx_p->watchdirwslash, ctx_p->watchdirwslash, ctx_p->destdir?ctx_p->destdir:"", ctx_p->destdirwslash?ctx_p->destdirwslash:""); if (ctx_p->flags[MODE] == MODE_SO) { api_eventinfo_t *ei = dosync_arg_p->api_ei; return so_call_sync(ctx_p, indexes_p, dosync_arg_p->evcount, ei); } if (ctx_p->flags[MODE] == MODE_RSYNCSO) return so_call_rsync( ctx_p, indexes_p, dosync_arg_p->outf_path, *(dosync_arg_p->excf_path) ? dosync_arg_p->excf_path : NULL); callback_arg_p = xcalloc(1, sizeof(*callback_arg_p)); if (ctx_p->synchandler_argf & SHFL_INCLUDE_LIST_PATH) callback_arg_p->incfpath = strdup(dosync_arg_p->outf_path); if (ctx_p->synchandler_argf & SHFL_EXCLUDE_LIST_PATH) callback_arg_p->excfpath = strdup(dosync_arg_p->excf_path); { int rc; dosync_arg_p->list_type_str = ctx_p->flags[MODE]==MODE_RSYNCDIRECT || ctx_p->flags[MODE]==MODE_RSYNCSHELL ? "rsynclist" : "synclist"; debug(9, "dosync_arg_p->include_list_count == %u", dosync_arg_p->include_list_count); char **argv = sync_customargv(ctx_p, dosync_arg_p, &ctx_p->synchandler_args[SHARGS_PRIMARY]); while (dosync_arg_p->include_list_count) free((char *)dosync_arg_p->include_list[--dosync_arg_p->include_list_count]); rc = SYNC_EXEC_ARGV( ctx_p, indexes_p, sync_idle_dosync_collectedevents_cleanup, callback_arg_p, argv); if (!SHOULD_THREAD(ctx_p)) // If it's a thread then it will free the argv in GC. If not a thread then we have to free right here. argv_free(argv); return rc; } } return 0; #ifdef DOXYGEN sync_exec_argv(NULL, NULL); sync_exec_argv_thread(NULL, NULL); #endif } void sync_inclist_rotate(ctx_t *ctx_p, struct dosync_arg *dosync_arg_p) { int ret; char newexc_path[PATH_MAX+1]; if (ctx_p->synchandler_argf & SHFL_EXCLUDE_LIST_PATH) { // TODO: optimize this out { if ((ret=sync_idle_dosync_collectedevents_uniqfname(ctx_p, newexc_path, "exclist"))) { error("Cannot get unique file name."); exit(ret); } if ((ret=fileutils_copy(dosync_arg_p->excf_path, newexc_path))) { error("Cannot copy file \"%s\" to \"%s\".", dosync_arg_p->excf_path, newexc_path); exit(ret); } // } // That's required to copy excludes' list file for every rsync execution. // The problem appears do to unlink()-ing the excludes' list file on callback function // "sync_idle_dosync_collectedevents_cleanup()" of every execution. } if ((ret=sync_idle_dosync_collectedevents_commitpart(dosync_arg_p))) { error("Cannot commit list-file \"%s\"", dosync_arg_p->outf_path); exit(ret); // TODO: replace with kill(0, ...); } if (ctx_p->synchandler_argf & SHFL_INCLUDE_LIST_PATH) { #ifdef VERYPARANOID require_strlen_le(newexc_path, PATH_MAX); #endif strcpy(dosync_arg_p->excf_path, newexc_path); // TODO: optimize this out if ((ret=sync_idle_dosync_collectedevents_listcreate(dosync_arg_p, "list"))) { error("Cannot create new list-file"); exit(ret); // TODO: replace with kill(0, ...); } } return; } void sync_idle_dosync_collectedevents_listpush(gpointer fpath_gp, gpointer evinfo_gp, gpointer arg_gp) { struct dosync_arg *dosync_arg_p = (struct dosync_arg *)arg_gp; char *fpath = (char *)fpath_gp; eventinfo_t *evinfo = (eventinfo_t *)evinfo_gp; //int *evcount_p =&dosync_arg_p->evcount; FILE *outf = dosync_arg_p->outf; ctx_t *ctx_p = dosync_arg_p->ctx_p; int *linescount_p = &dosync_arg_p->linescount; indexes_t *indexes_p = dosync_arg_p->indexes_p; api_eventinfo_t **api_ei_p = &dosync_arg_p->api_ei; int *api_ei_count_p = &dosync_arg_p->api_ei_count; debug(3, "\"%s\" with int-flags %p. " "evinfo: seqid_min == %u, seqid_max == %u type_o == %i, type_n == %i", fpath, (void *)(unsigned long)evinfo->flags, evinfo->seqid_min, evinfo->seqid_max, evinfo->objtype_old, evinfo->objtype_new ); // so-module case: if (ctx_p->flags[MODE] == MODE_SO) { api_eventinfo_t *ei = &(*api_ei_p)[(*api_ei_count_p)++]; ei->evmask = evinfo->evmask; ei->flags = evinfo->flags; ei->objtype_old = evinfo->objtype_old; ei->objtype_new = evinfo->objtype_new; ei->path_len = strlen(fpath); ei->path = strdup(fpath); return; } if (ctx_p->synchandler_argf & SHFL_INCLUDE_LIST) { dosync_arg_p->include_list[dosync_arg_p->include_list_count++] = strdup(fpath); if ( dosync_arg_p->include_list_count >= (MAXARGUMENTS - MAX( ctx_p->synchandler_args[SHARGS_PRIMARY].c, ctx_p->synchandler_args[SHARGS_INITIAL].c ) ) ) sync_inclist_rotate(ctx_p, dosync_arg_p); } // Finish if we don't use list files if (!(ctx_p->synchandler_argf & ( SHFL_INCLUDE_LIST_PATH | SHFL_EXCLUDE_LIST_PATH ) )) return; // List files cases: // non-RSYNC case if (!( (ctx_p->flags[MODE] == MODE_RSYNCSHELL) || (ctx_p->flags[MODE] == MODE_RSYNCDIRECT) || (ctx_p->flags[MODE] == MODE_RSYNCSO) )) { if (ctx_p->flags[SYNCLISTSIMPLIFY]) { critical_on ( fprintf(outf, "%s\n", fpath) <= 0 ); } else { critical_on ( fprintf(outf, "sync %s %i %s\n", ctx_p->label, evinfo->evmask, fpath) <= 0); } return; } // RSYNC case if (ctx_p->rsyncinclimit && (*linescount_p >= ctx_p->rsyncinclimit)) sync_inclist_rotate(ctx_p, dosync_arg_p); int ret; if ((ret=rsync_listpush(indexes_p, fpath, strlen(fpath), evinfo->flags, linescount_p))) { error("Got error from rsync_listpush(). Exit."); exit(ret); } return; } int sync_idle_dosync_collectedevents(ctx_t *ctx_p, indexes_t *indexes_p) { debug(3, ""); struct dosync_arg dosync_arg = {0}; dosync_arg.ctx_p = ctx_p; dosync_arg.indexes_p = indexes_p; char isrsyncpreferexclude = ( (ctx_p->flags[MODE] == MODE_RSYNCDIRECT) || (ctx_p->flags[MODE] == MODE_RSYNCSHELL) || (ctx_p->flags[MODE] == MODE_RSYNCSO) ) && (!ctx_p->flags[RSYNCPREFERINCLUDE]); #ifdef PARANOID if(ctx_p->listoutdir != NULL) { g_hash_table_remove_all(indexes_p->fpath2ei_ht); if(isrsyncpreferexclude) g_hash_table_remove_all(indexes_p->exc_fpath_ht); } #endif // Setting the time to sync not before it: ctx_p->synctime = time(NULL) + ctx_p->syncdelay; debug(3, "Next sync will be not before: %u", ctx_p->synctime); int queue_id=0; while (queue_id < QUEUE_MAX) { int ret; if ((queue_id == QUEUE_LOCKWAIT) && (ctx_p->flags[THREADING] != PM_SAFE)) { queue_id++; continue; } queue_id_t *queue_id_p = (queue_id_t *)&dosync_arg.data; *queue_id_p = queue_id; ret = sync_idle_dosync_collectedevents_aggrqueue(queue_id, ctx_p, indexes_p, &dosync_arg); if(ret) { error("Got error while processing queue #%i\n.", queue_id); g_hash_table_remove_all(indexes_p->fpath2ei_ht); if(isrsyncpreferexclude) g_hash_table_remove_all(indexes_p->exc_fpath_ht); return ret; } queue_id++; } if (!dosync_arg.evcount) { debug(3, "Summary events' count is zero. Return 0."); return 0; } if (ctx_p->flags[MODE] == MODE_SO) { //dosync_arg.evcount = g_hash_table_size(indexes_p->fpath2ei_ht); debug(3, "There's %i events. Processing.", dosync_arg.evcount); dosync_arg.api_ei = (api_eventinfo_t *)xmalloc(dosync_arg.evcount * sizeof(*dosync_arg.api_ei)); } { int ret; if ((ctx_p->listoutdir != NULL) || (ctx_p->flags[MODE] == MODE_SO)) { if (!(ctx_p->flags[MODE]==MODE_SO)) { *(dosync_arg.excf_path) = 0x00; if (isrsyncpreferexclude) { if ((ret=sync_idle_dosync_collectedevents_listcreate(&dosync_arg, "exclist"))) { error("Cannot create list-file"); return ret; } #ifdef PARANOID g_hash_table_remove_all(indexes_p->out_lines_aggr_ht); #endif g_hash_table_foreach_remove(indexes_p->exc_fpath_ht, sync_idle_dosync_collectedevents_rsync_exclistpush, &dosync_arg); g_hash_table_foreach_remove(indexes_p->out_lines_aggr_ht, rsync_aggrout, &dosync_arg); critical_on(fclose(dosync_arg.outf)); #ifdef VERYPARANOID require_strlen_le(dosync_arg.outf_path, PATH_MAX); #endif strcpy(dosync_arg.excf_path, dosync_arg.outf_path); // TODO: remove this strcpy() } if ((ret=sync_idle_dosync_collectedevents_listcreate(&dosync_arg, "list"))) { error("Cannot create list-file"); return ret; } } } if ((ctx_p->listoutdir != NULL) || (ctx_p->flags[MODE] == MODE_SO) || (ctx_p->synchandler_argf & SHFL_INCLUDE_LIST)) { #ifdef PARANOID g_hash_table_remove_all(indexes_p->out_lines_aggr_ht); #endif g_hash_table_foreach(indexes_p->fpath2ei_ht, sync_idle_dosync_collectedevents_listpush, &dosync_arg); if ((ret=sync_idle_dosync_collectedevents_commitpart(&dosync_arg))) { error("Cannot submit to sync the list \"%s\"", dosync_arg.outf_path); // TODO: free dosync_arg.api_ei on case of error g_hash_table_remove_all(indexes_p->fpath2ei_ht); return ret; } g_hash_table_remove_all(indexes_p->fpath2ei_ht); } } finish_iteration(ctx_p); return 0; } int apievinfo2rsynclist(indexes_t *indexes_p, FILE *listfile, int n, api_eventinfo_t *apievinfo) { int i; if (listfile == NULL) { error("listfile == NULL."); return EINVAL; } i=0; while (iout_lines_aggr_ht, rsync_aggrout, &dosync_arg); return 0; } int sync_idle(ctx_t *ctx_p, indexes_t *indexes_p) { // Collecting garbage int ret=thread_gc(ctx_p); if(ret) return ret; // Checking if we can sync if (ctx_p->flags[STANDBYFILE]) { struct stat st; if (!stat(ctx_p->standbyfile, &st)) { state_t state_old; state_old = ctx_p->state; ctx_p->state = STATE_HOLDON; main_status_update(ctx_p); debug(1, "Found standby file. Holding over syncs. Sleeping "XTOSTR(SLEEP_SECONDS)" second."); sleep(SLEEP_SECONDS); ctx_p->state = state_old; main_status_update(ctx_p); return 0; } } // Syncing debug(3, "calling sync_idle_dosync_collectedevents()"); #ifdef CLUSTER_SUPPORT ret = cluster_lock_byindexes(); if(ret) return ret; #endif ret = sync_idle_dosync_collectedevents(ctx_p, indexes_p); if(ret) return ret; #ifdef CLUSTER_SUPPORT ret = cluster_unlock_all(); if(ret) return ret; #endif return 0; } int notify_wait(ctx_t *ctx_p, indexes_t *indexes_p) { static struct timeval tv; time_t tm = time(NULL); long delay = ((unsigned long)~0 >> 1); threadsinfo_t *threadsinfo_p = thread_info(); debug(4, "pthread_mutex_unlock(&threadsinfo_p->mutex[PTHREAD_MUTEX_STATE])"); pthread_cond_broadcast(&threadsinfo_p->cond[PTHREAD_MUTEX_STATE]); pthread_mutex_unlock(&threadsinfo_p->mutex[PTHREAD_MUTEX_STATE]); long queue_id = 0; while (queue_id < QUEUE_MAX) { queueinfo_t *queueinfo = &ctx_p->_queues[queue_id++]; if (!queueinfo->stime) continue; if (queueinfo->collectdelay == COLLECTDELAY_INSTANT) { debug(3, "There're events in instant queue (#%i), don't waiting.", queue_id-1); return 0; } int qdelay = queueinfo->stime + queueinfo->collectdelay - tm; debug(3, "queue #%i: %i %i %i -> %i", queue_id-1, queueinfo->stime, queueinfo->collectdelay, tm, qdelay); if (qdelay < -(long)ctx_p->syncdelay) qdelay = -(long)ctx_p->syncdelay; delay = MIN(delay, qdelay); } long synctime_delay = ((long)ctx_p->synctime) - ((long)tm); synctime_delay = synctime_delay > 0 ? synctime_delay : 0; debug(3, "delay = MAX(%li, %li)", delay, synctime_delay); delay = MAX(delay, synctime_delay); delay = delay > 0 ? delay : 0; if (ctx_p->flags[THREADING]) { time_t _thread_nextexpiretime = thread_nextexpiretime(); debug(3, "thread_nextexpiretime == %i", _thread_nextexpiretime); if(_thread_nextexpiretime) { long thread_expiredelay = (long)thread_nextexpiretime() - (long)tm + 1; // +1 is to make "tm>threadinfo_p->expiretime" after select() definitely TRUE debug(3, "thread_expiredelay == %i", thread_expiredelay); thread_expiredelay = thread_expiredelay > 0 ? thread_expiredelay : 0; debug(3, "delay = MIN(%li, %li)", delay, thread_expiredelay); delay = MIN(delay, thread_expiredelay); } } if ((!delay) || (ctx_p->state != STATE_RUNNING)) return 0; if (ctx_p->flags[EXITONNOEVENTS]) { // zero delay if "--exit-on-no-events" is set tv.tv_sec = 0; tv.tv_usec = 0; } else { debug(3, "sleeping for %li second(s).", SLEEP_SECONDS); sleep(SLEEP_SECONDS); delay = ((long)delay)>SLEEP_SECONDS ? delay-SLEEP_SECONDS : 0; tv.tv_sec = delay; tv.tv_usec = 0; } debug(4, "pthread_mutex_lock(&threadsinfo_p->mutex[PTHREAD_MUTEX_STATE])"); pthread_mutex_lock(&threadsinfo_p->mutex[PTHREAD_MUTEX_STATE]); if (ctx_p->state != STATE_RUNNING) return 0; debug(4, "pthread_mutex_unlock(&threadsinfo_p->mutex[PTHREAD_MUTEX_STATE])"); pthread_cond_broadcast(&threadsinfo_p->cond[PTHREAD_MUTEX_STATE]); pthread_mutex_lock(&threadsinfo_p->mutex[PTHREAD_MUTEX_SELECT]); pthread_mutex_unlock(&threadsinfo_p->mutex[PTHREAD_MUTEX_STATE]); debug(8, "ctx_p->notifyenginefunct.wait() [%p]", ctx_p->notifyenginefunct.wait); int ret = ctx_p->notifyenginefunct.wait(ctx_p, indexes_p, &tv); pthread_mutex_unlock(&threadsinfo_p->mutex[PTHREAD_MUTEX_SELECT]); if ((ret == -1) && (errno == EINTR)) { errno = 0; ret = 0; } debug(4, "pthread_mutex_lock(&threadsinfo_p->mutex[PTHREAD_MUTEX_STATE])"); pthread_mutex_lock(&threadsinfo_p->mutex[PTHREAD_MUTEX_STATE]); if ((ctx_p->flags[EXITONNOEVENTS]) && (ret == 0)) { // if not events and "--exit-on-no-events" is set if (ctx_p->flags[PREEXITHOOK]) ctx_p->state = STATE_PREEXIT; else ctx_p->state = STATE_EXIT; } return ret; } #define SYNC_LOOP_IDLE {\ int ret;\ if((ret=sync_idle(ctx_p, indexes_p))) {\ error("got error while sync_idle().");\ return ret;\ }\ } #define SYNC_LOOP_CONTINUE_UNLOCK {\ pthread_cond_broadcast(&threadsinfo_p->cond[PTHREAD_MUTEX_STATE]);\ debug(4, "pthread_mutex_unlock()");\ pthread_mutex_unlock(&threadsinfo_p->mutex[PTHREAD_MUTEX_STATE]);\ continue;\ } void hook_preexit(ctx_t *ctx_p) { debug(2, "\"%s\" \"%s\"", ctx_p->preexithookfile, ctx_p->label); #ifdef VERYPARANOID if (ctx_p->preexithookfile == NULL) critical("ctx_p->preexithookfile == NULL"); #endif char *argv[] = { ctx_p->preexithookfile, ctx_p->label, NULL}; exec_argv(argv, NULL); return; } int sync_loop(ctx_t *ctx_p, indexes_t *indexes_p) { int ret; threadsinfo_t *threadsinfo_p = thread_info(); state_p = &ctx_p->state; ctx_p->state = ctx_p->flags[SKIPINITSYNC] ? STATE_RUNNING : STATE_INITSYNC; while (ctx_p->state != STATE_EXIT) { int events; debug(4, "pthread_mutex_lock()"); pthread_mutex_lock(&threadsinfo_p->mutex[PTHREAD_MUTEX_STATE]); debug(3, "current state is %i (iteration: %u/%u); threadsinfo_p->used == %u", ctx_p->state, ctx_p->iteration_num, ctx_p->flags[MAXITERATIONS], threadsinfo_p->used); while ((ctx_p->flags[THREADING] == PM_OFF) && threadsinfo_p->used) { debug(1, "We are in non-threading mode but have %u syncer threads. Waiting for them end.", threadsinfo_p->used); pthread_cond_wait(&threadsinfo_p->cond[PTHREAD_MUTEX_STATE], &threadsinfo_p->mutex[PTHREAD_MUTEX_STATE]); pthread_mutex_unlock(&threadsinfo_p->mutex[PTHREAD_MUTEX_STATE]); } events = 0; switch (ctx_p->state) { case STATE_THREAD_GC: main_status_update(ctx_p); if (thread_gc(ctx_p)) { ctx_p->state = STATE_EXIT; break; } ctx_p->state = STATE_RUNNING; SYNC_LOOP_CONTINUE_UNLOCK; case STATE_INITSYNC: if (!ctx_p->flags[THREADING]) { ctx_p->iteration_num = 0; setenv_iteration(ctx_p->iteration_num); } main_status_update(ctx_p); pthread_cond_broadcast(&threadsinfo_p->cond[PTHREAD_MUTEX_STATE]); pthread_mutex_unlock(&threadsinfo_p->mutex[PTHREAD_MUTEX_STATE]); ret = sync_initialsync(ctx_p->watchdir, ctx_p, indexes_p, INITSYNC_FULL); if(ret) return ret; if(ctx_p->flags[ONLYINITSYNC]) { SYNC_LOOP_IDLE; ctx_p->state = STATE_EXIT; return ret; } ctx_p->state = STATE_RUNNING; continue; case STATE_PREEXIT: case STATE_RUNNING: if ((!ctx_p->flags[THREADING]) && ctx_p->flags[MAXITERATIONS]) { if (ctx_p->flags[MAXITERATIONS] == ctx_p->iteration_num-1) ctx_p->state = STATE_PREEXIT; else if (ctx_p->flags[MAXITERATIONS] <= ctx_p->iteration_num) ctx_p->state = STATE_EXIT; } switch (ctx_p->state) { case STATE_PREEXIT: main_status_update(ctx_p); if (ctx_p->flags[PREEXITHOOK]) hook_preexit(ctx_p); ctx_p->state = STATE_TERM; case STATE_RUNNING: events = notify_wait(ctx_p, indexes_p); break; default: SYNC_LOOP_CONTINUE_UNLOCK; } break; case STATE_REHASH: main_status_update(ctx_p); debug(1, "rehashing."); main_rehash(ctx_p); ctx_p->state = STATE_RUNNING; SYNC_LOOP_CONTINUE_UNLOCK; case STATE_TERM: main_status_update(ctx_p); ctx_p->state = STATE_EXIT; case STATE_EXIT: main_status_update(ctx_p); SYNC_LOOP_CONTINUE_UNLOCK; default: critical("internal error: ctx_p->state == %u", ctx_p->state); break; } pthread_cond_broadcast(&threadsinfo_p->cond[PTHREAD_MUTEX_STATE]); pthread_mutex_unlock(&threadsinfo_p->mutex[PTHREAD_MUTEX_STATE]); if (events == 0) { debug(2, "sync_x_wait(ctx_p, indexes_p) timed-out."); SYNC_LOOP_IDLE; continue; // Timeout } if (events < 0) { error("Got error while waiting for event from notify subsystem."); return errno; } int count = ctx_p->notifyenginefunct.handle(ctx_p, indexes_p); if (count <= 0) { error("Cannot handle with notify events."); return errno; } main_status_update(ctx_p); if (ctx_p->flags[EXITONNOEVENTS]) // clsync exits on no events, so sync_idle() is never called. We have to force the calling of it. SYNC_LOOP_IDLE; } SYNC_LOOP_IDLE; debug(1, "end"); return exitcode; #ifdef DOXYGEN sync_idle(0, NULL, NULL); #endif } void sync_sig_int(int signal) { debug(2, "%i: Thread %p", signal, pthread_self()); return; } #ifdef PARANOID int _sync_tryforcecycle_i; #endif int sync_tryforcecycle(ctx_t *ctx_p, pthread_t pthread_parent) { debug(3, "sending signal to interrupt blocking operations like select()-s and so on (ctx_p->blockthread_count == %i)", ctx_p->blockthread_count); //pthread_kill(pthread_parent, SIGUSR_BLOPINT); int i, count; count = ctx_p->blockthread_count; i = 0; while (i < count) { debug(2, "Sending SIGUSR_BLOPINT to thread %p", ctx_p->blockthread[i]); pthread_kill(ctx_p->blockthread[i], SIGUSR_BLOPINT); i++; } #ifdef PARANOID if (++_sync_tryforcecycle_i > KILL_TIMEOUT) { error("Seems we got a deadlock."); return EDEADLK; } #endif #ifdef SYNC_SWITCHSTATE_COND_TIMEDWAIT // Hangs struct timespec time_timeout; clock_gettime(CLOCK_REALTIME, &time_timeout); time_timeout.tv_sec++; // time_timeout.tv_sec = now.tv_sec; debug(3, "pthread_cond_timedwait() until %li.%li", time_timeout.tv_sec, time_timeout.tv_nsec); if (pthread_cond_timedwait(pthread_cond_state, pthread_mutex_state, &time_timeout) != ETIMEDOUT) return 0; #else debug(9, "sleep("XTOSTR(SLEEP_SECONDS)")"); sleep(SLEEP_SECONDS); // TODO: replace this with pthread_cond_timedwait() #endif return EINPROGRESS; } int sync_switch_state(ctx_t *ctx_p, pthread_t pthread_parent, int newstate) { if (state_p == NULL) { debug(3, "sync_switch_state(ctx_p, %p, %i), but state_p == NULL", pthread_parent, newstate); return 0; } debug(3, "sync_switch_state(ctx_p, %p, %i)", pthread_parent, newstate); // Getting mutexes threadsinfo_t *threadsinfo_p = thread_info(); if (threadsinfo_p == NULL) { // If no mutexes, just change the state goto l_sync_parent_interrupt_end; } if (!threadsinfo_p->mutex_init) { // If no mutexes, just change the state goto l_sync_parent_interrupt_end; } pthread_mutex_t *pthread_mutex_state = &threadsinfo_p->mutex[PTHREAD_MUTEX_STATE]; pthread_mutex_t *pthread_mutex_select = &threadsinfo_p->mutex[PTHREAD_MUTEX_SELECT]; pthread_cond_t *pthread_cond_state = &threadsinfo_p->cond [PTHREAD_MUTEX_STATE]; // Locking all necessary mutexes #ifdef PARANOID _sync_tryforcecycle_i = 0; #endif debug(4, "while(pthread_mutex_trylock( pthread_mutex_state ))"); while (pthread_mutex_trylock(pthread_mutex_state) == EBUSY) { int rc = sync_tryforcecycle(ctx_p, pthread_parent); if (rc && rc != EINPROGRESS) return rc; if (!rc) break; } #ifdef PARANOID _sync_tryforcecycle_i = 0; #endif debug(4, "while(pthread_mutex_trylock( pthread_mutex_select ))"); while (pthread_mutex_trylock(pthread_mutex_select) == EBUSY) { int rc = sync_tryforcecycle(ctx_p, pthread_parent); if (rc && rc != EINPROGRESS) return rc; if (!rc) break; } // Changing the state *state_p = newstate; #ifdef PARANOID pthread_kill(pthread_parent, SIGUSR_BLOPINT); #endif // Unlocking mutexes debug(4, "pthread_cond_broadcast(). New state is %i.", *state_p); pthread_cond_broadcast(pthread_cond_state); debug(4, "pthread_mutex_unlock( pthread_mutex_state )"); pthread_mutex_unlock(pthread_mutex_state); debug(4, "pthread_mutex_unlock( pthread_mutex_select )"); pthread_mutex_unlock(pthread_mutex_select); return thread_info_unlock(0); l_sync_parent_interrupt_end: *state_p = newstate; pthread_kill(pthread_parent, SIGUSR_BLOPINT); return thread_info_unlock(0); } /* === DUMP === */ enum dump_dirfd_obj { DUMP_DIRFD_ROOT = 0, DUMP_DIRFD_QUEUE, DUMP_DIRFD_THREAD, DUMP_DIRFD_MAX }; enum dump_ltype { DUMP_LTYPE_INCLUDE, DUMP_LTYPE_EXCLUDE, DUMP_LTYPE_EVINFO, }; struct sync_dump_arg { ctx_t *ctx_p; int dirfd[DUMP_DIRFD_MAX]; int fd_out; int data; }; void sync_dump_liststep(gpointer fpath_gp, gpointer evinfo_gp, gpointer arg_gp) { char *fpath = (char *)fpath_gp; eventinfo_t *evinfo = (eventinfo_t *)evinfo_gp; struct sync_dump_arg *arg = arg_gp; char act, num; if (fpath == NULL || evinfo == NULL) return; switch (arg->data) { case DUMP_LTYPE_INCLUDE: act = '+'; num = '1'; break; case DUMP_LTYPE_EXCLUDE: act = '-'; num = '1'; break; case DUMP_LTYPE_EVINFO: act = '+'; num = evinfo->flags&EVIF_RECURSIVELY ? '*' : (evinfo->flags&EVIF_CONTENTRECURSIVELY ? '/' : '1'); break; default: act = '?'; num = '?'; } dprintf(arg->fd_out, "%c%c\t%s\n", act, num, fpath); return; } int sync_dump_thread(threadinfo_t *threadinfo_p, void *_arg) { struct sync_dump_arg *arg = _arg; char buf[BUFSIZ]; snprintf(buf, BUFSIZ, "%u-%u-%lx", threadinfo_p->iteration, threadinfo_p->thread_num, (long)threadinfo_p->pthread); arg->fd_out = openat(arg->dirfd[DUMP_DIRFD_THREAD], buf, O_WRONLY|O_CREAT, DUMP_FILEMODE); if (arg->fd_out == -1) return errno; { char **argv; dprintf(arg->fd_out, "thread:\n\titeration == %u\n\tnum == %u\n\tpthread == %lx\n\tstarttime == %lu\n\texpiretime == %lu\n\tchild_pid == %u\n\ttry_n == %u\nCommand:", threadinfo_p->iteration, threadinfo_p->thread_num, (long)threadinfo_p->pthread, threadinfo_p->starttime, threadinfo_p->expiretime, threadinfo_p->child_pid, threadinfo_p->try_n ); argv = threadinfo_p->argv; while (*argv != NULL) dprintf(arg->fd_out, " \"%s\"", *(argv++)); dprintf(arg->fd_out, "\n"); } arg->data = DUMP_LTYPE_EVINFO; g_hash_table_foreach(threadinfo_p->fpath2ei_ht, sync_dump_liststep, arg); close(arg->fd_out); return 0; } int sync_dump(ctx_t *ctx_p, const char *const dir_path) { indexes_t *indexes_p = ctx_p->indexes_p; int rootfd, fd_out; struct sync_dump_arg arg = {0}; enum dump_dirfd_obj dirfd_obj; arg.ctx_p = ctx_p; debug(3, "%s", dir_path); if (dir_path == NULL) return EINVAL; static const char *const subdirs[] = { [DUMP_DIRFD_QUEUE] = "queue", [DUMP_DIRFD_THREAD] = "threads" }; errno = 0; rootfd = mkdirat_open(dir_path, AT_FDCWD, DUMP_DIRMODE); if (rootfd == -1) { error("Cannot open directory \"%s\"", dir_path); goto l_sync_dump_end; } fd_out = openat(rootfd, "instance", O_WRONLY|O_CREAT, DUMP_FILEMODE); if (fd_out == -1) { error("Cannot open file \"%s\" for writing"); goto l_sync_dump_end; } dprintf(fd_out, "status == %s\n", getenv("CLSYNC_STATUS")); // TODO: remove getenv() from here arg.fd_out = fd_out; arg.data = DUMP_LTYPE_EVINFO; if (indexes_p->nonthreaded_syncing_fpath2ei_ht != NULL) g_hash_table_foreach(indexes_p->nonthreaded_syncing_fpath2ei_ht, sync_dump_liststep, &arg); close(fd_out); arg.dirfd[DUMP_DIRFD_ROOT] = rootfd; dirfd_obj = DUMP_DIRFD_ROOT+1; while (dirfd_obj < DUMP_DIRFD_MAX) { const char *const subdir = subdirs[dirfd_obj]; arg.dirfd[dirfd_obj] = mkdirat_open(subdir, rootfd, DUMP_DIRMODE); if (arg.dirfd[dirfd_obj] == -1) { error("Cannot open directory \"%s\"", subdir); goto l_sync_dump_end; } dirfd_obj++; } int queue_id = 0; while (queue_id < QUEUE_MAX) { char buf[BUFSIZ]; snprintf(buf, BUFSIZ, "%u", queue_id); arg.fd_out = openat(arg.dirfd[DUMP_DIRFD_QUEUE], buf, O_WRONLY|O_CREAT, DUMP_FILEMODE); arg.data = DUMP_LTYPE_EVINFO; g_hash_table_foreach(indexes_p->fpath2ei_coll_ht[queue_id], sync_dump_liststep, &arg); if (indexes_p->exc_fpath_coll_ht[queue_id] != NULL) { arg.data = DUMP_LTYPE_EXCLUDE; g_hash_table_foreach(indexes_p->exc_fpath_coll_ht[queue_id], sync_dump_liststep, &arg); } close(arg.fd_out); queue_id++; } threads_foreach(sync_dump_thread, STATE_RUNNING, &arg); l_sync_dump_end: dirfd_obj = DUMP_DIRFD_ROOT; while (dirfd_obj < DUMP_DIRFD_MAX) { if (arg.dirfd[dirfd_obj] != -1 && arg.dirfd[dirfd_obj] != 0) close(arg.dirfd[dirfd_obj]); dirfd_obj++; } if (errno) error("Cannot create the dump to \"%s\"", dir_path); return errno; } /* === /DUMP === */ void sync_sigchld() { debug(9, ""); privileged_check(); return; } int *sync_sighandler_exitcode_p = NULL; int sync_sighandler(sighandler_arg_t *sighandler_arg_p) { int signal = 0, ret; sigset_t sigset_full; ctx_t *ctx_p = sighandler_arg_p->ctx_p; // indexes_t *indexes_p = sighandler_arg_p->indexes_p; pthread_t pthread_parent = sighandler_arg_p->pthread_parent; // sigset_t *sigset_p = sighandler_arg_p->sigset_p; int *exitcode_p = sighandler_arg_p->exitcode_p; sync_sighandler_exitcode_p = exitcode_p; sethandler_sigchld(sync_sigchld); sigfillset(&sigset_full); while (state_p == NULL || ((ctx_p->state != STATE_TERM) && (ctx_p->state != STATE_EXIT))) { debug(3, "waiting for signal (is sigset filled == %i)", sigismember(&sigset_full, SIGTERM)); ret = sigwait(&sigset_full, &signal); if (state_p == NULL) { switch (signal) { case SIGALRM: *exitcode_p = ETIME; case SIGQUIT: case SIGTERM: case SIGINT: case SIGCHLD: // TODO: remove the exit() from here. Main thread should exit itself exit(*exitcode_p); break; default: warning("Got signal %i, but the main loop is not started, yet. Ignoring the signal.", signal); break; } continue; } debug(3, "got signal %i. ctx_p->state == %i.", signal, ctx_p->state); if (ret) { // TODO: handle an error here } if (ctx_p->customsignal[signal] != NULL) { if (config_block_parse(ctx_p, ctx_p->customsignal[signal])) { *exitcode_p = errno; signal = SIGTERM; } continue; } switch (signal) { case SIGALRM: *exitcode_p = ETIME; case SIGQUIT: if (ctx_p->flags[PREEXITHOOK]) sync_switch_state(ctx_p, pthread_parent, STATE_PREEXIT); else sync_switch_state(ctx_p, pthread_parent, STATE_TERM); break; case SIGTERM: case SIGINT: sync_switch_state(ctx_p, pthread_parent, STATE_TERM); // bugfix of https://github.com/xaionaro/clsync/issues/44 while (ctx_p->children) { // Killing children if non-pthread mode or/and (mode=="so" or mode=="rsyncso") pid_t child_pid = ctx_p->child_pid[--ctx_p->children]; if (privileged_kill_child(child_pid, signal, 0) == ENOENT) continue; if (signal != SIGQUIT) if (privileged_kill_child(child_pid, SIGQUIT, 0) == ENOENT) continue; if (signal != SIGTERM) if (privileged_kill_child(child_pid, SIGTERM, 0) == ENOENT) continue; if (privileged_kill_child(child_pid, SIGKILL, 0) == ENOENT) continue; } break; case SIGHUP: sync_switch_state(ctx_p, pthread_parent, STATE_REHASH); break; case SIGCHLD: sync_sigchld(); break; case SIGUSR_THREAD_GC: sync_switch_state(ctx_p, pthread_parent, STATE_THREAD_GC); break; case SIGUSR_INITSYNC: sync_switch_state(ctx_p, pthread_parent, STATE_INITSYNC); break; case SIGUSR_DUMP: sync_dump(ctx_p, ctx_p->dump_path); break; default: error("Unknown signal: %i. Exit.", signal); sync_switch_state(ctx_p, pthread_parent, STATE_TERM); break; } } debug(3, "signal handler closed."); return 0; } int sync_term(int exitcode) { *sync_sighandler_exitcode_p = exitcode; return pthread_kill(pthread_sighandler, SIGTERM); } int sync_run(ctx_t *ctx_p) { int ret; sighandler_arg_t sighandler_arg = {0}; indexes_t indexes = {NULL}; debug(9, "Creating signal handler thread"); { int i; register_blockthread(); sigset_t sigset_sighandler; sigemptyset(&sigset_sighandler); sigaddset(&sigset_sighandler, SIGALRM); sigaddset(&sigset_sighandler, SIGHUP); sigaddset(&sigset_sighandler, SIGQUIT); sigaddset(&sigset_sighandler, SIGTERM); sigaddset(&sigset_sighandler, SIGINT); sigaddset(&sigset_sighandler, SIGCHLD); sigaddset(&sigset_sighandler, SIGUSR_THREAD_GC); sigaddset(&sigset_sighandler, SIGUSR_INITSYNC); sigaddset(&sigset_sighandler, SIGUSR_DUMP); i = 0; while (i < MAXSIGNALNUM) { if (ctx_p->customsignal[i] != NULL) sigaddset(&sigset_sighandler, i); i++; } ret = pthread_sigmask(SIG_BLOCK, &sigset_sighandler, NULL); if (ret) return ret; sighandler_arg.ctx_p = ctx_p; sighandler_arg.pthread_parent = pthread_self(); sighandler_arg.exitcode_p = &ret; sighandler_arg.sigset_p = &sigset_sighandler; ret = pthread_create(&pthread_sighandler, NULL, (void *(*)(void *))sync_sighandler, &sighandler_arg); if (ret) return ret; sigset_t sigset_parent; sigemptyset(&sigset_parent); sigaddset(&sigset_parent, SIGUSR_BLOPINT); ret = pthread_sigmask(SIG_UNBLOCK, &sigset_parent, NULL); if (ret) return ret; signal(SIGUSR_BLOPINT, sync_sig_int); } debug(9, "Creating hash tables"); { int i; ctx_p->indexes_p = &indexes; indexes.wd2fpath_ht = g_hash_table_new_full(g_direct_hash, g_direct_equal, 0, 0); indexes.fpath2wd_ht = g_hash_table_new_full(g_str_hash, g_str_equal, free, 0); indexes.fpath2ei_ht = g_hash_table_new_full(g_str_hash, g_str_equal, free, free); indexes.exc_fpath_ht = g_hash_table_new_full(g_str_hash, g_str_equal, free, 0); indexes.out_lines_aggr_ht = g_hash_table_new_full(g_str_hash, g_str_equal, free, 0); indexes.fileinfo_ht = g_hash_table_new_full(g_str_hash, g_str_equal, free, free); i=0; while (iflags[MODE] == MODE_SO || ctx_p->flags[MODE] == MODE_RSYNCSO) { /* security checks before dlopen */ struct stat so_stat; if (stat(ctx_p->handlerfpath, &so_stat) == -1) { error("Can't stat shared object file \"%s\": %s", ctx_p->handlerfpath, strerror(errno)); return errno; } // allow normal files only (stat will follow symlinks) if (!S_ISREG(so_stat.st_mode)) { error("Shared object \"%s\" must be a regular file (or symlink to a regular file).", ctx_p->handlerfpath, so_stat.st_uid); return EPERM; } // allowed owners are: root and real uid (who started clsync prior to setuid) if (so_stat.st_uid && so_stat.st_uid != getuid()) { /* check for rare case when clsync binary owner is neither root nor current uid */ struct stat cl_stat; char *cl_str = alloca(20); // allocate for "/proc/PID/exe" int ret; snprintf(cl_str, 20, "/proc/%i/exe", getpid()); // stat clsync binary itself to get its owner's uid if ((ret = stat(cl_str, &cl_stat)) == -1) { error("Can't stat clsync binary file \"%s\": %s", cl_str, strerror(errno)); } if (ret == -1 || so_stat.st_uid != cl_stat.st_uid) { error("Wrong owner for shared object \"%s\": %i. " "Only root, clsync file owner and user started the program are allowed.", ctx_p->handlerfpath, so_stat.st_uid); return EPERM; } } // do not allow special bits and g+w,o+w if (so_stat.st_mode & (S_ISUID | S_ISGID | S_ISVTX | S_IWGRP | S_IWOTH)) { error("Wrong shared object \"%s\" permissions: %#lo" "Special bits, group and world writable are not allowed.", ctx_p->handlerfpath, so_stat.st_mode & 07777); return EPERM; } // dlopen() void *synchandler_handle = dlopen(ctx_p->handlerfpath, RTLD_NOW|RTLD_LOCAL); if (synchandler_handle == NULL) { error("Cannot load shared object file \"%s\": %s", ctx_p->handlerfpath, dlerror()); return -1; } // resolving init, sync and deinit functions' handlers ctx_p->handler_handle = synchandler_handle; ctx_p->handler_funct.init = (api_funct_init) dlsym(ctx_p->handler_handle, API_PREFIX"init"); if (ctx_p->flags[MODE] == MODE_RSYNCSO) { ctx_p->handler_funct.rsync = (api_funct_rsync)dlsym(ctx_p->handler_handle, API_PREFIX"rsync"); if (ctx_p->handler_funct.rsync == NULL) { char *dlerror_str = dlerror(); error("Cannot resolve symbol "API_PREFIX"rsync in shared object \"%s\": %s", ctx_p->handlerfpath, dlerror_str != NULL ? dlerror_str : "No error description returned."); } } else { ctx_p->handler_funct.sync = (api_funct_sync)dlsym(ctx_p->handler_handle, API_PREFIX"sync"); if (ctx_p->handler_funct.sync == NULL) { char *dlerror_str = dlerror(); error("Cannot resolve symbol "API_PREFIX"sync in shared object \"%s\": %s", ctx_p->handlerfpath, dlerror_str != NULL ? dlerror_str : "No error description returned."); } } ctx_p->handler_funct.deinit = (api_funct_deinit)dlsym(ctx_p->handler_handle, API_PREFIX"deinit"); // running init function if (ctx_p->handler_funct.init != NULL) if ((ret = ctx_p->handler_funct.init(ctx_p, &indexes))) { error("Cannot init sync-handler module."); return ret; } } // Initializing rand-generator if it's required if (ctx_p->listoutdir) srand(time(NULL)); if (!ctx_p->flags[ONLYINITSYNC]) { debug(9, "Initializing FS monitor kernel subsystem in this userspace application"); if (sync_notify_init(ctx_p)) return errno; } if ((ret=privileged_init(ctx_p))) return ret; { // Preparing monitor subsystem context function pointers switch (ctx_p->flags[MONITOR]) { #ifdef INOTIFY_SUPPORT case NE_INOTIFY: ctx_p->notifyenginefunct.add_watch_dir = inotify_add_watch_dir; ctx_p->notifyenginefunct.wait = inotify_wait; ctx_p->notifyenginefunct.handle = inotify_handle; break; #endif #ifdef KQUEUE_SUPPORT case NE_KQUEUE: ctx_p->notifyenginefunct.add_watch_dir = kqueue_add_watch_dir; ctx_p->notifyenginefunct.wait = kqueue_wait; ctx_p->notifyenginefunct.handle = kqueue_handle; break; #endif #ifdef BSM_SUPPORT case NE_BSM: case NE_BSM_PREFETCH: ctx_p->notifyenginefunct.add_watch_dir = bsm_add_watch_dir; ctx_p->notifyenginefunct.wait = bsm_wait; ctx_p->notifyenginefunct.handle = bsm_handle; break; #endif #ifdef GIO_SUPPORT case NE_GIO: ctx_p->notifyenginefunct.add_watch_dir = gio_add_watch_dir; ctx_p->notifyenginefunct.wait = gio_wait; ctx_p->notifyenginefunct.handle = gio_handle; break; #endif #ifdef DTRACEPIPE_SUPPORT case NE_DTRACEPIPE: ctx_p->notifyenginefunct.add_watch_dir = dtracepipe_add_watch_dir; ctx_p->notifyenginefunct.wait = dtracepipe_wait; ctx_p->notifyenginefunct.handle = dtracepipe_handle; break; #endif #ifdef VERYPARANOID default: critical("Unknown FS monitor subsystem: %i", ctx_p->flags[MONITOR]); #endif } } #ifdef CLUSTER_SUPPORT // Initializing cluster subsystem if(ctx_p->cluster_iface != NULL) { ret = cluster_init(ctx_p, &indexes); if(ret) { error("Cannot initialize cluster subsystem."); cluster_deinit(); return ret; } } #endif #ifdef ENABLE_SOCKET // Creating control socket if (ctx_p->socketpath != NULL) ret = control_run(ctx_p); #endif if (!ctx_p->flags[ONLYINITSYNC]) { // Marking file tree for FS monitor debug(30, "Running recursive notify marking function"); ret = sync_mark_walk(ctx_p, ctx_p->watchdir, &indexes); if (ret) return ret; } // "Infinite" loop of processling the events ret = sync_loop(ctx_p, &indexes); if (ret) return ret; debug(1, "sync_loop() ended"); #ifdef ENABLE_SOCKET // Removing control socket if (ctx_p->socketpath != NULL) control_cleanup(ctx_p); #endif debug(1, "killing sighandler"); // TODO: Do cleanup of watching points pthread_kill(pthread_sighandler, SIGINT); #ifdef VALGRIND pthread_join(pthread_sighandler, NULL); // TODO: fix a deadlock #endif // Killing children thread_cleanup(ctx_p); debug(2, "Deinitializing the FS monitor subsystem"); switch (ctx_p->flags[MONITOR]) { #ifdef INOTIFY_SUPPORT case NE_INOTIFY: inotify_deinit(ctx_p); break; #endif #ifdef KQUEUE_SUPPORT case NE_KQUEUE: kqueue_deinit(ctx_p); break; #endif #ifdef BSM_SUPPORT case NE_BSM: case NE_BSM_PREFETCH: bsm_deinit(ctx_p); break; #endif #ifdef GIO_SUPPORT case NE_GIO: gio_deinit(ctx_p); break; #endif #ifdef DTRACEPIPE_SUPPORT case NE_DTRACEPIPE: dtracepipe_deinit(ctx_p); break; #endif } // Closing shared libraries if (ctx_p->flags[MODE] == MODE_SO) { int _ret; if (ctx_p->handler_funct.deinit != NULL) if ((_ret = ctx_p->handler_funct.deinit())) { error("Cannot deinit sync-handler module."); if(!ret) ret = _ret; } if (dlclose(ctx_p->handler_handle)) { error("Cannot unload shared object file \"%s\": %s", ctx_p->handlerfpath, dlerror()); if (!ret) ret = -1; } } // Cleaning up run-time routines rsync_escape_cleanup(); // Removing hash-tables { int i; debug(3, "Closing hash tables"); g_hash_table_destroy(indexes.wd2fpath_ht); g_hash_table_destroy(indexes.fpath2wd_ht); g_hash_table_destroy(indexes.fpath2ei_ht); g_hash_table_destroy(indexes.exc_fpath_ht); g_hash_table_destroy(indexes.out_lines_aggr_ht); g_hash_table_destroy(indexes.fileinfo_ht); i = 0; while (icluster_iface != NULL) { int _ret; _ret = cluster_deinit(); if (_ret) { error("Cannot deinitialize cluster subsystem.", strerror(_ret), _ret); ret = _ret; } } #endif // One second for another threads #ifdef VERYPARANOID debug(9, "sleep("TOSTR(SLEEP_SECONDS)")"); sleep(SLEEP_SECONDS); #endif if (ctx_p->flags[EXITHOOK]) { char *argv[] = { ctx_p->exithookfile, ctx_p->label, NULL}; exec_argv(argv, NULL); } // Cleaning up cgroups staff #ifdef CGROUP_SUPPORT debug(3, "Cleaning up cgroups staff"); if (ctx_p->flags[FORBIDDEVICES]) error_on(privileged_clsync_cgroup_deinit(ctx_p)); #endif debug(3, "privileged_deinit()"); ret |= privileged_deinit(ctx_p); debug(3, "finish"); return ret; } clsync-0.4.1/sync.h000066400000000000000000000054471252417542300141530ustar00rootroot00000000000000/* clsync - file tree sync utility based on fanotify and inotify Copyright (C) 2013 Dmitry Yu Okunev 0x8E30679C This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include // GHashTable struct thread_callbackfunct_arg { char *excfpath; char *incfpath; }; typedef struct thread_callbackfunct_arg thread_callbackfunct_arg_t; typedef int (*thread_callbackfunct_t)(ctx_t *ctx_p, thread_callbackfunct_arg_t *arg_p); struct threadinfo { int thread_num; uint32_t iteration; thread_callbackfunct_t callback; thread_callbackfunct_arg_t *callback_arg; char **argv; pthread_t pthread; int exitcode; int errcode; state_t state; ctx_t *ctx_p; time_t starttime; time_t expiretime; int child_pid; GHashTable *fpath2ei_ht; // file path -> event information int try_n; // for so-synchandler int n; api_eventinfo_t *ei; }; typedef struct threadinfo threadinfo_t; struct threadsinfo { pthread_mutex_t mutex[PTHREAD_MUTEX_MAX]; pthread_cond_t cond [PTHREAD_MUTEX_MAX]; char mutex_init; int allocated; int used; threadinfo_t *threads; threadinfo_t **threadsstack; // stack of threadinfo_t to be used on thread_new() int stacklen; }; typedef struct threadsinfo threadsinfo_t; extern int sync_run(struct ctx *ctx); extern int sync_dump(struct ctx *ctx, const char *const dest_dir); extern int sync_term(int exitcode); extern int threads_foreach(int (*funct)(threadinfo_t *, void *), state_t state, void *arg); extern threadsinfo_t *thread_info(); extern time_t thread_nextexpiretime(); extern int sync_prequeue_loadmark ( int fsmon_d, struct ctx *ctx_p, struct indexes *indexes_p, const char *path_full, const char *path_rel, stat64_t *lstat_p, eventobjtype_t objtype_old, eventobjtype_t objtype_new, uint32_t event_mask, int event_wd, mode_t st_mode, off_t st_size, char **path_buf_p, size_t *path_buf_len_p, struct eventinfo *evinfo ); extern int sync_prequeue_unload(struct ctx *ctx_p, struct indexes *indexes_p); extern const char *sync_parameter_get(const char *variable_name, void *_dosync_arg_p); extern pthread_t pthread_sighandler; clsync-0.4.1/syscalls.c000066400000000000000000000022471252417542300150220ustar00rootroot00000000000000/* clsync - file tree sync utility based on inotify/kqueue Copyright (C) 2013-2014 Dmitry Yu Okunev 0x8E30679C This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ /* based on busybox's code: * http://git.busybox.net/busybox/plain/libbb/syscalls.c?h=0_60_stable * / #include #ifdef __NR_pivot_root int pivot_root(const char *new_root, const char *old_root) { return(syscall(__NR_pivot_root, new_root, put_old)); } #else int pivot_root(const char *new_root, const char *old_root) { return errno=ENOSYS; } #endif */ clsync-0.4.1/syscalls.h000066400000000000000000000023711252417542300150250ustar00rootroot00000000000000/* clsync - file tree sync utility based on inotify/kqueue Copyright (C) 2013-2014 Dmitry Yu Okunev 0x8E30679C This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ extern int pivot_root(const char *new_root, const char *old_root); static inline ssize_t read_inf(int fd, void *buf, size_t count) { ssize_t ret; errno = 0; do { ret = read(fd, buf, count); } while ((ret == -1) && (errno == EINTR)); return ret; } static inline ssize_t write_inf(int fd, const void *buf, size_t count) { ssize_t ret; errno = 0; do { ret = write(fd, buf, count); } while ((ret == -1) && (errno == EINTR)); return ret; }